MyBatis緩存機制學習(一級緩存,二級緩存,二級緩存擊中情況)


簡介

MyBatis是常見的Java數據庫訪問層框架。

一級緩存介紹

在應用運行過程中,我們有可能在一次數據庫會話中,執行多次查詢條件完全相同的SQL,MyBatis提供了一級緩存的方案優化這部分場景,如果是相同的SQL語句,會優先命中一級緩存,避免直接對數據庫進行查詢,提高性能。具體執行過程如下圖所示。

 
 

每個SqlSession中持有了Executor(譯:執行器),每個Executor中有一個LocalCache。當用戶發起查詢時,MyBatis根據當前執行的語句生成MappedStatement(譯:映射聲明),在Local Cache進行查詢,如果緩存命中的話,直接返回結果給用戶,如果緩存沒有命中的話,查詢數據庫,結果寫入Local Cache,最后返回結果給用戶

 

一級緩存配置

如何使用MyBatis一級緩存。開發者只需在MyBatis的配置文件中,添加如下語句,就可以使用一級緩存。共有兩個選項,SESSION或者STATEMENT,默認是SESSION級別,即在一個MyBatis會話中執行的所有語句,都會共享這一個緩存。一種是STATEMENT級別,可以理解為緩存只對當前執行的這一個Statement有效。

 
<setting name="localCacheScope" value="SESSION"/> 

一級緩存實驗

創建示例表student,創建對應的POJO類和增改的方法

 
DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(200) COLLATE utf8_bin DEFAULT NULL, `age` tinyint(3) unsigned DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; LOCK TABLES `student` WRITE; INSERT INTO `student` (`id`, `name`, `age`) VALUES (1,'點點',16),(2,'平平',16),(3,'美美',16),(4,'團團',16); UNLOCK TABLES; 

實驗1

開啟一級緩存,范圍為會話級別,調用三次getStudentById,代碼如下所示

 
public void getStudentById() throws Exception { SqlSession sqlSession = factory.openSession(true); // 自動提交事務 StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); System.out.println(studentMapper.getStudentById(1)); System.out.println(studentMapper.getStudentById(1)); System.out.println(studentMapper.getStudentById(1)); } 

 

 

只有第一次真正查詢了數據庫,后續的查詢使用了一級緩存。

 

實驗2

增加了對數據庫的修改操作,驗證在一次數據庫會話中,如果對數據庫發生了修改操作,一級緩存是否會失效。

 
@Test public void addStudent() throws Exception { SqlSession sqlSession = factory.openSession(true); // 自動提交事務 StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); System.out.println(studentMapper.getStudentById(1)); System.out.println("增加了" + studentMapper.addStudent(buildStudent()) + "個學生"); System.out.println(studentMapper.getStudentById(1)); sqlSession.close(); } 

 

 

在修改操作后執行的相同查詢,查詢了數據庫,一級緩存失效。

 

實驗3

開啟兩個SqlSession,在sqlSession1中查詢數據,使一級緩存生效,在sqlSession2中更新數據庫,驗證一級緩存只在數據庫會話內部共享。

 
@Test public void testLocalCacheScope() throws Exception { SqlSession sqlSession1 = factory.openSession(true); SqlSession sqlSession2 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1)); System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1)); System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑",1) + "個學生的數據"); System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1)); System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1)); } 

 

 

sqlSession2更新了id為1的學生的姓名,從凱倫改為了小岑,但session1之后的查詢中,id為1的學生的名字還是凱倫, 出現了臟數據,也證明了之前的設想,一級緩存只在數據庫會話內部共享。

 

一級緩存工作流程

 

 
核心類

