mybatis缓存和设计模式

1、mybatis使用了设计模式 装饰器模式 引入了二级缓存,装饰器模式是为了在原先的 事情 上添加一些装饰,而在 mybatis 中则是在 Executor 中对simple,reuse,batch引入了cache这个装饰,通过使用装饰器mybatis引入了二级缓存

2、mybatis 一级缓存是每一个SqlSession都存在的,存在sqlSession和statement2个级别,sqlSession级别在同一个sqlSession执行期间,默认对select操作的结果会进行缓存,缓存的key是cacheKey使用的,具体的比较可参见 CacheKey#equals 方法,总的来时key = statemtnId + rowBounds.getOffset() + rowBounds.getLimit()+boundSql.getSql()+参数 + 可选的environment标识符

mybatis装饰器模式源代码如下

  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  2. ....
  3. ////解释为什么mybatis的一级缓存是sqlSession级别和statement , 每一个sqlSession都有一个executor(缓存),sqlSession关闭时executor清空缓存,被回收(sqlSession级别)
  4. final Executor executor = configuration.newExecutor(tx, execType);
  5. return new DefaultSqlSession(configuration, executor, autoCommit);}
  6. }
  7. private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
  8. ....
  9. final Executor executor = configuration.newExecutor(tx, execType);
  10. return new DefaultSqlSession(configuration, executor, autoCommit);
  11. }

Configuration#newExecutor

  1. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  2. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;//默认的执行类型
  3. Executor executor;
  4. if (ExecutorType.BATCH == executorType) {
  5. executor = new BatchExecutor(this, transaction);
  6. } else if (ExecutorType.REUSE == executorType) {
  7. executor = new ReuseExecutor(this, transaction);
  8. } else {
  9. executor = new SimpleExecutor(this, transaction);
  10. }
  11. //settings的缓存设置开启了,那么就采用CachingExecutor对executor进行缓存装饰,典型的装饰器模式运用,加入了缓存功能,但是加入的缓存功能还需要mapper.xml加入<cache/> //标签才能打开
  12. if (cacheEnabled) {
  13. executor = new CachingExecutor(executor);
  14. }
  15. executor = (Executor) interceptorChain.pluginAll(executor);
  16. return executor;
  17. }

1、一级缓存的statement级别 BaseExecutor#query

  1. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  2. ...省略查询过程,查询缓存,不中查询数据,放入缓存的过程
  3. //可见如果手动设置成STATEMENT级别,那么每次查询完之后都会清空缓存,导致实际上的功能时关闭了缓存
  4. if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
  5. // issue #482
  6. clearLocalCache();
  7. }
  8. return list;
  9. }

mybatis的sqlSession的推荐使用是方法级别的

  1. public List<PerformData> selectPerformDataByKey(Integer key) {
  2. try (SqlSession sqlSessionTemplate = this.getSqlSessionTemplate()) {
  3. return sqlSessionTemplate.selectList("PerformData.selectPerformDataByKey", key);
  4. }
  5. }

try(){} 的语法会自动调用close方法,可猜想sqlSession的缓存肯定是在close中被清理掉了

  1. @Override
  2. public void close(boolean forceRollback) {
  3. ...
  4. localCache = null;
  5. closed = true;
  6. }

2、二级缓存,如果采用mybatis提供的默认设置,Configuration#cacheEnabled为true,默认开启二级缓存,但同时需要开启 xml标签才能开启,查看源代码 MappedStatement#getCache() 二级缓存的代码仅在 CachingExecutor 中可

  1. @Override
  2. public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  3. throws SQLException {
  4. //1 要求mapper开启了 <cache /> 不然 ms.getCache()==null
  5. //2 其次刷新缓存不能为true
  6. Cache cache = ms.getCache();
  7. if (cache != null) {
  8. flushCacheIfRequired(ms);
  9. # if (ms.isUseCache() && resultHandler == null) {
  10. List<E> list = (List<E>) tcm.getObject(cache, key);
  11. if (list == null) {
  12. list = delegate.query(ms, parameterObject, rowBounds, null, key, boundSql);
  13. tcm.putObject(cache, key, list); // issue #578 and #116
  14. }
  15. return list;
  16. }
  17. }
  18. return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  19. }

到这一步一级缓存和二级缓存都已经讲到差不多了,接下来就是实际演练一下,配置cacheEnable=true,打开Cache标签

  • 同sqlSession依次查询2次,观察结果
  • 不同sqlSesion依次查询2次,观察结果
  • 使用sqlSession1查询一次,sqlSession2更新,sqlSession1再次查询,观察结果
  • 同sqlSession依次查询2次,更新或者插入一次,再查询一次,观察结果

测试的时候可以引入我的agent监控sql语句是否执行或者加入log日志去查看是否真正的发生了sql查询而不是命中缓存

缓存的戏份不多,毕竟在实际中却用不到,实际的生产缓解中,比较常见的是采用多点tomcat/容器+nginx做负载均衡,而mybatis缓存是本地缓存,也就是说可能造成A机器修改了,但是B机器还是用缓存的脏数据问题

总结

写的不多,也很粗糙

2018-10-18