精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler
该系列文档是本人在学习 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的SQL执行过程
在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了
那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:
- 《SQL执行过程(一)之Executor》
- 《SQL执行过程(二)之StatementHandler》
- 《SQL执行过程(三)之ResultSetHandler》
- 《SQL执行过程(四)之延迟加载》
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接口与映射文件》中的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
会在后续讲到精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler的更多相关文章
- 精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - 基础支持层
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - 插件机制
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - 文章导读
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- MyBatis 源码篇-SQL 执行的流程
本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...
- 精尽MyBatis源码分析 - MyBatis-Spring 源码分析
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
随机推荐
- JAVA递归实现线索化二叉树
JAVA递归实现线索化二叉树 基础理论 首先,二叉树递归遍历分为先序遍历.中序遍历和后序遍历. 先序遍历为:根节点+左子树+右子树 中序遍历为:左子树+根节点+右子树 后序遍历为:左子树+右子树+根节 ...
- 蓝桥杯 Island Hopping Java代码
问题描述 太平洋岛网公司(PLN)已经瞄向了太平洋中的一些群岛.这些群岛没有快捷的互联网连接.PLN计划向群岛提供互联网服务,以开发这个太平洋中潜在的市场.每组群岛的核心岛屿已经被深海电缆 ...
- Stream(一)
public class Test06 { /* * StreamAPI: * StreamAPI是用来处理数据,处理集合等容器中的数据,处理操作有:查询.筛选.删除.过滤.统计.映射等. * 希望能 ...
- localStorage.getItem得到的是[object Object] 的解决方案
设计背景: 购物车要实现本地存储,避免刷新页面数据丢失 实现方案: 1,本地储存,进入页面获取本地数据,在进行数据操作 2,每操作一次数据就将数据传给后台进行保存,(操作数据多,用户量大对服务器造成压 ...
- LC滤波器简单设计法 - 一文读懂LC滤波器简单设计方法及原理介绍,LC值计算方法
LC滤波器概述 LC滤波器也称为无源滤波器,是传统的谐波补偿装置.LC滤波器之所以称为无源滤波器,顾名思义,就是该装置不需要额外提供电源.LC滤波器一般是由滤波电容器.电抗器和电阻器适当组合而成,与谐 ...
- E. Almost Regular Bracket Sequence 解析(思維)
Codeforce 1095 E. Almost Regular Bracket Sequence 解析(思維) 今天我們來看看CF1095E 題目連結 題目 給你一個括號序列,求有幾個字元改括號方向 ...
- A. Cubes Sorting 解析(思維)
Codeforce 1420 A. Cubes Sorting 解析(思維) 今天我們來看看CF1420 題目連結 題目 給一個數列\(a\),求能不能在不超過\(\frac{n(n-1)}{2}-1 ...
- Luogu P5087 数学
题意 给定一个长度为 \(n\) 的序列 \(a_i\),求出在这个序列中所有选出 \(k\) 个元素方案中元素的乘积之和. \(\texttt{Data Range:}1\leq n\leq 10^ ...
- lora传输模块的特点概述
现今Lora已经是一种在物联网中广泛应用的技术,它是一种无线调制的方式,相对于传统的FSK调制技术来说,Lora在抑制同频干扰方面有非常大的优势,它解决了无法同时兼顾距离.抗扰和功耗不足的问题;另外l ...
- 11张图和源码带你解析Spring Bean的生命周期,建议收藏~!
在网上已经有跟多Bean的生命周期的博客,但是很多都是基于比较老的版本了,最近把整个流程画成了一个流程图.待会儿使用流程图,说明以及代码的形式来说明整个声明周期的流程.注意因为代码比较多,这里的流程图 ...