MyBatis 源码分析 - SQL执行过程(二)之 StatementHandler
参考 知识星球 中 芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址、Mybatis-Spring 源码分析 GitHub 地址、Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读
MyBatis 版本:3.5.2
MyBatis-Spring 版本:2.0.3
MyBatis-Spring-Boot-Starter 版本:2.1.4
该系列其他文档请查看:《精尽 MyBatis 源码分析 - 文章导读》
MyBatis的SQL执行过程
在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了
那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:
MyBatis中SQL执行的整体过程如下图所示:
在 SqlSession 中,会将执行 SQL 的过程交由Executor
执行器去执行,过程大致如下:
- 通过
DefaultSqlSessionFactory
创建与数据库交互的SqlSession
“会话”,其内部会创建一个Executor
执行器对象 - 然后
Executor
执行器通过StatementHandler
创建对应的java.sql.Statement
对象,并通过ParameterHandler
设置参数,然后执行数据库相关操作 - 如果是数据库更新操作,则可能需要通过
KeyGenerator
先设置自增键,然后返回受影响的行数 - 如果是数据库查询操作,则需要将数据库返回的
ResultSet
结果集对象包装成ResultSetWrapper
,然后通过DefaultResultSetHandler
对结果集进行映射,最后返回 Java 对象
上面还涉及到一级缓存、二级缓存和延迟加载等其他处理过程
SQL执行过程(二)之StatementHandler
在上一篇文档中,已经详细地分析了在MyBatis的SQL执行过程中,SqlSession会话将数据库操作交由Executor执行器去完成,实际上需要通过StatementHandler
创建相应的Statement
对象,并做一些准备工作,然后通过Statement
执行数据库操作,查询结果则需要通过ResultSetHandler
对结果集进行映射转换成Java对象,那么接下来我们先来看看StatementHandler
到底做哪些操作
StatementHandler接口的实现类如下图所示:
-
org.apache.ibatis.executor.statement.RoutingStatementHandler
:实现StatementHandler接口,装饰器模式,根据Statement类型创建对应的StatementHandler对象,所有的方法执行交由该对象执行 -
org.apache.ibatis.executor.statement.BaseStatementHandler
:实现StatementHandler接口,提供骨架方法,指定的几个抽象方法交由不同的子类去实现 -
org.apache.ibatis.executor.statement.SimpleStatementHandler
:继承BaseStatementHandler抽象类,创建java.sql.Statement
进行数据库操作 -
org.apache.ibatis.executor.statement.PreparedStatementHandler
:继承BaseStatementHandler抽象类,创建java.sql.PreparedStatement
进行数据库操作(默认) -
org.apache.ibatis.executor.statement.CallableStatementHandler
:继承BaseStatementHandler抽象类,创建java.sql.CallableStatement
进行数据库操作,用于存储过程
我们先回顾一下StatementHandler是在哪里被创建的,可以在《SQL执行过程(一)之Executor》的SimpleExecutor小节中有讲到,创建的是RoutingStatementHandler
对象
StatementHandler
org.apache.ibatis.executor.statement.StatementHandler
:Statement处理器接口,代码如下:
public interface StatementHandler {
/**
* 准备操作,可以理解成创建 Statement 对象
*
* @param connection Connection 对象
* @param transactionTimeout 事务超时时间
* @return Statement 对象
*/
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
/**
* 设置 Statement 对象的参数
*
* @param statement Statement 对象
*/
void parameterize(Statement statement) throws SQLException;
/**
* 添加 Statement 对象的批量操作
*
* @param statement Statement 对象
*/
void batch(Statement statement) throws SQLException;
/**
* 执行写操作
*
* @param statement Statement 对象
* @return 影响的条数
*/
int update(Statement statement) throws SQLException;
/**
* 执行读操作
*
* @param statement Statement 对象
* @param resultHandler ResultHandler 对象,处理结果
* @param <E> 泛型
* @return 读取的结果
*/
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
/**
* 执行读操作,返回 Cursor 对象
*
* @param statement Statement 对象
* @param <E> 泛型
* @return Cursor 对象
*/
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;
/**
* @return BoundSql 对象
*/
BoundSql getBoundSql();
/**
* @return ParameterHandler 对象
*/
ParameterHandler getParameterHandler();
}
每个方法可以根据注释先理解它的作用,在实现类中的会讲到
RoutingStatementHandler
org.apache.ibatis.executor.statement.RoutingStatementHandler
:实现StatementHandler接口,采用装饰器模式,在初始化的时候根据Statement类型,创建对应的StatementHandler对象,代码如下:
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
// 根据不同的类型,创建对应的 StatementHandler 实现类
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
}
-
在构造函数中初始化
delegate
委托对象,根据MappedStatement
(每个SQL对应的对象)的statementType
类型,创建对应的StatementHandler实现类 -
其余所有的方法都是直接交由
delegate
去执行的,这里就不列出来了,就是实现StatementHandler接口的方法
回顾到《MyBatis初始化(二)之加载Mapper接口与XML映射文件》中的XMLStatementBuilder小节,在parseStatementNode
方法中的第10
步如下:
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
所以说statementType
的默认值为PREPARED
,委托对象也就是PreparedStatementHandler
类型
BaseStatementHandler
org.apache.ibatis.executor.statement.BaseStatementHandler
:实现StatementHandler接口,提供骨架方法,指定的几个抽象方法交由不同的子类去实现
构造方法
public abstract class BaseStatementHandler implements StatementHandler {
/**
* 全局配置
*/
protected final Configuration configuration;
/**
* 实例工厂
*/
protected final ObjectFactory objectFactory;
/**
* 类型处理器注册表
*/
protected final TypeHandlerRegistry typeHandlerRegistry;
/**
* 执行结果处理器
*/
protected final ResultSetHandler resultSetHandler;
/**
* 参数处理器,默认 DefaultParameterHandler
*/
protected final ParameterHandler parameterHandler;
/**
* 执行器
*/
protected final Executor executor;
/**
* SQL 相关信息
*/
protected final MappedStatement mappedStatement;
/**
* 分页条件
*/
protected final RowBounds rowBounds;
/**
* SQL 语句
*/
protected BoundSql boundSql;
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
// <1> 如果 boundSql 为空,更新数据库的操作这里传入的对象会为 null
if (boundSql == null) { // issue #435, get the key before calculating the statement
// <1.1> 生成 key,定义了 <selectKey /> 且配置了 order="BEFORE",则在 SQL 执行之前执行
generateKeys(parameterObject);
// <1.2> 创建 BoundSql 对象
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// <2> 创建 ParameterHandler 对象,默认为 DefaultParameterHandler
// PreparedStatementHandler 实现的 parameterize 方法中需要对参数进行预处理,进行参数化时需要用到
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// <3> 创建 DefaultResultSetHandler 对象
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
}
关于它的属性可以根据注释进行理解
-
如果入参中的
boundSql
为null
,则需要进行初始化,可以会看到SimpleExecutor
中执行数据库的更新操作时,传入的boundSql
为null
,数据库的查询操作才会传入该对象的值-
调用
generateKeys(Object parameter)
方法,根据配置的KeyGenerator
对象,在SQL执行之前执行查询操作获取值,设置到入参对象对应属性中,代码如下:protected void generateKeys(Object parameter) { /* * 获得 KeyGenerator 对象 * 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象 * 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象 * 否则为 NoKeyGenerator 对象 */ KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); ErrorContext.instance().store(); // 前置处理,创建自增编号到 parameter 中 keyGenerator.processBefore(executor, mappedStatement, null, parameter); ErrorContext.instance().recall(); }
只有配置的
<selectKey />
标签才有前置处理,这就是为什么数据库的更新操作传入的boundSql
为null
的原因,因为入参中有的属性值可能需要提前生成一个值(执行配置的SQL语句),KeyGenerator
会在后续讲到😈 -
通过
MappedStatement
对象根据入参获取BoundSql
对象,在《MyBatis初始化(四)之SQL初始化(下)》中的SqlSource小节中有讲到这个方法,如果是动态SQL则需要进行解析,获取到最终的SQL,替换成?
占位符
-
-
创建
ParameterHandler
对象,用于对参数进行预处理,默认为DefaultParameterHandler
,这个也在《MyBatis初始化(四)之SQL初始化(下)》中有讲过可以看到Configuration的
newParameterHandler
方法:public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { // 创建 ParameterHandler 对象 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); // 应用插件 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }
-
创建
ResultSetHandler
,用于返回结果的映射,默认为DefaultResultSetHandler
,这个映射过程非常复杂,会有单独一篇文档进行分析😈可以看到Configuration的
newResultSetHandler
方法:public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { // 创建 DefaultResultSetHandler 对象 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); // 应用插件 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; }
prepare方法
创建Statement对象,做一些初始化工作,代码如下:
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// <1> 创建 Statement 对象
statement = instantiateStatement(connection);
// <2> 设置执行和事务的超时时间
setStatementTimeout(statement, transactionTimeout);
// <3> 设置 fetchSize,为驱动的结果集获取数量(fetchSize)设置一个建议值
setFetchSize(statement);
return statement;
} catch (SQLException e) {
// 发生异常,进行关闭
closeStatement(statement);
throw e;
} catch (Exception e) {
// 发生异常,进行关闭
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
-
创建 Statement 对象,调用
instantiateStatement(Connection connection)
抽象方法,交由不同的子类去实现 -
设置执行和事务的超时时间,调用
setStatementTimeout(Statement stmt, Integer transactionTimeout)
方法,如下:protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException { Integer queryTimeout = null; // 获得 queryTimeout if (mappedStatement.getTimeout() != null) { queryTimeout = mappedStatement.getTimeout(); } else if (configuration.getDefaultStatementTimeout() != null) { queryTimeout = configuration.getDefaultStatementTimeout(); } // 设置执行的超时时间 if (queryTimeout != null) { stmt.setQueryTimeout(queryTimeout); } // 设置事务超时时间 StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout); }
-
设置 fetchSize,为驱动的结果集获取数量(fetchSize)设置一个建议值(无默认值),调用
setFetchSize(Statement stmt)
方法,如下:protected void setFetchSize(Statement stmt) throws SQLException { // 获得 fetchSize 配置 Integer fetchSize = mappedStatement.getFetchSize(); if (fetchSize != null) { stmt.setFetchSize(fetchSize); return; } // 获得 fetchSize 的默认配置 Integer defaultFetchSize = configuration.getDefaultFetchSize(); if (defaultFetchSize != null) { stmt.setFetchSize(defaultFetchSize); } }
-
发生任何异常都会关闭 Statement 对象
SimpleStatementHandler
org.apache.ibatis.executor.statement.SimpleStatementHandler
:继承BaseStatementHandler抽象类,创建java.sql.Statement
进行数据库操作,部分代码如下:
public class SimpleStatementHandler extends BaseStatementHandler {
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public void batch(Statement statement) throws SQLException {
String sql = boundSql.getSql();
// 添加到批处理
statement.addBatch(sql);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
// <1> 执行查询
statement.execute(sql);
// <2> 处理返回结果
return resultSetHandler.handleResultSets(statement);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleCursorResultSets(statement);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// 创建Statement对象
return connection.createStatement();
} else {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) {
// N/A
}
}
-
上面的方法都很简单,都是直接通过
Statement
对象执行数据库操作 -
在查询方法中,需要通过
resultSetHandler
对结果集进行映射,返回对应的Java对象
update方法
执行数据库更新操作,方法如下:
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
/*
* 获得 KeyGenerator 对象
* 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象
* 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象
* 否则为 NoKeyGenerator 对象
*/
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) { // 如果是 Jdbc3KeyGenerator 类型
// <1.1> 执行写操作,设置返回自增键,可通过 getGeneratedKeys() 方法获取
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
// <1.2> 获得更新数量
rows = statement.getUpdateCount();
// <1.3> 执行 keyGenerator 的后置处理逻辑,也就是对我们配置的自增键进行赋值
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) { // 如果是 SelectKeyGenerator 类型
// <2.1> 执行写操作
statement.execute(sql);
// <2.2> 获得更新数量
rows = statement.getUpdateCount();
// <2.3>执行 keyGenerator 的后置处理逻辑,也就是对我们配置的自增键进行赋值
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
// <3.1> 执行写操作
statement.execute(sql);
// <3.2> 获得更新数量
rows = statement.getUpdateCount();
}
return rows;
}
为什么数据库更新操作的方法需要做这么多处理,其实目的就一个,支持用户配置的自增键,设置到入参中
在BaseStatementHandler
的构造方法已经有过KeyGenerator
的前置处理了,那里是在SQL执行之前,执行查询操作获取值,设置到入参对象对应属性中
而这里需要做的就是在SQL执行的后置处理了,在SQL执行之后,执行查询操作获取值或者设置需要返回哪些自增键,设置到入参对象对应属性中
-
如果
KeyGenerator
是Jdbc3KeyGenerator
类型,也就是配置useGeneratedKeys="true"
- 执行写操作,设置需要返回自增键,可通过
getGeneratedKeys()
方法获取 - 获得受影响的行数
- 执行后置处理,调用其
processAfter
方法,也就是将我们配置的自增键设置到入参对象中
- 执行写操作,设置需要返回自增键,可通过
-
如果
KeyGenerator
是SelectKeyGenerator
类型,也就是添加了<selectKey />
标签- 执行写操作
- 获得受影响的行数
- 执行后置处理,调用其
processAfter
方法,也就是执行查询操作获取值,设置到入参对象对应属性中
-
如果没有配置
KeyGenerator
- 执行写操作
- 获得受影响的行数
PreparedStatementHandler
org.apache.ibatis.executor.statement.PreparedStatementHandler
:继承BaseStatementHandler抽象类,创建java.sql.PreparedStatement
进行数据库操作(默认),部分代码如下:
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 添加到批处理
ps.addBatch();
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行
ps.execute();
// 结果处理器并返回结果
return resultSetHandler.handleResultSets(ps);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行
ps.execute();
// 结果处理器并返回 Cursor 结果
return resultSetHandler.handleCursorResultSets(ps);
}
}
-
上面的方法都很简单,都是直接通过
PreparedStatement
对象执行数据库操作 -
在查询方法中,需要通过
resultSetHandler
对结果集进行映射,返回对应的Java对象
instantiateStatement方法
创建一个PreparedStatement
对象,方法如下:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
/*
* 获得 KeyGenerator 对象
* 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象
* 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象
* 否则为 NoKeyGenerator 对象
*/
// <1> 处理 Jdbc3KeyGenerator 的情况
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
// <1.1> 获得 keyColumn 配置
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
// <1.2 >创建 PreparedStatement 对象,并返回自增键,并可通过 getGeneratedKeys() 方法获取
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
// <1.3> 创建 PreparedStatement 对象,并返回我们配置的 column 列名自增键,并可通过 getGeneratedKeys() 方法获取
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// <2> 创建 PrepareStatement 对象
return connection.prepareStatement(sql);
} else {
// <3> 创建 PrepareStatement 对象,指定 ResultSetType
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
-
处理
Jdbc3KeyGenerator
的情况,也就是配置了useGeneratedKeys="true"
- 获得
keyColumn
配置,哪些自增键需要返回 - 如果keyColumn为null,返回
PreparedStatement
对象,设置RETURN_GENERATED_KEYS
,表示所有自增列都返回,可通过getGeneratedKeys()
方法获取 - 如果keyColumn不为null,返回
PreparedStatement
对象,设置需要返回的自增列为keyColumn,可通过getGeneratedKeys()
方法获取
- 获得
-
没有配置Jdbc3KeyGenerator对象,创建
PreparedStatement
对象返回,默认情况 -
没有配置Jdbc3KeyGenerator对象,但是指定了ResultSetType,则返回
PreparedStatement
对象,指定ResultSetType
parameterize方法
设置PreparedStatement的占位符参数,方法如下:
@Override
public void parameterize(Statement statement) throws SQLException {
// 通过 DefaultParameterHandler 设置 PreparedStatement 的占位符参数
parameterHandler.setParameters((PreparedStatement) statement);
}
update方法
执行数据库更新操作,方法如下:
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行
ps.execute();
// 获得更新数量
int rows = ps.getUpdateCount();
// 入参对象
Object parameterObject = boundSql.getParameterObject();
/*
* 获得 KeyGenerator 对象
* 1. 配置了 <selectKey /> 则会生成 SelectKeyGenerator 对象
* 2. 配置了 useGeneratedKeys="true" 则会生成 Jdbc3KeyGenerator 对象
* 否则为 NoKeyGenerator 对象
*/
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// 执行 keyGenerator 的后置处理逻辑,也就是对我们配置的自增键进行赋值
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
- 执行数据库更新操作
- 获得受影响行数
- 根据配置的
KeyGenerator
对象,执行后置处理,执行查询操作获取值,或者获取返回的自增键,设置到入参对象对应属性中
CallableStatementHandler
org.apache.ibatis.executor.statement.CallableStatementHandler
:继承BaseStatementHandler抽象类,创建java.sql.CallableStatement
进行数据库操作,用于存储过程
在执行完数据库的操作后需要调用DefaultResultSetHandler
的handleOutputParameters
方法,处理需要作为出参的参数,这里就不做过得的讲述了😈
KeyGenerator
在上面已经讲到在执行数据库更新操作时,需要通过KeyGenerator来进行前置处理或者后置处理,我一般用于自增主键
先来看看它的实现类,如下图所示:
-
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
:实现KeyGenerator接口,只有后置处理的实现,获取返回的自增键,设置到入参的属性中,配置了useGeneratedKeys="true"
则会创建该对象 -
org.apache.ibatis.executor.keygen.SelectKeyGenerator
:实现KeyGenerator接口,执行数据库查询操作,获取到对应的返回结果设置到入参的属性中,添加了<selectKey />
标签则会创建该对象,前后置处理可以配置 -
org.apache.ibatis.executor.keygen.NoKeyGenerator
:实现KeyGenerator接口,空实现,一个单例,没有配置上面的两种方式,则默认为该对象
KeyGenerator接口,代码如下:
public interface KeyGenerator {
/**
* 在 SQL 执行后设置自增键到入参中
*
* @param executor 执行器
* @param ms MappedStatement 对象
* @param stmt Statement对象
* @param parameter 入参对象
*/
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
/**
* 在 SQL 执行前设置自增键到入参中
*
* @param executor 执行器
* @param ms MappedStatement 对象
* @param stmt Statement对象
* @param parameter 入参对象
*/
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
Jdbc3KeyGenerator
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
:实现KeyGenerator接口,只有后置处理的实现,获取返回的自增键,设置到入参的属性中,配置了useGeneratedKeys="true"
则会创建该对象,在《MyBatis初始化(二)之加载Mapper接口与XML映射文件》的XMLStatementBuilder小节的parseStatementNode
方法中的第8
步看到
实现方法:
public class Jdbc3KeyGenerator implements KeyGenerator {
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// 批处理多个自增键
processBatch(ms, stmt, parameter);
}
}
可以看到前置处理的实现方法为空,后置处理的实现方法调用processBatch
方法
processBatch方法
processBatch(MappedStatement ms, Statement stmt, Object parameter)
方法,从结果集中获自增键设置到入参对象的属性中,代码如下:
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// 获取 keyProperty 配置
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
// 获取 Statement 执行后自增键对应的 ResultSet 对象
try (ResultSet rs = stmt.getGeneratedKeys()) {
// 获取 ResultSet 的 ResultSetMetaData 元数据对象
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) { // 自增键与 keyProperty 数量不一致则跳过
// Error?
} else {
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
- 获取
keyProperty
配置,也就是需要将自增键设置到入参对象的属性名称 - 通过
Statement
的getGeneratedKeys()
方法获取到自增键 - 如果自增键与属性个数不相同则跳过,不进行处理了
- 否则调用
assignKeys
方法,分配自增键给对应的属性
assignKeys方法
/**
* 关于 ParamMap,可以看到 {@link org.apache.ibatis.reflection.ParamNameResolver#getNamedParams} 这个方法
* 根据入参获取获取参数名称与参数值的映射关系
* 如果为多个入参或者一个入参并且添加了 @Param 注解,则会返回 ParamMap 对象,key为参数名称,value为参数值
*
* 需要使用到获取自增键时,我们一般入参都是一个实体类,则进入的是下面第3种情况
*/
@SuppressWarnings("unchecked")
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
Object parameter) throws SQLException {
if (parameter instanceof ParamMap || parameter instanceof StrictMap) { // <1>
// Multi-param or single param with @Param
assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
&& ((ArrayList<?>) parameter).get(0) instanceof ParamMap) { // <2>
// Multi-param or single param with @Param in batch operation
assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, ((ArrayList<ParamMap<?>>) parameter));
} else { // <3>
// Single param without @Param
assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
}
}
- 因为我的入参通常是一个实体类的时候,配置自增键的生成,这里我们直接看第三种情况,其他两种可参考我的注释进行阅读
assignKeysToParam方法
private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, Object parameter) throws SQLException {
// 将入参对象 parameter 转换成集合,因为批处理时可能传入多个入参对象
Collection<?> params = collectionize(parameter);
if (params.isEmpty()) {
return;
}
List<KeyAssigner> assignerList = new ArrayList<>();
for (int i = 0; i < keyProperties.length; i++) {
// 每一个 keyProperty 都创建一个 KeyAssigner 对象,设置 column 的位置
assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
}
Iterator<?> iterator = params.iterator();
while (rs.next()) {
if (!iterator.hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
}
Object param = iterator.next();
// 往每个入参对象中设置配置的 keyProperty 为对应的自增键
assignerList.forEach(x -> x.assign(rs, param));
}
}
- 将入参对象
parameter
转换成集合,因为批处理时可能传入多个入参对象 - 每一个
keyProperty
都创建一个KeyAssigner
对象,设置自增键的位置,通过该对象往入参中设置该属性值 - 遍历入参
params
集合,每个入参对象都遍历assignerList
集合,通过KeyAssigner
往入参中设置属性值
KeyAssigner
定义在Jdbc3KeyGenerator的一个内部类,单个自增键的分配者,将该自增键设置到入参对象的属性中,构造方法如下:
private class KeyAssigner {
/**
* 全局配置对象
*/
private final Configuration configuration;
/**
* Statement 执行后返回的 元数据对象
*/
private final ResultSetMetaData rsmd;
/**
* 类型处理器注册中心
*/
private final TypeHandlerRegistry typeHandlerRegistry;
/**
* 属性对应列所在的位置
*/
private final int columnPosition;
/**
* 参数名称,添加了 @Param 注解时才有
*/
private final String paramName;
/**
* Java 属性名称
*/
private final String propertyName;
/**
* 类型处理器
*/
private TypeHandler<?> typeHandler;
protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName,
String propertyName) {
super();
this.configuration = configuration;
this.rsmd = rsmd;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.columnPosition = columnPosition;
this.paramName = paramName;
this.propertyName = propertyName;
}
}
- 定义了结果元数据
ResultSetMetaData
对象、自增键所在位置、属性名称、类型处理器
assign方法
assign(ResultSet rs, Object param)
方法,将当前自增键设置到param
入参的属性中,代码如下:
protected void assign(ResultSet rs, Object param) {
if (paramName != null) {
// If paramName is set, param is ParamMap
param = ((ParamMap<?>) param).get(paramName);
}
MetaObject metaParam = configuration.newMetaObject(param);
try {
if (typeHandler == null) {
if (metaParam.hasSetter(propertyName)) {
Class<?> propertyType = metaParam.getSetterType(propertyName);
// 根据 Java Type 和 Jdbc Type 获取对应的类型处理器
typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, JdbcType.forCode(rsmd.getColumnType(columnPosition)));
} else {
throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
+ metaParam.getOriginalObject().getClass().getName() + "'.");
}
}
if (typeHandler == null) {
// Error?
} else {
// 将 Jdbc Type 转换成 Java Type
Object value = typeHandler.getResult(rs, columnPosition);
// 将该属性值设置到 入参对象中
metaParam.setValue(propertyName, value);
}
} catch (SQLException e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
e);
}
}
- 从结果集中根据位置获取到该自增键,然后设置到入参对象的属性中
SelectKeyGenerator
org.apache.ibatis.executor.keygen.SelectKeyGenerator
:实现KeyGenerator接口,执行数据库查询操作,获取到对应的返回结果设置到入参的属性中,添加了<selectKey />
标签则会创建该对象,前后置处理可以配置,在《MyBatis初始化(二)之加载Mapper接口与XML映射文件》的XMLStatementBuilder小节的parseStatementNode
方法中的第7
步看到
实现方法:
public class SelectKeyGenerator implements KeyGenerator {
/**
* <selectKey /> 解析成 MappedStatement 对象的 id 的后缀
* 例如 <selectKey /> 所在的 MappedStatement 的 id 为 namespace.test
* 那么它的 id 就是 namespace.test!selectKey
*/
public static final String SELECT_KEY_SUFFIX = "!selectKey";
/**
* 是否在 SQL 执行后执行
*/
private final boolean executeBefore;
/**
* <selectKey /> 解析成 MappedStatement 对象
*/
private final MappedStatement keyStatement;
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (executeBefore) { // 如果是在 SQL 执行之前进行生成 key
processGeneratedKeys(executor, ms, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (!executeBefore) { // 如果是在 SQL 执行之后进行生成 key
processGeneratedKeys(executor, ms, parameter);
}
}
}
-
可以看到
<selectKey />
标签还会生成一个MappedStatement
对象,用于执行查询语句 -
executeBefore
属性表示,是否为前置处理,所以<selectKey />
要么就是前置处理,要么就是后置处理,都是调用processGeneratedKeys
方法
processGeneratedKeys方法
执行数据库的查询操作,生成“主键”,设置到入参对象的属性中,代码如下:
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
try {
if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
// <1> 获取 keyProperty 配置
String[] keyProperties = keyStatement.getKeyProperties();
final Configuration configuration = ms.getConfiguration();
// <2> 创建入参 MetaObject 对象
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (keyProperties != null) {
// Do not close keyExecutor.
// The transaction will be closed by parent executor.
// <3> 创建一个 SimpleExecutor 执行器
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
// <4> 执行数据库查询
List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
if (values.size() == 0) {
throw new ExecutorException("SelectKey returned no data.");
} else if (values.size() > 1) {
throw new ExecutorException("SelectKey returned more than one value.");
} else {
// <5> 为数据库查询的到数据创建 MetaObject 对象
MetaObject metaResult = configuration.newMetaObject(values.get(0));
if (keyProperties.length == 1) { // <6.1> 单个属性
if (metaResult.hasGetter(keyProperties[0])) {
// 往入参中设置该属性为从数据库查询到的数据
setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
} else {
// no getter for the property - maybe just a single value object
// so try that
setValue(metaParam, keyProperties[0], values.get(0));
}
} else { //<6.2> 多个属性
handleMultipleProperties(keyProperties, metaParam, metaResult);
}
}
}
}
} catch (ExecutorException e) {
throw e;
} catch (Exception e) {
throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
}
}
private void setValue(MetaObject metaParam, String property, Object value) {
if (metaParam.hasSetter(property)) {
metaParam.setValue(property, value);
} else {
throw new ExecutorException("省略...");
}
}
- 获取
keyProperty
配置,也就是需要设置值到入参中的属性名称 - 创建入参的
MetaObject
对象metaParam
,便于操作 - 创建一个
SimpleExecutor
执行器 - 使用该执行器执行数据库查询操作
- 为数据库查询获取到的结果创建
MetaObject
对象metaResult
- 将查询结果设置到入参对象中
- 如果
keyProperty
配置的仅仅是一个属性,则从metaResult
中获取查询结果设置到metaParam
入参对象的该属性中 - 如果多个属性需要赋值,则调用
handleMultipleProperties
方法将查询结果设置到入参对象的多个属性中
- 如果
handleMultipleProperties方法
private void handleMultipleProperties(String[] keyProperties, MetaObject metaParam, MetaObject metaResult) {
// 获取 keyColumn 配置
String[] keyColumns = keyStatement.getKeyColumns();
if (keyColumns == null || keyColumns.length == 0) { // 没有配置列名则直接去属性名
// no key columns specified, just use the property names
for (String keyProperty : keyProperties) {
// 往入参中设置该属性为从数据库查询到的数据,从查询到的结果中取属性名
setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));
}
} else {
if (keyColumns.length != keyProperties.length) { // 列名和属性名的个数不一致则抛出异常
throw new ExecutorException("If SelectKey has key columns, the number must match the number of key properties.");
}
for (int i = 0; i < keyProperties.length; i++) {
// 往入参中设置该属性为从数据库查询到的数据,从查询到的结果中取列名
setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));
}
}
}
- 没有配置列名,则根据
keyProperty
属性名称从metaResult
查询结果中获取结果,一个一个设置到metaParam
入参对象的该属性中 - 没有配置列名,则根据
keyColumn
列名从metaResult
查询结果中获取结果,一个一个设置到metaParam
入参对象的该属性中
NoKeyGenerator
org.apache.ibatis.executor.keygen.NoKeyGenerator
:实现KeyGenerator接口,空实现,一个单例,没有配置上面的两种方式,则默认为该对象,代码如下:
public class NoKeyGenerator implements KeyGenerator {
/**
* A shared instance.
* @since 3.4.3
*/
public static final NoKeyGenerator INSTANCE = new NoKeyGenerator();
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// Do Nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// Do Nothing
}
}
总结
本文分析了MyBatis在执行SQL的过程中,SimpleExecutor
(默认类型)执行器需要通过PrepareStatementHandler
(默认)来执行数据库的操作,创建PrepareStatement
(默认)对象来完成数据操作
如果你配置了useGeneratedKeys="true"
,则需要在执行完数据库更新操作后,通过Jdbc3KeyGenerator
设置自增键到入参对象中(后置处理)
如果你添加了<selectKey />
标签,则需要通过SelectKeyGenerator
执行数据库查询操作获取到结果,设置到入参对象中(前置处理、后置处理)
如果是查询操作则需要通过ResultSetHandler
对结果集进行映射转换成Java对象,这就是我们下一篇文档需要分析的内容😄
参考文章:芋道源码《精尽 MyBatis 源码分析》