一、前言

Executor 是 Mybatis 最重要的类之一,它是 Mybatis 的执行器引擎,所有的 SQL 的 增、删、改、查都是交给它来完成。

二、API 及继承关系

2.1 API

Executor_api_interface.png
updatequery 接口是比较重要的。

2.2 继承体系

Executor.png

  • Executor: 总教头,定义了关于数据查询、事务操作等 API。
  • BaseExecutor: 抽象类,封装了通用的缓存操作逻辑。
  • SimpleExecutor: 简单执行器,根据对应的 SQL 直接执行即可,不会做一些额外的操作。
  • ReuseExecutor: 通过批量操作来优化性能。当批量更新操作时,由于内部有缓存,所以必须调用 flushStatements() 方法清除缓存。
  • BatchExecutor: 可重用执行器,重用 Statement 对象,该执行器会缓存同一个 SQL 的 Statement 对象,作用域为 SqlSession 。调用完后必须调用 flushStatements() 方法清除缓存。
  • ClosedExecutor: 空实现。

    三、Executor 初始化

    Executor 的初始化是从 SqlSessionFactory 工厂获取 SqlSession 会话时创建,代码如下:
    Executor_newExecutor0.png
    Executor_newExecutor.pngExecutor 初始化比较简单,根据 ExecutorType 实例化不同的 Executor ,在配置有全局缓存的情况下,使用装饰者模式包装已创建好的 Executor
    值得注意的是 Mybatis 提供拦截器方式扩展 Executor
    全局配置二级缓存如下
    globle_cache_setting.png
    每次通过 SqlSessionFactory 获取 SqlSession 都会创建 Executor 新的实例。

    四、无参数查询

    注意: 此操作关闭二级缓存。

Query_.png
查询语句如上所示,这是最简单的 SELECT 查询 SQL。下图是当前 SQL 语句的方法栈,最终的执行我们会来到 org.apache.ibatis.executor.statement.PreparedStatementHandler#query() 方法,通过 ps.execute(); 调用底层 JDBC 驱动程序并获取数据。
PreparedStatement_execute.png
原来 Executor 还不是与驱动最近的那个类,而是 PreparedStatementHandler ,它把 java.sql.PreparedStatement 进行包装。
Executor 到底完成了什么工作呢? 我们简单分析一下。

五、Executor 体系解析

5.1 BaseExecutor

先说结论: BaseExecutor 封装缓存通用逻辑,封装 Mybatis 一级缓存 处理逻辑。
下图为 BaseExecutor
BaseExecutor.png
可以看到里面存在 PerpetualCache 缓存对象,其实该对象内部维护一个 Map 集合以充当缓存实现。
那什么是 Mybatis 的一级缓存呢?

5.2 一级缓存

5.2.1 存在原因

  • MyBatis 利用本地缓存机制(Local Cache)防止循环引用
  • 加速重复的嵌套查询。
  • 默认值为 SESSION,会缓存一个会话中执行的所有查询。

    5.2.2 生命周期

    Mybatis 使用 SessionFactory 单例对象创建 SqlSessionExecutor 执行器的生命周期与 SqlSession 绑定在一起,共生死,而 Executor 对象内部(BaseExecutor),持有 PerpetualCache 缓存对象,所以一级缓存作用域在 SqlSession 内部。

    5.2.3 如何判断一级缓存是否存在

    Mybatis 是根据以下规则判断两次查询是否完全一样:

  • statementId

  • 结果集范围
  • SQL 语句
  • 参数值
  • Environment ID

缓存生成代码如下:
Cache_generate.png
缓存
Cache_detail.png

5.2.4 一级缓存添加、删除在哪里?

结论

  • 前置清空: 查询前清空缓存
    • Mapper 映射文件配置 flushCache="true"
    • 增、删、改
  • 后置清空: 查询后清空缓存

    • statementType=”STATEMENT”
    • 提交前 清空
    • 回滚前 清空

      缓存添加

      添加操作是在查询数据库得到结果后添加到 localCache 中。
      查询操作添加缓存
      Cache_Level_One.png
      增、删、改操作添加缓存
      BaseExecutor_update_clear_cache.png
      事务提交前清空
      BaseExecutor_commit_clear_cache.png
      回滚清空
      BaseExecutor_rollback_clear_cache.png

      缓存清空

      清空 操作代码如下:
      BaseExecutor_clear_cache.png
      从上面代码看出,Executor 并没有暴露根据指定的 key 清除特定缓存,而是清除当前 SqlSession 的所有本地缓存。

      5.2.5 一级缓存小结

  • 一级缓存作用域为 SqlSession ,一旦当前 SqlSession 被关闭,一级缓存不同用。由于 SqlSession 非线程安全,不能存在多个线程使用一个缓存。所以一级缓存存在时间很短。

  • 一级缓存用于防止循环引用,加速重复嵌套类的嵌套查询。
  • BaseExecutor 抽象类最重要的工作是抽象了一级缓存操作逻辑。

    5.3 子类实现抽象方法

    再看一下 Executor 继承关系图:
    Executor.png
    既然 BaseExecutor 帮我们做了关于一级缓存的公共抽象代码逻辑,那各个子类需要做些什么操作呢?
    我们看一下 BaseExecutor 定义的四个抽象方法:
    BaseExecutor_abstract_method.png这四个抽象方法需要由子类型实现,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

SimpleExecutor_doQuery.png
运行时
Simple_getStatement.png
有四个执行步骤:

  1. MappedStatement 对象中获取配置类。每个 MappedStatement 都持有全局配置类的对象。
  2. 根据配置文件实体化 StatementHandler
  3. 准备 java.sql.Statement 实例对象。
    1. 获取数据库连接
    2. 根据连接获取 java.sql.Statement 实例对象,一般为 PreparedStatement
    3. 参数预编译(涉及 TypeHandler)
  4. SQL 查询

这里,出现了一个船新对象 StatementHandler ,他的解析留到下一篇讲。
不同的 ExecutorprepareStatement() 方法处理也是不同。看看 SimpleExecutor
SimpleExecutor_prepareStatemnet.png
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()