2020年的Flag已经立完,不知道靠不靠谱,可是挡不住对将来美好的向往java
启用二级缓存二级缓存的位置二级缓存的样子二级缓存的工做原理装饰器模式事务型预缓存二级缓存的刷新总结:web
Mybatis 的二级缓存相比一级缓存就复杂的多了,若是用一句话来讲明Mybatis的二级缓存:算法
二级缓存是一个全局性,事务性,多样性的缓存sql
那问题来了:数据库
二级缓存在哪里?缓存
二级缓存长什么样子?微信
全局性,事务性,多样性如何体现?session
工做原理是怎么样的呢?mybatis
来一探究竟app
分为三步走:
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接口的装饰器。
若是本文任何错误,请批评指教,不胜感激 !
若是以为文章不错,点个赞吧
微信公众号:源码行动
享学源码,行动起来,来源码行动