一、MyBatis 緩存中的常用概念
MyBatis 緩存:它用來(lái)優(yōu)化 SQL 數(shù)據(jù)庫(kù)查詢的,但是可能會(huì)產(chǎn)生臟數(shù)據(jù)。
SqlSession:代表和數(shù)據(jù)庫(kù)的一次會(huì)話,向用戶提供了操作數(shù)據(jù)庫(kù)的方法。
MappedStatement:代表要發(fā)往數(shù)據(jù)庫(kù)執(zhí)行的指令,可以理解為是 SQL 的抽象表示。
Executor:代表用來(lái)和數(shù)據(jù)庫(kù)交互的執(zhí)行器,接受 MappedStatment 作為參數(shù)。
namespace:每個(gè) Mapper 文件只能配置一個(gè) namespace,用來(lái)做 Mapper 文件級(jí)別的緩存共享。
映射接口:定義了一個(gè)接口,然后里面的接口方法對(duì)應(yīng)要執(zhí)行 SQL 的操作,具體要執(zhí)行的 SQL 語(yǔ)句是寫在映射文件中。
映射文件:MyBatis 編寫的 XML 文件,里面有一個(gè)或多個(gè) SQL 語(yǔ)句,不同的語(yǔ)句用來(lái)映射不同的接口方法。通常來(lái)說,每一張單表都對(duì)應(yīng)著一個(gè)映射文件。
二、MyBatis 一級(jí)緩存
2.1 一級(jí)緩存原理
在一次 SqlSession 中(數(shù)據(jù)庫(kù)會(huì)話),程序執(zhí)行多次查詢,且查詢條件完全相同,多次查詢之間程序沒有其他增刪改操作,則第二次及后面的查詢可以從緩存中獲取數(shù)據(jù),避免走數(shù)據(jù)庫(kù)。
每個(gè)SqlSession中持有了Executor,每個(gè)Executor中有一個(gè)LocalCache。當(dāng)用戶發(fā)起查詢時(shí),MyBatis根據(jù)當(dāng)前執(zhí)行的語(yǔ)句生成MappedStatement
,在Local Cache進(jìn)行查詢,如果緩存命中的話,直接返回結(jié)果給用戶,如果緩存沒有命中的話,查詢數(shù)據(jù)庫(kù),結(jié)果寫入Local Cache
,最后返回結(jié)果給用戶。
Local Cache 其實(shí)是一個(gè) hashmap 的結(jié)構(gòu):
private Map<Object, Object> cache = new HashMap<Object, Object>();
如下圖所示,有兩個(gè) SqlSession,分別為 SqlSession1 和 SqlSession2,每個(gè) SqlSession 中都有自己的緩存,緩存是 hashmap 結(jié)構(gòu),存放的鍵值對(duì)。
鍵是 SQL 語(yǔ)句組成的 Key :
Statement Id + Offset + Limmit + Sql + Params
值是 SQL 查詢的結(jié)果:
2.2 一級(jí)緩存配置
在 mybatis-config.xml 文件配置,name=localCacheScope
,value有兩種值:SESSION
和 STATEMENT
<configuration>
<settings>
<setting name="localCacheScope" value="SESSION"/>
<span class="hljs-name"settings>
<configuration>
SESSION:開啟一級(jí)緩存功能
STATEMENT:緩存只對(duì)當(dāng)前執(zhí)行的這一個(gè) SQL 語(yǔ)句有效,也就是沒有用到一級(jí)緩存功能。
首先我們通過幾個(gè)考題來(lái)體驗(yàn)下 MyBatis 一級(jí)緩存。
2.3 一級(jí)緩存考題
考題(1)只開啟了一級(jí)緩存,下面的代碼調(diào)用了三次查詢操作 getStudentById,請(qǐng)判斷,下列說法正確的是?
// 打開一個(gè) SqlSession
SqlSession sqlSession = factory.openSession(true);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1));
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1));
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1));
答案:第一次從數(shù)據(jù)庫(kù)查詢到的數(shù)據(jù),第二次和第二次從 MyBatis 一級(jí)緩存查詢的數(shù)據(jù)。
解答:第一次從數(shù)據(jù)庫(kù)查詢后,后續(xù)查詢走 MyBatis 一級(jí)緩存
考題(2)只開啟了一級(jí)緩存,下面代碼示例中,開啟了一個(gè) SqlSession 會(huì)話,調(diào)用了一次查詢,然后對(duì)數(shù)據(jù)進(jìn)行了更改,又調(diào)用了一次查詢,下列關(guān)于兩次查詢的說法,正確的是?
// 打開一個(gè) SqlSession
SqlSession sqlSession = factory.openSession(true);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1));
// 插入了一條學(xué)生數(shù)據(jù),改變了數(shù)據(jù)庫(kù)
System.out.println("增加了" + studentMapper.addStudent(buildStudent()) + "個(gè)學(xué)生");
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1));
sqlSession.close();
答案:第一次從數(shù)據(jù)庫(kù)查詢到的數(shù)據(jù),第二次從數(shù)據(jù)庫(kù)查詢的數(shù)據(jù)
解答:第一次從數(shù)據(jù)庫(kù)查詢后,后續(xù)更新(包括增刪改)數(shù)據(jù)庫(kù)中的數(shù)據(jù)后,這條 SQL 語(yǔ)句的緩存失效了,后續(xù)查詢需要重新從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)。
考題(3)當(dāng)開啟了一級(jí)緩存,下面的代碼中,開啟了兩個(gè) SqlSession,第一個(gè) SqlSession 查詢了兩次學(xué)生 A 的姓名,第二次 SqlSession 更新了一次學(xué)生 A 的姓名,請(qǐng)判斷哪個(gè)選項(xiàng)符合最后的查詢結(jié)果。
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
解答:只開啟一級(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ù)庫(kù)寫操作會(huì)引起臟數(shù)據(jù),建議設(shè)定緩存級(jí)別為Statement
- 一級(jí)緩存的配置中,默認(rèn)是 SESSION 級(jí)別,即在一個(gè)MyBatis會(huì)話中執(zhí)行的所有語(yǔ)句,都會(huì)共享這一個(gè)緩存。
三、MyBatis 二級(jí)緩存
3.1 MyBatis 二級(jí)緩存概述
- MyBatis的二級(jí)緩存相對(duì)于一級(jí)緩存來(lái)說,實(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
下的所有操作語(yǔ)句,都影響著同一個(gè)Cache。
每個(gè) Mapper 文件只能配置一個(gè) namespace,用來(lái)做 Mapper 文件級(jí)別的緩存共享。
<mapper namespace="mapper.StudentMapper"><span class="hljs-name"mapper>
二級(jí)緩存被同一個(gè) namespace
下的多個(gè) SqlSession
共享,是一個(gè)全局的變量。MyBatis 的二級(jí)緩存不適應(yīng)用于映射文件中存在多表查詢的情況。
通常我們會(huì)為每個(gè)單表創(chuàng)建單獨(dú)的映射文件,由于MyBatis的二級(jí)緩存是基于namespace
的,多表查詢語(yǔ)句所在的namspace
無(wú)法感應(yīng)到其他namespace
中的語(yǔ)句對(duì)多表查詢中涉及的表進(jìn)行的修改,引發(fā)臟數(shù)據(jù)問題。
3.3 MyBatis緩存查詢的順序
- 先查詢二級(jí)緩存,因?yàn)槎?jí)緩存中可能會(huì)有其他程序已經(jīng)查出來(lái)的數(shù)據(jù),可以拿來(lái)直接使用
- 如果二級(jí)緩存沒有命中,再查詢一級(jí)緩存
- 如果一級(jí)緩存也沒有命中,則查詢數(shù)據(jù)庫(kù)
- 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é)果是什么?
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讀取數(shù)據(jù): " + studentMapper.getStudentById(1));
sqlSession1.commit();
System.out.println("studentMapper2讀取數(shù)據(jù): " + studentMapper2.getStudentById(1)); studentMapper3.updateStudentName("李四",1);
sqlSession3.commit();
System.out.println("studentMapper2讀取數(shù)據(jù): " + studentMapper2.getStudentById(1));
答案:
張三
張三
李四
解答:三個(gè) SqlSession 是共享 MyBatis 緩存,SqlSession2 更新數(shù)據(jù)后,MyBatis 的 namespace 緩存(StudentMapper) 就失效了,SqlSession2 最后是從數(shù)據(jù)庫(kù)查詢到的數(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è)自定義緩存,我們可以直接拿來(lái)用,不需要自己去實(shí)現(xiàn) MyBatis 的 org.apache.ibatis.cache.Cache
接口。
添加 mybatis-ehcache 依賴包。
<dependency>
<groupId>org.mybatis.caches<span class="hljs-name"groupId>
<artifactId>mybatis-ehcache<span class="hljs-name"artifactId>
<version>1.2.1<span class="hljs-name"version>
<span class="hljs-name"dependency>
創(chuàng)建EHCache的配置文件ehcache.xml。
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="D:\\passjava\\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<span class="hljs-name"defaultCache>
<span class="hljs-name"ehcache>
設(shè)置二級(jí)緩存的類型,在xxxMapper.xml文件中設(shè)置二級(jí)緩存類型
4.3 EHCache配置文件說明
五、總結(jié)
本篇分別介紹了 MyBatis 一級(jí)緩存、二級(jí)緩存、自定義緩存的原理和使用,其中還穿插了 4 道考題來(lái)驗(yàn)證 MyBatis 緩存的功能。不足之處是 MyBatis 緩存源碼未分析。
審核編輯:劉清
-
XML
+關(guān)注
關(guān)注
0文章
186瀏覽量
33011 -
SQL
+關(guān)注
關(guān)注
1文章
751瀏覽量
43985 -
mybatis
+關(guān)注
關(guān)注
0文章
58瀏覽量
6693
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論