SqlSession: 對外提供了用戶和數據庫之間交互需要的所有方法,隱藏了底層的細節。默認實現類是DefaultSqlSession。
Executor(執行器): SqlSession向用戶提供操作數據庫的方法,但和數據庫操作有關的職責都會委托給Executor。
BaseExecutor: BaseExecutor是一個實現了Executor接口的抽象類,定義若干抽象方法,在執行的時候,把具體的操作委托給子類進行執行。
Local Cache的查詢和寫入是在Executor內部完成的。在閱讀BaseExecutor的代碼后發現Local Cache是BaseExecutor內部的一個成員變量,如下代碼所示。
Cache: MyBatis中的Cache接口,提供了和緩存相關的最基本的操作

過程描述

1.首先需要初始化SqlSession,通過DefaultSqlSessionFactory開啟SqlSession
2.在初始化SqlSesion時,會使用Configuration類創建一個全新的Executor,作為DefaultSqlSession構造函數的參數
3.SqlSession創建完畢后,根據Statment的不同類型,會進入SqlSession的不同方法中,如果是Select語句的話,最后會執行到SqlSession的selectList
4.SqlSession把具體的查詢職責委托給了Executor。如果只開啟了一級緩存的話,首先會進入BaseExecutor的query方法。
5.在上述代碼中,會先根據傳入的參數生成CacheKey
6.將MappedStatement的Id、SQL的offset、SQL的limit、SQL本身以及SQL中的參數傳入了CacheKey這個類,最終構成CacheKey。
7.CacheKey內容有:首先是成員變量和構造函數,有一個初始的hachcode和乘數,同時維護了一個內部的updatelist。在CacheKey的update方法中,會進行一個hashcode和checksum的計算,同時把傳入的參數添加進updatelist中,同時重寫了CacheKey的equals方法
8.除去hashcode、checksum和count的比較外,只要updatelist中的元素一一對應相等,那么就可以認為是CacheKey相等。只要兩條SQL的下列五個值相同,即可以認為是相同的SQL。

 
Statement Id + Offset + Limmit + Sql + Params

9.BaseExecutor的query方法繼續往下走,如果查不到的話,就從數據庫查,在queryFromDatabase中,會對localcache進行寫入。在query方法執行的最后,會判斷一級緩存級別是否是STATEMENT級別,如果是的話,就清空緩存,這也就是STATEMENT級別的一級緩存無法共享localCache的原因。
10.SqlSession的insert方法和delete方法,都會統一走update的流程.每次執行update前都會清空localCache。

一級緩存的總結

MyBatis一級緩存的生命周期和SqlSession一致。
MyBatis一級緩存內部設計簡單,只是一個沒有容量限定的HashMap,在緩存的功能性上有所欠缺。
MyBatis的一級緩存最大范圍是SqlSession內部,有多個SqlSession或者分布式的環境下,數據庫寫操作會引起臟數據,建議設定緩存級別為Statement

二級緩存

1.MyBatis的二級緩存相對於一級緩存來說,實現了SqlSession之間緩存數據的共享,同時粒度更加的細,能夠到namespace級別,通過Cache接口實現類不同的組合,對Cache的可控性也更強。
2.MyBatis在多表查詢時,極大可能會出現臟數據,有設計上的缺陷,安全使用二級緩存的條件比較苛刻。
3.在分布式環境下,由於默認的MyBatis Cache實現都是基於本地的,分布式環境下必然會出現讀取到臟數據,需要使用集中式緩存將MyBatis的Cache接口實現,有一定的開發成本,直接使用Redis、Memcached等分布式緩存可能成本更低,安全性也更高。

什么時候才能命中二級緩存,什么時候才能存到二級緩存里

如果二級緩存想要命中實現,則必須要將上一次sqlSession commit之后才能生效,不然將不會命中,原因:兩個不同的session必須提交前面一個session才能緩存生效的原因是因為mybatis的緩存會被一個transactioncache類包裝住,所有的cache.putObject全部都會被暫時存到一個map里,等事務提交以后,這個map里的緩存對象才會被真正的cache類執行putObject操作。這么設計的原因是為了防止事務執行過程中出異常導致回滾,如果get到object后直接put進緩存,萬一發生回滾,就很容易導致mybatis緩存被臟讀





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM