Mybatis快取機制

2022-12-01 18:00:12

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

快取(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的快取清理機制過於死板,所以,一般在開發實踐中並不怎麼使用!更多的是使用其它的快取工具並自行制定快取策略