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接口的裝飾器。
若是本文任何錯誤,請批評指教,不勝感激 !
若是以爲文章不錯,點個贊吧
微信公衆號:源碼行動
享學源碼,行動起來,來源碼行動