一、前言
Executor
是 Mybatis 最重要的类之一,它是 Mybatis 的执行器引擎,所有的 SQL 的 增、删、改、查都是交给它来完成。
二、API 及继承关系
2.1 API
2.2 继承体系
Executor:
总教头,定义了关于数据查询、事务操作等 API。BaseExecutor:
抽象类,封装了通用的缓存操作逻辑。SimpleExecutor:
简单执行器,根据对应的 SQL 直接执行即可,不会做一些额外的操作。ReuseExecutor:
通过批量操作来优化性能。当批量更新操作时,由于内部有缓存,所以必须调用flushStatements()
方法清除缓存。BatchExecutor:
可重用执行器,重用Statement
对象,该执行器会缓存同一个 SQL 的Statement
对象,作用域为SqlSession
。调用完后必须调用flushStatements()
方法清除缓存。ClosedExecutor:
空实现。三、Executor 初始化
Executor
的初始化是从SqlSessionFactory
工厂获取SqlSession
会话时创建,代码如下:Executor
初始化比较简单,根据ExecutorType
实例化不同的Executor
,在配置有全局缓存的情况下,使用装饰者模式包装已创建好的Executor
。
值得注意的是 Mybatis 提供拦截器方式扩展Executor
。
全局配置二级缓存如下
每次通过SqlSessionFactory
获取SqlSession
都会创建Executor
新的实例。四、无参数查询
注意: 此操作关闭二级缓存。
查询语句如上所示,这是最简单的 SELECT
查询 SQL。下图是当前 SQL 语句的方法栈,最终的执行我们会来到 org.apache.ibatis.executor.statement.PreparedStatementHandler#query()
方法,通过 ps.execute();
调用底层 JDBC
驱动程序并获取数据。
原来 Executor
还不是与驱动最近的那个类,而是 PreparedStatementHandler
,它把 java.sql.PreparedStatement
进行包装。
那 Executor
到底完成了什么工作呢? 我们简单分析一下。
五、Executor 体系解析
5.1 BaseExecutor
先说结论: BaseExecutor
封装缓存通用逻辑,封装 Mybatis 一级缓存
处理逻辑。
下图为 BaseExecutor
可以看到里面存在 PerpetualCache
缓存对象,其实该对象内部维护一个 Map 集合以充当缓存实现。
那什么是 Mybatis 的一级缓存呢?
5.2 一级缓存
5.2.1 存在原因
- MyBatis 利用本地缓存机制(Local Cache)防止
循环引用
- 加速重复的嵌套查询。
-
5.2.2 生命周期
Mybatis 使用
SessionFactory
单例对象创建SqlSession
,Executor
执行器的生命周期与SqlSession
绑定在一起,共生死,而Executor
对象内部(BaseExecutor),持有PerpetualCache
缓存对象,所以一级缓存作用域在SqlSession
内部。5.2.3 如何判断一级缓存是否存在
Mybatis 是根据以下规则判断两次查询是否完全一样:
statementId
- 结果集范围
- SQL 语句
- 参数值
- Environment ID
5.2.4 一级缓存添加、删除在哪里?
结论
前置清空
: 查询前清空缓存- Mapper 映射文件配置
flushCache="true"
- 增、删、改
- Mapper 映射文件配置
后置清空
: 查询后清空缓存一级缓存作用域为
SqlSession
,一旦当前SqlSession
被关闭,一级缓存不同用。由于SqlSession
非线程安全,不能存在多个线程使用一个缓存。所以一级缓存存在时间很短。- 一级缓存用于防止循环引用,加速重复嵌套类的嵌套查询。
BaseExecutor
抽象类最重要的工作是抽象了一级缓存操作逻辑。5.3 子类实现抽象方法
再看一下
Executor
继承关系图:
既然BaseExecutor
帮我们做了关于一级缓存的公共抽象代码逻辑,那各个子类需要做些什么操作呢?
我们看一下BaseExecutor
定义的四个抽象方法:这四个抽象方法需要由子类型实现,
Executor
存在三种子类,分别是SimpleExecutor
ReuseExecutor
BatchExecutor
主要的区别是对 Statement
对象的处理以及是否批量处理。
SimpleExecutor
很简单,每执行一次我都用新建一个Statement
对象,用完之后就关闭。ReuseExecutor
则缓存Statement
对象,以 SQL 为 key,它不关闭Statement
对象,供下一次相同的 SQL 使用。BatchExecutor
只针对UPDATE
操作,将所有的 SQL 都添加到批处理中 (addBatch()
),等待统一执行executeBatch()
,同时它缓存了多个Statement
对象。与 JDBC 批处理逻辑相同。Mybatis
Executor
默认配置为SIMPLE
。
说清楚 Executor
之间的差别,接下来我们就看一下 SimpleExecutor
的源码吧。
六、SimpleExecutor
6.1 doQuery
运行时
有四个执行步骤:
- 从
MappedStatement
对象中获取配置类。每个MappedStatement
都持有全局配置类的对象。 - 根据配置文件实体化
StatementHandler
。 - 准备
java.sql.Statement
实例对象。- 获取数据库连接
- 根据连接获取
java.sql.Statement
实例对象,一般为PreparedStatement
- 参数预编译(涉及 TypeHandler)
- SQL 查询
这里,出现了一个船新对象 StatementHandler
,他的解析留到下一篇讲。
不同的 Executor
对 prepareStatement()
方法处理也是不同。看看 SimpleExecutor
,PreparedStatement
方法是准备 java.sql.Statement
对象,熟悉 JDBC 编程的应该十分熟悉。SimpleExecutor
执行器通过配置类获取数据库连接,再从数据库连接获取 Statement
(这里是 Mybatis 的代理对象,添加了日志切面),最后使用参数处理器进行 SQL 赋值。
七、总结
以上我们简单讨论了 SimpleExecutor
执行器的源码,其他的执行器流程大致类似,就不做过多讨论,而且 SimpleExecutor
也是 Mybatis
的默认执行器。
有了Executor
为什么还要 StatementHandler
,各人认为两者侧重点不一样,StatementHandler
是直接封装了 JDBC 底层的执行调用,比如 execute()
、execute(sql)
、addBatch(sql)
,它处于 JDBC 第一线,职责单一,而 Executor
持有 StatementHandler
对象,它也就拥有上述方法的能力,它需要做的是前面几步:
- 建立数据库连接
- 预编译 SQL
- 参数化 SQL (由参数处理器完成)
两个类侧重点不一样,StatementHandler
专注底层 JDBC 的调用,Executor
专注执行的链路情况。
总结一下各执行器特点:
SimpleExecutor
直接生成Statement
实例对象,并没有缓存。ReuseExecutor 则使用一个 Map 集合缓存 Statement 对象
BatchExecutor 则是攒够一批批 Statement 对象后,等待统一执行 executeBatch()