# 精尽 MyBatis 源码分析 —— SQL 执行(一)之 Executor # 1. 概述 从本文开始,我们来分享 SQL **执行**的流程。在 [《精尽 MyBatis 源码分析 —— 项目结构一览》](http://svip.iocoder.cn/MyBatis/intro) 中,我们简单介绍这个流程如下: > 对应 `executor` 和 `cursor` 模块。前者对应**执行器**,后者对应执行**结果的游标**。 > > SQL 语句的执行涉及多个组件 ,其中比较重要的是 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 。 > > - **Executor** 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给 StatementHandler完成。 > - **StatementHandler** 首先通过 **ParameterHandler** 完成 SQL 语句的实参绑定,然后通过 `java.sql.Statement` 对象执行 SQL 语句并得到结果集,最后通过 **ResultSetHandler** 完成结果集的映射,得到结果对象并返回。 > > 整体过程如下图: > > [![整体过程](21-mybatis-SQL 执行(一)之 Executor.assets/05.png)](http://static.iocoder.cn/images/MyBatis/2020_01_04/05.png)整体过程 下面,我们在看看 `executor` 包下的列情况,如下图所示:[![`executor` 包](21-mybatis-SQL 执行(一)之 Executor.assets/01.png)](http://static.iocoder.cn/images/MyBatis/2020_02_28/01.png)`executor` 包 - 正如该包下的分包情况,每个包对应一个功能。 - `statement` 包,实现向数据库发起 SQL 命令。 - ``` parameter ``` 包,实现设置 PreparedStatement 的占位符参数。 - 目前只有一个 ParameterHandler 接口,在 [《精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource》](http://svip.iocoder.cn/MyBatis/scripting-2) 已经详细解析。 - `keygen` 包,实现数据库主键生成( 获得 )的功能。 - `resultset` 包,实现 ResultSet 结果集的处理,将其映射成对应的结果对象。 - `result` 包,结果的处理,被 `resultset` 包所调用。可能胖友会好奇为啥会有 `resultset` 和 `result` 两个“重叠”的包。答案见 [《精尽 MyBatis 源码分析 —— SQL 执行(四)之 ResultSetHandler》](http://svip.iocoder.cn/MyBatis/executor-4) 。 - `loader` 包,实现延迟加载的功能。 - 根目录,Executor 接口及其实现类,作为 SQL 执行的核心入口。 考虑到整个 `executor` 包的代码量近 5000 行,所以我们将每一个子包,作为一篇文章,逐包解析。所以,本文我们先来分享 根目录,也就是 Executor 接口及其实现类。 # 2. Executor `org.apache.ibatis.executor.Executor` ,执行器接口。代码如下: ``` // Executor.java public interface Executor { // 空 ResultHandler 对象的枚举 ResultHandler NO_RESULT_HANDLER = null; // 更新 or 插入 or 删除,由传入的 MappedStatement 的 SQL 所决定 int update(MappedStatement ms, Object parameter) throws SQLException; // 查询,带 ResultHandler + CacheKey + BoundSql List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; // 查询,带 ResultHandler List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; // 查询,返回值为 Cursor Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException; // 刷入批处理语句 List flushStatements() throws SQLException; // 提交事务 void commit(boolean required) throws SQLException; // 回滚事务 void rollback(boolean required) throws SQLException; // 创建 CacheKey 对象 CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql); // 判断是否缓存 boolean isCached(MappedStatement ms, CacheKey key); // 清除本地缓存 void clearLocalCache(); // 延迟加载 void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class targetType); // 获得事务 Transaction getTransaction(); // 关闭事务 void close(boolean forceRollback); // 判断事务是否关闭 boolean isClosed(); // 设置包装的 Executor 对象 void setExecutorWrapper(Executor executor); } ``` - 读和写操作相关的方法 - 事务相关的方法 - 缓存相关的方法 - 设置延迟加载的方法 - 设置包装的 Executor 对象的方法 ------ Executor 的实现类如下图所示:[![Executor 类图](21-mybatis-SQL 执行(一)之 Executor.assets/02.png)](http://static.iocoder.cn/images/MyBatis/2020_02_28/02.png)Executor 类图 - 我们可以看到,Executor 的直接子类有 BaseExecutor 和 CachingExecutor 两个。 - 实际上,CachingExecutor 在 BaseExecutor 的基础上,实现**二级缓存**功能。 - 在下文中,BaseExecutor 的**本地**缓存,就是**一级**缓存。 下面,我们按照先看 BaseExecutor 侧的实现类的源码解析,再看 CachingExecutor 的。 # 3. BaseExecutor `org.apache.ibatis.executor.BaseExecutor` ,实现 Executor 接口,提供骨架方法,从而使子类只要实现指定的几个抽象方法即可。 ## 3.1 构造方法 ``` // BaseExecutor.java /** * 事务对象 */ protected Transaction transaction; /** * 包装的 Executor 对象 */ protected Executor wrapper; /** * DeferredLoad( 延迟加载 ) 队列 */ protected ConcurrentLinkedQueue deferredLoads; /** * 本地缓存,即一级缓存 */ protected PerpetualCache localCache; /** * 本地输出类型的参数的缓存 */ protected PerpetualCache localOutputParameterCache; protected Configuration configuration; /** * 记录嵌套查询的层级 */ protected int queryStack; /** * 是否关闭 */ private boolean closed; protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue<>(); this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; // 自己 } ``` - 和延迟加载相关,后续文章,详细解析。详细解析,见 《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》 。 - `queryStack` 属性,记录**递归**嵌套查询的层级。 - `deferredLoads` 属性,DeferredLoad( 延迟加载 ) 队列。 - `wrapper` 属性,在构造方法中,初始化为 `this` ,即自己。 - `localCache` 属性,本地缓存,即**一级缓存**。那什么是一级缓存呢? > 基于 [《MyBatis 的一级缓存实现详解及使用注意事项》](https://blog.csdn.net/luanlouis/article/details/41280959) 进行修改 > > 每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个 SqlSession 对象表示一次数据库会话,**而每个 SqlSession 都会创建一个 Executor 对象**。 > > 在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。 > > 为了解决这一问题,减少资源的浪费,MyBatis 会在表示会话的SqlSession 对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。😈 **注意,这个“简单的缓存”就是一级缓存,且默认开启,无法关闭**。 > > 如下图所示,MyBatis 会在一次会话的表示 —— 一个 SqlSession 对象中创建一个本地缓存( `localCache` ),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。 > > [![整体过程](21-mybatis-SQL 执行(一)之 Executor.assets/03.png)](http://static.iocoder.cn/images/MyBatis/2020_02_28/03.png)整体过程 > > - 关于这段话,胖友要理解 SqlSession 和 Executor 和一级缓存的关系。 > - 😈 另外,下文,我们还会介绍**二级缓存**是什么。 - `transaction` 属性,事务对象。该属性,是通过构造方法传入。为什么呢?待我们看 `org.apache.ibatis.session.session` 包。 ## 3.2 clearLocalCache `#clearLocalCache()` 方法,清理一级(本地)缓存。代码如下: ``` // BaseExecutor.java @Override public void clearLocalCache() { if (!closed) { // 清理 localCache localCache.clear(); // 清理 localOutputParameterCache localOutputParameterCache.clear(); } } ``` ## 3.3 createCacheKey `#createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)` 方法,创建 CacheKey 对象。代码如下: ``` // BaseExecutor.java @Override public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } // <1> 创建 CacheKey 对象 CacheKey cacheKey = new CacheKey(); // <2> 设置 id、offset、limit、sql 到 CacheKey 对象中 cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); // <3> 设置 ParameterMapping 数组的元素对应的每个 value 到 CacheKey 对象中 List parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic 这块逻辑,和 DefaultParameterHandler 获取 value 是一致的。 for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } cacheKey.update(value); } } // <4> 设置 Environment.id 到 CacheKey 对象中 if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; } ``` - `<1>` 处,创建 CacheKey 对象。关于 CacheKey 类,在 [《精尽 MyBatis 源码分析 —— 缓存模块》](http://svip.iocoder.cn/MyBatis/cache-package) 已经详细解析。 - `<2>` 处,设置 `id`、`offset`、`limit`、`sql` 到 CacheKey 对象中。 - `<3>` 处,设置 ParameterMapping 数组的元素对应的每个 `value` 到 CacheKey 对象中。注意,这块逻辑,和 DefaultParameterHandler 获取 `value` 是一致的。 - `<4>` 处,设置 `Environment.id` 到 CacheKey 对象中。 ## 3.4 isCached `#isCached(MappedStatement ms, CacheKey key)` 方法,判断一级缓存是否存在。代码如下: ``` // BaseExecutor.java @Override public boolean isCached(MappedStatement ms, CacheKey key) { return localCache.getObject(key) != null; } ``` ## 3.5 query ① `#query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)` 方法,读操作。代码如下: ``` // BaseExecutor.java @Override public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // <1> 获得 BoundSql 对象 BoundSql boundSql = ms.getBoundSql(parameter); // <2> 创建 CacheKey 对象 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // <3> 查询 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } ``` - `<1>` 处,调用 `MappedStatement#getBoundSql(Object parameterObject)` 方法,获得 BoundSql 对象。 - `<2>` 处,调用 `#createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)` 方法,创建 CacheKey 对象。 - `<3>` 处,调用 `#query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)` 方法,读操作。通过这样的方式,两个 `#query(...)` 方法,实际是统一的。 ② `#query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)` 方法,读操作。代码如下: ``` // BaseExecutor.java @Override public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); // <1> 已经关闭,则抛出 ExecutorException 异常 if (closed) { throw new ExecutorException("Executor was closed."); } // <2> 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List list; try { // <3> queryStack + 1 queryStack++; // <4.1> 从一级缓存中,获取查询结果 list = resultHandler == null ? (List) localCache.getObject(key) : null; // <4.2> 获取到,则进行处理 if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); // <4.3> 获得不到,则从数据库中查询 } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { // <5> queryStack - 1 queryStack--; } if (queryStack == 0) { // <6.1> 执行延迟加载 for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 // <6.2> 清空 deferredLoads deferredLoads.clear(); // <7> 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } ``` - `<1>` 处,已经关闭,则抛出 ExecutorException 异常。 - `<2>` 处,调用 `#clearLocalCache()` 方法,清空本地缓存,如果 `queryStack` 为零,并且要求清空本地缓存。例如:`` 方式,开启需要清空缓存。 - 调用 `TransactionalCache#clear()` 方法,清空缓存。**注意**,此**时**清空的仅仅,当前事务中查询数据产生的缓存。而**真正**的清空,在事务的提交时。这是为什么呢?还是因为**二级缓存**是跨 Session 共享缓存,在事务尚未结束时,不能对二级缓存做任何修改。😈 可能有点绕,胖友好好理解。 - `<2.2>` 处,当 `MappedStatement#isUseCache()` 方法,返回 `true` 时,才使用二级缓存。默认开启。可通过 `@Options(useCache = false)` 或 `