从本文开始,我们来分享 SQL 执行的流程。在 《精尽 MyBatis 源码分析 —— 项目结构一览》 中,我们简单介绍这个流程如下:
对应
executor
和cursor
模块。前者对应执行器,后者对应执行结果的游标。SQL 语句的执行涉及多个组件 ,其中比较重要的是 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 。
- Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给 StatementHandler完成。
- StatementHandler 首先通过 ParameterHandler 完成 SQL 语句的实参绑定,然后通过
java.sql.Statement
对象执行 SQL 语句并得到结果集,最后通过 ResultSetHandler 完成结果集的映射,得到结果对象并返回。整体过程如下图:
下面,我们在看看 executor
包下的列情况,如下图所示:
statement
包,实现向数据库发起 SQL 命令。parameter
包,实现设置 PreparedStatement 的占位符参数。
keygen
包,实现数据库主键生成( 获得 )的功能。resultset
包,实现 ResultSet 结果集的处理,将其映射成对应的结果对象。result
包,结果的处理,被 resultset
包所调用。可能胖友会好奇为啥会有 resultset
和 result
两个“重叠”的包。答案见 《精尽 MyBatis 源码分析 —— SQL 执行(四)之 ResultSetHandler》 。loader
包,实现延迟加载的功能。考虑到整个 executor
包的代码量近 5000 行,所以我们将每一个子包,作为一篇文章,逐包解析。所以,本文我们先来分享 根目录,也就是 Executor 接口及其实现类。
org.apache.ibatis.executor.Executor
,执行器接口。代码如下:
// Executor.java
|
Executor 的实现类如下图所示:
下面,我们按照先看 BaseExecutor 侧的实现类的源码解析,再看 CachingExecutor 的。
org.apache.ibatis.executor.BaseExecutor
,实现 Executor 接口,提供骨架方法,从而使子类只要实现指定的几个抽象方法即可。
// BaseExecutor.java
|
queryStack
属性,记录递归嵌套查询的层级。deferredLoads
属性,DeferredLoad( 延迟加载 ) 队列。wrapper
属性,在构造方法中,初始化为 this
,即自己。localCache
属性,本地缓存,即一级缓存。那什么是一级缓存呢?
基于 《MyBatis 的一级缓存实现详解及使用注意事项》 进行修改
每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个 SqlSession 对象表示一次数据库会话,而每个 SqlSession 都会创建一个 Executor 对象。
在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
为了解决这一问题,减少资源的浪费,MyBatis 会在表示会话的SqlSession 对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。?? 注意,这个“简单的缓存”就是一级缓存,且默认开启,无法关闭。
如下图所示,MyBatis 会在一次会话的表示 —— 一个 SqlSession 对象中创建一个本地缓存(
localCache
),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。
- 关于这段话,胖友要理解 SqlSession 和 Executor 和一级缓存的关系。
- ?? 另外,下文,我们还会介绍二级缓存是什么。
transaction
属性,事务对象。该属性,是通过构造方法传入。为什么呢?待我们看 org.apache.ibatis.session.session
包。
#clearLocalCache()
方法,清理一级(本地)缓存。代码如下:
// BaseExecutor.java
|
#createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)
方法,创建 CacheKey 对象。代码如下:
// BaseExecutor.java
|
<1>
处,创建 CacheKey 对象。关于 CacheKey 类,在 《精尽 MyBatis 源码分析 —— 缓存模块》 已经详细解析。<2>
处,设置 id
、offset
、limit
、sql
到 CacheKey 对象中。<3>
处,设置 ParameterMapping 数组的元素对应的每个 value
到 CacheKey 对象中。注意,这块逻辑,和 DefaultParameterHandler 获取 value
是一致的。<4>
处,设置 Environment.id
到 CacheKey 对象中。#isCached(MappedStatement ms, CacheKey key)
方法,判断一级缓存是否存在。代码如下:
// BaseExecutor.java
|
① #query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
方法,读操作。代码如下:
// BaseExecutor.java
|
<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
|
<1>
处,已经关闭,则抛出 ExecutorException 异常。<2>
处,调用 #clearLocalCache()
方法,清空本地缓存,如果 queryStack
为零,并且要求清空本地缓存。例如:<select flushCache="true"> ... </a>
。<3>
处,queryStack
+ 1 。<4.1>
处,从一级缓存 localCache
中,获取查询结果。
<4.2>
处,获取到,则进行处理。对于 #handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql)
方法,是处理存储过程的情况,所以我们就忽略。<4.3>
处,获得不到,则调用 #queryFromDatabase()
方法,从数据库中查询。详细解析,见 「3.5.1 queryFromDatabase」 。<5>
处,queryStack
- 1 。<6.1>
处,遍历 DeferredLoad 队列,逐个调用 DeferredLoad#load()
方法,执行延迟加载。详细解析,见 《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》 。
<6.2>
处,清空 DeferredLoad 队列。<7>
处,如果缓存级别是 LocalCacheScope.STATEMENT
,则调用 #clearLocalCache()
方法,清空本地缓存。默认情况下,缓存级别是 LocalCacheScope.SESSION
。代码如下:
// Configuration.java
|
#queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
方法,从数据库中读取操作。代码如下:
// BaseExecutor.java
|
<1>
处,在缓存中,添加占位对象。此处的占位符,和延迟加载有关,后续可见 DeferredLoad#canLoad()
方法。
// BaseExecutor.java
|
<2>
处,调用 #doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
执行读操作。这是个抽象方法,由子类实现。
<3>
处,从缓存中,移除占位对象。<4>
处,添加结果到缓存中。<5>
处,暂时忽略,存储过程相关。
// BaseExecutor.java
|
#queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds)
方法,执行查询,返回的结果为 Cursor 游标对象。代码如下:
// BaseExecutor.java
|
<1>
处,调用 MappedStatement#getBoundSql(Object parameterObject)
方法,获得 BoundSql 对象。<2>
处,调用 #doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
方法,执行读操作。这是个抽象方法,由子类实现。
// BaseExecutor.java
|
#update(MappedStatement ms, Object parameter)
方法,执行写操作。代码如下:
// BaseExecutor.java
|
<1>
处,已经关闭,则抛出 ExecutorException 异常。<2>
处,调用 #clearLocalCache()
方法,清空本地缓存。因为,更新后,可能缓存会失效。但是,又没很好的办法,判断哪一些失效。所以,最稳妥的做法,就是全部清空。<3>
处,调用 #doUpdate(MappedStatement ms, Object parameter)
方法,执行写操作。这是个抽象方法,由子类实现。
// BaseExecutor.java
|
#flushStatements()
方法,刷入批处理语句。代码如下:
// BaseExecutor.java
|
isRollBack
属性,目前看下来没什么逻辑。唯一看到在 BatchExecutor 中,如果 isRollBack = true
,则不执行刷入批处理语句。有点奇怪。<1>
处,已经关闭,则抛出 ExecutorException 异常。<2>
处,调用 #doFlushStatements(boolean isRollback)
方法,执行刷入批处理语句。这是个抽象方法,由子类实现。
// BaseExecutor.java
|
至此,我们已经看到了 BaseExecutor 所定义的四个抽象方法:
#getTransaction()
方法,获得事务对象。代码如下:
// BaseExecutor.java
|
// BaseExecutor.java
|
// BaseExecutor.java
|
#close()
方法,关闭执行器。代码如下:
// BaseExecutor.java
|
// BaseExecutor.java
|
详细解析,见 《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》 。
#setExecutorWrapper(Executor wrapper)
方法,设置包装器。代码如下:
// BaseExecutor.java
|
// BaseExecutor.java
|
org.apache.ibatis.executor.statement.StatementUtil
查看。org.apache.ibatis.executor.SimpleExecutor
,继承 BaseExecutor 抽象类,简单的 Executor 实现类。
// SimpleExecutor.java
|
老艿艿:从此处开始,我们会看到 StatementHandler 相关的调用,胖友可以结合 《精尽 MyBatis 源码分析 —— SQL 执行(二)之 StatementHandler》 一起看。
// SimpleExecutor.java
|
<1>
处,调用 Configuration#newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
方法,创建 StatementHandler 对象。<2>
处,调用 #prepareStatement(StatementHandler handler, Log statementLog)
方法,初始化 StatementHandler 对象。代码如下:
// SimpleExecutor.java
|
<2.1>
处,调用 #getConnection(Log statementLog)
方法,获得 Connection 对象。<2.2>
处,调用 StatementHandler#prepare(Connection connection, Integer transactionTimeout)
方法,创建 Statement 或 PrepareStatement 对象。<2.3>
处,调用 StatementHandler#prepare(Statement statement)
方法,设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符。<3>
处,调用 StatementHandler#query(Statement statement, ResultHandler resultHandler)
方法,进行读操作。<4>
处,调用 #closeStatement(Statement stmt)
方法,关闭 StatementHandler 对象。
// SimpleExecutor.java
|
#doQuery(...)
方法的思路是一致的,胖友自己看下。
// SimpleExecutor.java
|
#doQuery(...)
方法,差异点在 <3>
处,换成了调用 StatementHandler#update(Statement statement)
方法,进行写操作。
// SimpleExecutor.java
|
org.apache.ibatis.executor.ReuseExecutor
,继承 BaseExecutor 抽象类,可重用的 Executor 实现类。
// ReuseExecutor.java
|
// ReuseExecutor.java
|
差异一,在于 <1>
处,调用 #prepareStatement(StatementHandler handler, Log statementLog)
方法,初始化 StatementHandler 对象。代码如下:
// ReuseExecutor.java
|
调用 #hasStatementFor(String sql)
方法,判断是否存在对应的 Statement 对象。代码如下:
// ReuseExecutor.java
|
存在
<1.1>
处,调用 #getStatement(String s)
方法,获得 Statement 对象。代码如下:
// ReuseExecutor.java
|
<1.2>
处,调用 #applyTransactionTimeout(Statement stmt)
方法,设置事务超时时间。不存在
<2.1>
处,获得 Connection 对象。<2.2>
处,调用 StatementHandler#prepare(Connection connection, Integer transactionTimeout)
方法,创建 Statement 或 PrepareStatement 对象。【差异】<2.3>
处,调用 #putStatement(String sql, Statement stmt)
方法,添加 Statement 对象到缓存中。代码如下:
// ReuseExecutor.java
|
<2>
处,调用 StatementHandler#prepare(Statement statement)
方法,设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符。
// ReuseExecutor.java
|
// ReuseExecutor.java
|
// ReuseExecutor.java
|
#doFlushStatements(boolean isRollback)
方法中。而 BaseExecutor 在关闭 #close()
方法中,最终也会调用该方法,从而完成关闭缓存的 Statement 对象们org.apache.ibatis.executor.BatchExecutor
,继承 BaseExecutor 抽象类,批量执行的 Executor 实现类。
FROM 祖大俊 《Mybatis3.3.x技术内幕(四):五鼠闹东京之执行器Executor设计原本》
执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)
// BatchExecutor.java
|
currentSql
和 currentStatement
属性,当前 SQL 和 MappedStatement 对象。batchResultList
和 statementList
属性,分别是 BatchResult 和 Statement 数组。并且,每一个 batchResultList
的 BatchResult 元素,对应一个 statementList
的 Statement 元素。#doUpdate(...)
和 #doFlushStatements(...)
方法。org.apache.ibatis.executor.BatchResult
,相同 SQL 聚合的结果。代码如下:
// BatchResult.java
|
// BatchExecutor.java
|
<1>
处,调用 Configuration#newStatementHandler(...)
方法,创建 StatementHandler 对象。<2>
和 <3>
处,就是两种不同的情况,差异点在于传入的 sql
和 ms
是否匹配当前的 currentSql
和 currentStatement
。如果是,则继续聚合到最后的 BatchResult 中,否则,创建新的 BatchResult 对象,进行“聚合”。<2>
块:
<2.1>
、<2.2>
、<2.3>
处,逻辑和 ReuseExecutor 相似,使用可重用的 Statement 对象,并进行初始化。<2.4>
处,获得最后一次的 BatchResult 对象,并添加参数到其中。<3>
块:
<3.1>
、<3.2>
、<3.3>
处,逻辑和 SimpleExecutor 相似,创建新的 Statement 对象,并进行初始化。<3.4>
处,重新设置 currentSql
和 currentStatement
,为当前传入的 sql
和 ms
。<3.5>
处,添加 Statement 到 statementList
中。<3.6>
处,创建 BatchResult 对象,并添加到 batchResultList
中。sql
和 ms
进来,就会聚合到目前新创建的 BatchResult 对象中。<4>
处,调用 StatementHandler#batch(Statement statement)
方法,批处理。代码如下:
// 有多个实现类,先看两个
|
// BatchExecutor.java
|
<1>
处,如果 isRollback
为 true
,返回空数组。<2>
处,遍历 statementList
和 batchResultList
数组,逐个 Statement 提交批处理。
<2.1>
处,获得 Statement 和 BatchResult 对象。<2.2>
处,调用 Statement#executeBatch()
方法,批量执行。执行完成后,将结果赋值到 BatchResult.updateCounts
中。<2.3>
处,处理主键生成。详细解析,见 《精尽 MyBatis 源码分析 —— SQL 执行(三)之 KeyGenerator》 。<2.4>
处,调用 #closeStatement(stmt)
方法,关闭 Statement 对象。<2.5>
处,添加到结果集。<3.1>
处,关闭 Statement
们。<3.2>
处,置空 currentSql
、statementList
、batchResultList
属性。
// BatchExecutor.java
|
<1>
处,发生查询之前,先调用 #flushStatements()
方法,刷入批处理语句。
// BatchExecutor.java
|
<1>
处,发生查询之前,先调用 #flushStatements()
方法,刷入批处理语句。在开始看具体源码之间,我们先来理解二级缓存的定义:
FROM 凯伦 《聊聊MyBatis缓存机制》
在上文中提到的一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰 Executor ,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示。
那么,二级缓存,反应到具体代码里,是长什么样的呢?我们来打开 MappedStatement 类,代码如下:
// MappedStatement.java
|
cache
属性。在前面的文章中,我们已经看到,每个 XML Mapper 或 Mapper 接口的每个 SQL 操作声明,对应一个 MappedStatement 对象。通过 @CacheNamespace
或 <cache />
来声明,创建其所使用的 Cache 对象;也可以通过 @CacheNamespaceRef
或 <cache-ref />
来声明,使用指定 Namespace 的 Cache 对象。最终在 Configuration 类中的体现,代码如下:
// Configuration.java
|
通过在 mybatis-config.xml
中,配置如下开启二级缓存功能:
<setting name="cacheEnabled" value="true"/>
|
org.apache.ibatis.executor.CachingExecutor
,实现 Executor 接口,支持二级缓存的 Executor 的实现类。
// CachingExecutor.java
|
tcm
属性,TransactionalCacheManager 对象,支持事务的缓存管理器。因为二级缓存是支持跨 Session 进行共享,此处需要考虑事务,那么,必然需要做到事务提交时,才将当前事务中查询时产生的缓存,同步到二级缓存中。这个功能,就通过 TransactionalCacheManager 来实现。<1>
处,设置 delegate
属性,为被委托的 Executor 对象。<2>
处,调用 delegate
属性的 #setExecutorWrapper(Executor executor)
方法,设置 delegate
被当前执行器所包装。CachingExecutor 的如下方法,具体的实现代码,是直接调用委托执行器 delegate
的对应的方法。代码如下:
// CachingExecutor.java
|
// CachingExecutor.java
|
<1>
处,调用 MappedStatement#getCache()
方法,获得 Cache 对象,即当前 MappedStatement 对象的二级缓存。<3>
处,如果没有 Cache 对象,说明该 MappedStatement 对象,未设置二级缓存,则调用 delegate
属性的 #query(...)
方法,直接从数据库中查询。<2>
处,如果有 Cache 对象,说明该 MappedStatement 对象,有设置二级缓存:
<2.1>
处,调用 #flushCacheIfRequired(MappedStatement ms)
方法,如果需要清空缓存,则进行清空。代码如下:
// CachingExecutor.java
|
@Options(flushCache = Options.FlushCachePolicy.TRUE)
或 <select flushCache="true">
方式,开启需要清空缓存。TransactionalCache#clear()
方法,清空缓存。注意,此时清空的仅仅,当前事务中查询数据产生的缓存。而真正的清空,在事务的提交时。这是为什么呢?还是因为二级缓存是跨 Session 共享缓存,在事务尚未结束时,不能对二级缓存做任何修改。?? 可能有点绕,胖友好好理解。<2.2>
处,当 MappedStatement#isUseCache()
方法,返回 true
时,才使用二级缓存。默认开启。可通过 @Options(useCache = false)
或 <select useCache="false">
方法,关闭。<2.3>
处,调用 TransactionalCacheManager#getObject(Cache cache, CacheKey key)
方法,从二级缓存中,获取结果。<2.4.1>
处,调用 delegate
属性的 #query(...)
方法,再从数据库中查询。<2.4.2>
处,调用 TransactionalCacheManager#put(Cache cache, CacheKey key, Object value)
方法,缓存结果到二级缓存中。?? 当然,正如上文所言,实际上,此处结果还没添加到二级缓存中。那具体是怎么样的呢?答案见 TransactionalCache 。<2.5>
处,如果存在,则直接返回结果。
// CachingExecutor.java
|
delegate
对应的方法。
// CachingExecutor.java
|
// CachingExecutor.java
|
delegate
和 tcm
先后提交。
// CachingExecutor.java
|
delegate
和 tcm
先后回滚。
// CachingExecutor.java
|
forceRollback
属性,进行 tcm
和 delegate
对应的操作。org.apache.ibatis.cache.TransactionalCacheManager
,TransactionalCache 管理器。
// TransactionalCacheManager.java
|
transactionalCaches
是一个使用 Cache 作为 KEY ,TransactionalCache 作为 VALUE 的 Map 对象。TransactionalCache 是怎么创建的呢?答案在 #getTransactionalCache(Cache cache)
方法,代码如下:
// TransactionalCacheManager.java
|
transactionalCaches
获得 Cache 对象,对应的 TransactionalCache 对象。transactionalCaches
中。#putObject(Cache cache, CacheKey key, Object value)
方法,添加 Cache + KV ,到缓存中。代码如下:
// TransactionalCacheManager.java
|
#getObject(Cache cache, CacheKey key)
方法,获得缓存中,指定 Cache + K 的值。代码如下:
// TransactionalCacheManager.java
|
#clear()
方法,清空缓存。代码如下:
// TransactionalCacheManager.java
|
#commit()
方法,提交所有 TransactionalCache 。代码如下:
// TransactionalCacheManager.java
|
#rollback()
方法,回滚所有 TransactionalCache 。代码如下:
// TransactionalCacheManager.java
|
org.apache.ibatis.cache.decorators.TransactionalCache
,实现 Cache 接口,支持事务的 Cache 实现类,主要用于二级缓存中。英语比较好的胖友,可以看看如下注释:
This class holds all cache entries that are to be added to the 2nd level cache during a Session.
Entries are sent to the cache when commit is called or discarded if the Session is rolled back.
Blocking cache support has been added. Therefore any get() that returns a cache miss
will be followed by a put() so any lock associated with the key can be released.
// TransactionalCache.java
|
entriesToAddOnCommit
属性,会暂存当前事务新产生的缓存 KV 对。entriesToAddOnCommit
属性,会同步到二级缓存 delegate
中。
// TransactionalCache.java
|
<1>
处,调用 delegate
的 #getObject(Object key)
方法,从 delegate
中获取 key
对应的 value 。<2>
处,如果不存在,则添加到 entriesMissedInCache
中。这是个神奇的逻辑???答案见 commit()
和 #rollback()
方法。<3>
处,如果 clearOnCommit
为 true
,表示处于持续清空状态,则返回 null
。因为在事务未结束前,我们执行的清空缓存操作不好同步到 delegate
中,所以只好通过 clearOnCommit
来标记处于清空状态。那么,如果处于该状态,自然就不能返回 delegate
中查找的结果。<4>
处,返回 value 。#putObject(Object key, Object object)
方法,暂存 KV 到 entriesToAddOnCommit
中。代码如下:
// TransactionalCache.java
|
// TransactionalCache.java
|
#clear()
方法,清空缓存。代码如下:
// TransactionalCache.java
|
<1>
处,标记 clearOnCommit
为 true
。<2>
处,清空 entriesToAddOnCommit
。delegate
的缓存。真正的清空,在事务提交时。#commit()
方法,提交事务。重头戏,代码如下:
// TransactionalCache.java
|
<1>
处,如果 clearOnCommit
为 true
,则清空 delegate
缓存。<2>
处,调用 #flushPendingEntries()
方法,将 entriesToAddOnCommit
、entriesMissedInCache
同步到 delegate
中。代码如下:
// TransactionalCache.java
|
entriesMissedInCache
同步到 delegate
中,会不会存在问题。因为当前事务未查找到,不代表其他事务恰好实际能查到。这样,岂不是会将缓存错误的置空。后来一想,缓存即使真的被错误的置空,最多也就多从数据库中查询一次罢了。??<3>
处,调用 #reset()
方法,重置对象。代码如下:
// TransactionalCache.java
|
#rollback()
方法,回滚事务。代码如下:
// TransactionalCache.java
|
<1>
处,调用 #unlockMissedEntries()
方法,将 entriesMissedInCache
同步到 delegate
中。代码如下:
// TransactionalCache.java
|
entriesMissedInCache
不存在对应的缓存。<2>
处,调用 #reset()
方法,重置对象。在上面的文章中,我们已经看了各种 Executor 的实现代码。那么,Executor 对象究竟在 MyBatis 中,是如何被创建的呢?Configuration 类中,提供 #newExecutor(Transaction transaction, ExecutorType executorType)
方法,代码如下:
// Configuration.java
|
<1>
处,获得执行器类型。可以通过在 mybatis-config.xml
配置文件,如下:
// value 有三种类型:SIMPLE REUSE BATCH
|
org.apache.ibatis.session.ExecutorType
,执行器类型。代码如下:
// ExecutorType.java
|
<2>
处,创建对应实现的 Executor 对象。默认为 SimpleExecutor 对象。
<3>
处,如果开启缓存,创建 CachingExecutor 对象,进行包装。<4>
处,应用插件。关于插件,我们在后续的文章中,详细解析。老艿艿:写到凌晨 1 点多,以为已经写完了,结果发现….
在 ResultLoaderMap 类中,有一个 ClosedExecutor 内部静态类,继承 BaseExecutor 抽象类,已经关闭的 Executor 实现类。代码如下:
// ResultLoaderMap.java
|
org.apache.ibatis.executor.ErrorContext
,错误上下文,负责记录错误日志。代码比较简单,也蛮有意思,胖友可以自己研究研究。
原文:https://www.cnblogs.com/siye1989/p/11624048.html