4

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

 3 years ago
source link: https://blog.csdn.net/weixin_38405646/article/details/120165793
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

专栏收录该内容
7 篇文章 0 订阅

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK