首頁 > 藝術
圖解|聊聊 MyBatis 快取
由 ITPUB 發表于 藝術2023-02-07
簡介3 一級快取考題考題(1)只開啟了一級快取,下面的程式碼呼叫了三次查詢操作 getStudentById,請判斷,下列說法正確的是
pagehelper是先查詢後分頁嗎
本文主要內容如下:
一、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 的結構:
private Map
如下圖所示,有兩個 SqlSession,分別為 SqlSession1 和 SqlSession2,每個 SqlSession 中都有自己的快取,快取是 hashmap 結構,存放的鍵值對。
鍵是 SQL 語句組成的 Key :
Statement Id + Offset + Limmit + Sql + Params
值是 SQL 查詢的結果:
2。2 一級快取配置
在 mybatis-config。xml 檔案配置,name=localCacheScope,value有兩種值:SESSION 和 STATEMENT
SESSION:開啟一級快取功能
STATEMENT:快取只對當前執行的這一個 SQL 語句有效,也就是沒有用到一級快取功能。
首先我們通過幾個考題來體驗下 MyBatis 一級快取。
2。3 一級快取考題
考題(1)只開啟了一級快取,下面的程式碼呼叫了三次查詢操作 getStudentById,請判斷,下列說法正確的是?
// 開啟一個 SqlSession
SqlSession sqlSession = factory。openSession(true);
StudentMapper studentMapper = sqlSession。getMapper(StudentMapper。class);
// 根據 id=1 查詢學生資訊
System。out。println(studentMapper。getStudentById(1));
// 根據 id=1 查詢學生資訊
System。out。println(studentMapper。getStudentById(1));
// 根據 id=1 查詢學生資訊
System。out。println(studentMapper。getStudentById(1));
答案:第一次從資料庫查詢到的資料,第二次和第二次從 MyBatis 一級快取查詢的資料。
解答:第一次從資料庫查詢後,後續查詢走 MyBatis 一級快取
考題(2)只開啟了一級快取,下面程式碼示例中,開啟了一個 SqlSession 會話,呼叫了一次查詢,然後對資料進行了更改,又呼叫了一次查詢,下列關於兩次查詢的說法,正確的是?
// 開啟一個 SqlSession
SqlSession sqlSession = factory。openSession(true);
StudentMapper studentMapper = sqlSession。getMapper(StudentMapper。class);
// 根據 id=1 查詢學生資訊
System。out。println(studentMapper。getStudentById(1));
// 插入了一條學生資料,改變了資料庫
System。out。println(“增加了” + studentMapper。addStudent(buildStudent()) + “個學生”);
// 根據 id=1 查詢學生資訊
System。out。println(studentMapper。getStudentById(1));
sqlSession。close();
答案:第一次從資料庫查詢到的資料,第二次從資料庫查詢的資料
解答:第一次從資料庫查詢後,後續更新(包括增刪改)資料庫中的資料後,這條 SQL 語句的快取失效了,後續查詢需要重新從資料庫獲取資料。
考題(3)當開啟了一級快取,下面的程式碼中,開啟了兩個 SqlSession,第一個 SqlSession 查詢了兩次學生 A 的姓名,第二次 SqlSession 更新了一次學生 A 的姓名,請判斷哪個選項符合最後的查詢結果。
SqlSession sqlSession1 = factory。openSession(true);
SqlSession sqlSession2 = factory。openSession(true);
StudentMapper studentMapper = sqlSession1。getMapper(StudentMapper。class);
StudentMapper studentMapper2 = sqlSession2。getMapper(StudentMapper。class); studentMapper2。updateStudentName(“B”,1);
System。out。println(studentMapper。getStudentById(1));
System。out。println(studentMapper2。getStudentById(1));
答案:
A
B
解答:只開啟一級快取的情況下,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 sqlSession1 = factory。openSession(true);
SqlSession sqlSession2 = factory。openSession(true);
SqlSession sqlSession3 = factory。openSession(true);
StudentMapper studentMapper = sqlSession1。getMapper(StudentMapper。class);
StudentMapper studentMapper2 = sqlSession2。getMapper(StudentMapper。class);
StudentMapper studentMapper3 = sqlSession3。getMapper(StudentMapper。class); System。out。println(“studentMapper讀取資料: ” + studentMapper。getStudentById(1));
sqlSession1。commit();
System。out。println(“studentMapper2讀取資料: ” + studentMapper2。getStudentById(1)); studentMapper3。updateStudentName(“李四”,1);
sqlSession3。commit();
System。out。println(“studentMapper2讀取資料: ” + studentMapper2。getStudentById(1));
答案:
張三
張三
李四
解答:三個 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。
<?xml version=“1。0” encoding=“utf-8” ?>
xsi:noNamespaceSchemaLocation=“。。/config/ehcache。xsd”> <!—— 磁碟儲存路徑 ——> maxElementsInMemory=“1000” maxElementsOnDisk=“10000000” eternal=“false” overflowToDisk=“true” timeToIdleSeconds=“120” timeToLiveSeconds=“120” diskExpiryThreadIntervalSeconds=“120” memoryStoreEvictionPolicy=“LRU”>
設定二級快取的型別,在xxxMapper。xml檔案中設定二級快取型別
4。3 EHCache配置檔案說明
五、總結
本篇分別介紹了 MyBatis 一級快取、二級快取、自定義快取的原理和使用,其中還穿插了 4 道考題來驗證 MyBatis 快取的功能。不足之處是 MyBatis 快取原始碼未分析。
參考資料:
https://tech。meituan。com/2018/01/19/mybatis-cache。html