关于作者在"聊聊MyBatis缓存机制"中一级缓存执行流程的一些质疑

关于作者在聊聊MyBatis缓存机制中一级缓存执行流程的一些质疑

MyBatis 是默认开启 cacheEnabled =true , 也就是说,cachingExecutor 默认是执行的.

笔者认为 聊聊MyBatis缓存机制文章在一级缓存执行流程解释说明时存在漏洞(会给读者(比如我)带来误解), 希望可以修改一下原文.

结论:

MyBatis 查询默认执行流程:

二级缓存 (CachingExecutor)-> 一级缓存(BaseExecutor.localCache) ->DB

studentMapper.getStudentById(1) 

	-> mapperMethod.execute()

		 ->DefaultSqlSession.selectOne() 

			-> DefaultSqlSession.selectList()

				 -> CachingExecutor.query()

					->BaseExecutor.query()

						 -> SimpleExecutor.doQuery()    

测试:

修改配置文件 MyBatis-config.xml :

注销 localCacheScope, 以及cacheEnabled 的配置, 两者是默认开启配置的.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//MyBatis.org//DTD Config 3.0//EN"
        "http://MyBatis.org/dtd/MyBatis-3-config.dtd">
<configuration>
    <!--配置文件地址-->
    <properties resource="local-mysql.properties"/>

    <settings>
<!--        <setting name="localCacheScope" value="SESSION"/>-->
<!--        <setting name="cacheEnabled" value="true"/>-->
        <!--开启驼峰式命名,数据库的列名能够映射到去除下划线驼峰命名后的字段名-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="logImpl" value="LOG4J"/>
    </settings>

    <environments default="development">
        <environment id="development">
            <!--使用默认的JDBC事务管理-->
            <transactionManager type="JDBC"/>
            <!--使用连接池-->
            <dataSource type="POOLED">
                <!--这里会替换为local-mysql.properties中的对应字段的值-->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--SQL映射文件,MyBatis的核心-->
    <mappers>
        <mapper resource="mapper/studentMapper.xml"/>
        <mapper resource="mapper/classMapper.xml"/>
    </mappers>
</configuration>

测试方法:

@Test
public void testLocalCache() throws Exception {
    SqlSession sqlSession = factory.openSession(true); // 自动提交事务
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);


    System.out.println(studentMapper.getStudentById(1));
    System.out.println(studentMapper.getStudentById(1));
    System.out.println(studentMapper.getStudentById(1));

    sqlSession.close();
}

创建Executor 过程分析:

  • 结论: 先创建 BaseExecutor的子类(这里是SimpleExecutor) ,然后将当前executor 委托给CachingExecutor 执行.

SqlSession sqlSession = factory.openSession(true);

image-20210317105856233

openSession() 调用的堆栈信息:

"main@1" prio=5 tid=0x1 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
	  at org.apache.ibatis.executor.CachingExecutor.<init>(CachingExecutor.java:45)
	  at org.apache.ibatis.session.Configuration.newExecutor(Configuration.java:566)
	  at org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource(DefaultSqlSessionFactory.java:96)
	  at org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession(DefaultSqlSessionFactory.java:52)
	  at mapper.StudentMapperTest.testLocalCache(StudentMapperTest.java:39)
	  at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
	  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	  at java.lang.reflect.Method.invoke(Method.java:497)
	  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	  at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	  at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	  at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	  at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	  at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	  at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	  at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	  at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	  at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

MapperProxy 查询过程分析:

studentMapper.getStudentById(1)调用堆栈信息:

"main@1" prio=5 tid=0x1 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
	  at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:61)
	  at com.sun.proxy.$Proxy7.getResultSet(Unknown Source:-1)
	  at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getFirstResultSet(DefaultResultSetHandler.java:235)
	  at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:185)
	  at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:64)
	  at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
	  at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)
	  at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
	  at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
	  at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:103)
	  at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
	  at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
	  at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
	  at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:77)
	  at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:82)
	  at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
	  at com.sun.proxy.$Proxy5.getStudentById(Unknown Source:-1)
	  at mapper.StudentMapperTest.testLocalCache(StudentMapperTest.java:43)
	  at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
	  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	  at java.lang.reflect.Method.invoke(Method.java:497)
	  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	  at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	  at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	  at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	  at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	  at org.junit.runners.Suite.runChild(Suite.java:128)
	  at org.junit.runners.Suite.runChild(Suite.java:27)
	  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	  at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	  at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	  at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	  at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	  at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	  at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

由堆栈信息可知MyBatis 执行流程:

二级缓存 (CachingExecutor)-> 一级缓存(BaseExecutor.localCache) ->DB

CachingExecutor.java

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  Cache cache = ms.getCache(); // 获取 cache 即StudentMapper.xml 配置的<cache/> 若不配置, 直接交给simpleExecutor执行 查询 
  if (cache != null) { // 处理二级缓存
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
    // 交付给 SimpleExecutor(BaseExecutor的子类) 执行
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}