mybatis源码分析(7)-----缓存Cache(一级缓存,二级缓存)

写在前面

    MyBatis 提供查询缓存,用于减轻数据库压力,提高数据库性能。

  MyBatis缓存分为一级缓存和二级缓存。

  

  通过对于Executor 的设计。也可以发现MyBatis的缓存机制(采用模板方法模式+装饰设计模式)

  Executor 

    BaseExecutor

      SimpleExecuor

      BatchExecutor

      ReuseExecutor

    CacheExecutor

  sqlSession最终持有的是Executor。sqlSession --> statementHandler --> executor(CacheExecutor),Executor的执行顺序是:

  CacheExecutor(query方法,二级缓存查询)--->BaseExecutor(query方法,已经缓存查询) --> SimpleExecuor(doQuery方法,数据库查询)

 

一级缓存

  一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。

 

1. 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的信息
2. 如果没有:从数据库查询,得到用户信息,将此信息存在一级缓存中
3. 如果sqlsession去执行commit操作(执行插入,更新,删除),会清空sqlsession中的缓存(这样做的目的:让缓存中永远存储的是最新信息,避免脏读)
4. 第二次发起查询用户id为1的用户信息,先去找缓存中查找,有,则获得

5.手动清空缓存simpleTempalte.clearCache()

 //BaseExecutor.java 文件 
@SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      //检查是否存在一级缓存,如果存在直接拿取结果
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
//myBatis默认开启一级缓存,会将查询数据结果,写到缓存集合中 list
= queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }

 

 

 

 

二级缓存(鸡肋)

    二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域。

  二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。

   1、在sqlmapconfig.xml中设置二级缓存总开关。在和spring集成中(默认是开启的

    

         mybatis二级缓存对细粒度的数据级别的缓存实现不好,对同时缓存较多条数据的缓存,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。需要使用三级缓存

  最后还是建议,放弃二级缓存,在业务层使用可控制的缓存代替更好。(建议使用eHcache第三方作为二级缓存使用)

 CacheExecutor.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(); // 检查是否存在二级缓存 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; } }
//cacheExecutor 持有的BaseExecutor 被装饰的对象
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

 

posted @ 2017-04-13 10:29  qtyy  阅读(478)  评论(0编辑  收藏  举报