mybatis源码学习(三)-一级缓存二级缓存

本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点

 1.一级缓存配置信息

 2.一级缓存源码学习笔记

 3.二级缓存配置信息

 4.二级缓存源码

 5.一级缓存、二级缓存总结

1.一级缓存配置:

  一级缓存是SqlSession级别的,同一个sqlSession执行多次相同的查询语句时,第二次会从缓存中获取数据,不查询数据库;一级缓存是无法关闭的,默认是SESSION级别

  mybatis一级缓存默认是开启的,一级缓存有两个配置值,SESSION级别和STATEMENT级别,这两个级别的区别是:

   如果是SESSION级别(默认值),那么在同一个sqlSession中,都会共享这个缓存

   如果是STATEMENT级别,那么会把一级缓存中的缓存清空,也就是说,同一个sqlSession,多次请求同一个sql,每次都会查询数据库

默认是session级别,此时为开启二级缓存:

如果把一级缓存的级别设置为STATEMENT,那么同一个sqlSession执行多次相同的SQL,都会查询数据库:

 

那下面,我们来源码中验证

2.一级缓存源码:

  1.首先会根据statement等参数,生成缓存中需要用到的key  org.apache.ibatis.executor.CachingExecutor#createCacheKey

  2.根据key从一级缓存中获取值,一级缓存是PerpetualCache这个类  

   org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)

  3.如果从一级缓存中取到值,就不查询数据库;否则查询数据库

    org.apache.ibatis.executor.BaseExecutor#handleLocallyCachedOutputParameters

  4.如果一级缓存中没有取到值,就从数据库中查询,并将数据库中查询到的数据,存到一级缓存中

    org.apache.ibatis.executor.BaseExecutor#queryFromDatabase

  5.判断缓存级别是否是STATEMENT,如果是,将一级缓存清空

 1 @SuppressWarnings("unchecked")
 2 @Override
 3 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 4 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
 5 if (closed) {
 6   throw new ExecutorException("Executor was closed.");
 7 }
 8 if (queryStack == 0 && ms.isFlushCacheRequired()) {
 9   clearLocalCache();
10 }
11 List<E> list;
12 try {
13   queryStack++;
14   list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
15   if (list != null) {
16     handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
17   } else {
18     list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
19   }
20 } finally {
21   queryStack--;
22 }
23 if (queryStack == 0) {
24   for (DeferredLoad deferredLoad : deferredLoads) {
25     deferredLoad.load();
26   }
27   // issue #601
28   deferredLoads.clear();
29   if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
30     // issue #482
31     clearLocalCache();
32   }
33 }
34 return list;
35 }

   这段源码中,第14行,是尝试从一级缓存中根据key,取值;如果不为null,就从一级缓存中取值,第18行,是从数据中查询数据;

   在 queryFromDatabase()方法中,会将数据库中查询到的数据,put到一级缓存中

   源码中的第29行,就是对一级缓存是否是STATEMENT级别做判断,如果是,就clear;

  我们再来说,insert、update、delete,这三种操作,都会走update,

  org.apache.ibatis.executor.BaseExecutor#update

  

1 @Override
2 public int update(MappedStatement ms, Object parameter) throws SQLException {
3   ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
4   if (closed) {
5     throw new ExecutorException("Executor was closed.");
6   }
7   clearLocalCache();
8   return doUpdate(ms, parameter);
9 }

可以看到,执行doUpdate()方法之前,会先clear一级缓存

3.二级缓存配置

  二级缓存是mapper级别的,需要配置才会开启

  需要在mybatis的配置文件中,增加以下配置

  

1 <settings>
2     <setting name="cacheEnabled" value="true"/>
3 </settings>

并且在mapper.xml文件中增加

<cache></cache>

这样,二级缓存就开启了

4.二级缓存源码

  二级缓存的源码,首先我们要看 org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)  这个方法

 1 @Override
 2 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
 3   throws SQLException {
 4  Cache cache = ms.getCache();
 5 if (cache != null) {
 6   flushCacheIfRequired(ms);
 7   if (ms.isUseCache() && resultHandler == null) {
 8     ensureNoOutParams(ms, parameterObject, boundSql);
 9     @SuppressWarnings("unchecked")
10     List<E> list = (List<E>) tcm.getObject(cache, key);
11     if (list == null) {
12       list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
13       tcm.putObject(cache, key, list); // issue #578 and #116
14     }
15     return list;
16   }
17 }
18 return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
19 }

在第7行,首先会判断,当前sql,是否开启useCache这个属性,对于select 默认是true;update、insert、delete默认是 false;这个点的源码是在解析xml的时候,最终会把每个select、update、insert、delete节点

build成一个mappedStatement,在build之前,有这么一段代码

1 boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
2 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
3 boolean useCache = context.getBooleanAttribute("useCache", isSelect);

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

这里的意思是:如果是select,那么useCache就是true,否则就是false

接着上面的说,第10行会尝试从二级缓存中获取数据,这里的调用链是这样的

这个调用链这几个cache,还没有搞明白,具体的作用,后面学习之后,会更新;

二级缓存有一个点,需要明确:二级缓存只有在sqlSession执行commit的时候,才会更新;否则是不生效的

 这个是commit的时候,二级缓存Put的调用链

5.总结

  一级缓存是sqlSession级别的,二级缓存是mapper级别的,二级缓存的粒度更细,

  一级缓存无法关闭,二级缓存可以开启或者关闭

  一级缓存如果在分布式的场景下,可能会存在脏数据;

 如果使用缓存,感觉还是Redis靠谱吧;

  

  

猜你喜欢

转载自www.cnblogs.com/mpyn/p/11982371.html