mybatis源码笔记1——JDBC和整体架构

JDBC

JDBC实例代码

Class.forName ("com.mysql.jdbc.Driver" );
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" , "root" , "mysql" );

// 设置手动提交事务
connection.setAutoCommit(false );

//
String sql = "select ...." ;
preparedStatement = connection.prepareStatement(sql);//Statement:SQL 语句的执行器
preparedStatement.setString(1, " 王五 " );
resultSet rs = preparedStatement.executeQuery();
while (rs.next()){
String username = rs.getString("user_name" );
}

// 增删改
String sql2 = "update/delete ...." ;
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.setString(1, " 王五 " );
int i = preparedStatement.executeUpdate();

// 关闭资源
resultSet.close();
preparedStatement.close();

// 提交事务
connection.commit();

// 关闭连接
connection.close();

各组组件的关系

Connection

概述

* 一个Connetion对象表示通过JDBC与数据库建立起连接。从另一个角度看,他也表示一个客户端会话

* 可以通过两种方法获得Connection

  • 通过DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "mysql")获取

  • 通过DataSource接口的getConnection()获取。支持连接池和分布式事务,把连接信息抽取成一个类,可以防止硬编码且容易维护

当然底层还是得依靠驱动DriverManager发起连接

Driver

Connetion的建立需要借助驱动驱动程序Driver,这些使用数据库厂商提供的,驱动程序的实现有许多种,主要是基于Native Protocol Driver,比如我们开发使用的mysql-connector-java包。原理是把JDBC编写的代码转换为数据库特定的网络通信协议,日如下图

Driver实现类(以JDBCDriver为例)

当此类被加载时,就会执行静态代码块,执行DriverManager.registerDriver(driverInstance);会把自己注册到DriverManager里的注册表中。

public class JDBCDriver implements Driver {

public JDBCDriver() { }

public Connection connect(String var1, Properties var2) throws SQLException {
//....
return getConnection (var1, var2);
}

public static Connection getConnection(String var0, Properties var1) throws SQLException {
//....
return new JDBCConnection(var2);
}

static {
try {
DriverManager.registerDriver(driverInstance);
} catch (Exception var1) {
;
}
}

DriverManager

* DriverManager管理了一组可用的驱动实现类。主要作用是维护驱动表和获取Connection

* 驱动注册表,为写时复制表。对表的遍历、add和remove实现了getDrivers() 、registerDriver和deregisterDriver方法。

* 当一个具体Driver实现类被加载时,在其Static会调用DriverManager.registerDriver以注册进来

* 提供getConnection()方法,根据url在表里找到指定的Driver对象,再调用connect(url, info)获取Connection。

* 静态代码调用loadInitialDrivers()方法,两个作用

  • 获取系统参数jdbc.drivers指定的Driver,通过Class.forName一一加载

  • 是使用JAVA SPI机制(服务发现机制),来自动的注册Driver

public class DriverManager {
// 驱动注册表,写时复制表,有加锁
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

public static synchronized void de/registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
if (driver != null ) {
// 把指定的 Driver 实现类注册到表表中
registeredDrivers .addIfAbsent(new DriverInfo(driver, da));
//deregisterDriver 移除 Driver
//registeredDrivers.remove(aDriver);
} else {
throw new NullPointerException();
}

}

private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
for (DriverInfo aDriver : registeredDrivers ) {
if (isDriverAllowed(aDriver.driver, callerCL)) {
try {
Connection con = aDriver.driver.connect(url, info);
return (con);
}
} catch (SQLException ex) {
if (reason == null ) {
reason = ex;
}
}
}
throw new SQLException("No suitable driver found for " + url, "08001" );
}

static {
loadInitialDrivers();
println("JDBC DriverManager initialized" );
}

private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers" );
}
});
} catch (Exception ex) {
drivers = null ;
}

AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//SPI 机制
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class );
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try {
while (driversIterator.hasNext()) {
driversIterator.next();
}
} catch (Throwable t) {
// Do nothing
}
return null ;
}
});
if (drivers == null || drivers.equals("" )) {
return ;
}
String\[\] driversList = drivers.split(":" );
for (String aDriver : driversList) {
try {
Class.forName(aDriver, true ,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
}

}
}

}

SPI

* 使用SPI机制的服务提供方,需要在classpath下的META-INF/services目录中新建一个以服务接口全路径为名的文件,内容则是服务实现类的全路径名,一行一个实现。

* 服务实现的查找、加载则是通过JDK自带的java.util.ServiceLoader类的load静态方法实现的

该方法会查找全部本地以及引入的jar包中的META-INF/services目录下的具体服务实现类,并且封装到一个ServiceLoader对象中,该对象是一个Iterable的实现,

可以进行for-each循环迭代!此后,我们就可以对ServiceLoader进行迭代来选择适合自己需求的服务实现,在迭代的时候会实例化全部服务实现类!

* DriverManager作为数据库驱动的发现与管理者,在加载DriverManager类时,就会在静态块中采用SPI机制通过ServiceLoader.load方法来发现和注册数据库驱动,

这里也是双亲委派模型的破坏的一个案例。

* 以下是mysql 8的驱动jar包中的文件,可以看到符合SPI的规则,那么DriverManager类在加载时将会在静态块张中自动加载mysql驱动,

因此如果我们使用DriverManager来操作数据库,那么我们根本不用手动通过Class.forName注册mysql驱动。

DataSource

* DataSouce接口的作用就是方法getConnection()返回Connection。但是底层还是借助DriverManager来获取实际的连接。只是它要求封装了Connection的整个获取流程。

把Connection提升为高一逻辑Connetion。用户只需通过DataSouce.getConnection()获取链接即可,至于此链接如何获取是不关注的。

* 对此,DataSouce实现类,可以根据不同的需求对Connetion进行不同的封装。

比如连接池DataSouce,他的getConnection()的实现就会从创建好的连接池里获取一个可以用的Connection,如果获取不到就会通过DriverManager去获取。等等

Statement

概述

* Statement就代表一次sql操作

  • 会封装好指定的sql语句、参数、其他查询属性等等,

  • 并基于Connetion提供的通信功能与目标数据库通信

  • Statement就会把结果封装好ResultSet对象返回。

* JDBC提供3个接口:Statement接口及子接口prepareStatement、callableStatement

Statement接口

创建Statement对象

在使用Statement对象执行SQL语句之前,需要使用Connection对象的createStatement()方法创建一个Statement对象,如以下示例所示:

Statement stmt = null;

stmt = conn.createStatement(

ResultSet.TYPE_FORWARD_ONLY,

ResultSet.CONCUR_UPDATABLE,

ResultSet.HOLD_CURSORS_OVER_COMMIT);

. . .

在创建Statement对象后,可以使用它来执行一个SQL语句,它有三个执行方法可以执行。它们分别是 -

boolean execute (String SQL) : 如果可以检索到ResultSet对象,则返回一个布尔值true; 否则返回false。使用此方法执行SQLDDL语句或需要使用真正的动态SQL,可使用于执行创建数据库,创建表的SQL语句等等。

