2020年的Flag已经立完,不知道靠不靠谱,但是挡不住对未来美好的向往
Mybatis 的二级缓存相比一级缓存就复杂的多了,如果用一句话来说明Mybatis的二级缓存:
二级缓存是一个全局性,事务性,多样性的缓存
那问题来了:
二级缓存在哪里?
二级缓存长什么样子?
全局性,事务性,多样性如何体现?
工作原理是怎么样的呢?
来一探究竟
分为三步走:
1)开启全局二级缓存配置:
2) 在需要使用二级缓存的Mapper配置文件中配置二级缓存类型
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; } 复制代码
可以看出:
二级缓存 具有多样性 ,我们可以根据需求配置不同类型的二级缓存。
有哪些呢?
大类 | 类型 | 缓存名称 | 描述 |
---|---|---|---|
基础实现 | 基础类 | 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); } 复制代码
流程:
事务型预缓存
在二级没有数据的情况下,通过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 public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } //TransactionalCache public void commit() { if (clearOnCommit) { delegate.clear(); } flushPendingEntries();//刷到二级缓存中 reset();//清空预缓存 } 复制代码
小结:
二级缓存的工作原理: 一个缓存执行器 + 一个预缓存 + 二级缓存
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接口的装饰器。
如果本文任何错误,请批评指教,不胜感激 !
如果觉得文章不错, 点个赞 吧
微信公众号:
享学源码,行动起来,来源码行动