ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Mybatis缓存(四)-二级缓存实现原理

2021-09-07 23:02:49  阅读:121  来源: 互联网

标签:缓存 Cache cache 二级缓存 delegate key Mybatis TransactionalCache


二级缓存实现原理

概述

MyBatis二级缓存在默认情况下是关闭的,因此需要通过设置cacheEnabled参数值为true来开启二级缓存

SqlSession 将 执行Mapper的逻辑 委托给 Executor组件完成,而Executor接口有几种不同的实现,分别为SimpleExecutor、BatchExecutor、ReuseExecutor
另外,还有一个比较特殊的CachingExecutor,CachingExecutor用到了装饰器模式,在其他几种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;
  }

如果cacheEnabled属性值为true(开启了二级缓存),则使用CachingExecutor对普通的Executor对象进行装饰,CachingExecutor在普通Executor的基础上增加了二级缓存功能

CachingExecutor类

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

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

CachingExecutor#query

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 先创建CacheKey
    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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 查询数据库之后添加到缓存
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 如果缓存为空,查询数据库
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

在CachingExecutor的query()方法中,首先调用createCacheKey()方法创建缓存Key对象
然后调用MappedStatement对象的getCache()方法 获取 MappedStatement对象中 维护的 二级缓存对象
然后尝试从 二级缓存对象中 获取结果,如果获取不到,则调用 目标Executor对象的query()方法 从数据库获取数据,再将数据添加到二级缓存中

CachingExecutor#update

当执行更新语句后,同一命名空间下的二级缓存将会被清空

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

CachingExecutor#flushCacheIfRequired

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    	// 如果配置了<cache/>节点或<cache-ref/>并且设置了flushCache为true
    if (cache != null && ms.isFlushCacheRequired()) {
    	 // 刷新二级缓存
      tcm.clear(cache);
    }
  }

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

最终执行的是TransactionalCacheManager#clear方法,传入的是 MappedStatement 的 缓存属性变量(Cache类型变量)

创建二级缓存

XMLMapperBuilder在解析Mapper配置时会调用cacheElement()方法解析<cache>标签

  private void cacheElement(XNode context) {
    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);
    }
  }

  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

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

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache); // 这里

TransactionalCacheManager

每个会话中持有一个CachingExecutor(缓存执行器)
所以每个会话都有自己单独的事务缓存管理器

public class TransactionalCacheManager {

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

	....

}

在TransactionalCacheManager类中,通过一个HashMap对象(暂存区集合) 维护 所有 二级缓存实例 对应的 TransactionalCache(事务缓存)对象
key是Cache实现类实例对象

在TransactionalCacheManager类的getObject()方法和putObject()方法中 都会调用 getTransactionalCache()方法 获取 二级缓存对象 对应的 TransactionalCache对象,然后 对TransactionalCache对象 进行操作

在getTransactionalCache()方法中,首先从HashMap对象中 获取 二级缓存对象 对应的 TransactionalCache对象
如果获取不到,则 创建新的TransactionalCache对象 添加到 HashMap对象中

TransactionalCacheManager#clear

 public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
 }

 private TransactionalCache getTransactionalCache(Cache cache) {
	return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
 }

根据 MappedStatement 的 缓存对象(Cache类型变量) 通过getTransactionalCache方法 在transactionalCaches集合中 查询 是否有对应的TransactionalCache对象
执行的是TransactionalCache#clear方法

TransactionalCacheManager#putObject

  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }

先根据 MappedStatement 的 缓存对象(Cache类型变量) 在transactionalCaches集合中 查询 是否有对应的 TransactionalCache对象
如果没有new一个TransactionalCache对象
然后向 TransactionalCache对象 中 放入CacheKey 和 查询结果
执行的是TransactionalCache#putObject方法

TransactionalCacheManager#getObject

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

传入的参数 是 MappedStatement 的 缓存对象(Cache类型变量) 和 CacheKey
执行的是TransactionalCache#getObject方法

TransactionalCache

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

	// 装饰模式
  private final Cache delegate;
  // 提交的时候 清除cache 的 标志位
  private boolean clearOnCommit;
  // 待提交的集合
  private final Map<Object, Object> entriesToAddOnCommit;
  // 未命中的缓存
  private final Set<Object> entriesMissedInCache;

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

TransactionalCache#clear

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

TransactionalCache#putObject

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

向 待提交集合中 放入CacheKey 和 查询结果

TransactionalCache#getObject

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

根据key从delegate(Cache实现类的变量实例)查抄,如果查询到为空,放入entriesMissedInCache-未命中集合

问题:putObject是放入entriesToAddOnCommit集合,getObject是从delegate(Cache实现类的变量实例)的cache集合中获取,那从entriesToAddOnCommit到delegate是在什么时候?
在事务commit的时候

  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();
    reset();
  }


  private void flushPendingEntries() {
    // 循环entriesToAddOnCommit,放入delegate
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    // 循环entriesMissedInCache,如果在entriesToAddOnCommit不存在,也放入delegate,缓存的查询结果为空
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

标签:缓存,Cache,cache,二级缓存,delegate,key,Mybatis,TransactionalCache
来源: https://blog.csdn.net/weixin_38405646/article/details/120165793

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有