int executeUpdate (String SQL): 返回受SQL语句执行影响的行数。使用此方法执行预期会影响多行的SQL语句,例如:INSERT,UPDATE或DELETE语句。

ResultSet executeQuery(String SQL):返回一个ResultSet对象。 当您希望获得结果集时,请使用此方法,就像使用SELECT语句一样

close():关闭Statement对

add/clear/excuteBatch():Batch相关操作

属性相关方法

PreparedStatement接口

PreparedStatement接口扩展了Statement接口,它添加了比Statement对象更好一些优点的功能。

此语句可以动态地提供/接受参数。

创建PreparedStatement对象

PreparedStatement pstmt = null;

try {

String SQL = "Update Employees SET age = ? WHERE id = ?";

pstmt = conn.prepareStatement(

SQL,

ResultSet.TYPE_FORWARD_ONLY,

ResultSet.CONCUR_UPDATABLE,);

. . .

}

JDBC中的所有参数都由 ? 符号作为占位符,这被称为参数标记。 在执行SQL语句之前,必须为每个参数(占位符)提供值。

setXXX()方法将值绑定到参数,其中XXX表示要绑定到输入参数的值的Java数据类型。 在其内部存在一个自动的数据转换的过程,将java类型转为对应的JDBC类。

每个参数标记是它其顺序位置引用。第一个标记表示位置1,下一个位置2等等。 该方法与Java数组索引不同(它不从0开始)。

所有Statement对象与数据库交互的方法(a)execute(),(b)executeQuery()和(c)executeUpdate()也可以用于PreparedStatement对象。 但是,这些方法被修改为可以使用输入参数的SQL语句。

ParameterMetaData()返回的是参数信息的实例,可获取参数的java类型、JDBC类型、值等。

close():关闭Statement对象

底层代码流程

下面以以org.hsqldb.jdbc为例,走源码流程

// 获取Connection对象

Connection connection = dataSource.getConnection();

PreparedStatement stmt = connection.prepareStatement("insert into user(create_time) values(?);");

stmt.setString(1,"2010-10-24 10:20:30");

ParameterMetaData pmd = stmt.getParameterMetaData();

resultSet rs = stmt.execute();

* 通过Connection创建JDBCPreparedStatement,并把Connetion传递进去。

Connection代码本身就只提供创建的动作,没有其他处理。就类似于PreparedStatement的构建者

public class JDBCConnection implements Connection {
public synchronized PreparedStatement prepareStatement(String var1) throws SQLException {
this .checkClosed();

try {
// 注意这里把 Connection sql 传进去 PreparedStatement
return new JDBCPreparedStatement(this , var1, 1003, 1007, this .rsHoldability, 2, (int \[\])null , (String\[\])null );
} catch (HsqlException var3) {
throw JDBCUtil.sqlException(var3);
}
}
}

* 创建JDBCPreparedStatement时,需要初始化好必要的属性

  • 保存传递进来的Connetion对象

  • 处理一下sql,并提前调用数据库执行下sql,以获取此sql相关的数据库元数据信息,并初始JDBCPreparedStatement对应的属性,把相应的信息封装到resultOut对象

resultOut封装当前JDBCPreparedStatement指定的sql执行的相关消息,包括sql语句、参数、结果类型等等属性,

在与hsqldb数据库交互时,会直接把此对象发送过去执行,同理数据库也会把结果封装到这个对象中返回

public class JDBCPreparedStatement extends JDBCStatementBase implements PreparedStatement {
// 主要属性
protected Object\[\] parameterValues ; // 参数列表,下标表示顺序、值为入参
protected ResultMetaData resultMetaData ;
protected ParameterMetaData pmd ;
protected String sql ;
protected long statementID ;
protected int statementRetType ;
protected final boolean isResult ;
protected SessionInterface session ;
// 父类继承的
protected JDBCConnection connection ;
protected JDBCResultSet currentResultSet ;
// 封装当前 JDBCPreparedStatement 指定的 sql 执行的相关消息,包括 sql 语句、参数、结果类型等等属性
// 在与 hsqldb 数据库交互时,会直接把此对象发送过去执行,同理数据库也会把结果封装到这个对象中返回
protected Result resultOut ;

// 创建实例
JDBCPreparedStatement(JDBCConnection var1, String var2, int var3, int var4, int var5, int var6, int \[\] var7, String\[\] var8) throws HsqlException, SQLException {
this .connection = var1; // 传递过来的 Connetion 对象
this .connectionIncarnation = this .connection .incarnation;
this .session = var1.sessionProxy;// 传递过来的 Connetion.sessionProxy
var2 = var1.nativeSQL(var2); // 传递过来的 sql
// sql 保存在 resultOut 对象中
this .resultOut .setPrepareOrExecuteProperties(var2, 0, 0, 0, this .queryTimeout, var9, var6, var7, var8);
this .sql = var2;
// 其他属性的初始化;并提前执行下 sql ,以获取此 sql 相关的数据库元数据信息 var10
Result var10 = this .session .execute(this .resultOut );
if (var10.mode == 2) {
throw JDBCUtil.sqlException(var10);
} else {
//...
this .statementID = var10.getStatementID();
this .statementRetType = var10.getStatementType();
this .resultMetaData = var10.metaData;
this .parameterMetaData = var10.parameterMetaData;
this .rsProperties = var10.rsProperties;
//...
}
}

}

* 设置参数

处理完参数的下标、对应的JDBC类型(去处理映射)之后存于JDBCPreparedStatement的相关属性parameterValues

public class JDBCPreparedStatement extends JDBCStatementBase implements PreparedStatement {
//...
void setParameter(int var1, Object var2) throws SQLException {
// 经过类型转换处理 , 确定和记录类型等处理
//...
this .parameterValuesvar1 = var2;
this .parameterSetvar1 = Boolean.TRUE ;
return ;

}
}

* 执行JDBCPreparedStatement

  • 需要借助Connetion提供的通信功能,于目标数据库进行交互,底层借助数据流DataInput/OutputStream

  • JDBCPreparedStatement包含了参数信息,resultOut也会作为参数传递给目标数据库,里面包含了sql执行的相关消息,包括sql语句、参数、结果类型等等属性

  • 执行之后,返回结果流写入这个resultOut中,把结果封装为ResultSet后之后返回即可

public synchronized boolean execute() throws SQLException {
this .fetchResult();
return this .statementRetType == 2;
}
void fetchResult() throws SQLException {
//... 通过 Conntion 提供的 session 去执行当前的 JDBCPreparedStatement
this .resultIn = this .session.execute(this .resultOut);
//... 处理结果
this .currentResultSet = new JDBCResultSet(this .connection, this , this .resultIn, this .resultIn.metaData);
//...
}
// 使用客户端 session 这种方法和数据库进行底层的通信(代码叫为简便,忽略了 http 通信代码)
// 底层使用了网络流进行读写
public synchronized Result execute(Result var1) {
if (this .isClosed) {
return Result.newErrorResult(Error.error(1353));
} else {
try {
var1.setSessionId(this .sessionID);
var1.setDatabaseId(this .databaseID);
this .write(var1);
return this .read();
} catch (Throwable var3) {
throw Error.error(1305, var3.toString());
}
}
}

