MyBatis 源码分析笔记 sql 执行
- 我们可以看到,Executor 的直接子类有 BaseExecutor 和 CachingExecutor 两个。
- 实际上,CachingExecutor 在 BaseExecutor 的基础上,实现二级缓存功能。
- 在下文中,BaseExecutor 的本地缓存,就是一级缓存。
每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个 SqlSession 对象表示一次数据库会话,而每个 SqlSession 都会创建一个 Executor 对象。
在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
为了解决这一问题,减少资源的浪费,MyBatis 会在表示会话的SqlSession 对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。注意,这个“简单的缓存”就是一级缓存,且默认开启,无法关闭。
如下图所示,MyBatis 会在一次会话的表示 —— 一个 SqlSession 对象中创建一个本地缓存( localCache
),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。
3.5 query
① #query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
方法,读操作
-
<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)
-
<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
3.5.1 queryFromDatabase
#queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
方法,从数据库中读取操作
3.7 update
#update(MappedStatement ms, Object parameter)
方法,执行写操作
org.apache.ibatis.executor.SimpleExecutor
,继承 BaseExecutor 抽象类,简单的 Executor 实现类。
- 每次开始读或写操作,都创建对应的 Statement 对象。
- 执行完成后,关闭该 Statement 对象
org.apache.ibatis.executor.ReuseExecutor
,继承 BaseExecutor 抽象类,可重用的 Executor 实现类。
- 每次开始读或写操作,优先从缓存中获取对应的 Statement 对象。如果不存在,才进行创建。
- 执行完成后,不关闭该 Statement 对象。
- 其它的,和 SimpleExecutor 是一致的。
- ReuseExecutor 考虑到重用性,但是 Statement 最终还是需要有地方关闭。答案就在
#doFlushStatements(boolean isRollback)
方法中。而 BaseExecutor 在关闭#close()
方法中,最终也会调用该方法,从而完成关闭缓存的 Statement 对象们
。 - 另外,BaseExecutor 在提交或者回滚事务方法中,最终也会调用该方法,也能完成关闭缓存的 Statement 对象们
org.apache.ibatis.executor.BatchExecutor
,继承 BaseExecutor 抽象类,批量执行的 Executor 实现类。
在上文中提到的一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰 Executor ,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示。
org.apache.ibatis.executor.CachingExecutor
,实现 Executor 接口,支持二级缓存的 Executor 的实现类。
-
tcm
属性,TransactionalCacheManager 对象,支持事务的缓存管理器。因为二级缓存是支持跨 Session 进行共享,此处需要考虑事务,那么,必然需要做到事务提交时,才将当前事务中查询时产生的缓存,同步到二级缓存中。这个功能,就通过 TransactionalCacheManager 来实现。 -
<1>
处,设置delegate
属性,为被委托的 Executor 对象。 -
<2>
处,调用delegate
属性的#setExecutorWrapper(Executor executor)
方法,设置delegate
被当前执行器所包装
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
SQL 执行之 StatementHandler
SQL 执行 之 ResultSetHandler
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
,实现 ResultSetHandler 接口,默认的 ResultSetHandler 实现类
#handleResultSets(Statement stmt)
方法,处理 java.sql.ResultSet
结果集,转换成映射的对应结果