Mybatis源码系列5-二级缓存

2020年的Flag已经立完,不知道靠不靠谱,但是挡不住对未来美好的向往


Mybatis 的二级缓存相比一级缓存就复杂的多了,如果用一句话来说明Mybatis的二级缓存:

二级缓存是一个全局性,事务性,多样性的缓存

那问题来了:

二级缓存在哪里?

二级缓存长什么样子?

全局性,事务性,多样性如何体现?

工作原理是怎么样的呢?

来一探究竟

启用二级缓存

分为三步走:

1)开启全局二级缓存配置:
2) 在需要使用二级缓存的Mapper配置文件中配置二级缓存类型

  • 为每一个Mapper分配一个Cache缓存对象(使用节点配置)
  • 多个Mapper共用一个Cache缓存对象(使用节点配置);

3)在具体CURD标签上配置 useCache=true

二级缓存的位置

上文开启二级缓存步骤中,可以看出,二级缓存的配置是在xml文件中。所以想要探究二级缓存在哪里。还是得从xml文件的解析过程入手。

xml文件的解析一文讲过,Mapper配置文件的解析是由XMLMapperBuilder 解析器解析的

//-----------XMLMapperBuilder类
private void configurationElement(XNode context) {
    try {
      ...
      //解析cache标签
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      ...
    } catch (Exception e) {
    }
}
//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();
        //构建助手帮助创建Cache
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
//-----------MapperBuilderAssistant类
public Cache useNewCache(...) {
    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);//添加到configuration一份
    currentCache = cache;//设置到当前临时变量
    return cache;
  }
public MappedStatement addMappedStatement(....){
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).....cache(currentCache);//设置临时缓存变量
    MappedStatement statement = statementBuilder.build();//创建MappedStatement
    configuration.addMappedStatement(statement);
    return statement;
}

可以看出:

  • 二级缓存就是每一个SQL模板MappedStatement 实例的cache属性,他部署
  • 同一个namespace下的所有MappedStatement.cache属性 指向同一个cache对象。共用一个二级缓存

二级缓存的样子

二级缓存具有多样性,我们可以根据需求配置不同类型的二级缓存。
有哪些呢?

大类类型缓存名称描述
基础实现基础类PerpetualCache基础缓存,本质是包装了HashMap
装饰类算法类FifoCache先进先出缓存
LruCache最近最少使用
引用类SoftCache软引用,内存不够发生GC时删除
WeakCache弱引用,发生GC就回收
技术增强类SerializedCache将缓存对象在保存前序列化和获取后反序列化
SynchronizedCache对缓存的所有方法都加上synchronized
业务增强类LoggingCache记录缓存的日志,比如什么时候进来的,什么时候被删除的
TransactionalCache事务性缓存
ScheduledCache定期删除缓存
BlockingCache缓存阻塞

可以看出二级缓存的种类很多。mybatis是如何组织二级缓存的呢?

重点就在参数配置上,

参数描述
type缓存底层实现,默认是PerpetualCache
eviction清除策略 LRU、FIFO
flushInterval刷新间隔,ScheduledCache
size缓存的大小
readWrite缓存的读写
blocking当缓存key不存在时,是否直接查询数据库。默认false

参数的不同直接影响了二级缓存的样子。