ResultSet接口

概述

* ResultSet实现类封装了结果集,底层应该有一个继承了iterator集合表示table,元素为一行记录对象,且保存了结果集元数据。

* 提供操作结果集的方法,主要为

  • 对ResultSet游标的操作方式,即控制访问结果集的前后方向、修改权限、定位方法等

  • 对ResultSet的修改对数据库的影响,即ResultSet与数据库是否强关联

对ResultSet游标的操作方式

* 在创建Statement时,可以指定游标的操作方式,如

stmt = conn.createStatement(

ResultSet.TYPE_FORWARD_ONLY,

ResultSet.CONCUR_UPDATABLE,

ResultSet.HOLD_CURSORS_OVER_COMMIT);

  • FETCH_FORWARD:指定处理记录集中行的顺序是由前到后,即从第一行开始处理

  • FETCH_REVERSE:指定处理记录集中行的顺序是由后到前,即从最后一行开始处理。

  • FETCH_UNKNOWN不指定处理记录集中行的顺序,由JDBC驱动程序和数据库系统决定。

  • TYPE_FORWARD_ONLY:指定数据库游标的移动方向是向前,不允许向后移动,即只能使用ResultSet接口的next()方法,而不能使用previous()方法,否则会产生错误。

  • TYPE_SCROLL_INSENSITIVE:指定数据库游标可以在记录集中前后移动,并且当前数据库用户获取的记录集对其他用户的操作不敏感,

就是说,当前用户正在浏览记录集中的数据,与此同时,其他用户更新了数据库中的数据,但是当前用户所获取的记录集中的数据不会受到任何影响。

  • TYPE_SCROLL_SENSITIVE:指定数据库游标可以在记录集中前后移动,并且当数据库用户获取的记录集对其他用户的操作敏感,建议慎重使用该常数。

  • CONCUR_READ_ONLY: 指定当前记录集的协作方式为只读

  • CONCUR_UPDATABLE:指定当前记录集的协作方式为读写

  • HOLD_CURSORS_OVER_COMMIT:当调用Connection.commit()时,不关闭当前事务创建的ResultSet

  • CLOSE_CURSORS_AT_COMMIT:当调用Connection.commit()时,会关闭当前事务创建的ResultSet

* 提供了一整套的游标一定方法

  • absolute(int row);

该方法的作用是将记录集中的某一行设定为当前行,亦即将数据库游标移动到指定的行,参数row指定了目标行的行号,

这是绝对的行号,由记录集的第一行开始计算, 不是相对的行号。

  • relative(int rows);

该方法的作用也是将记录集中的某一行设定为当前行,但是它的参数rows表示目标行相对于当前行的行号,例如当前行是第3行,现在需要移动么第6行去,既可以使用

  • first/last()

该方法的作用是将当前行定位到数据库记录集的第一行/最后一行。

  • isFirst/isLast/isAfterLast()

该方法的作用是检查当前行是否记录集的第一行,如果是,返回true,否 则,返回false。略

  • afterLast/beforeFirst/beforeFirst/beforeFirst/isAfterLast();

该方法的作用是将数据库游标移到记录集的最后,位于记录集最后一行的 后面,如果该记录集不包含任何的行,该方法不产生作用。略

  • next();

该方法的作用是将数据库游标向前移动一位,使得下一行成为当前行,当刚 刚打开记录集对象时,数据库游标的位置在记录集的最前面,

第一次使用next()方法,将会使数据库游标定位到记录集的第一行,第二次使用next()方法,将会使数据库游标定位到记录集的第二行,以此类推。

注意:如果在当前行打开了一个输入流(Input Stream),那么再次使用next()方法时,将会自动关闭 该输入流。

  • previous()

该方法的作用是将数据库游标向后移动一位,使得上一行成为当前行。

-getRow()

返回记录集最后一行的行索引。该索引就等于记录集所包含记录的个数,也就 是记录集的行数

  • ResultSet接口添加了对行操作的支持 前提操作方式为CONCUR_UPDATABLE

如rowDeleted();如果当前记录集的某行被删除了,那么记录集中将会留出一个空位,

调用rowDelete()方法,如果探测到空位的存在,那么就返回true,如果没有要测到空位的存在,就返回false值。

其他略

关闭ResultSet

DatabaseMateData

* 表示数据库元信息,可通过Connection.getMetadata获取

  • 数据源的基本信息

如URL、版本、null排序相关、关键字等

  • 数据源支持特性

查询是否支持事务相关、批量更新、存储过程、大小写忽略等

  • 数据源限制

获取最大行数、最大连接数、最大字符、最大列数等

  • 获取sql相关对象属性

获取Schema、表、主键、函数等

JDBC事务

* 代码实例

// 内部会调用静态方法 DriverManager.registerDriver(new Driver()) 把自己注册到 registeredDrivers;
Class.forName("com.mysql.jdbc.Driver" );
// 底层使用 Connection con = driver.connect(url, info); 使用 Driver connect 方法获取实际的数据库连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" , "root" , "mysql" );
connection.setTransactionIsolation(TransactionIsolationLevel.REPEATABLE_READ);

// 设置手动提交事务
connection.setAutoCommit(false );

// 查,第一条执行操作。事务就开始了
String sql = "select ...." ;
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, " 王五 " );
resultSet rs = preparedStatement.executeQuery();
while (rs.next()){
String username = rs.getString("user_name" );
}

// 增删改
String sql2 = "update/delete ...." ;
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.setString(1, " 王五 " );
int i = preparedStatement.executeUpdate();

resultSet.close();
preparedStatement.close();
connection.commit(); // 提交事务
connection.close();

* 一个事务里的操作必须使用同一个Connection连接

* 何时开启一个事务是由JDBC驱动或数据库隐式决定的。

一般当sql语句需要事务时就会开启,什么sql语句需要事务由SQL标准规范决定

* Connection的autoCommit属性决定什么时候结束一个事务,设置自动提交时每个sql语句执行完就提交事务;设置手动提交时就需要手动commit()或rollback()

* 事务级别,默认级别由JDBC驱动决定,通常就是目标数据库的默认级别,可以通过Connection的TransactionIsolation属性来设置

整体框架概况

架构图

核心部件

SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能

Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护

StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将结果集转换成List集合。

ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,

ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;

TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换

MappedStatement 维护了一条<select|update|delete|insert>节点的封装,

SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

BoundSql 表示动态生成的SQL语句以及相应的参数信息

Configuration MyBatis所有的配置信息都维持在Configuration对象之中。

实例代码

/*
* 1. 加载 mybatis 的配置文件,初始化 mybatis ,创建出 SqlSessionFactory ,是创建 SqlSession 的工厂
* 为了演示的需要, SqlSessionFactory 临时创建出来,在实际的使用中, SqlSessionFactory 只需要创建一次,当作单例来使用
*/
InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml" );
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder .build(inputStream );

//2. SqlSession 工厂 SqlSessionFactory 中创建一个 SqlSession ,进行数据库操作
// mybatis 默认每次获取 session 都会开启一个事务,且不自动提交事务。如果更新操作完成后不手动 commit ,则在连接断开时会将更新操作回滚
SqlSession sqlSession = factory .openSession(); // 默认为 false 手动提交, true- 自动提交

