您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)如何理解Mybatis源碼中的Cache,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
通過復(fù)雜業(yè)務(wù)計(jì)算得來的數(shù)據(jù),在計(jì)算過程中可能耗費(fèi)大量的時(shí)間,需要將數(shù)據(jù)緩存
讀多寫少的數(shù)據(jù)
緩存的容量
緩存的有效時(shí)間
訪問的緩存不存在,直接去訪問數(shù)據(jù)庫。通常查找的key沒有對應(yīng)的緩存,可以設(shè)計(jì)為返回空值,不去查找數(shù)據(jù)庫。
大量的緩存穿透會導(dǎo)致有大量請求,訪問都會落到數(shù)據(jù)庫上,造成緩存雪崩。所以如果訪問的key在緩存中找不到,不要直接去查詢數(shù)據(jù)庫,也就是要避免緩存穿透,可以設(shè)置緩存為永久緩存,然后通過后臺定時(shí)更新緩存。也可以為緩存更新添加鎖保護(hù),確保當(dāng)前只有一個(gè)線程更新數(shù)據(jù)。
使用范圍:SESSION,STATEMENT。默認(rèn)為SESSION,如果不想使用一級緩存,可以修改為STATEMENT,每次調(diào)用后都會清掉緩存。
//STATEMENT或者SESSION,默認(rèn)為SESSION <setting name="localCacheScope" value="STATEMENT"/>
一級緩存執(zhí)行的流程:
//BaseExecutor.query public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); ... List<E> list; try { ... //先根據(jù)cachekey從localCache去查 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { //若查到localCache緩存,處理localOutputParameterCache handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //從數(shù)據(jù)庫查 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { //清空堆棧 queryStack--; } ... if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 //如果是STATEMENT,清本地緩存 clearLocalCache(); } return list; }
什么情況下有效?在,一個(gè)sqlSession打開和關(guān)閉的范圍內(nèi),所以,如果被Spring托管并且沒有開啟事務(wù),那么一級緩存是失效的。
注意: 打開兩個(gè)sqlsession: sqlsession1,sqlsession2,sqlsession1.select從一級緩存中取數(shù)據(jù),sqlsession2更新此條數(shù)據(jù),sqlsession1.select再次獲取數(shù)據(jù),還是緩存中的。
SessionFactory層面給各個(gè)SqlSession 對象共享
怎么配置:
<setting name="cacheEnabled" value="true" /> (或@CacheNamespace) <cache/>,<cache-ref/>或@CacheNamespace //每個(gè)select設(shè)置 <select id="queryDynamicFlightVOs" resultType="com.ytkj.aoms.aiis.flightschedule.vo.DynamicFlightVO" useCache="false">
如果配置了二級緩存,那么在獲取Executor的時(shí)候會返回CachingExecutor
//Configuration.java //產(chǎn)生執(zhí)行器 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; //這句再做一下保護(hù),,防止粗心大意的人將defaultExecutorType設(shè)成null? executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; //然后就是簡單的3個(gè)分支,產(chǎn)生3種執(zhí)行器BatchExecutor/ReuseExecutor/SimpleExecutor if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } //如果要求緩存,生成另一種CachingExecutor(默認(rèn)就是有緩存),裝飾者模式,所以默認(rèn)都是返回CachingExecutor if (cacheEnabled) { executor = new CachingExecutor(executor); } //此處調(diào)用插件,通過插件可以改變Executor行為 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
開啟了二級緩存后,會先使用CachingExecutor查詢,查詢不到在查一級緩存。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //namespace配置作用 Cache cache = ms.getCache(); if (cache != null) { //是否執(zhí)行刷新緩存操作 this.flushCacheIfRequired(ms); //useCache標(biāo)簽作用 if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, parameterObject, boundSql); List<E> list = (List)this.tcm.getObject(cache, key); if (list == null) { list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); } return list; } } return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
由于二級緩存中的數(shù)據(jù)是基于namespace的,即不同namespace中的數(shù)據(jù)互不干擾。在多個(gè)namespace中若均存在對同一個(gè)表的操作,那么這多個(gè)namespace中的數(shù)據(jù)可能就會出現(xiàn)不一致現(xiàn)象
在分布式環(huán)境下,由于默認(rèn)的MyBatis Cache實(shí)現(xiàn)都是基于本地的,分布式環(huán)境下必然會出現(xiàn)讀取到臟數(shù)據(jù),需要使用集中式緩存將MyBatis的Cache接口實(shí)現(xiàn),有一定的開發(fā)成本,直接使用Redis、Memcached等分布式緩存可能成本更低,安全性也更高
通過裝飾器模式,我們可以向一個(gè)現(xiàn)有的對象添加新的功能,同時(shí)不改變其結(jié)構(gòu)。
Mybatis中不同類型的緩存,正是使用了此類設(shè)計(jì)。
PerpetualCache實(shí)現(xiàn)了Cache接口,完成了基本的Cache功能,其他的裝飾類對其進(jìn)行功能擴(kuò)展。以LruCache為例:
public class LruCache implements Cache { //持有實(shí)現(xiàn)類 private final Cache delegate; //額外用了一個(gè)map才做lru,但是委托的Cache里面其實(shí)也是一個(gè)map,這樣等于用2倍的內(nèi)存實(shí)現(xiàn)lru功能 private Map<Object, Object> keyMap; private Object eldestKey; public LruCache(Cache delegate) { this.delegate = delegate; setSize(1024); } ..... }
LruCache使用了一個(gè)LinkedHashMap作為keyMap,最多存1024個(gè)key的值,超過之后新加對象到緩存時(shí),會將不經(jīng)常使用的key從keyMap中移除,并且刪除掉緩存中對應(yīng)的key。利用了LinkedHashMap的特性:
每次訪問或者插入一個(gè)元素都會把元素放到鏈表末尾
//LinkedHashMap的get方法 public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder) afterNodeAccess(e); return e.value; }
調(diào)用put,putAll方法的時(shí)候會調(diào)用removeEldestEntry方法,所以LruCache在new LinkedHashMap的時(shí)候重寫了removeEldestEntry方法
public void setSize(final int size) { keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; //核心就是覆蓋 LinkedHashMap.removeEldestEntry方法, //返回true或false告訴 LinkedHashMap要不要?jiǎng)h除此最老鍵值 //LinkedHashMap內(nèi)部其實(shí)就是每次訪問或者插入一個(gè)元素都會把元素放到鏈表末尾, //這樣不經(jīng)常訪問的鍵值肯定就在鏈表開頭啦 @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { boolean tooBig = size() > size; if (tooBig) { //根據(jù)eldestKey去緩存中刪除 eldestKey = eldest.getKey(); } return tooBig; } }; } private void cycleKeyList(Object key) { keyMap.put(key, key); //keyMap是linkedhashmap,最老的記錄已經(jīng)被移除了,然后這里我們還需要移除被委托的那個(gè)cache的記錄 if (eldestKey != null) { delegate.removeObject(eldestKey); eldestKey = null; } }
BlockingCache:阻塞版本的緩存裝飾器,它保證只有一個(gè)線程到數(shù)據(jù)庫中查找指定key對應(yīng)的數(shù)據(jù), 加入線程A 在BlockingCache 中未查找到keyA對應(yīng)的緩存項(xiàng)時(shí),線程A會獲取keyA對應(yīng)的鎖,這樣后續(xù)線程在查找keyA是會發(fā)生阻塞。
FifoCache:先進(jìn)先出緩存,F(xiàn)ifoCache在putObject的時(shí)候會將Key放到一個(gè)List中,list長度為1024,如果超過,則刪除第一個(gè)緩存,最后一位添加當(dāng)前緩存
LoggingCache:日志緩存,它持有日志接口,根據(jù)日志的設(shè)置可打印sql和命中率等。
LruCache:最近最少使用算法。核心思想是當(dāng)緩存滿時(shí),會優(yōu)先淘汰那些近期最少使用的緩存。
ScheduledCache:定時(shí)調(diào)度緩存,每次訪問和添加緩存的時(shí)候會判斷當(dāng)前時(shí)間和上次清理的時(shí)間是否超過閾值,超過則會清理緩存。
SerializedCache:序列化緩存。緩存的值會被序列化。取值的時(shí)候會反序列化。要求緩存的對象實(shí)現(xiàn)Serializable接口。
SoftCache:軟引用緩存。對于軟引用關(guān)聯(lián)著的對象,只有在內(nèi)存不足的時(shí)候JVM才會回收該對象,使用SoftReference可以避免OOM異常
WeakCache:弱引用緩存,類似SoftCache。與軟引用區(qū)別在于當(dāng)JVM進(jìn)行垃圾回收時(shí),無論內(nèi)存是否充足,都會回收被弱引用關(guān)聯(lián)的對象。
TransactionalCache:事務(wù)緩存
關(guān)于如何理解Mybatis源碼中的Cache就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。