Mybatis一级缓存,二级缓存的实现就是这么简单
介紹
又到了一年面試季,所以打算寫一點(diǎn)面試常問的東西,爭(zhēng)取說的通俗易懂。面試高級(jí)崗,如果你說熟悉Mybatis,下面這些問題基本上都會(huì)問
Mybatis插件的實(shí)現(xiàn)原理?
如何寫一個(gè)分頁插件?
Mybaits只寫了接口為什么能運(yùn)行?
Mybatis的一級(jí)緩存和二級(jí)緩存的工作原理,會(huì)遇到什么問題?
一級(jí)緩存和二級(jí)緩存的生命周期分別是?
Mybatis和Spring整合后,一級(jí)緩存為什么會(huì)失效?
同時(shí)配置一級(jí)緩存和二級(jí)緩存后,先查詢哪個(gè)緩存?
今天就來聊一下Mybatis一級(jí)緩存和二級(jí)緩存
我們知道Mybatis有一級(jí)緩存和二級(jí)緩存,底層都是用HashMap實(shí)現(xiàn)的
key為CacheKey對(duì)象(后續(xù)說原因),value為從數(shù)據(jù)庫中查出來的值。
Mybatis的二級(jí)緩存模塊是裝飾器的典型實(shí)現(xiàn),不清楚裝飾者模式的看如下文章
裝飾者模式在JDK和Mybatis中是怎么應(yīng)用的?
畫一個(gè)簡(jiǎn)易的裝飾者模式類圖
Component(組件):組件接口或抽象類定義了全部組件實(shí)現(xiàn)類以及所有裝飾器實(shí)現(xiàn)的行為。
ConcreteComponent(具體組件實(shí)現(xiàn)類):具體組件實(shí)現(xiàn)類實(shí)現(xiàn)了Component接口或抽象類。通常情況下,具體組件實(shí)現(xiàn)類就是被裝飾器裝飾的原始對(duì)象,該類提供了Component接口中定義的最基本的功能,其他高級(jí)功能或后序添加的新功能,都是通過裝飾器的方式添加到該類的對(duì)象之上的。
ConcreteDecorator(具體的裝飾器):該實(shí)現(xiàn)類要向被裝飾對(duì)象添加某些功能
mybatis中caceh模塊的類圖
其中只有PerpetualCache是具組件實(shí)現(xiàn)類,提供了Cache接口的基本實(shí)現(xiàn)。而FifoCache
,LoggingCache等都是具體裝飾者,在具體實(shí)現(xiàn)上加額外功能
測(cè)試一級(jí)緩存
測(cè)試的具體過程引用自參考博客
github地址:https://github.com/kailuncen/mybatis-cache-demo
接下來通過實(shí)驗(yàn),了解MyBatis一級(jí)緩存的效果,每個(gè)單元測(cè)試后都請(qǐng)恢復(fù)被修改的數(shù)據(jù)。
首先是創(chuàng)建示例表student,創(chuàng)建對(duì)應(yīng)的POJO類和增改的方法,具體可以在entity包和mapper包中查看。
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=4?DEFAULT?CHARSET=utf8?COLLATE=utf8_bin;在以下實(shí)驗(yàn)中,id為1的學(xué)生名稱是凱倫
實(shí)驗(yàn)1
開啟一級(jí)緩存,范圍為會(huì)話級(jí)別,調(diào)用三次getStudentById,代碼如下所示:
public?void?getStudentById()?throws?Exception?{SqlSession?sqlSession?=?factory.openSession(true);?//?自動(dòng)提交事務(wù)StudentMapper?studentMapper?=?sqlSession.getMapper(StudentMapper.class);System.out.println(studentMapper.getStudentById(1));System.out.println(studentMapper.getStudentById(1));System.out.println(studentMapper.getStudentById(1));}執(zhí)行結(jié)果:
我們可以看到,只有第一次真正查詢了數(shù)據(jù)庫,后續(xù)的查詢使用了一級(jí)緩存。
實(shí)驗(yàn)2
增加了對(duì)數(shù)據(jù)庫的修改操作,驗(yàn)證在一次數(shù)據(jù)庫會(huì)話中,如果對(duì)數(shù)據(jù)庫發(fā)生了修改操作,一級(jí)緩存是否會(huì)失效。
@Test public?void?addStudent()?throws?Exception?{SqlSession?sqlSession?=?factory.openSession(true);?//?自動(dòng)提交事務(wù)StudentMapper?studentMapper?=?sqlSession.getMapper(StudentMapper.class);System.out.println(studentMapper.getStudentById(1));System.out.println("增加了"?+?studentMapper.addStudent(buildStudent())?+?"個(gè)學(xué)生");System.out.println(studentMapper.getStudentById(1));sqlSession.close(); }執(zhí)行結(jié)果:
我們可以看到,在修改操作后執(zhí)行的相同查詢,查詢了數(shù)據(jù)庫,一級(jí)緩存失效。
實(shí)驗(yàn)3
開啟兩個(gè)SqlSession,在sqlSession1中查詢數(shù)據(jù),使一級(jí)緩存生效,在sqlSession2中更新數(shù)據(jù)庫,驗(yàn)證一級(jí)緩存只在數(shù)據(jù)庫會(huì)話內(nèi)部共享。(這個(gè)實(shí)驗(yàn)在原文上略有修改)
@Test public?void?testLocalCacheScope()?throws?Exception?{SqlSession?sqlSession1?=?factory.openSession(true);?//?自動(dòng)提交事務(wù)SqlSession?sqlSession2?=?factory.openSession(true);?//?自動(dòng)提交事務(wù)StudentMapper?studentMapper1?=?sqlSession1.getMapper(StudentMapper.class);StudentMapper?studentMapper2?=?sqlSession2.getMapper(StudentMapper.class);System.out.println("studentMapper1讀取數(shù)據(jù):?"?+?studentMapper1.getStudentById(1));System.out.println("studentMapper2讀取數(shù)據(jù):?"?+?studentMapper2.getStudentById(1));System.out.println("studentMapper2更新了"?+?studentMapper2.updateStudentName("小岑",1)?+?"個(gè)學(xué)生的數(shù)據(jù)");System.out.println("studentMapper1讀取數(shù)據(jù):?"?+?studentMapper1.getStudentById(1));System.out.println("studentMapper2讀取數(shù)據(jù):?"?+?studentMapper2.getStudentById(1));}輸出如下
DEBUG?[main]?-?Cache?Hit?Ratio?[mapper.StudentMapper]:?0.0 DEBUG?[main]?-?==>??Preparing:?SELECT?id,name,age?FROM?student?WHERE?id?=??? DEBUG?[main]?-?==>?Parameters:?1(Integer) TRACE?[main]?-?<==????Columns:?id,?name,?age TRACE?[main]?-?<==????????Row:?1,?凱倫,?16 DEBUG?[main]?-?<==??????Total:?1 studentMapper1讀取數(shù)據(jù):?StudentEntity{id=1,?name='凱倫',?age=16,?className='null'} DEBUG?[main]?-?Cache?Hit?Ratio?[mapper.StudentMapper]:?0.0 DEBUG?[main]?-?==>??Preparing:?SELECT?id,name,age?FROM?student?WHERE?id?=??? DEBUG?[main]?-?==>?Parameters:?1(Integer) TRACE?[main]?-?<==????Columns:?id,?name,?age TRACE?[main]?-?<==????????Row:?1,?凱倫,?16 DEBUG?[main]?-?<==??????Total:?1 studentMapper2讀取數(shù)據(jù):?StudentEntity{id=1,?name='凱倫',?age=16,?className='null'} DEBUG?[main]?-?==>??Preparing:?UPDATE?student?SET?name?=???WHERE?id?=??? DEBUG?[main]?-?==>?Parameters:?小岑(String),?1(Integer) DEBUG?[main]?-?<==????Updates:?1 studentMapper2更新了1個(gè)學(xué)生的數(shù)據(jù) DEBUG?[main]?-?Cache?Hit?Ratio?[mapper.StudentMapper]:?0.0 studentMapper1讀取數(shù)據(jù):?StudentEntity{id=1,?name='凱倫',?age=16,?className='null'} DEBUG?[main]?-?Cache?Hit?Ratio?[mapper.StudentMapper]:?0.0 DEBUG?[main]?-?==>??Preparing:?SELECT?id,name,age?FROM?student?WHERE?id?=??? DEBUG?[main]?-?==>?Parameters:?1(Integer) TRACE?[main]?-?<==????Columns:?id,?name,?age TRACE?[main]?-?<==????????Row:?1,?小岑,?16 DEBUG?[main]?-?<==??????Total:?1 studentMapper2讀取數(shù)據(jù):?StudentEntity{id=1,?name='小岑',?age=16,?className='null'}sqlSession1和sqlSession2讀的時(shí)相同的數(shù)據(jù),但是都查詢了數(shù)據(jù)庫,說明了一級(jí)緩存只在數(shù)據(jù)庫會(huì)話層面共享
sqlSession2更新了id為1的學(xué)生的姓名,從凱倫改為了小岑,但sqlSession1之后的查詢中,id為1的學(xué)生的名字還是凱倫,出現(xiàn)了臟數(shù)據(jù),也證明了之前的設(shè)想,一級(jí)緩存只在數(shù)據(jù)庫會(huì)話層面共享
一級(jí)緩存
一級(jí)緩存的生命周期與SqlSession相同,如果你對(duì)SqlSession不熟悉,你可以把它類比為JDBC編程中的Connection,即數(shù)據(jù)庫的一次會(huì)話。
要想了解緩存,就必須得了解一下Executor,這個(gè)Executor是干嘛的呢?你可以理解為要執(zhí)行的SQL都會(huì)經(jīng)過這個(gè)類的方法,在這個(gè)類的方法中調(diào)用StatementHandler最終執(zhí)行SQL
Executor的實(shí)現(xiàn)也是一個(gè)典型的裝飾者模式
我相信你已經(jīng)看出來,SimpleExecutor,BatchExecutor是具體組件實(shí)現(xiàn)類,而CachingExecutor是具體的裝飾器。可以看到具體組件實(shí)現(xiàn)類有一個(gè)父類BaseExecutor,而這個(gè)父類是一個(gè)模板模式的典型應(yīng)用,操作一級(jí)緩存的操作都在這個(gè)類中,而具體的操作數(shù)據(jù)庫的功能則讓子類去實(shí)現(xiàn)。
至此終于搞明白了,一級(jí)緩存的所有操作都在BaseExecutor這個(gè)類中啊,看看具體操作
query方法
??@Overridepublic?<E>?List<E>?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler)?throws?SQLException?{BoundSql?boundSql?=?ms.getBoundSql(parameter);CacheKey?key?=?createCacheKey(ms,?parameter,?rowBounds,?boundSql);return?query(ms,?parameter,?rowBounds,?resultHandler,?key,?boundSql);}當(dāng)執(zhí)行select操作,會(huì)先生成一個(gè)CacheKey,如果根據(jù)CacheKey能從HashMap中拿到值則放回,如果拿不到值則先查詢數(shù)據(jù)庫,從數(shù)據(jù)庫中查出來后再放到HashMap中。追一下
query方法就知道了,代碼就不貼了,比較簡(jiǎn)單
update方法
??@Overridepublic?int?update(MappedStatement?ms,?Object?parameter)?throws?SQLException?{ErrorContext.instance().resource(ms.getResource()).activity("executing?an?update").object(ms.getId());if?(closed)?{throw?new?ExecutorException("Executor?was?closed.");}clearLocalCache();return?doUpdate(ms,?parameter);}當(dāng)執(zhí)行update操作時(shí),可以看到會(huì)調(diào)用clearLocalCache()方法,而這個(gè)方法則會(huì)清空一級(jí)緩存,即清空HashMap
總結(jié)
MyBatis一級(jí)緩存的生命周期和SqlSession一致。
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ìn)行如下配置
原因也很簡(jiǎn)單,看BaseExecutor的query()方法,當(dāng)配置成STATEMENT時(shí),每次查詢完都會(huì)清空緩存
???if?(configuration.getLocalCacheScope()?==?LocalCacheScope.STATEMENT)?{//?issue?#482clearLocalCache();}mybatis和spring整合的一些注意事項(xiàng)
在未開啟事物的情況之下,每次查詢,spring都會(huì)關(guān)閉舊的sqlSession而創(chuàng)建新的sqlSession,因此此時(shí)的一級(jí)緩存是沒有起作用的
在開啟事物的情況之下,spring使用threadLocal獲取當(dāng)前資源綁定同一個(gè)sqlSession,因此此時(shí)一級(jí)緩存是有效的
CacheKey
前面說到緩存的key是CacheKey對(duì)象,因?yàn)镸ybatis中涉及動(dòng)態(tài)SQL等多方面的因素,緩存的key不能僅僅通過String來表示,而是通過一個(gè)updateList,只有updateList的元素完全相同,則認(rèn)為這2個(gè)CacheKey相同
public?class?CacheKey?implements?Cloneable,?Serializable?{//?參與hash計(jì)算的乘數(shù)private?final?int?multiplier;//?CacheKey的hash值,在update函數(shù)中實(shí)時(shí)運(yùn)算出來的,這些值都是為了方便更快的比較,具體可以看equals函數(shù)private?int?hashcode;//?校驗(yàn)和,hash值的和private?long?checksum;//?updateList中的元素個(gè)數(shù)private?int?count;//?將判等的對(duì)象放到這個(gè)list中private?List<Object>?updateList; }CacheKey的其他屬性都是為了加快比較的速度,具體可以看這個(gè)類的equals函數(shù)
CacheKey的updateList放置了如下幾個(gè)對(duì)象
mappedStatment的id
指定查詢結(jié)構(gòu)集的范圍
查詢所使用SQL語句
用戶傳遞給SQL語句的實(shí)際參數(shù)值
怎么知道CacheKey是這些對(duì)象呢?你可以參考BaseExecutor的createCacheKey方法
測(cè)試二級(jí)緩存
測(cè)試的具體過程引用自參考博客
二級(jí)緩存是基于namespace實(shí)現(xiàn)的,即一個(gè)mapper映射文件用一個(gè)緩存,當(dāng)然你可以配成多個(gè)mapper映射文件用一個(gè)緩存
在本實(shí)驗(yàn)中,id為1的學(xué)生名稱初始化為點(diǎn)點(diǎn)。
實(shí)驗(yàn)1
測(cè)試二級(jí)緩存效果,不提交事務(wù),sqlSession1查詢完數(shù)據(jù)后,sqlSession2相同的查詢是否會(huì)從緩存中獲取數(shù)據(jù)。
@Test public?void?testCacheWithoutCommitOrClose()?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讀取數(shù)據(jù):?"?+?studentMapper.getStudentById(1));System.out.println("studentMapper2讀取數(shù)據(jù):?"?+?studentMapper2.getStudentById(1)); }執(zhí)行結(jié)果:
我們可以看到,當(dāng)sqlsession沒有調(diào)用commit()方法時(shí),二級(jí)緩存并沒有起到作用。
實(shí)驗(yàn)2
測(cè)試二級(jí)緩存效果,當(dāng)提交事務(wù)時(shí),sqlSession1查詢完數(shù)據(jù)后,sqlSession2相同的查詢是否會(huì)從緩存中獲取數(shù)據(jù)。
@Test public?void?testCacheWithCommitOrClose()?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讀取數(shù)據(jù):?"?+?studentMapper.getStudentById(1));sqlSession1.commit();System.out.println("studentMapper2讀取數(shù)據(jù):?"?+?studentMapper2.getStudentById(1)); }執(zhí)行結(jié)果:
從圖上可知,sqlsession2的查詢,使用了緩存,緩存的命中率是0.5。
實(shí)驗(yàn)3
測(cè)試update操作是否會(huì)刷新該namespace下的二級(jí)緩存。
@Test public?void?testCacheWithUpdate()?throws?Exception?{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)); }執(zhí)行結(jié)果:
我們可以看到,在sqlSession3更新數(shù)據(jù)庫,并提交事務(wù)后,sqlsession2的StudentMapper namespace下的查詢走了數(shù)據(jù)庫,沒有走Cache。
實(shí)驗(yàn)4
驗(yàn)證MyBatis的二級(jí)緩存不適應(yīng)用于映射文件中存在多表查詢的情況。
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;INSERT?INTO?`student`?(`id`,?`name`,?`age`)?VALUES?(1,'點(diǎn)點(diǎn)',16),(2,'平平',16),(3,'美美',16),(4,'團(tuán)團(tuán)',16);CREATE?TABLE?`class`?(`id`?int(11)?unsigned?NOT?NULL?AUTO_INCREMENT,`name`?varchar(20)?COLLATE?utf8_bin?DEFAULT?NULL,PRIMARY?KEY?(`id`) )?ENGINE=InnoDB?AUTO_INCREMENT=3?DEFAULT?CHARSET=utf8?COLLATE=utf8_bin;INSERT?INTO?`class`?(`id`,?`name`)?VALUES?(1,'一班'),(2,'二班');CREATE?TABLE?`classroom`?(`id`?int(11)?unsigned?NOT?NULL?AUTO_INCREMENT,`class_id`?int(11)?DEFAULT?NULL,`student_id`?int(11)?DEFAULT?NULL,PRIMARY?KEY?(`id`) )?ENGINE=InnoDB?AUTO_INCREMENT=5?DEFAULT?CHARSET=utf8?COLLATE=utf8_bin;INSERT?INTO?`classroom`?(`id`,?`class_id`,?`student_id`)?VALUES?(1,1,1),(2,1,2),(3,2,3),(4,2,4);getStudentByIdWithClassInfo的定義如下
<select?id="getStudentByIdWithClassInfo"?parameterType="int"?resultType="entity.StudentEntity">SELECT??s.id,s.name,s.age,class.name?as?classNameFROM?classroom?cJOIN?student?s?ON?c.student_id?=?s.idJOIN?class?ON?c.class_id?=?class.idWHERE?s.id?=?#{id}; </select>通常我們會(huì)為每個(gè)單表創(chuàng)建單獨(dú)的映射文件,由于MyBatis的二級(jí)緩存是基于namespace的,多表查詢語句所在的namspace無法感應(yīng)到其他namespace中的語句對(duì)多表查詢中涉及的表進(jìn)行的修改,引發(fā)臟數(shù)據(jù)問題。
@Test public?void?testCacheWithDiffererntNamespace()?throws?Exception?{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);ClassMapper?classMapper?=?sqlSession3.getMapper(ClassMapper.class);System.out.println("studentMapper讀取數(shù)據(jù):?"?+?studentMapper.getStudentByIdWithClassInfo(1));sqlSession1.close();System.out.println("studentMapper2讀取數(shù)據(jù):?"?+?studentMapper2.getStudentByIdWithClassInfo(1));classMapper.updateClassName("特色一班",1);sqlSession3.commit();System.out.println("studentMapper2讀取數(shù)據(jù):?"?+?studentMapper2.getStudentByIdWithClassInfo(1)); }執(zhí)行結(jié)果:
在這個(gè)實(shí)驗(yàn)中,我們引入了兩張新的表,一張class,一張classroom。class中保存了班級(jí)的id和班級(jí)名,classroom中保存了班級(jí)id和學(xué)生id。我們?cè)赟tudentMapper中增加了一個(gè)查詢方法getStudentByIdWithClassInfo,用于查詢學(xué)生所在的班級(jí),涉及到多表查詢。在ClassMapper中添加了updateClassName,根據(jù)班級(jí)id更新班級(jí)名的操作。
當(dāng)sqlsession1的studentmapper查詢數(shù)據(jù)后,二級(jí)緩存生效。保存在StudentMapper的namespace下的cache中。當(dāng)sqlSession3的classMapper的updateClassName方法對(duì)class表進(jìn)行更新時(shí),updateClassName不屬于StudentMapper的namespace,所以StudentMapper下的cache沒有感應(yīng)到變化,沒有刷新緩存。當(dāng)StudentMapper中同樣的查詢?cè)俅伟l(fā)起時(shí),從緩存中讀取了臟數(shù)據(jù)。
實(shí)驗(yàn)5
為了解決實(shí)驗(yàn)4的問題呢,可以使用Cache ref,讓ClassMapper引用StudenMapper命名空間,這樣兩個(gè)映射文件對(duì)應(yīng)的SQL操作都使用的是同一塊緩存了。
mapper文件中的配置如下
<cache-ref?namespace="mapper.StudentMapper"/>執(zhí)行結(jié)果:
不過這樣做的后果是,緩存的粒度變粗了,多個(gè)Mapper namespace下的所有操作都會(huì)對(duì)緩存使用造成影響。
二級(jí)緩存的實(shí)現(xiàn)
前面說了一級(jí)緩存的實(shí)現(xiàn)在BaseExecutor中,那么二級(jí)緩存的實(shí)現(xiàn)在哪呢?提示一下,前面提到的Executor。沒錯(cuò),就是CachingExecutor。下面詳細(xì)介紹一下
二級(jí)緩存的相關(guān)配置有如下3個(gè)
1.mybatis-config.xml
<settings><setting?name="cacheEnabled"?value="true"/> </settings>這個(gè)是二級(jí)緩存的總開關(guān),只有當(dāng)該配置項(xiàng)設(shè)置為true時(shí),后面兩項(xiàng)的配置才會(huì)有效果
從Configuration類的newExecutor方法可以看到,當(dāng)cacheEnabled為true,就用緩存裝飾器裝飾一下具體組件實(shí)現(xiàn)類,從而讓二級(jí)緩存生效
//?開啟二級(jí)緩存,用裝飾器模式裝飾一下 if?(cacheEnabled)?{executor?=?new?CachingExecutor(executor); }2.mapper映射文件中
mapper映射文件中如果配置了<cache\>和<cache-ref\>中的任意一個(gè)標(biāo)簽,則表示開啟了二級(jí)緩存功能,沒有的話表示不開啟
二級(jí)緩存的部分配置如上,type就是填寫一個(gè)全類名,你看我上面畫的圖,二級(jí)緩存是用Cache表示的,一級(jí)緩存是用HashMap表示的。這就說明二級(jí)緩存的實(shí)現(xiàn)類你可以可以自己提供的,不一定得用默認(rèn)的HashMap(對(duì),二級(jí)緩存默認(rèn)是用HashMap實(shí)現(xiàn)的),Mybatis能和Redis,ehcache整合的原因就在這
這個(gè)eviction表示緩存清空策略,可填選項(xiàng)如下
| LRU | 最近最少使用的:移除最長時(shí)間不被使用的對(duì)象 | LruCache |
| FIFO | 先進(jìn)先出:按對(duì)象進(jìn)入緩存的順序來移除它們 | FifoCache |
| SOFT | 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對(duì)象 | SoftCache |
| WEAK | 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對(duì)象 | WeakCache |
可以看到在Mybatis中換緩存清空策略就是換裝飾器。還有就是如果面試官讓你寫一個(gè)FIFO算法或者LRU算法,這不就是現(xiàn)成的實(shí)現(xiàn)嗎?
3.<select\>節(jié)點(diǎn)中的useCache屬性
該屬性表示查詢產(chǎn)生的結(jié)果是否要保存的二級(jí)緩存中,useCache屬性的默認(rèn)值為true,這個(gè)配置可以將二級(jí)緩存細(xì)分到語句級(jí)別
CachingExecutor利用了2個(gè)組件TransactionalCacheManager和TransactionalCache來管理二級(jí)緩存,為什么要多這2個(gè)組件呢?因?yàn)槎?jí)緩存不像一級(jí)緩存那樣查詢完直接放入一級(jí)緩存,而是要等事務(wù)提交時(shí)才會(huì)將查詢出來的數(shù)據(jù)放到二級(jí)緩存中。
因?yàn)槿绻聞?wù)1查出來直接放到二級(jí)緩存,此時(shí)事務(wù)2從二級(jí)緩存中拿到了事務(wù)1緩存的數(shù)據(jù),但是事務(wù)1回滾了,此時(shí)事務(wù)2不就發(fā)生了臟讀了嗎?
二級(jí)緩存的具體實(shí)現(xiàn)也不難,追一下CachingExecutor,TransactionalCacheManager,TransactionalCache就明白了,可以參考《Mybatis技術(shù)內(nèi)幕一書》
總結(jié)
MyBatis的二級(jí)緩存相對(duì)于一級(jí)緩存來說,實(shí)現(xiàn)了SqlSession之間緩存數(shù)據(jù)的共享
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等分布式緩存可能成本更低,安全性也更高。
問題回答
一級(jí)緩存和二級(jí)緩存的生命周期分別是?
一級(jí)緩存的生命周期是會(huì)話級(jí)別,因?yàn)橐患?jí)緩存是存在Sqlsession的成員變量Executor的成員變量localCache中的。而二級(jí)緩存的生命周期是整個(gè)應(yīng)用級(jí)別,因?yàn)槎?jí)緩存是存在Configuration對(duì)象中,而這個(gè)對(duì)象在應(yīng)用啟動(dòng)后一直存在
同時(shí)配置一級(jí)緩存和二級(jí)緩存后,先查詢哪個(gè)緩存?
當(dāng)然是先查詢二級(jí)緩存再查詢一級(jí)緩存啊,因?yàn)橐患?jí)緩存的實(shí)現(xiàn)在BaseExecutor,而二級(jí)緩存的實(shí)現(xiàn)在CachingExecutor,CachingExecutor是BaseExecutor的裝飾器
參考博客
[1]https://tech.meituan.com/2018/01/19/mybatis-cache.html
最后,再附上我歷時(shí)三個(gè)月總結(jié)的?Java 面試 + Java 后端技術(shù)學(xué)習(xí)指南,筆者這幾年及春招的總結(jié),github 1.4k star,拿去不謝!
下載方式
1.?首先掃描下方二維碼
2.?后臺(tái)回復(fù)「Java面試」即可獲取
總結(jié)
以上是生活随笔為你收集整理的Mybatis一级缓存,二级缓存的实现就是这么简单的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一些恶心的代码片段,你看了就知道!
- 下一篇: 这么写注释,老板会不会开除我?