//3. 使用 SqlSession 查询
Map<String,Object> params = new HashMap<String,Object>();
params.put("min_salary" ,10000);
// 查询工资低于 10000 的员工
List<Employee> result = sqlSession.selectList("com.dao.EmployeesMapper.selectByMinSalary" ,params);
System.out.println(" 薪资低于 10000 的员工数: " +result.size());//~output : 查询到的数据总数: 5

//4. 使用 SqlSession 修改
Map<String,Object> params = new HashMap<String,Object>();
params.put("user_name" ,"kaka" );
params.put("min_salary" ,20000);
// 查询工资低于 10000 的员工
int result = sqlSession.update("com.dao.EmployeesMapper.updateSalary" ,params);
// 需要手动提交
sqlSession.commit();

// 关闭 sqlSession
sqlSession.close();

<select id ="selectByMinSalary" resultMap ="BaseResultMap" parameterType ="java.util.Map" >
select
EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
from LOUIS.EMPLOYEES
<if test ="min_salary != null" >
where SALARY < #{min_salary,jdbcType=DECIMAL}
</if >
</select >

获取SqlSessionFactory

概述

* SqlSessionFactory(会话工厂,单例的),根据对应的配置文件创建工厂

SqlSessionFactory= SqlSessionFactoryBuilder().build(inputStream);inputStream为 SqlMapConfig.xml

作用:创建SqlSession = SqlSessionFactory.openSession()

* 在创建工厂的过程中,就伴随着对主配置文件的解析,里面就明确指定了数据源的设置

也就是说可以把SqlSessionFactory当做DataSource维度的东西,其每次创建的SqlSession = SqlSessionFactory.openSession()就是一个Connection

Builder 解析 configuration .xml构建DefaultSqlSessionFactory

* 主体流程代码

InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml" );
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream,environment,properties);

* SqlSessionFactoryBuilder使用构建者模式,用于接受以上三个参数的组合,构建SqlSessionFactory

  • Reader reader:mybatis的全局配置文件,这是必要的参数

  • String environment:指定配置文件中使用哪个<environments>

  • Properties properties:属性配置

* 使用如上的参数构建XMLConfigBuilder

执行其parse方法,对xml文件进行解析,解析结果封装为Configuration对象

* 最后使用此Configuration对象封装到DefaultSqlSessionFactory返回

public class SqlSessionFactoryBuilder {

public SqlSessionFactory build(Reader reader) {
return build(reader, null , null );
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null );
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null , properties);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException ("Error building SqlSession." , e);
} finally {
ErrorContext.instance ().reset();
try {
reader.close();
} catch (IOException e) {
}
}
}

public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null , null );
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null );
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null , properties);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 把配置文件输入流、环境对象、属性对象封装为 XMLConfigBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 执行 XMLConfigBuilder parse 返回解析完成的 Configuration 对象
// 使用此 Configuration 对象封装到 DefaultSqlSessionFactory 返回
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException ("Error building SqlSession." , e);
} finally {
ErrorContext.instance ().reset();
try {
inputStream.close();
} catch (IOException e) {
}
}
}

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

}

DefaultSqlSessionFactory

* SqlSessionFactory接口

  • getConfiguration():获取会话工厂的配置对象

  • openSession():打开一个sql会话对象SqlSession,此作用和一个JDBC Connection会话连接的作用是类似的

public interface SqlSessionFactory {

SqlSession openSession();

SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);

SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);

Configuration getConfiguration();

}

* DefaultSqlSessionFactory实现

  • 构造参数中封装了从SqlSessionFactoryBuilder解析出来的Configuration configuration

  • openSession的实现,基于Transaction在封装为Executor,最后Configuration 、Executor一并封装为DefaultSqlSession

  • TransactionFactory工厂有3种:

jdbc:依赖于从environment.getDataSource或者参数传递的Connection对象数据源得到的连接来管理事务作用域

managed:和jdbc类似,它从来不提交或回滚一个连接(commit() 和rollback()是空实现),而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。

默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为

SpringManagedTransactionFactory:从 Spring管理之中获取连接,此连接会优先从当前线程变量中获取,这样就整和Spring事务

*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {

private final Configuration configuration ;

public DefaultSqlSessionFactory(Configuration configuration) {
this .configuration = configuration;
}

@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration .getDefaultExecutorType(), null , false );
}

@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration .getDefaultExecutorType(), null , autoCommit);
}

@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null , false );
}

@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration .getDefaultExecutorType(), level, false );
}

@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false );
}

@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null , autoCommit);
}

@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration .getDefaultExecutorType(), connection);
}

@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}

@Override
public Configuration getConfiguration() {
return configuration ;
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null ;
try {
// 获取 Mybatis 主配置文件配置的环境信息
final Environment environment = configuration .getEnvironment();
// 创建事务管理器工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务管理器
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据 Mybatis 主配置文件中指定的 Executor 类型创建对应的 Executor 实例
final Executor executor = configuration .newExecutor(tx, execType);
// 创建 DefaultSqlSession 实例
return new DefaultSqlSession(configuration , executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException ("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance ().reset();
}
}

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true ;
}
final Environment environment = configuration .getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration .newExecutor(tx, execType);
return new DefaultSqlSession(configuration , executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException ("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance ().reset();
}
}

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null ) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}

private void closeTransaction(Transaction tx) {
if (tx != null ) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}

}

解析 配置文件 Configuration

解析的时机在使用SqlSessionFactoryBuilder进行build的方法里面

执行XMLConfigBuilder的parse返回解析完成的Configuration对象

InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml" );
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {

try {

// 把配置文件输入流、环境对象、属性对象封装为 XMLConfigBuilder

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

// 执行 XMLConfigBuilder parse 返回解析完成的 Configuration 对象

// 使用此 Configuration 对象封装到 DefaultSqlSessionFactory 返回

return build(parser.parse());

..

}

九大类全局配置文件内容

properties属性配置

properties为属性配置文件,它为上下文提供相关的资源,properties有三种配置资源的方式:

* 通过properties标签子元素配置

通过properties子元素property配置username和password变量,然后在properties下文,如environment节点中引用这些变量,${key}

<properties >
<property name ="username" value ="root" />
<property name ="password" value ="root" />
</properties >

* 通过properise标签的属性配置

<properties resource ="demo/mybatis/resources/jdbc.properties" />

* 通过属性参数传递配置,在最初的SqlSessionFactoryBuilder中传递

把属性传递到new SqlSessionFactoryBuilder().build(reader, environment, props);props会存为Configuration的 variables字段。

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
this .configuration.setVariables(props);
// 。。。
}

* 当在mybatis配置文件中同时存在三种方式时,执行顺序如下,排在后面的配置覆盖排在前面同名属性的配置

  • 首先读取properties子元素属性--defaults.putAll(properties)

  • 次读取properties元素的属性指定的文件(resource和url) --defaults.putAll(resource和url)

  • 最后读取作为方法参数传递的属性,并覆盖以读取的同名属性 ----defaults.putAll(Configuration.getVariables)

