mybatis 的緩存

一、前言

因為在做項目時候遇到了mybatis緩存的坑,所以全面學習了下mybaits的緩存知識,一來避免后面再次采坑,二來為其他童鞋提供前車之鑒。

二、Mybaits緩存作用

為了提高數據庫查詢性能,緩解數據查詢壓力,后面會具體看到一級是在sqlsession級別緩存了查詢結果和二級緩存則是在namespace級別緩存了查詢結果。

三、Mybaits一級緩存

3.1 問題示例

在做項目時候遇到一個問題,就是數據庫里面有個任務表,單擊頁面上面設備測試時候,會觸發一個事件,這個事件被定時鐘輪詢線程捕獲后,修改任務的狀態,而在單擊設備測試的同時會有一個rpc請求去查看任務狀態,這個rpc的bo層是個循環,循環查詢任務狀態。結果發現定時鐘線程已經修改了任務狀態,但是rpc的bo層循環查找的狀態還是修改前面的,但是明明數據庫里面狀態已經修改了啊。經過斷點發現,rpc的bo層循環查找的結果一直和第一次查找結果一樣,好奇怪,為啥類,第一想法是不是事務隔離性問題啊,畢竟mysql默認配置的隔離水平是Repeated read,但是查看配置我用的mysql是Read Commited,那就郁悶了啊,想知道答案請看3.2

3.2一級緩存原理

Mybatis的一級緩存是SqlSession級別的,我們知道每個mapper接口對應一個SqlSession(這樣說應該不準確,應該是一個線程中一個mapper接口對應一個sqlsession),所以Mybatis的一級緩存在不同mapper之間是隔離,相互不影響的。另外在執行Add,Update,Del時候,會清空當前線程SqlSession的一級緩存避免臟讀。默認情況下mybaits開啟一級緩存。
Mybaits一級緩存結構圖:



screenshot.png

然后我們深入一個SqlSession看看它是怎么玩的?



screenshot.png
劇透下同一個mapper在第一次執行select時候會發現sqlsession緩存沒有記錄,會去數據庫查找,然后把結果保存到緩存,第二次同等條件查詢下,就會從緩存中查找到結果。另外為了避免臟讀,每次執行更新新增刪除時候會清空當前sqlsession緩存。
下面從代碼時序圖看下:


代碼為:

 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());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
   //根據配置是否刷新緩存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
     //從緩存獲取結果
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //緩存中不存在,則在數據庫中查詢,查詢后把結果放入緩存
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

由于內部insert,update,delete最終調用的都是update方法,所以看下update代碼:

public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
   //清空一級緩存
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

由于默認情況下mybatis開啟一級緩存,所以如果你需要每次查詢都從數據庫查詢,可以在mapper.xml里面具體sql語句添加flushCache="true";

 <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long"  flushCache="true">
 select 
    <include refid="Base_Column_List" />
    from COMPANY
    where ID = #{id,jdbcType=NUMERIC}
    and is_deleted = 'n'
</select>

3.1節的問題通過配置這個解決。

四、Mybatis二級緩存

4.1介紹

二級緩存是namespace級別的,這個namespace就是指mapper文件里面那個namepsace,同一個namespace下的搜尋語句共享一個二級緩存。那么二級緩存是怎么樣的構造那,先上個圖:


screenshot.png

4.2 原理

上CachingExecutor的查詢代碼如下:

 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);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
         //緩存找不到則代理給SimpleExecutor查找,
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
   //沒有設置二級緩存,則直接委托給SimpleExecutor查找
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

如果開啟了二級緩存,則先從二級緩存中查找,查找不到則委托為SimpleExecutor查找,而它則會先從一級緩存中查找,查找不到則從數據庫查找。

五、總結

mybaits的二級緩存一般不怎么使用,默認一級緩存是開啟的,如果項目中遇到數據更新后查詢出來的數據卻沒有改變,那么可以從數據隔離性和mybaits緩存方面查找問題所在。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容