zoukankan      html  css  js  c++  java
  • mybatis两级缓存原理剖析

    https://blog.csdn.net/zhurhyme/article/details/81064108

    对于mybatis的缓存认识一直有一个误区,所以今天写一篇文章帮自己订正一下。mybatis的缓存分一级缓存与二级缓存。下面分别对这两种缓存进行详细解说。
    mybatis 的调用链

    首先我们需要大致的了解一下mybatis的调用链,然后才能更好的理解mybatis的缓存。
    主要的api罗列如下:

    public interface SqlSessionFactory {    
      SqlSession openSession();
    }


    public class DefaultSqlSessionFactory implements SqlSessionFactory {

      @Override
      public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }

             private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
            Transaction tx = null;
            try {
              final Environment environment = configuration.getEnvironment();
              final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
              tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
              final Executor executor = configuration.newExecutor(tx, execType);//重点是这个方法
              return new DefaultSqlSession(configuration, executor, autoCommit);
            } catch (Exception e) {
              closeTransaction(tx); // may have fetched a connection so lets call close()
              throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
            } finally {
              ErrorContext.instance().reset();
            }
          }
    }

    Configuration中的
        public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
            executorType = executorType == null ? defaultExecutorType : executorType;
            executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
            Executor executor;
            if (ExecutorType.BATCH == executorType) {
              executor = new BatchExecutor(this, transaction);
            } else if (ExecutorType.REUSE == executorType) {
              executor = new ReuseExecutor(this, transaction);
            } else {
              executor = new SimpleExecutor(this, transaction);
            }
            if (cacheEnabled) {// 二级缓存开关
              executor = new CachingExecutor(executor);
            }
            executor = (Executor) interceptorChain.pluginAll(executor); //mybatis的插件机制
            return executor;
          }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47

    上面的代码大致描述了,我们为了获取一个sqlSession实例的过程。下面的方法为SqlSession的实现类DefaultSqlSession中的一个查询方法.

         @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          MappedStatement ms = configuration.getMappedStatement(statement);
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

    从该方法我们可知SqlSession的方法实现全部委托给了Executor实例。Executor接口的类图:

    这里写图片描述
    mybatis 一级缓存

    上面的executor.query如何执行呢?public abstract class BaseExecutor 中的方法给了我们答案.

     @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }

      @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 {
            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;
      }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42

    请注意localCache是什么呢?在BaseExecutor的构造器有这么一句代码: this.localCache = new PerpetualCache(“LocalCache”); 即locaCache为缓存,即mybatis的一级缓存。

    localCache生命周期是与SqlSession的生命周期是一样;不同的sqlSession会生成不同的localCache.这就是mybatis的一级缓存,它是与sqlSession息息相关,只能单个sqlSession实例独享。并且默认是开启的,没有开关可以关闭它。但是它的使用非常有局限。所以mybatis才需要在二级缓存,即突破SqlSession范围的缓存。mybatis的二级缓存与我们通常认知的缓存没有区别。
    mybatis 的二级缓存

    mybatis的二级缓存即通过CachingExecutor来实现。

    CachingExecutor 中的query方法代码如下:

      @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, 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;
          }
        }
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

    即当cache不为null时,则从tcm中获取,如果获取不到则再从delegate中获取。
    现在关键是tcm.getObject(cache, key);
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    TransactionalCacheManager的部分源代码

        public class TransactionalCacheManager {

          private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

          public Object getObject(Cache cache, CacheKey key) {
            return getTransactionalCache(cache).getObject(key);
          }


          private TransactionalCache getTransactionalCache(Cache cache) {
        /*
        关键代码,它的作用从transactionalCaches 中获取TransactionalCache ,如果没有,则将cache作为参数,重新new一个,这段代码涉及jdk8的新语法及以及新特性,
        不太容易懂,如果有小朋友看不懂,我建议你好好学习一下jdk8
        */  
            return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
          }

        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18

    TransactionalCache 部分源码

         public TransactionalCache(Cache delegate) {
            this.delegate = delegate;
            this.clearOnCommit = false;
            this.entriesToAddOnCommit = new HashMap<>();
            this.entriesMissedInCache = new HashSet<>();
          }

          @Override
          public Object getObject(Object key) {
            // issue #116
            Object object = delegate.getObject(key);
            if (object == null) {
              entriesMissedInCache.add(key);
            }
            // issue #146
            if (clearOnCommit) {
              return null;
            } else {
              return object;
            }
          }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

    峰回路转

    Cache cache = ms.getCache(); //这句是重点

        1

    即只要MappedStatement ms 自己有cache,则直接使用,这个就是mybatis的二级缓存。关于开启mybatis的二级缓存,则需要在config中配置cacheEnabled 为ture,它的默认值即为true。
    另外需要在mapper中配置

    <cache />

        1

    这样就可以最简单的方式使用mybatis的二级缓存了。至于如何扩展mybatis的二级缓存,不在本文之列。
    mybatis 二级缓存絮叨

    至此mybatis 的二级缓存好像已经说明白了。但是还想在此絮叨一下,原因是为了更好的理解什么是缓存。mybatis的二级缓存cache它是跟在MappedStatement上的。我们设置 cache标签,是以mapper为单位的。即这个mapper文件内的statement 共用这个cache标签。cache它与sqlSession没有任何关系,即不同的sqlSession执行同一个statement就可以共用同一个cache实例了。

    那么是否有更大的cache使用范围呢?答案是有的,举个栗子。 部门表被cache;人员表被cache,从上面我们可知,这是两个不同的cache实例。在人员mapper中如何出人员与部门的联合查询,则当部门更新时,会出现局部的数据不一致。如何解决这个问题呢?一种答案是去掉缓存;另一种就是mybatis给我们提供的

    <cache-ref >

        1

    标签,它可以使多个mapper使用同一个cache实例,从而达到缓存数据的一致性(关于缓存数据的一致性,性能,缓存数据范围,在此不讨论)。
    ---------------------
    作者:zhurhyme
    来源:CSDN
    原文:https://blog.csdn.net/zhurhyme/article/details/81064108
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    GitHub上创建项目
    html5的标签中,哪些是行内元素,哪些是块级元素。
    盒子模型及其他的层次结构关系
    二维码生成
    SSH整合笔记
    Spring回顾
    struts2 测试错题解析
    Java Script基础
    Java OOP考试错题分析
    接口的用法及注意点
  • 原文地址:https://www.cnblogs.com/yibutian/p/10254045.html
Copyright © 2011-2022 走看看