//根据属性配置来构建cache
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();
  //build方法
 public Cache build() {
    setDefaultImplementations();
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
   
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }
//一个标准的二级缓存应该是这样的。
private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
      //如果配置了清理时间,使用ScheduledCache装饰
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
      //如果配置了读写,使用SerializedCache装饰
        cache = new SerializedCache(cache);
      }
      //使用LoggingCache装饰
      cache = new LoggingCache(cache);
      //使用SynchronizedCache 装饰
      cache = new SynchronizedCache(cache);
      if (blocking) {
      //如果配置阻塞,使用BlockingCache装饰
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

二级缓存采用装饰器模式来设计。通过不同的配置,使用不同功能的缓存装饰器来装饰基础缓存,使基础缓存具有特殊的功能。

也就是说:
二级缓存= 多级装饰器+ 基础缓存类

二级缓存的工作原理

说到二级缓存的工作原理,可以用两个知识点来总结

  • 装饰器
  • 事务型预缓存
装饰器模式

CachingExecutor
在创建DefaultSqlSession的执行器Executor时,如果开启了二级缓存功能,会创建一个装饰器CachingExecutor,来装饰基础Executor。

 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    ...
    if (cacheEnabled) {
    //二级缓存装饰器
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
    
  }

CachingExecutor 执行器内部创建一个TransactionalCacheManager 事务缓存管理,并使用delegate 指向基础Executor

public class CachingExecutor implements Executor {

 //目标Executor
  private Executor delegate;
  private TransactionalCacheManager tcm = new TransactionalCacheManager();

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

当开启二级缓存的情况下执行sqlsession的select方法时,首先会执行CachingExecutor的query方法。

public <E> List<E> query(...) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(...);
  }
 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, parameterObject, boundSql);
   		//先查询二级缓存
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
        //二级缓存没有,交给基础执行器Executor 去执行查询操作
          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);
  }

流程:

  • 先获取二级缓存。
  • 判断当前SQL是否开启二级缓存。
  • 开启的情况下,先去二级缓存查询。
  • 二级缓存有,直接返回
  • 二级缓存没有,交给基础执行器,走一级缓存执行过程。并把直接结果放入事务预缓存区。
事务型预缓存

在二级没有数据的情况下,通过BaseExecutor从数据库中查询到结果后,并没有直接放入二级缓存。而是先放入的事务预缓存中。

tcm.putObject(cache, key, list);

来看看这个预缓存区,如何工作。

//事务缓存管理者
public class TransactionalCacheManager {
private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
}
private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
  }
}
//事务预缓存
public class TransactionalCache implements Cache {
  private Cache delegate;
  private boolean clearOnCommit;
  private Map<Object, Object> entriesToAddOnCommit;
  private Set<Object> entriesMissedInCache;
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }
}

事务型缓存TransactionalCache,也可以理解为预缓存区,是通过装饰器模式设计的预缓存,通过delegate属性指向二级缓存,他使得二级缓存具有事务特性。

TransactionalCache 由TransactionalCacheManager事务缓存管理者,进行统一管理。

工作原理:

List<E> list = (List<E>) tcm.getObject(cache, key);
//获取当前key在二级缓存是否对应数据
 public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  //获取事务预缓存。
 private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
  }

查询过程

  • 通过TransactionalCacheManager 查询二级缓存中是否由当前key对应的缓存数据。
  • TransactionalCacheManager 首先会检查当前当前二级缓存是否被事务缓存TransactionalCache装饰,如果没有装饰,就创建一个TransactionalCache装饰一下二级缓存。
  • TransactionalCache#getObject(Key)方法会去二级缓存中查询。

缓存过程:

//TransactionalCacheManager
public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }
//TransactionalCache
public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();//刷到二级缓存中
    reset();//清空预缓存
  }
  • 通过TransactionalCacheManager#putObject 方法把从数据查询的结果放入TransactionalCache预缓存中。
  • 当Sqlsession执行commit时,执行TransactionalCacheManager#commit方法,把当前预缓存中的数据正式提交到二级缓存中。并清空预缓存区。

小结:

二级缓存的工作原理: 一个缓存执行器 + 一个预缓存 + 二级缓存

二级缓存的刷新

insert、update、delete操作后都会引发二级缓存的刷新

public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);//刷新二级缓存
    return delegate.update(ms, parameterObject);
  }
private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
      tcm.clear(cache);//清空二级缓存
    }
  }

总结:

在二级缓存的设计上,MyBatis大量地运用了装饰者模式,如CachingExecutor, 以及各种Cache接口的装饰器。

  • 二级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
  • 二级缓存具有丰富的缓存策略。
  • 二级缓存可由多个装饰器,与基础缓存组合而成
  • 二级缓存工作由 一个缓存装饰执行器CachingExecutor和 一个事务型预缓存TransactionalCache 完成。

如果本文任何错误,请批评指教,不胜感激 !
如果觉得文章不错,点个赞

微信公众号:源码行动
享学源码,行动起来,来源码行动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值