configuration.setVariables(defaults)

settings 运行时配置选项

settings决定mybatis运行时行为,均有很多可配置的行为,如下为一个较完整的settings配置

<settings >
<!-- 全局全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true-->
<setting name ="cacheEnabled" value ="true" />
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置
fetchType 属性来覆盖该项的开关状态。默认值为 false -->
<setting name ="lazyLoadingEnabled" value ="false" />
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 , 默认值 false-->
<setting name ="aggressiveLazyLoading" value ="false" />
<!-- 是否允许单一语句返回多结果集 , 默认值为 true -->
<setting name ="multipleResultSetsEnabled" value ="true" />
<!-- 使用列标签代替列名 , 默认值为 true -->
<setting name ="useColumnLabel" value ="true" />
<!-- 允许 JDBC 支持自动生成主键,需要驱动兼容 , 默认值为 false -->
<setting name ="useGeneratedKeys" value ="false" />
<!-- 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射; PARTIAL
只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集
(无论是否嵌套) , 默认值为 PARTIAL-->
<setting name ="autoMappingBehavior" value ="PARTIAL" />
<!-- 指定发现自动映射目标未知列(或者未知属性类型)的行为。 NONE: 不做任何反应;
ARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'
的日志等级必须设置为 WARN) FAILING: 映射失败 ( 抛出 SqlSessionException) ,默认值为 NONE -->
<setting name ="autoMappingUnknownColumnBehavior" value ="NONE" />
<!-- 配置默认的执行器。 SIMPLE 就是普通的执行器; REUSE 执行器会重用预处理语句( prepared statements );
BATCH 执行器将重用语句并执行批量更新。默认值为 SIMPLE -->
<setting name ="defaultExecutorType" value ="SIMPLE" />
<!-- 设置超时时间,它决定驱动等待数据库响应的秒数。参数为任意正整数,未设置默认值 -->
<setting name ="defaultStatementTimeout" value ="25" />
<!-- 为驱动的结果集获取数量( fetchSize )设置一个提示值。此参数只可以在查询设置中被覆盖。参数为任意正整数,
未设置默认值 -->
<setting name ="defaultFetchSize" value ="100" />
<!-- 允许在嵌套语句中使用分页( RowBounds )。如果允许使用则设置为 false ,默认值未 false -->
<setting name ="safeRowBoundsEnabled" value ="false" />
<!-- 允许在嵌套语句中使用分页( ResultHandler )。如果允许使用则设置为 false 。默认值为 true -->
<setting name ="safeResultHandlerEnabled" value ="true" />
<!-- 是否开启自动驼峰命名规则( camel case )映射,即从经典数据库列名 A_COLUMN 到经典 Java
属性名 aColumn 的类似映射 , 默认值为 false -->
<setting name ="mapUnderscoreToCamelCase" value ="false" />
<!--MyBatis 利用本地缓存机制( Local Cache )防止循环引用( circular references )和加速重复嵌套查询。
默认值为 SESSION ,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT ,本地会话仅用在语
句执行上,对相同 SqlSession 的不同调用将不会共享数据。 -->
<setting name ="localCacheScope" value ="SESSION" />
<!-- 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情
况直接用一般类型即可,比如 NULL VARCHAR OTHER 。默认值为 OTHER -->
<setting name ="jdbcTypeForNull" value ="OTHER" />
<!-- 指定哪个对象的方法触发一次延迟加载。 -->
<setting name ="lazyLoadTriggerMethods" value ="equals,clone,hashCode,toString" />
<!-- 指定动态 SQL 生成的默认语言。 -->
<setting name ="defaultScriptingLanguage" value ="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver" />
<!-- 指定当结果集中值为 null 的时候是否调用映射对象的 setter map 对象时为 put )方法,这对于有 Map.keySet() 依赖或
null 值初始化的时候是有用的。注意基本类型( int boolean 等)是不能设置成 null 的。 -->
<setting name ="callSettersOnNulls" value ="false" />
<!-- 当返回行的所有列都是空时, MyBatis 默认返回 null 。 当开启这个设置时, MyBatis 会返回一个空实例。 请注意,它也适用于嵌套的
结果集 (i.e. collectioin and association) 。(从 3.4.2 开始 -->
<setting name ="returnInstanceForEmptyRow" value ="false" />
<!-- 指定 MyBatis 增加到日志名称的前缀 -->
<setting name ="logPrefix" value ="log" />
<!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING -->
<setting name ="logImpl" value ="LOG4J" />
<!-- 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 CGLIB | JAVASSIST-->
<setting name ="proxyFactory" value ="JAVASSIST" />
<!-- 指定 VFS 的实现 -->
<setting name ="vfsImpl" value ="vfs" />
<!-- 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用 Java 8 编译,并且加上 -parameters 选项。
(从 3.4.1 开始) -->
<setting name ="useActualParamName" value ="true" />
<!-- 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的懒加载属性值。
这个类必须包含一个签名方法 static Configuration getConfiguration(). ( 3.2.3 版本开始 )-->
<setting name ="configurationFactory" value ="configClass" />
</settings >

typeAliases 别名配置

* 类型别名为Java类型设置一个简短名字,它只与Mapper.xml有关,用来减少类完全限定名的冗余

这样配置后,任何使用demo.mybatis.entity.UserInfo的地方,都可用UserInfo来代替

<typeAliases >
<typeAlias type ="demo.mybatis.entity.UserInfo" alias ="UserInfo" />
</typeAliases >

* 除此之外,还可以指定一个包名,mybatis会在包名下搜索需要的Java Bean,比如:

<typeAliases >
<package name ="demo.mybatis.entity" />
</typeAliases >

* 可以使用注解@Alias指定别名

在没注解的情况下,每个在demo.mybatis.entity中的Java Bean,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如demo.mybatis.entity.UserInfo的别名为userInfo.

@Alias("Users" )
public class User{ }

typeHandlers 类型转换器配置

* typeHandlers主要将获取的值合理地转化为java类型,可以转换预处理阶段(PreparedStatement)的参数和结果集中的值。

* mybatis提供了标准的类型处理

|-------------------------|---------------------|---------------------------------------|
| 类型处理器 | Java 类型 | JDBC 类型 |
| BooleanTypeHandler | Boolean,boolean | 任何兼容的布尔值:BIT |
| ByteTypeHandler | Byte,byte | 任何兼容的数字或字节类型 |
| ShortTypeHandler | Short,short | 任何兼容的数字或短整型:TINYINT、SMALLINT |
| IntegerTypeHandler | Integer,int | 任何兼容的数字和整型:INTEGER |
| LongTypeHandler | Long,long | 任何兼容的数字或长整型:BINGINT |
| FloatTypeHandler | Float,float | 任何兼容的数字或单精度浮点型:REAL |
| DoubleTypeHandler | Double,double | 任何兼容的数字或双精度浮点型:FLOAT、DOUBLE |
| BigDecimalTypeHandler | BigDecimal | 任何兼容的数字或十进制小数类型:DECIMAL、NUMERIC |
| StringTypeHandler | String | CHAR和VARCHAR类型 |
| ClobTypeHandler | String | CLOB和LONGVARCHAR类型 |
| NStringTypeHandler | String | NVARCHAR和NCHAR类型 |
| NClobTypeHandler | String | NCLOB类型 |
| ByteArrayTypeHandler | byte\[\] | 任何兼容的字节流类型BINARY、VARBINARY、LONGBINARY |
| BlobTypeHandler | byte\[\] | BLOB和LONGVARBINARY类型 |
| DateTypeHandler | Date(java.util) | TIMESTAMP类型 |
| DateOnlyTypeHandler | Date(java.util) | DATE类型 |
| TimeOnlyTypeHandler | Date(java.util) | TIME类型 |
| SqlTimestampTypeHandler | Timestamp(java.sql) | TIMESTAMP类型 |
| SqlDateTypeHandler | Date(java.sql) | DATE类型 |
| SqlTimeTypeHandler | Time(java.sql) | TIME类型 |
| ObjectTypeHandler | 任意 | 其他或未指定类型 |
| EnumTypeHandler | Enumeration类型 | VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。 |

* 同时,mybatis也支持之定义处理类型,在自定义处理类型时,只需实现org.apache.ibatis.type.TypeHandler接口,或继承org.apache.ibatis.type.BaseTypeHandler

例如向数据库写数据,要写的是一个Date对象,但是写到数据库(数据库对应类型为VARCHAR)之后这个Date对象就变成了Date对象所描述的时间到1970年的秒数了,

然后当我从数据库读取这个秒数之后,系统又会自动帮我将这个秒数转为Date对象

  • 实现BaseTypeHandler接口,定义映射关系

@MappedJdbcTypes({JdbcType.VARCHAR }) // 定义的是 JdbcType 类型,必须要是枚举类 org.apache.ibatis.type.JdbcType 所枚举的数据类型
@MappedTypes({Date.class }) // 定义的是 JavaType 的数据类型,描述了哪些 Java 类型可被拦截
public class MyDateTypeHandler extends BaseTypeHandler<Date> {

// 在我们启用了我们自定义的这个 TypeHandler 之后,数据的读写都会被这个类所过滤
// setNonNullParameter 方法中,我们重新定义要写往数据库的数据
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, String.valueOf (date.getTime()));
}

