美文网首页
mybatis MyBatis二级缓存实现原理

mybatis MyBatis二级缓存实现原理

作者: dylan丶QAQ | 来源:发表于2020-09-02 20:58 被阅读0次

    起因:在工作中常常要用到mybatis框架,如果对其执行流程不清楚的话,就会有一种出了bug不知道要去什么地方找的尴尬。本文为学习《Mybatis源码深度解析》后的总结。感谢江荣波的这本书。


    1.SqlSession将执行Mapper的逻辑委托给Executor组件完成

      MyBatis二级缓存在默认情况下是关闭的,因此需要通过设置cacheEnabled参数值为true来开启二级缓存。
      SqlSession将执行Mapper的逻辑委托给Executor组件完成,而Executor接口有几种不同的实现,分别为SimpleExecutor、BatchExecutor、ReuseExecutor。另外,还有一个比较特殊的CachingExecutor,CachingExecutor用到了装饰器模式,在其他几种Executor的基础上增加了二级缓存功能。

    2.Executor实例

      Executor实例采用工厂模式创建,Configuration类提供了一个工厂方法newExecutor(),该方法返回一个Executor对象,我们可以关注一下该方法的实现

      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);
        return executor;
      }
    

      如上面的代码所示,Configuration类的newExecutor()工厂方法的逻辑比较简单,根据defaultExecutorType参数指定的Executor类型创建对应的Executor实例。

    3.CachingExecutor类的实现

      如果cacheEnabled属性值为true(开启了二级缓存),则使用CachingExecutor对普通的Executor对象进行装饰,CachingExecutor在普通Executor的基础上增加了二级缓存功能,我们可以重点关注一下CachingExecutor类的实现。下面是CachingExecutor类的属性信息:

    public class CachingExecutor implements Executor {
    
      private final Executor delegate;
      private final TransactionalCacheManager tcm = new TransactionalCacheManager();
    
    }
    

      CachingExecutor类中维护了一个TransactionalCacheManager实例,TransactionalCacheManager用于管理所有的二级缓存对象。

    public class TransactionalCacheManager {
    
      private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
    
      public void clear(Cache cache) {
        getTransactionalCache(cache).clear();
      }
    
      public Object getObject(Cache cache, CacheKey key) {
        return getTransactionalCache(cache).getObject(key);
      }
      
      public void putObject(Cache cache, CacheKey key, Object value) {
        getTransactionalCache(cache).putObject(key, value);
      }
    
      public void commit() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
          txCache.commit();
        }
      }
    
      public void rollback() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
          txCache.rollback();
        }
      }
    
      private TransactionalCache getTransactionalCache(Cache cache) {
        TransactionalCache txCache = transactionalCaches.get(cache);
        if (txCache == null) {
          txCache = new TransactionalCache(cache);
          transactionalCaches.put(cache, txCache);
        }
        return txCache;
      }
    
    }
    

    4.TransactionalCacheManager类

      在TransactionalCacheManager类中,通过一个HashMap对象维护所有二级缓存实例对应的TransactionalCache对象,在TransactionalCacheManager类的getObject()方法和putObject()方法中都会调用getTransactionalCache()方法获取二级缓存对象对应的TransactionalCache对象,然后对TransactionalCache对象进行操作。在getTransactionalCache()方法中,首先从HashMap对象中获取二级缓存对象对应的TransactionalCache对象,如果获取不到,则创建新的TransactionalCache对象添加到HashMap对象中。

    5.二级缓存的工作机制

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

      在CachingExecutor的query()方法中,首先调用createCacheKey()方法创建缓存Key对象,然后调用MappedStatement对象的getCache()方法获取MappedStatement对象中维护的二级缓存对象。然后尝试从二级缓存对象中获取结果,如果获取不到,则调用目标Executor对象的query()方法从数据库获取数据,再将数据添加到二级缓存中。当执行更新语句后,同一命名空间下的二级缓存将会被清空。

      @Override
      public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        flushCacheIfRequired(ms);
        return delegate.update(ms, parameterObject);
      }
    

      在flushCacheIfRequired()方法中会判断<select|update|delete|insert>标签的flushCache属性,如果属性值为true,就清空缓存。<select>标签的flushCache属性值默认为false,而<update|delete|insert>标签的flushCache属性值默认为true。

      MappedStatement对象创建过程中二级缓存实例的创建。XMLMapperBuilder在解析Mapper配置时会调用cacheElement()方法解析<cache>标签,

      private void cacheElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type", "PERPETUAL");
          Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
          String eviction = context.getStringAttribute("eviction", "LRU");
          Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
          Long flushInterval = context.getLongAttribute("flushInterval");
          Integer size = context.getIntAttribute("size");
          boolean readWrite = !context.getBooleanAttribute("readOnly", false);
          boolean blocking = context.getBooleanAttribute("blocking", false);
          Properties props = context.getChildrenAsProperties();
          builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
      }
    

      在获取<cache>标签的所有属性信息后,调用MapperBuilderAssistant对象的userNewCache()方法创建二级缓存实例,然后通过MapperBuilderAssistant的currentCache属性保存二级缓存对象的引用。在调用MapperBuilderAssistant对象的addMappedStatement()方法创建MappedStatement对象时会将当前命名空间对应的二级缓存对象的引用添加到MappedStatement对象中。


    不要以为每天把功能完成了就行了,这种思想是要不得的,互勉~!

    相关文章

      网友评论

          本文标题:mybatis MyBatis二级缓存实现原理

          本文链接:https://www.haomeiwen.com/subject/hzabsktx.html