Mybatis快取機制

請叫我阿杰 發表於 2022-12-01
MyBatis

什麼是快取? 為什麼使用快取? 什麼場景下使用快取?

快取(Cache)就是資料交換的緩衝區,一個臨時儲存資料的地方,當我們讀取資料時會首先從快取中查詢需要的資料,如果找到了則直接執行,找不到的話再從記憶體中找。

在實際開發中,我們會經常對資料庫進行資料查詢,而從資料庫讀取資料的效率是非常低下的,並且頻繁地去訪問資料庫會增大資料庫壓力降低資料庫查詢效能等,所以我們可以將經常查詢且不經常改變的資料儲存到快取中(快取就是記憶體中的一個物件),這樣使用者在查詢的時候就不用到資料庫中查詢(磁碟),從而減少與資料庫的交付次數,從而提高查詢效率,解決了高併發系統的效能問題

快取的本質就是用空間換時間,犧牲資料的實時性,以伺服器記憶體中的資料暫時代替從資料庫讀取最新的資料,減少資料庫IO,減輕伺服器壓力,減少網路延遲,從而提高訪問速度。

Mybatis的快取機制

Mybatis一級快取(sqlSession級別)

​ 一級快取是SqlSession級別的快取,在運算元據庫時需要構造SqlSession物件,在物件中有一個 資料結構(HashMap) 用於儲存快取資料,不同的SqlSession之間的快取資料區域(HashMap)互不影響。

​ 當在同一個sqlSession (會話) 中執行兩次相同的SQL語句時,第一次執行完畢會將從資料庫中查詢的資料寫到快取(記憶體),第二次查詢時會從快取中獲取資料,不再去底層資料庫查詢,從而提高查詢效率。需要注意的是,如果sqlSession執行了DML操作(insert、update、delete),並提交到資料庫,MyBatis則會清空sqlSession中的一級快取,這樣做的目的是為了保證快取中儲存的是最新的資訊,避免出現髒讀現象。

MyBatis預設開啟一級快取,不需要進行任何配置,當一個sqlSession結束後該sqlSession的一級快取也就不在了,一級快取是不能關閉的。

測試說明:

​ 我們可以建立一張學生表,寫sql查詢語句根據id查詢學生資訊,定義一個方法,在方法內呼叫三次該查詢,提前開啟日誌列印方便我們在控制檯檢視列印的sql語句,我們可以看到,只有第一次列印了sql語句也就是真正查詢了資料庫,後面的查詢使用了一級快取,直接在快取中讀取的資料並沒有訪問資料庫。

​ 我們接著對上面的資料進行測試,從上面我們可以知道學生表中的資料已經存入到快取中,接下來我們可以對資料進行(增/刪/改)測試(insert、update、delete),再進行查詢。可以發現進行了增、刪、改操作後控制檯列印了後面的查詢sql語句,也就是再次訪問了資料庫進行查詢,所以清空了一級快取導致失效了。

​ 我們繼續測試,這次我們開啟兩個SqlSession(會話),在SqlSession1中我們進行查詢操作從而開啟一級快取,在SqlSession2我們可以進行(增/刪/改)操作,再用SqlSession1去查詢,可以發現出現了髒資料,SqlSession1並沒有查詢到SqlSession2修改後的資料。所以驗證了一級快取只在資料庫會話內部共享

小結:

  • 一級快取(本地快取), 作用域預設為sqlSession。當 Session flush 或 close 後, 該Session 中的所有Cache 將被清空。

  • 本地快取不能被關閉, 但可以呼叫clearCache()來清空本地快取, 或者改變快取的作用域。

  • 在mybatis3.1之後,可以配置本地快取的作用域,在 mybatis.xml 中配置。

  • 讓一級快取失效的幾種情況:

    ① 不同的SqlSession對應不同的一級快取
    ② 同一個SqlSession但是查詢條件不同
    ③ 同一個SqlSession的兩次查詢期間執行了增刪改操作
    ④ 同一個SqlSession的兩次查詢期間手動清空了快取

Mybatis二級快取