// 在另外三个方法中我们将从数据库读出的数据类型进行转换
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
return new Date(resultSet.getLong(s));
}

public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
return new Date(resultSet.getLong(i));
}

public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return callableStatement.getDate(i);
}
}

  • 在全局配置文件中进行配置

我们需要在我们的mybatis配置文件中注册typeHandler,注册有两种不同的方式,可以像下面这样一个类一个类的注册:

<typeHandlers >
<typeHandler handler ="org.sang.db.MyDateTypeHandler" />
</typeHandlers >

也可以直接注册一个包中所有的typeHandler,系统在启动时会自动扫描包下的所有文件,如下:

<typeHandlers >
<package name ="org.sang.db" />
</typeHandlers >

  • 在Mapper中进行配置

自定义好了typeHandler之后,接下来我们需要在userMapper.xml中进行简单的配置,首

-- 查询结果映射配置

先我们可以像上文说的,配置resultMap,如下:

<resultMap id ="userResultMap" type ="org.sang.bean.User" >
<result typeHandler ="org.sang.db.MyDateTypeHandler" column ="regTime" javaType ="java.util.Date"
jdbcType ="VARCHAR"
property ="regTime" />
</resultMap >

配置resultMap的时候我们指定了javaType和jdbcType,同时也指定了处理的typeHandler,然后在select中使用这个resultMap:

<select id ="getUser" resultMap ="userResultMap" >
select * from user4
</select >

--- 插入的时候启用自定义的typeHandler

需要我们在insert节点中简单配置一下,如下:

