MyBatis 源码分析笔记 sql 执行

MyBatis 源码分析笔记 sql 执行

MyBatis 源码分析笔记 sql 执行

MyBatis 源码分析笔记 sql 执行

  • 我们可以看到,Executor 的直接子类有 BaseExecutor 和 CachingExecutor 两个。
  • 实际上,CachingExecutor 在 BaseExecutor 的基础上,实现二级缓存功能。
  • 在下文中,BaseExecutor 的本地缓存,就是一级缓存。

每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个 SqlSession 对象表示一次数据库会话,而每个 SqlSession 都会创建一个 Executor 对象

在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

为了解决这一问题,减少资源的浪费,MyBatis 会在表示会话的SqlSession 对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。注意,这个“简单的缓存”就是一级缓存,且默认开启,无法关闭

如下图所示,MyBatis 会在一次会话的表示 —— 一个 SqlSession 对象中创建一个本地缓存( localCache ),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。

 

MyBatis 源码分析笔记 sql 执行

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) 方法,执行写操作

MyBatis 源码分析笔记 sql 执行

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 进行二级缓存的查询,具体的工作流程如下所示。

 

MyBatis 源码分析笔记 sql 执行

org.apache.ibatis.executor.CachingExecutor ,实现 Executor 接口,支持二级缓存的 Executor 的实现类。

 

  • tcm 属性,TransactionalCacheManager 对象,支持事务的缓存管理器。因为二级缓存是支持跨 Session 进行共享,此处需要考虑事务,那么,必然需要做到事务提交时,才将当前事务中查询时产生的缓存,同步到二级缓存中。这个功能,就通过 TransactionalCacheManager 来实现。
  • <1> 处,设置 delegate 属性,为被委托的 Executor 对象。
  • <2> 处,调用 delegate 属性的 #setExecutorWrapper(Executor executor) 方法,设置 delegate 被当前执行器所包装

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

SQL 执行之 StatementHandler

MyBatis 源码分析笔记 sql 执行

MyBatis 源码分析笔记 sql 执行

MyBatis 源码分析笔记 sql 执行

 SQL 执行 之 ResultSetHandler

MyBatis 源码分析笔记 sql 执行

org.apache.ibatis.executor.resultset.DefaultResultSetHandler ,实现 ResultSetHandler 接口,默认的 ResultSetHandler 实现类

#handleResultSets(Statement stmt) 方法,处理 java.sql.ResultSet 结果集,转换成映射的对应结果

MyBatis 源码分析笔记 sql 执行

MyBatis 源码分析笔记 sql 执行

MyBatis 源码分析笔记 sql 执行