日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Mybatis一级缓存,二级缓存的实现就是这么简单

發(fā)布時(shí)間:2025/3/20 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Mybatis一级缓存,二级缓存的实现就是这么简单 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

介紹

又到了一年面試季,所以打算寫一點(diǎn)面試常問的東西,爭取說的通俗易懂。面試高級(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è)簡易的裝飾者模式類圖

    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)上加額外功能

    測試一級(jí)緩存

    測試的具體過程引用自參考博客

    github地址:https://github.com/kailuncen/mybatis-cache-demo

    接下來通過實(shí)驗(yàn),了解MyBatis一級(jí)緩存的效果,每個(gè)單元測試后都請(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方法就知道了,代碼就不貼了,比較簡單

    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ì)簡單,只是一個(gè)沒有容量限定的HashMap,在緩存的功能性上有所欠缺。

  • MyBatis的一級(jí)緩存最大范圍是SqlSession內(nèi)部,有多個(gè)SqlSession或者分布式的環(huán)境下,數(shù)據(jù)庫寫操作會(huì)引起臟數(shù)據(jù),建議設(shè)定緩存級(jí)別為Statement,即進(jìn)行如下配置

  • <setting?name="localCacheScope"?value="STATEMENT"/>

    原因也很簡單,看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方法

    測試二級(jí)緩存

    測試的具體過程引用自參考博客

    二級(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

    測試二級(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

    測試二級(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

    測試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í)緩存功能,沒有的話表示不開啟

    <cache?type=""?eviction="FIFO"?size="512"></cache>

    二級(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)如下

    選項(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一级缓存,二级缓存的实现就是这么简单的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。