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