<insert id ="insertUser" parameterType ="org.sang.bean.User" >
INSERT INTO user4(username,password,regTime) VALUES (#{username},#{password},#{regTime,javaType=Date,jdbcType=VARCHAR,typeHandler=org.sang.db.MyDateTypeHandler})
</insert >

也可以只配置javaType和jdbcType,如下:

<insert id ="insertUser2" >
INSERT INTO user4(username,password,regTime) VALUES (#{username},#{password},#{regTime,javaType=Date,jdbcType=VARCHAR})
</insert >

或者只配置typeHandler

<insert id ="insertUser3" >
INSERT INTO user4(username,password,regTime) VALUES (#{username},#{password},#{regTime,typeHandler=org.sang.db.MyDateTypeHandler})
</insert >

O bejctFactory /ObjectWrapperFactory/ReflectorFactory工厂

*ObejctFactory对象工厂

  • 当其sql映射配置文件中的 sql 语句所得到的查询结果,被动态映射到 resultType 或其他处理结果集的参数配置对应的 Java 类型,其中就有 JavaBean 等封装类。

而objectFactory 对象工厂就是用来创建实体对象的类,有两种方式可以将查询结果的值映射到对应的目标类,

一种是通过目标类的默认构造方法,另外一种就是通过目标类的有参构造方法。

  • 有时候创建实体对象时(通过构造方法或者有参构造方法),在得到对象之前需要处理一些逻辑,或者在执行该类的有参构造方法时,在传入参数之前,要对参数进行一些处理,

这时就可以创建自己的 objectFactory 来加载该类型的对象

<objectFactory type ="com.daily.objectfactory. GP ObjectFactory" >
<property name ="prop1" value ="value1" />
</objectFactory >

plugins 拦截器配置

即在MyBatis执行过程中暴露出来的可以加进行拦截处理的地方,可以自定义自己的interceptor

<plugins >
<plugin interceptor ="com.example.ExamplePlugin" >
<property name ="someProperty" value ="100" />
</plugin >
</plugins >

environments 数据源配置

* environments数据源环境配置,mybatis支持多种配置,从而有助于适应多种数据库。

  • 但需要注意的是,尽管可以配置多个环境,一个SqlSessionFactory实例只能选择其一。

  • 一个SqlSessionFactory在创建的时候就已经指定了一个具体的DataSource

* 在SqlSessionFactoryBuilder可以指定使用一个id字符串标识指定哪一个environments数据源

没有指定的话使用environments标签的default属性指定了

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

* 如下为一个环境配置示例

<!-- default 默认的环境 ID -->
<environments default ="development" >
<!-- 一个 environment 的配置, id 是唯一标识 -->
<environment id ="development" >
<!-- 事务管理器的配置 -->
<transactionManager type ="JDBC" />
<!-- 数据源的配置 -->
<dataSource type ="POOLED" >
<property name ="driver" value ="{driver}"**** /\> \<****property**** ****name**** ****="url"**** ****value**** ****="{url}" />
<property name ="username" value ="{username}"**** /\> \<****property**** ****name**** ****="password"**** ****value**** ****="{password}" />
</dataSource >
</environment >
</environments >

* 事务管理器:

Mybatis支持两种类型的事务管理器工厂:

typeAliasRegistry .registerAlias("JDBC" , JdbcTransactionFactory.class );

typeAliasRegistry .registerAlias("MANAGED" , ManagedTransactionFactory.class );

  • JDBC,对应JdbcTransactionFactory实现类

所有涉及事务相关的操作如提交回滚,都是直接依赖于构造参数传递进来数据源DataSource或者Connection来实现的

  • MANAGED,对应ManagedTransactionFactory实现类

会接依赖于构造参数传递进来数据源或者Connection来获取一个数据库连接。

但是它提交回滚方法实现时空方法体,就是说让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。

默认情况下它是的close()方法会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。例如:

<transactionManager type ="MANAGED" >

<property name ="closeConnection" value ="false" />

</transactionManager >

  • 有些框架会自定义TransactionFactory实现类

它不会在这里配置,而是在创建 DefaultSqlSessionFactory(Configuration configuration) 是,在其参数Configuration configuration中进行设置

全局配置文件xml的所有消息都会解析为Configuration configuration,其他框架可以手动setting此Configuration对象的Enviroment属性就会覆盖xml中指定的

例如Spring适配MyBatis时,使用SpringManagedTransactionFactory作为其选择,他会优先从当前线程本地变量ConnectionHolder中获取DataSource作为底层数据源

其事务相关方法如提交回滚就会取决ConnectionHolder的处理,这时为了适配@Transactional注解的事务管理

* dataSource数据源设置

配置JDBC连接对象的资源,有三种内建的数据源类型:

typeAliasRegistry .registerAlias("JNDI" , JndiDataSourceFactory.class );

typeAliasRegistry .registerAlias("POOLED" , PooledDataSourceFactory.class );

typeAliasRegistry .registerAlias("UNPOOLED" , UnpooledDataSourceFactory.class );

  • UNPOOLED,对应的实现类是UnpooledDataSourceFactory

底层UnpooledDataSource类是对JDBC DataSource接口的简单实现,其底层实现都是每次从驱动中获取:Connection connection = DriverManager.getConnection(url, properties);

这个数据源的实现只是每次被请求时打开和关闭连接

  • POOLED,对应的实现类是PooledDataSourceFactory

这种数据源的实现利用"池"的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间

这是MyBatis提供的数据库连接池的简单实现,其原理和其他中间件类似

  • JNDI,对应的实现类是PooledDataSourceFactory

这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用

使用JNDI寻找数据源:dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));

databaseIdProvider 多数据库支持

* 有什么作用

databaseIdProvider和databaseId的作用简单来说就是让一个项目支持不同的数据库。

由于不同的数据库支持的sql语法稍有差别,所以某些功能需要根据数据库的不同书写不同的sql语句。

对于这种需求,首先能够想到的解决方案就是针对不同的数据库维护不同的mapper.xml文件,但是这种方案会严重增加开发和维护的成本。

因为不同数据库支持的语法大部分都是相同的,不同的毕竟是少数,我们希望只在同一个Mapper里重写不同的部分而重用相同的部分。

* 针对这种情况,MyBatis提供了解决方案,即databaseIdProvider和databaseId。通过MyBatis提供的这种功能,我们就只需要维护一套mapper.xml文件便可。

  • 一个SqlSessionFactory在创建的时候就已经指定了一个具体的DataSource

  • mybatis在初始化的时候会根据所使用的数据源得到当前databaseId的名称,得到的databaseId的名称供mybatis选择映射文件中相应的语句。

比如下面的配置,我们使用的是Mysql数据库,则得到的databaseId名称为"mysql"。

  • 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略"。

也就是说如果在mybatis的配置文件中没有配置 databaseIdProvider,则在映射文件中配置的databaseId不会生效

* 如何配置

  • 配置databaseIdProvider(在mybatis的配置文件中配置,比如mybatis-config.xml)

<databaseIdProvider type ="DB_VENDOR" >
<property name ="DB2" value ="db2" />
<property name ="Oracle" value ="oracle" />
<property name ="Adaptive Server Enterprise" value ="sybase" />
<property name ="MySQL" value ="mysql" />
</databaseIdProvider >

上述配置用于决定当前databaseId的名称。在每一个property标签中,name代表数据库的productName(DatabaseMetaData#getDatabaseProductName()),

value是用户自定义的databaseId名称。比如我们使用的是Mysql数据库,则得到的databaseId名称为"mysql"。

  • 配置databaseId(在映射文件中配置,比如:BlogMapper.xml)

<mapper namespace ="info.songjie365.mybatis.sample.mapper.BlogMapper" >
<select id ="selectBlog" resultType ="info.songjie365.mybatis.sample.domain.Blog" databaseId ="mysql" >
select * from blog where id = #{id}
</select >
</mapper >

mappers 文件路径指定

mappers关联映射配置文件,即告诉mybatis到哪里去找映射配置文件。有四种基本方式可以关联映射配置文件:

<!-- 方式一:类路径方式:使用相对于类路径的资源引用 -->
<mapper resource ="demo/mybatis/resources/userInfo-config.xml" />

<!-- 方式二:使用完全限定资源定位符 URL
假设映射文件路径为: E:\userInfo-config.xml
则采用 url 方式如下: ( 格式为 file:///url)-->
<mapper url ="file:///E:/userInfo-config.xml" />

<!-- 方式三:使用映射器接口实现类的完全限定名 -->
<mapper class ="demo.mybatis.service.UserInfoAnnotation" />

<!-- 方式四:将包内的映射器接口实现全部注册为映射器 -->
<package name ="demo.mybatis.service" />

Configuration对象 内容

* 主要为配置文件对应的属性

  • 即properties、settings、typeAliases、typeHandlers、obejctFactory、plugins(interceptor),environments、 databaseIdProvider、mappers 标签对应的内容

  • 其他属性,如缓存信息caches、sql碎片sqlFragments、一些工厂对象ReflectorFactory、 ObjectWrapperFactory等

* 每一个Mapper.xml解析/接口解析后的的结果

保存Mapper解析后的StatementsId->MappedStatement、StatementsId->resultMap、StatementsId->parameterMap、StatementsId-><SelectKey>标签指定的结果表

* 存放解析异常的结果

存放解析异常的XMLStatementBuilder对象、CacheRefResolver对象、ResultMapResolver对象、MethodResolver对象

* 在typeAliasRegistry注册一些必要的别名,如

typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);

typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

* 提供生成4个核心处理器的方法

  • newParameterHandler、newResultSetHandler、 newStatementHandler 、newExecutor

  • 使用拦截器对XXXtHandler进行拦截处理后再返回

public class Configuration {

//<Environment> 对应的属性
protected Environment environment ;

//<setter> 内对应的选项
protected boolean safeRowBoundsEnabled ;
protected boolean safeResultHandlerEnabled = true ;
protected boolean mapUnderscoreToCamelCase ;
protected boolean aggressiveLazyLoading ;
protected boolean multipleResultSetsEnabled = true ;
protected boolean useGeneratedKeys ;
protected boolean useColumnLabel = true ;
protected boolean cacheEnabled = true ;
protected boolean callSettersOnNulls ;
protected boolean useActualParamName = true ;
protected boolean returnInstanceForEmptyRow ;
protected String logPrefix ;
protected Class<? extends Log> logImpl ;
protected Class<? extends VFS> vfsImpl ;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION ;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER ;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList (new String\[\]{"equals" , "clone" , "hashCode" , "toString" }));
protected Integer defaultStatementTimeout ;
protected Integer defaultFetchSize ;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE ;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL ;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE ;
protected boolean lazyLoadingEnabled = false ;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

//<Properties> 内对应的选项
protected Properties variables = new Properties();

//<ObjectFactory> 内对应的选项
protected ObjectFactory objectFactory = new DefaultObjectFactory();

//<databaseId> 内对应解析后
protected String databaseId ;

protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected Class<?> configurationFactory ;

//<mapper> 指定 Mapper.xml 或者接口的注册表
protected final MapperRegistry mapperRegistry = new MapperRegistry(this );

// 保存<plugin>标签指定的拦截器
protected final InterceptorChain interceptorChain = new InterceptorChain();

//TypeHandlerRegistry 类型转换处理器注册表,会保存默认的和自定义的
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();

//<typeAliases> 别名 - 类型的注册表,会保存默认的和自定义的
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

// 保存 Mapper 解析后的 StatementsId->MappedStatement
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection" );
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection" );
// 保存 Mapper 解析后的 StatementsId->resultMap
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection" );
// 保存 Mapper 解析后的 StatementsId->parameterMap
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection" );
// 保存 Mapper 解析后的 StatementsId-><SelectKey> 标签指定
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection" );

// 保存 mapper 的引入地址, <mapper> 标签中 resource/url 属性指定的
protected final Set<String> loadedResources = new HashSet<String>();

// 保存 <sql> 片段
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers" );

// 存放解析异常的 XMLStatementBuilder 对象
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
// 存放解析异常的 CacheRefResolver 对象
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
// 存放解析异常的 ResultMapResolver 对象
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
// 存放解析异常的 MethodResolver 对象
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();

public Configuration(Environment environment) {
this ();
this .environment = environment;
}

public Configuration() {

// 注册默认的别名
typeAliasRegistry .registerAlias("JDBC" , JdbcTransactionFactory.class );
typeAliasRegistry .registerAlias("MANAGED" , ManagedTransactionFactory.class );

typeAliasRegistry .registerAlias("JNDI" , JndiDataSourceFactory.class );
typeAliasRegistry .registerAlias("POOLED" , PooledDataSourceFactory.class );
typeAliasRegistry .registerAlias("UNPOOLED" , UnpooledDataSourceFactory.class );

typeAliasRegistry .registerAlias("PERPETUAL" , PerpetualCache.class );
typeAliasRegistry .registerAlias("FIFO" , FifoCache.class );
typeAliasRegistry .registerAlias("LRU" , LruCache.class );
typeAliasRegistry .registerAlias("SOFT" , SoftCache.class );
typeAliasRegistry .registerAlias("WEAK" , WeakCache.class );

typeAliasRegistry .registerAlias("DB_VENDOR" , VendorDatabaseIdProvider.class );

typeAliasRegistry .registerAlias("XML" , XMLLanguageDriver.class );
typeAliasRegistry .registerAlias("RAW" , RawLanguageDriver.class );

typeAliasRegistry .registerAlias("SLF4J" , Slf4jImpl.class );
typeAliasRegistry .registerAlias("COMMONS_LOGGING" , JakartaCommonsLoggingImpl.class );
typeAliasRegistry .registerAlias("LOG4J" , Log4jImpl.class );
typeAliasRegistry .registerAlias("LOG4J2" , Log4j2Impl.class );
typeAliasRegistry .registerAlias("JDK_LOGGING" , Jdk14LoggingImpl.class );
typeAliasRegistry .registerAlias("STDOUT_LOGGING" , StdOutImpl.class );
typeAliasRegistry .registerAlias("NO_LOGGING" , NoLoggingImpl.class );

typeAliasRegistry .registerAlias("CGLIB" , CglibProxyFactory.class );
typeAliasRegistry .registerAlias("JAVASSIST" , JavassistProxyFactory.class );

languageRegistry .setDefaultDriverClass(XMLLanguageDriver.class );
languageRegistry .register(RawLanguageDriver.class );
}

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 执行拦截器链的拦截逻辑
parameterHandler = (ParameterHandler) interceptorChain .pluginAll(parameterHandler);
return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 执行拦截器链的拦截逻辑
resultSetHandler = (ResultSetHandler) interceptorChain .pluginAll(resultSetHandler);
return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 执行拦截器链的拦截逻辑
statementHandler = (StatementHandler) interceptorChain .pluginAll(statementHandler);
return statementHandler;
}

public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType );
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据 executor 类型创建对象的 Executor 对象
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this , transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this , transaction);
} else {
executor = new SimpleExecutor(this , transaction);
}
// 如果 cacheEnabled 属性为 ture ,这使用 CachingExecutor 对上面创建的 Executor 进行装饰
if (cacheEnabled ) {
executor = new CachingExecutor(executor);
}
// 执行拦截器链的拦截逻辑
executor = (Executor) interceptorChain .pluginAll(executor);
return executor;
}

