圖解 | 聊聊 MyBatis 緩存
你好,我是悟空。
本文主要內(nèi)容如下:

一、MyBatis 緩存中的常用概念
MyBatis 緩存:它用來優(yōu)化 SQL 數(shù)據(jù)庫查詢的,但是可能會(huì)產(chǎn)生臟數(shù)據(jù)。
SqlSession:代表和數(shù)據(jù)庫的一次會(huì)話,向用戶提供了操作數(shù)據(jù)庫的方法。
MappedStatement:代表要發(fā)往數(shù)據(jù)庫執(zhí)行的指令,可以理解為是 SQL 的抽象表示。
Executor:代表用來和數(shù)據(jù)庫交互的執(zhí)行器,接受 MappedStatment 作為參數(shù)。
namespace:每個(gè) Mapper 文件只能配置一個(gè) namespace,用來做 Mapper 文件級(jí)別的緩存共享。
映射接口:定義了一個(gè)接口,然后里面的接口方法對(duì)應(yīng)要執(zhí)行 SQL 的操作,具體要執(zhí)行的 SQL 語句是寫在映射文件中。
映射文件:MyBatis 編寫的 XML 文件,里面有一個(gè)或多個(gè) SQL 語句,不同的語句用來映射不同的接口方法。通常來說,每一張單表都對(duì)應(yīng)著一個(gè)映射文件。
二、MyBatis 一級(jí)緩存
2.1 一級(jí)緩存原理
在一次 SqlSession 中(數(shù)據(jù)庫會(huì)話),程序執(zhí)行多次查詢,且查詢條件完全相同,多次查詢之間程序沒有其他增刪改操作,則第二次及后面的查詢可以從緩存中獲取數(shù)據(jù),避免走數(shù)據(jù)庫。

每個(gè)SqlSession中持有了Executor,每個(gè)Executor中有一個(gè)LocalCache。當(dāng)用戶發(fā)起查詢時(shí),MyBatis根據(jù)當(dāng)前執(zhí)行的語句生成MappedStatement,在Local Cache進(jìn)行查詢,如果緩存命中的話,直接返回結(jié)果給用戶,如果緩存沒有命中的話,查詢數(shù)據(jù)庫,結(jié)果寫入Local Cache,最后返回結(jié)果給用戶。
Local Cache 其實(shí)是一個(gè) hashmap 的結(jié)構(gòu):
如下圖所示,有兩個(gè) SqlSession,分別為 SqlSession1 和 SqlSession2,每個(gè) SqlSession 中都有自己的緩存,緩存是 hashmap 結(jié)構(gòu),存放的鍵值對(duì)。
鍵是 SQL 語句組成的 Key :
值是 SQL 查詢的結(jié)果:

2.2 一級(jí)緩存配置
在 mybatis-config.xml 文件配置,name=localCacheScope,value有兩種值:SESSION 和 STATEMENT
SESSION:開啟一級(jí)緩存功能
STATEMENT:緩存只對(duì)當(dāng)前執(zhí)行的這一個(gè) SQL 語句有效,也就是沒有用到一級(jí)緩存功能。
首先我們通過幾個(gè)考題來體驗(yàn)下 MyBatis 一級(jí)緩存。
2.3 一級(jí)緩存考題
考題(1)只開啟了一級(jí)緩存,下面的代碼調(diào)用了三次查詢操作 getStudentById,請(qǐng)判斷,下列說法正確的是?
答案:第一次從數(shù)據(jù)庫查詢到的數(shù)據(jù),第二次和第二次從 MyBatis 一級(jí)緩存查詢的數(shù)據(jù)。
解答:第一次從數(shù)據(jù)庫查詢后,后續(xù)查詢走 MyBatis 一級(jí)緩存
考題(2)只開啟了一級(jí)緩存,下面代碼示例中,開啟了一個(gè) SqlSession 會(huì)話,調(diào)用了一次查詢,然后對(duì)數(shù)據(jù)進(jìn)行了更改,又調(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)開啟了一級(jí)緩存,下面的代碼中,開啟了兩個(gè) SqlSession,第一個(gè) SqlSession 查詢了兩次學(xué)生 A 的姓名,第二次 SqlSession 更新了一次學(xué)生 A 的姓名,請(qǐng)判斷哪個(gè)選項(xiàng)符合最后的查詢結(jié)果。
答案:
解答:只開啟一級(jí)緩存的情況下,SqlSession 級(jí)別是不共享的。代碼示例中,分別創(chuàng)建了兩個(gè) SqlSession,在第一個(gè) SqlSession 中查詢學(xué)生 A 的姓名,第二個(gè) SqlSession 中修改了學(xué)生 A 的姓名為 B,SqlSession2 更新了數(shù)據(jù)后,不會(huì)影響 SqlSession1,所以 SqlSession1 查到的數(shù)據(jù)還是 A。
2.4 MyBatis 一級(jí)緩存失效的場(chǎng)景
- 不同的SqlSession對(duì)應(yīng)不同的一級(jí)緩存
 - 同一個(gè)SqlSession但是查詢條件不同
 - 同一個(gè)SqlSession兩次查詢期間執(zhí)行了任何一次增刪改操作
 - 同一個(gè)SqlSession兩次查詢期間手動(dòng)清空了緩存
 
