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
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    变量的创建和初始化
    HDU 1114 Piggy-Bank (dp)
    HDU 1421 搬寝室 (dp)
    HDU 2059 龟兔赛跑 (dp)
    HDU 2571 命运 (dp)
    HDU 1574 RP问题 (dp)
    HDU 2577 How to Type (字符串处理)
    HDU 1422 重温世界杯 (dp)
    HDU 2191 珍惜现在,感恩生活 (dp)
    HH实习 acm算法部 1689
  • 原文地址:https://www.cnblogs.com/yibutian/p/10254045.html
Copyright © 2011-2022 走看看