二級快取也叫全域性快取,一級快取作用域太低了,二級快取預設是全域性開啟的,它是基於namespace級別的快取,一個名稱空間,對應一個二級快取,所以也稱之為“namespace快取”,需要在配置SQL語句的XML中新增節點, 以表示當前XML中的所有查詢都允許開通二級快取,並且,在節點上配置useCache=“true”,則對應的節點的查詢結果將被二級快取處理,並且,此查詢返回的結果的型別必須是實現了Serializable介面的,如果使用了配置如何封裝查詢結果,則必須使用節點來封裝主鍵的對映,滿足以上條件後,二級快取將可用,只要是當前namespace中查詢出來的結果,都會根據所執行的SQL語句及引數進行 結果的快取

  • 開啟二級快取後,會使用CachingExecutor裝飾Executor,進入一級快取的查詢流程前,先在CachingExecutor進行二級快取的查詢。
  • 二級快取開啟後,同一個namespace下的所有操作語句,都影響著同一個Cache,即二級快取被多個SqlSession共享,是一個全域性的變數。
  • 當開啟快取後,資料的查詢執行的流程就是 二級快取 -> 一級快取 -> 資料庫

開啟二級快取具體步驟:

  • 在mybatis-config.xml檔案中開啟快取

    
    <setting name="cacheEnabled"value="true"/>
    <!-- 全域性配置引數,需要時再設定 -->
        <settings>
           <!-- 開啟二級快取  預設值為true -->
        <setting name="cacheEnabled" value="true"/>
        </settings> 
    
  • 在mapper.xml配置檔案中使用二級快取

    • type:cache使用的型別,預設是PerpetualCache,這在一級快取中提到過。
    • eviction: 定義回收的策略,常見的有FIFO,LRU。
    • flushInterval: 配置一定時間自動重新整理快取,單位是毫秒。
    • size: 最多快取物件的個數。
    • readOnly: 是否只讀,若配置可讀寫,則需要對應的實體類能夠序列化。
    • blocking: 若快取中找不到對應的key,是否會一直blocking,直到有對應的資料進入快取。
    
    <!--在當前Mapper.xml檔案中使用二級快取-->
    <mapper namespace="cn.hpu.mybatis.mapper.UserMapper">
    
    <!-- 開啟本mapper namespace下的二級快取 -->
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    
    <cache/>
    

也可以直接在mapper.xml檔案中加入,但是要記得實體類要序列化,不然容易會報Caused by: java.io.NotSerializableException: com.xsq.pojo.User異常

  • 在實體類中實現序列化:

    
    public class User implements Serializable {
        //Serializable實現序列化,為了將來反序列化
    }
    

工作機制

  • 一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中;
  • 如果當前會話關閉了,這個會話對應的一級快取就沒了,但是我們想要的是,會話關閉了,一級快取中的資料被儲存到二級快取中;
  • 新的會話被查詢資訊,就可以從二級快取中獲取內容;
  • 不同的mapper查出的資料會放在自己對應的快取(map)中;

小結:

  • 只要開啟了二級快取,在同一個Mapper檔案下就有效;
  • 所有的資料都會先放在一級快取中;
  • 只有當會話提交,或者關閉的時候,才會提交到二級快取中。

總結:

  1. MyBatis一級快取的生命週期和SqlSession一致。
  2. MyBatis一級快取內部設計簡單,只是一個沒有容量限定的HashMap,在快取的功能性上有所欠缺。
  3. MyBatis的一級快取最大範圍是SqlSession內部,有多個SqlSession或者分散式的環境下,資料庫寫操作會引起髒資料,建議設定快取級別為Statement。
  4. MyBatis的二級快取相對於一級快取來說,實現了SqlSession之間快取資料的共享,同時粒度更加的細,能夠到namespace級別,透過Cache介面實現類不同的組合,對Cache的可控性也更強。
  5. MyBatis在多表查詢時,極大可能會出現髒資料,有設計上的缺陷,安全使用二級快取的條件比較苛刻。
  6. 在分散式環境下,由於預設的MyBatis Cache實現都是基於本地的,分散式環境下必然會出現讀取到髒資料,需要使用集中式快取將MyBatis的Cache介面實現,有一定的開發成本,直接使用Redis,Memcached等分散式快取可能成本更低,安全性也更高。

無論是一級快取還是二級快取,只要資料發生了寫操作(增、刪、改), 快取資料都將被自動清理

由於Mybatis的快取清理機制過於死板,所以,一般在開發實踐中並不怎麼使用!更多的是使用其它的快取工具並自行制定快取策略