圖解 | 聊聊 MyBatis 緩存
你好,我是悟空。
本文主要內容如下:
一、MyBatis 緩存中的常用概念
MyBatis 緩存:它用來優化 SQL 數據庫查詢的,但是可能會產生臟數據。
SqlSession:代表和數據庫的一次會話,向用戶提供了操作數據庫的方法。
MappedStatement:代表要發往數據庫執行的指令,可以理解為是 SQL 的抽象表示。
Executor:代表用來和數據庫交互的執行器,接受 MappedStatment 作為參數。
namespace:每個 Mapper 文件只能配置一個 namespace,用來做 Mapper 文件級別的緩存共享。
映射接口:定義了一個接口,然后里面的接口方法對應要執行 SQL 的操作,具體要執行的 SQL 語句是寫在映射文件中。
映射文件:MyBatis 編寫的 XML 文件,里面有一個或多個 SQL 語句,不同的語句用來映射不同的接口方法。通常來說,每一張單表都對應著一個映射文件。
二、MyBatis 一級緩存
2.1 一級緩存原理
在一次 SqlSession 中(數據庫會話),程序執行多次查詢,且查詢條件完全相同,多次查詢之間程序沒有其他增刪改操作,則第二次及后面的查詢可以從緩存中獲取數據,避免走數據庫。
每個SqlSession中持有了Executor,每個Executor中有一個LocalCache。當用戶發起查詢時,MyBatis根據當前執行的語句生成MappedStatement,在Local Cache進行查詢,如果緩存命中的話,直接返回結果給用戶,如果緩存沒有命中的話,查詢數據庫,結果寫入Local Cache,最后返回結果給用戶。
Local Cache 其實是一個 hashmap 的結構:
如下圖所示,有兩個 SqlSession,分別為 SqlSession1 和 SqlSession2,每個 SqlSession 中都有自己的緩存,緩存是 hashmap 結構,存放的鍵值對。
鍵是 SQL 語句組成的 Key :
值是 SQL 查詢的結果:
2.2 一級緩存配置
在 mybatis-config.xml 文件配置,name=localCacheScope,value有兩種值:SESSION 和 STATEMENT
SESSION:開啟一級緩存功能
STATEMENT:緩存只對當前執行的這一個 SQL 語句有效,也就是沒有用到一級緩存功能。
首先我們通過幾個考題來體驗下 MyBatis 一級緩存。
2.3 一級緩存考題
考題(1)只開啟了一級緩存,下面的代碼調用了三次查詢操作 getStudentById,請判斷,下列說法正確的是?
答案:第一次從數據庫查詢到的數據,第二次和第二次從 MyBatis 一級緩存查詢的數據。
解答:第一次從數據庫查詢后,后續查詢走 MyBatis 一級緩存
考題(2)只開啟了一級緩存,下面代碼示例中,開啟了一個 SqlSession 會話,調用了一次查詢,然后對數據進行了更改,又調用了一次查詢,下列關于兩次查詢的說法,正確的是?
答案:第一次從數據庫查詢到的數據,第二次從數據庫查詢的數據
解答:第一次從數據庫查詢后,后續更新(包括增刪改)數據庫中的數據后,這條 SQL 語句的緩存失效了,后續查詢需要重新從數據庫獲取數據。
考題(3)當開啟了一級緩存,下面的代碼中,開啟了兩個 SqlSession,第一個 SqlSession 查詢了兩次學生 A 的姓名,第二次 SqlSession 更新了一次學生 A 的姓名,請判斷哪個選項符合最后的查詢結果。
答案:
解答:只開啟一級緩存的情況下,SqlSession 級別是不共享的。代碼示例中,分別創建了兩個 SqlSession,在第一個 SqlSession 中查詢學生 A 的姓名,第二個 SqlSession 中修改了學生 A 的姓名為 B,SqlSession2 更新了數據后,不會影響 SqlSession1,所以 SqlSession1 查到的數據還是 A。
2.4 MyBatis 一級緩存失效的場景
- 不同的SqlSession對應不同的一級緩存
- 同一個SqlSession但是查詢條件不同
- 同一個SqlSession兩次查詢期間執行了任何一次增刪改操作
- 同一個SqlSession兩次查詢期間手動清空了緩存
2.5 MyBatis 一級緩存總結
MyBatis一級緩存內部設計簡單,只是一個沒有容量限定的 HashMap,在緩存的功能性上有所欠缺
MyBatis的一級緩存最大范圍是SqlSession內部,有多個SqlSession或者分布式的環境下,數據庫寫操作會引起臟數據,建議設定緩存級別為Statement
一級緩存的配置中,默認是 SESSION 級別,即在一個MyBatis會話中執行的所有語句,都會共享這一個緩存。
三、MyBatis 二級緩存
3.1 MyBatis 二級緩存概述
MyBatis的二級緩存相對于一級緩存來說,實現了SqlSession之間緩存數據的共享,同時粒度更加的細,能夠到namespace級別,通過Cache接口實現類不同的組合,對Cache的可控性也更強。
MyBatis在多表查詢時,極大可能會出現臟數據,有設計上的缺陷,安全使用二級緩存的條件比較苛刻。
在分布式環境下,由于默認的MyBatis Cache實現都是基于本地的,分布式環境下必然會出現讀取到臟數據,需要使用集中式緩存將 MyBatis的Cache 接口實現,有一定的開發成本,直接使用Redis、Memcached 等分布式緩存可能成本更低,安全性也更高。
3.2 MyBatis 二級緩存原理
一級緩存最大的共享范圍就是一個 SqlSession 內部,如果多個 SqlSession 之間需要共享緩存,則需要使用到二級緩存。
開啟二級緩存后,會使用 CachingExecutor 裝飾 Executor,進入一級緩存的查詢流程前,先在CachingExecutor 進行二級緩存的查詢。
二級緩存開啟后,同一個 namespace下的所有操作語句,都影響著同一個Cache。
每個 Mapper 文件只能配置一個 namespace,用來做 Mapper 文件級別的緩存共享。
二級緩存被同一個 namespace 下的多個 SqlSession 共享,是一個全局的變量。MyBatis 的二級緩存不適應用于映射文件中存在多表查詢的情況。
通常我們會為每個單表創建單獨的映射文件,由于MyBatis的二級緩存是基于namespace的,多表查詢語句所在的namspace無法感應到其他namespace中的語句對多表查詢中涉及的表進行的修改,引發臟數據問題。
3.3 MyBatis緩存查詢的順序
- 先查詢二級緩存,因為二級緩存中可能會有其他程序已經查出來的數據,可以拿來直接使用
- 如果二級緩存沒有命中,再查詢一級緩存
- 如果一級緩存也沒有命中,則查詢數據庫
- SqlSession關閉之后,一級緩存中的數據會寫入二級緩存。
3.4 二級緩存配置
開啟二級緩存需要在 mybatis-config.xml 中配置:
3.5 二級緩存考題
測試update操作是否會刷新該namespace下的二級緩存。
開啟了一級和二級緩存,通過三個SqlSession 查詢和更新 學生張三的姓名,判斷最后的輸出結果是什么?
答案:
解答:三個 SqlSession 是共享 MyBatis 緩存,SqlSession2 更新數據后,MyBatis 的 namespace 緩存(StudentMapper) 就失效了,SqlSession2 最后是從數據庫查詢到的數據。
四、MyBatis 自定義緩存
4.1 MyBatis 自定義緩存概述
當 MyBatis 二級緩存不能滿足要求時,可以使用自定義緩存替換。(較少使用)
自定義緩存需要實現 MyBatis 規定的接口:org.apache.ibatis.cache.Cache。這個接口里面定義了 7 個方法,我們需要自己去實現對應的緩存邏輯。
4.2 整合第三方緩存 EHCache
EHCache 和 MyBatis 已經幫我們整合好了一個自定義緩存,我們可以直接拿來用,不需要自己去實現 MyBatis 的 org.apache.ibatis.cache.Cache 接口。
添加 mybatis-ehcache 依賴包。
創建EHCache的配置文件ehcache.xml。
設置二級緩存的類型,在xxxMapper.xml文件中設置二級緩存類型
4.3 EHCache配置文件說明
五、總結
本篇分別介紹了 MyBatis 一級緩存、二級緩存、自定義緩存的原理和使用,其中還穿插了 4 道考題來驗證 MyBatis 緩存的功能。不足之處是 MyBatis 緩存源碼未分析。
參考資料: