一文詳解mybatis二級緩存執(zhí)行流程
mybatis二級緩存的執(zhí)行流程
1.二級緩存的生成
在mybatis啟動時會加載并解析配置文件,其中就會解析二級緩存的可選項配置,由此是否生成二級緩存,下面這段代碼就是解析xxxMapper.xml文件中的子標簽來生成二級緩存
//XMLmapperBuilder
private void configurationElement(XNode context) {
try {
// 獲取<mapper>標簽的namespace值,也就是命名空間
String namespace = context.getStringAttribute("namespace");
// 命名空間不能為空
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// MapperBuilderAssistant:構建MappedStatement對象的構建助手,設置當前的命名空間為namespace的值
builderAssistant.setCurrentNamespace(namespace);
......
// 解析<cache>子標簽,這里生成二級緩存
cacheElement(context.evalNode("cache"));
......
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
默認的二級緩存對象是PerpetuateCache,并把緩存封裝進MapperStatement,所以一個MapperStatement對應一個二級緩存,一個MapperStatement就是存儲一個xxxMapper.xml文件中的信息
//XMLmapperBuilder
private void cacheElement(XNode context) {
if (context != null) {
// 解析<cache>標簽type屬性的值,在這可以自定義type的值,比如redisCache,如果沒有指定默認就是PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 獲取負責過期的eviction對象,默認策略為LRU
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 清空緩存的頻率 0代表不清空
Long flushInterval = context.getLongAttribute("flushInterval");
// 緩存容器的大小
Integer size = context.getIntAttribute("size");
// 是否只讀
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 是否阻塞
boolean blocking = context.getBooleanAttribute("blocking", false);
// 獲得Properties屬性
Properties props = context.getChildrenAsProperties();
//builderAssistant是MapperBuilderAssistant,作用就是解耦建立MapperStatement,因為
//MapperStatement對象創(chuàng)建復雜,所以用這個類來解耦創(chuàng)建
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
PerpetualCache內部的緩存其實就是個HashMap
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
2.二級緩存的使用
在執(zhí)行查詢時,會先走二級緩存,若二級緩存沒有才走下一步查詢,并把查詢結果存到二級緩存中,但此時只是存到tcm(TransactionalCacheManager)中的一個map中
//CachingExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 獲取二級緩存,是從MapperStatement里面獲取的
Cache cache = ms.getCache();
if (cache != null) {
// 刷新tcm的緩存 (存在緩存且flushCache為true時)
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 從二級緩存中查詢數據
List<E> list = (List<E>) tcm.getObject(cache, key);
// 如果二級緩存中沒有查詢到數據,則查詢一級緩存及數據庫
if (list == null) {
// 委托給BaseExecutor執(zhí)行
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 將查詢結果 要存到二級緩存中(注意:此處只是存到map集合中,沒有真正存到二級緩存中)
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 如果沒有開啟二級緩存或開啟了沒查到二級緩存則委托給BaseExecutor執(zhí)行下一步查詢
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
TransactionalCacheManager顧名思義事務緩存管理器,只在一次事務中進行緩存管理,當事務commit后tcm就不存在了。在一次事務commit前可以進行多次數據庫操作,例如進行2次查詢。
public class TransactionalCacheManager {
// Cache 與 TransactionalCache 的映射關系表
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public Object getObject(Cache cache, CacheKey key) {
// 直接從TransactionalCache中獲取緩存
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
// 直接存入TransactionalCache的緩存中
getTransactionalCache(cache).putObject(key, value);
}
private TransactionalCache getTransactionalCache(Cache cache) {
// 從映射表中獲取 TransactionalCache,下面代碼等同于
// transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}
上面提到查詢時先走二級緩存,二級緩存查詢從這開始,從這里查一下二級緩存是否存在,不存在再走其他查詢結果,以下的delegate就是MapperStatement的二級緩存對象
public class TransactionalCache implements Cache {
/* 二級緩存 Cache 對象。
*/
private final Cache delegate;
/**
* 提交時,清空 {@link #delegate}
*
* 初始時,該值為 false
* 清理后{@link #clear()} 時,該值為 true ,表示持續(xù)處于清空狀態(tài)
*/
private boolean clearOnCommit;
/**
* // 在事務被提交前,所有從數據庫中查詢的結果將緩存在此集合中
*/
private final Map<Object, Object> entriesToAddOnCommit;
/**
* 在事務被提交前,當緩存未命中時,CacheKey 將會被存儲在此集合中
*/
private final Set<Object> entriesMissedInCache;
public Object getObject(Object key) {
// issue #116
// 查詢的時候是直接從delegate(就是MapperSatement的二級緩存對象)中去查詢的
Object object = delegate.getObject(key);
// 如果不存在,則添加到 entriesMissedInCache 中
if (object == null) {
// 緩存未命中,則將 key 存入到 entriesMissedInCache 中
entriesMissedInCache.add(key);
}
// issue #146
// 如果 clearOnCommit 為 true ,表示處于持續(xù)清空狀態(tài),則返回 null
if (clearOnCommit) {
return null;
} else {
// 返回 value
return object;
}
}
不存在二級緩存,走其他查詢后,把查詢結果放進二級緩存中,但這里其實并沒放進二級緩存,而是放到了entriesToAddOnCommit中,畢竟一次事務之后tcm才不存在,所以在事務commit后,再放進真正二級緩存
//TransactionalCache
public void putObject(Object key, Object object) {
// 將鍵值對存入到 entriesToAddOnCommit 這個Map中中,而非真實的緩存對象 delegate 中
entriesToAddOnCommit.put(key, object);
}
當一次事務commit后,會把查詢結果真正放到二級緩存中
public void commit() {
// 如果 clearOnCommit 為 true ,則清空 delegate 緩存
if (clearOnCommit) {
delegate.clear();
}
// 將 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate(cache) 中
flushPendingEntries();
// 重置
reset();
}
private void flushPendingEntries() {
// 將 entriesToAddOnCommit 中的內容轉存到 delegate 中
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 在這里真正的將entriesToAddOnCommit的對象逐個添加到delegate中,只有這時,二級緩存才真正的生效
delegate.putObject(entry.getKey(), entry.getValue());
}
// 將 entriesMissedInCache 刷入 delegate 中
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
到此這篇關于一文詳解mybatis二級緩存執(zhí)行流程的文章就介紹到這了,更多相關mybatis二級緩存執(zhí)行內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
hibernate-validator后端表單數據校驗的使用示例詳解
這篇文章主要介紹了hibernate-validator后端表單數據校驗的使用,hibernate-validator提供的校驗方式為在類的屬性上加入相應的注解來達到校驗的目的,本文結合示例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-08-08

