溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解Mybatis源碼中的Cache

發(fā)布時(shí)間:2021-09-14 10:36:08 來源:億速云 閱讀:100 作者:柒染 欄目:大數(shù)據(jù)

這篇文章將為大家詳細(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è)計(jì)的要點(diǎn)

  • 緩存的容量

  • 緩存的有效時(shí)間

實(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ù)。

Mybatis中的緩存分析與學(xué)習(xí)

MyBatis的一級緩存和二級緩存

一級緩存

  • 使用范圍:SESSION,STATEMENT。默認(rèn)為SESSION,如果不想使用一級緩存,可以修改為STATEMENT,每次調(diào)用后都會清掉緩存。

//STATEMENT或者SESSION,默認(rèn)為SESSION
<setting name="localCacheScope" value="STATEMENT"/>
  • 一級緩存執(zhí)行的流程: 如何理解Mybatis源碼中的Cache

//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查詢,查詢不到在查一級緩存。 如何理解Mybatis源碼中的Cache

   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的具體實(shí)現(xiàn) [Least Recently Used]

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;
    }
  }
Mybatis Cache類型與特性
  • 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ò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(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)容。

AI