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处理器接口,代码如下:
每个方法可以根据注释先理解它的作用,在实现类中的会讲到
RoutingStatementHandler
org.apache.ibatis.executor.statement.RoutingStatementHandler:实现StatementHandler接口,采用装饰器模式,在初始化的时候根据Statement类型,创建对应的StatementHandler对象,代码如下:
- 在构造函数中初始化
delegate委托对象,根据MappedStatement(每个SQL对应的对象)的statementType类型,创建对应的StatementHandler实现类 - 其余所有的方法都是直接交由
delegate去执行的,这里就不列出来了,就是实现StatementHandler接口的方法
回顾到 《MyBatis初始化(二)之加载Mapper接口与XML映射文件》中的XMLStatementBuilder小节,在parseStatementNode方法中的第10步如下:
所以说statementType的默认值为PREPARED,委托对象也就是PreparedStatementHandler类型
BaseStatementHandler
org.apache.ibatis.executor.statement.BaseStatementHandler:实现StatementHandler接口,提供骨架方法,指定的几个抽象方法交由不同的子类去实现
构造方法
关于它的属性可以根据注释进行理解
- 如果入参中的
boundSql为null,则需要进行初始化,可以会看到SimpleExecutor中执行数据库的更新操作时,传入的boundSql为null,数据库的查询操作才会传入该对象的值
- 调用
generateKeys(Object parameter)方法,根据配置的KeyGenerator对象,在SQL执行之前执行查询操作获取值,设置到入参对象对应属性中,代码如下:
只有配置的<selectKey />标签才有前置处理,这就是为什么数据库的更新操作传入的boundSql为null的原因,因为入参中有的属性值可能需要提前生成一个值(执行配置的SQL语句),KeyGenerator会在后续讲到😈
- 通过
MappedStatement对象根据入参获取BoundSql对象,在 《MyBatis初始化(四)之SQL初始化(下)》中的SqlSource小节中有讲到这个方法,如果是动态SQL则需要进行解析,获取到最终的SQL,替换成?占位符
- 创建
ParameterHandler对象,用于对参数进行预处理,默认为DefaultParameterHandler,这个也在 《MyBatis初始化(四)之SQL初始化(下)》中有讲过
可以看到Configuration的newParameterHandler方法:
- 创建
ResultSetHandler,用于返回结果的映射,默认为DefaultResultSetHandler,这个映射过程非常复杂,会有单独一篇文档进行分析😈
可以看到Configuration的newResultSetHandler方法:
prepare方法
创建Statement对象,做一些初始化工作,代码如下:
- 创建 Statement 对象,调用
instantiateStatement(Connection connection)抽象方法,交由不同的子类去实现 - 设置执行和事务的超时时间,调用
setStatementTimeout(Statement stmt, Integer transactionTimeout)方法,如下:
- 设置 fetchSize,为驱动的结果集获取数量(fetchSize)设置一个建议值(无默认值),调用
setFetchSize(Statement stmt)方法,如下:
- 发生任何异常都会关闭 Statement 对象
SimpleStatementHandler
org.apache.ibatis.executor.statement.SimpleStatementHandler:继承BaseStatementHandler抽象类,创建java.sql.Statement进行数据库操作,部分代码如下:
- 上面的方法都很简单,都是直接通过
Statement对象执行数据库操作 - 在查询方法中,需要通过
resultSetHandler对结果集进行映射,返回对应的Java对象
update方法
执行数据库更新操作,方法如下:
为什么数据库更新操作的方法需要做这么多处理,其实目的就一个,支持用户配置的自增键,设置到入参中
在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进行数据库操作(默认),部分代码如下:
- 上面的方法都很简单,都是直接通过
PreparedStatement对象执行数据库操作 - 在查询方法中,需要通过
resultSetHandler对结果集进行映射,返回对应的Java对象
instantiateStatement方法
创建一个PreparedStatement对象,方法如下:
- 处理
Jdbc3KeyGenerator的情况,也就是配置了useGeneratedKeys="true"
- 获得
keyColumn配置,哪些自增键需要返回 - 如果keyColumn为null,返回
PreparedStatement对象,设置RETURN_GENERATED_KEYS,表示所有自增列都返回,可通过getGeneratedKeys()方法获取 - 如果keyColumn不为null,返回
PreparedStatement对象,设置需要返回的自增列为keyColumn,可通过getGeneratedKeys()方法获取
- 没有配置Jdbc3KeyGenerator对象,创建
PreparedStatement对象返回,默认情况 - 没有配置Jdbc3KeyGenerator对象,但是指定了ResultSetType,则返回
PreparedStatement对象,指定ResultSetType
parameterize方法
设置PreparedStatement的占位符参数,方法如下:
update方法
执行数据库更新操作,方法如下:
- 执行数据库更新操作
- 获得受影响行数
- 根据配置的
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接口,代码如下:
Jdbc3KeyGenerator
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator:实现KeyGenerator接口,只有后置处理的实现,获取返回的自增键,设置到入参的属性中,配置了useGeneratedKeys="true"则会创建该对象,在 《MyBatis初始化(二)之加载Mapper接口与XML映射文件》的XMLStatementBuilder小节的parseStatementNode方法中的第8步看到
实现方法:
可以看到前置处理的实现方法为空,后置处理的实现方法调用processBatch方法
processBatch方法
processBatch(MappedStatement ms, Statement stmt, Object parameter)方法,从结果集中获自增键设置到入参对象的属性中,代码如下:
- 获取
keyProperty配置,也就是需要将自增键设置到入参对象的属性名称 - 通过
Statement的getGeneratedKeys()方法获取到自增键 - 如果自增键与属性个数不相同则跳过,不进行处理了
- 否则调用
assignKeys方法,分配自增键给对应的属性
assignKeys方法
- 因为我的入参通常是一个实体类的时候,配置自增键的生成,这里我们直接看第三种情况,其他两种可参考我的注释进行阅读
assignKeysToParam方法
- 将入参对象
parameter转换成集合,因为批处理时可能传入多个入参对象 - 每一个
keyProperty都创建一个KeyAssigner对象,设置自增键的位置,通过该对象往入参中设置该属性值 - 遍历入参
params集合,每个入参对象都遍历assignerList集合,通过KeyAssigner往入参中设置属性值
KeyAssigner
定义在Jdbc3KeyGenerator的一个内部类,单个自增键的分配者,将该自增键设置到入参对象的属性中,构造方法如下:
- 定义了结果元数据
ResultSetMetaData对象、自增键所在位置、属性名称、类型处理器
assign方法
assign(ResultSet rs, Object param)方法,将当前自增键设置到param入参的属性中,代码如下:
- 从结果集中根据位置获取到该自增键,然后设置到入参对象的属性中
SelectKeyGenerator
org.apache.ibatis.executor.keygen.SelectKeyGenerator:实现KeyGenerator接口,执行数据库查询操作,获取到对应的返回结果设置到入参的属性中,添加了<selectKey />标签则会创建该对象,前后置处理可以配置,在 《MyBatis初始化(二)之加载Mapper接口与XML映射文件》的XMLStatementBuilder小节的parseStatementNode方法中的第7步看到
实现方法:
- 可以看到
<selectKey />标签还会生成一个MappedStatement对象,用于执行查询语句 executeBefore属性表示,是否为前置处理,所以<selectKey />要么就是前置处理,要么就是后置处理,都是调用processGeneratedKeys方法
processGeneratedKeys方法
执行数据库的查询操作,生成“主键”,设置到入参对象的属性中,代码如下:
- 获取
keyProperty配置,也就是需要设置值到入参中的属性名称 - 创建入参的
MetaObject对象metaParam,便于操作 - 创建一个
SimpleExecutor执行器 - 使用该执行器执行数据库查询操作
- 为数据库查询获取到的结果创建
MetaObject对象metaResult - 将查询结果设置到入参对象中
- 如果
keyProperty配置的仅仅是一个属性,则从metaResult中获取查询结果设置到metaParam入参对象的该属性中 - 如果多个属性需要赋值,则调用
handleMultipleProperties方法将查询结果设置到入参对象的多个属性中
handleMultipleProperties方法
- 没有配置列名,则根据
keyProperty属性名称从metaResult查询结果中获取结果,一个一个设置到metaParam入参对象的该属性中 - 没有配置列名,则根据
keyColumn列名从metaResult查询结果中获取结果,一个一个设置到metaParam入参对象的该属性中
NoKeyGenerator
org.apache.ibatis.executor.keygen.NoKeyGenerator:实现KeyGenerator接口,空实现,一个单例,没有配置上面的两种方式,则默认为该对象,代码如下:
总结
本文分析了MyBatis在执行SQL的过程中,SimpleExecutor(默认类型)执行器需要通过PrepareStatementHandler(默认)来执行数据库的操作,创建PrepareStatement(默认)对象来完成数据操作
如果你配置了useGeneratedKeys="true",则需要在执行完数据库更新操作后,通过Jdbc3KeyGenerator设置自增键到入参对象中(后置处理)
如果你添加了<selectKey />标签,则需要通过SelectKeyGenerator执行数据库查询操作获取到结果,设置到入参对象中(前置处理、后置处理)
如果是查询操作则需要通过ResultSetHandler对结果集进行映射转换成Java对象,这就是我们下一篇文档需要分析的内容😄
979


之 StatementHandler&spm=1001.2101.3001.11974&articleId=143197698&d=1&t=3&u=e12e742bf8f74ece8b8a4107f5cf0037)

被折叠的 条评论
为什么被折叠?