protected void buildAllStatements() {
if (!incompleteResultMaps .isEmpty()) {
synchronized (incompleteResultMaps ) {
// This always throws a BuilderException.
incompleteResultMaps .iterator().next().resolve();
}
}
if (!incompleteCacheRefs .isEmpty()) {
synchronized (incompleteCacheRefs ) {
// This always throws a BuilderException.
incompleteCacheRefs .iterator().next().resolveCacheRef();
}
}
if (!incompleteStatements .isEmpty()) {
synchronized (incompleteStatements ) {
// This always throws a BuilderException.
incompleteStatements .iterator().next().parseStatementNode();
}
}
if (!incompleteMethods .isEmpty()) {
synchronized (incompleteMethods ) {
// This always throws a BuilderException.
incompleteMethods .iterator().next().resolve();
}
}
}

// .....

}

相关推荐
sunshine22 girl1 小时前
Java学习一 环境配置1 安装JDK,配置环境变量
java·开发语言·学习
秋饼1 小时前
Spring AI Session API 深度实战:从 ChatMemory 平滑迁移到事件溯源的企业级短期记忆
java·ai·技术分享·后端开发
QCodingDev1 小时前
Spring AI 2.0企业级RAG实战:引用校验、无依据拒答与知识治理怎么做?
java·人工智能·spring·ai
吴声子夜歌1 小时前
ApacheCommons——commons-math3(科学计算与线性代数)(一)
java·线性代数·算法·apache
砚底藏山河1 小时前
行情工程实战 M01|存储选型 CSVSQLiteMySQL
java·python·金融·maven
名字还没想好☜1 小时前
Java 用 LinkedHashMap 三行实现 LRU 缓存:accessOrder、removeEldestEntry 与线程安全
java·后端·安全·spring·缓存
谢亮_vipxieliang2 小时前
ValidX vs Google Guava Preconditions:验证 vs 断言
java·spring boot·后端·spring cloud·hibernate·guava
吴声子夜歌2 小时前
ApacheCommons——commons-configuration2(多数据源配置统一管理与热加载)
java·开发语言·算法·apache
星星落进兜里2 小时前
Java虚拟机面试题-补充
java·开发语言