2.5 MyBatis 一級(jí)緩存總結(jié)
MyBatis一級(jí)緩存內(nèi)部設(shè)計(jì)簡(jiǎn)單,只是一個(gè)沒有容量限定的 HashMap,在緩存的功能性上有所欠缺
MyBatis的一級(jí)緩存最大范圍是SqlSession內(nèi)部,有多個(gè)SqlSession或者分布式的環(huán)境下,數(shù)據(jù)庫寫操作會(huì)引起臟數(shù)據(jù),建議設(shè)定緩存級(jí)別為Statement
一級(jí)緩存的配置中,默認(rèn)是 SESSION 級(jí)別,即在一個(gè)MyBatis會(huì)話中執(zhí)行的所有語句,都會(huì)共享這一個(gè)緩存。
三、MyBatis 二級(jí)緩存
3.1 MyBatis 二級(jí)緩存概述
MyBatis的二級(jí)緩存相對(duì)于一級(jí)緩存來說,實(shí)現(xiàn)了SqlSession之間緩存數(shù)據(jù)的共享,同時(shí)粒度更加的細(xì),能夠到namespace級(jí)別,通過Cache接口實(shí)現(xiàn)類不同的組合,對(duì)Cache的可控性也更強(qiáng)。
MyBatis在多表查詢時(shí),極大可能會(huì)出現(xiàn)臟數(shù)據(jù),有設(shè)計(jì)上的缺陷,安全使用二級(jí)緩存的條件比較苛刻。
在分布式環(huán)境下,由于默認(rèn)的MyBatis Cache實(shí)現(xiàn)都是基于本地的,分布式環(huán)境下必然會(huì)出現(xiàn)讀取到臟數(shù)據(jù),需要使用集中式緩存將 MyBatis的Cache 接口實(shí)現(xiàn),有一定的開發(fā)成本,直接使用Redis、Memcached 等分布式緩存可能成本更低,安全性也更高。
3.2 MyBatis 二級(jí)緩存原理
一級(jí)緩存最大的共享范圍就是一個(gè) SqlSession 內(nèi)部,如果多個(gè) SqlSession 之間需要共享緩存,則需要使用到二級(jí)緩存。
開啟二級(jí)緩存后,會(huì)使用 CachingExecutor 裝飾 Executor,進(jìn)入一級(jí)緩存的查詢流程前,先在CachingExecutor 進(jìn)行二級(jí)緩存的查詢。
二級(jí)緩存開啟后,同一個(gè) namespace下的所有操作語句,都影響著同一個(gè)Cache。

每個(gè) Mapper 文件只能配置一個(gè) namespace,用來做 Mapper 文件級(jí)別的緩存共享。
二級(jí)緩存被同一個(gè) namespace 下的多個(gè) SqlSession 共享,是一個(gè)全局的變量。MyBatis 的二級(jí)緩存不適應(yīng)用于映射文件中存在多表查詢的情況。
通常我們會(huì)為每個(gè)單表創(chuàng)建單獨(dú)的映射文件,由于MyBatis的二級(jí)緩存是基于namespace的,多表查詢語句所在的namspace無法感應(yīng)到其他namespace中的語句對(duì)多表查詢中涉及的表進(jìn)行的修改,引發(fā)臟數(shù)據(jù)問題。
3.3 MyBatis緩存查詢的順序

- 先查詢二級(jí)緩存,因?yàn)槎?jí)緩存中可能會(huì)有其他程序已經(jīng)查出來的數(shù)據(jù),可以拿來直接使用
 - 如果二級(jí)緩存沒有命中,再查詢一級(jí)緩存
 - 如果一級(jí)緩存也沒有命中,則查詢數(shù)據(jù)庫
 - SqlSession關(guān)閉之后,一級(jí)緩存中的數(shù)據(jù)會(huì)寫入二級(jí)緩存。
 
3.4 二級(jí)緩存配置
開啟二級(jí)緩存需要在 mybatis-config.xml 中配置:
3.5 二級(jí)緩存考題
測(cè)試update操作是否會(huì)刷新該namespace下的二級(jí)緩存。
開啟了一級(jí)和二級(jí)緩存,通過三個(gè)SqlSession 查詢和更新 學(xué)生張三的姓名,判斷最后的輸出結(jié)果是什么?
答案:
解答:三個(gè) SqlSession 是共享 MyBatis 緩存,SqlSession2 更新數(shù)據(jù)后,MyBatis 的 namespace 緩存(StudentMapper) 就失效了,SqlSession2 最后是從數(shù)據(jù)庫查詢到的數(shù)據(jù)。
四、MyBatis 自定義緩存
4.1 MyBatis 自定義緩存概述
當(dāng) MyBatis 二級(jí)緩存不能滿足要求時(shí),可以使用自定義緩存替換。(較少使用)
自定義緩存需要實(shí)現(xiàn) MyBatis 規(guī)定的接口:org.apache.ibatis.cache.Cache。這個(gè)接口里面定義了 7 個(gè)方法,我們需要自己去實(shí)現(xiàn)對(duì)應(yīng)的緩存邏輯。

4.2 整合第三方緩存 EHCache
EHCache 和 MyBatis 已經(jīng)幫我們整合好了一個(gè)自定義緩存,我們可以直接拿來用,不需要自己去實(shí)現(xiàn) MyBatis 的 org.apache.ibatis.cache.Cache 接口。
添加 mybatis-ehcache 依賴包。
創(chuàng)建EHCache的配置文件ehcache.xml。
設(shè)置二級(jí)緩存的類型,在xxxMapper.xml文件中設(shè)置二級(jí)緩存類型
4.3 EHCache配置文件說明

五、總結(jié)
本篇分別介紹了 MyBatis 一級(jí)緩存、二級(jí)緩存、自定義緩存的原理和使用,其中還穿插了 4 道考題來驗(yàn)證 MyBatis 緩存的功能。不足之處是 MyBatis 緩存源碼未分析。
參考資料:















 
 
 











 
 
 
 