本文主要是个人学习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靠谱吧;