一文彻底读懂优秀开源产品MyBatis一级缓存设计!
孫玄
奈學(xué)教育CEO
讀完需要
3
分鐘速讀僅需 1 分鐘
孫玄, 現(xiàn)任奈學(xué)教育科技創(chuàng)始人&CEO ,畢業(yè)于浙大,前百度資深研發(fā)工程師、前 58 集團(tuán)技術(shù)委員會(huì)主席/高級(jí)系統(tǒng)架構(gòu)師到前轉(zhuǎn)轉(zhuǎn)公司技術(shù)委員會(huì)主席/首席架構(gòu)師/大中后臺(tái)技術(shù)負(fù)責(zé)人。江湖人稱“玄姐”,出版過《百萬年薪架構(gòu)師修煉之路》書籍。
1
? ?
前言
緩存是 MyBatis 中非常重要的特性。合理使用緩存能夠減少數(shù)據(jù)庫 IO,顯著提升系統(tǒng)性能。但是在分布式環(huán)境下,如果使用不當(dāng),則可能會(huì)帶來數(shù)據(jù)一致性問題。MyBatis 提供了一級(jí)緩存和二級(jí)緩存,其中一級(jí)緩存基于 SqlSession 實(shí)現(xiàn),而二級(jí)緩存基于 Mapper,本文將會(huì)詳細(xì)講解一級(jí)緩存。
2
? ?
CACHE 緩存
MyBatis 跟緩存相關(guān)的類都在 Cache 包里面,其中有一個(gè) Cache 接口,只有一個(gè)默認(rèn)的實(shí)現(xiàn)類 PerpetualCache,它是用 HashMap 實(shí)現(xiàn)的。除此之外,還有很多的裝飾器,通過這些裝飾器可以額外實(shí)現(xiàn)很多的功能:回收策略、日志記錄、定時(shí)刷新等等。
2.1
? ?
包結(jié)構(gòu)
如圖是緩存類所在源碼中所處的位置,從包名中我們可以知道 decorators 包中存放的是一些裝飾類。
2.2
? ?
查看裝飾后的結(jié)果
但是無論怎么裝飾,經(jīng)過多少層裝飾,最后使用的還是基本的實(shí)現(xiàn)類(默認(rèn) PerpetualCache)。我們 debug 看一下經(jīng)層層裝飾后的結(jié)果如圖:
CachingExecutor中Debug查看:緩存的層層裝飾
我們后面說的一級(jí)緩存緩存就是存放到這個(gè) PerpetualCache 里面。
2.3
? ?
對(duì)裝飾器的分類
3
? ?
一級(jí)緩存的特點(diǎn)
3.1
? ?
一級(jí)緩存默認(rèn)是開啟的,而且不能關(guān)閉
至于一級(jí)緩存為什么不能關(guān)閉,MyBatis 核心開發(fā)人員做出了解釋:
MyBatis 的一些關(guān)鍵特性(例如通過和建立級(jí)聯(lián)映射、避免循環(huán)引用(circular references)、加速重復(fù)嵌套查詢等)都是基于 MyBatis 一級(jí)緩存實(shí)現(xiàn)的,而且 MyBatis 結(jié)果集映射相關(guān)代碼重度依賴 CacheKey,所以目前 MyBatis 不支持關(guān)閉一級(jí)緩存。
雖然我們不能關(guān)閉一級(jí)緩存,但是我們可以更改他的作用范圍:
MyBatis 提供了一個(gè)配置參數(shù) localCacheScope,用于控制一級(jí)緩存的級(jí)別,該參數(shù)的取值為 SESSION、STATEMENT,當(dāng)指定 localCacheScope 參數(shù)值為 SESSION 時(shí),緩存對(duì)整個(gè) SqlSession 有效,只有執(zhí)行 DML 語句(更新語句)時(shí),緩存才會(huì)被清除。當(dāng) localCacheScope 值為 STATEMENT 時(shí),緩存僅對(duì)當(dāng)前執(zhí)行的語句有效,當(dāng)語句執(zhí)行完畢后,緩存就會(huì)被清空。
<settings><setting?name="localCacheScope"?value="STATEMENT"/> </settings>能更改一級(jí)緩存的作用范圍這一點(diǎn)很重要后面我們講解中會(huì)用到這一特性。
3.2
? ?
一級(jí)緩存默認(rèn)是 SqlSession 級(jí)別的
在操作數(shù)據(jù)庫時(shí)需要構(gòu)造 sqlSession 對(duì)象,在對(duì)象中有一個(gè)(內(nèi)存區(qū)域)數(shù)據(jù)結(jié)構(gòu)(HashMap)用于存儲(chǔ)緩存數(shù)據(jù)。不同的 sqlSession 之間的緩存數(shù)據(jù)區(qū)域(HashMap)是互相不影響的。用一張圖來表示一下一級(jí)緩存,其中每一個(gè) SqlSession 的內(nèi)部都會(huì)有一個(gè)一級(jí)緩存對(duì)象。
4
? ?
實(shí)驗(yàn)驗(yàn)證一級(jí)緩存的作用范圍
4.1
? ?
一級(jí)緩存同一個(gè)會(huì)話共享數(shù)據(jù)
模擬思路:打開一個(gè)會(huì)話,進(jìn)行兩次查詢通過日志查看第二次是否走數(shù)據(jù)庫。
代碼如下:
??@Testpublic?void?testSession()?throws?IOException?{try?(SqlSession?sqlSession?=?sqlSessionFactory.openSession()) {OrderMapper?orderMapper?=?sqlSession.getMapper(OrderMapper.class);List<Order>?orders?=?orderMapper.queryById(620898339119480832L);System.out.println("第一次查詢:"?+?JSON.toJSONString(orders));OrderMapper?orderMapper2?=?sqlSession.getMapper(OrderMapper.class);List<Order>?orders2?=?orderMapper2.queryById(620898339119480832L);System.out.println("第二次查詢:"?+?JSON.toJSONString(orders2));}}日志信息如下:
分析:第一次查詢打印了 sql 日志信息,說明是通過數(shù)據(jù)庫獲取到數(shù)據(jù),第二次也查詢到數(shù)據(jù)但是沒有打印日志信息,說明走了緩存。
結(jié)論:一級(jí)緩存同一個(gè)會(huì)話共享數(shù)據(jù)。
4.2
? ?
同一個(gè)會(huì)話如果有更新操作則緩存清除
代碼如下:
@Test public void testUpdate() throws IOException {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// 同一個(gè)會(huì)話 第一次查詢System.out.println("第一次會(huì)會(huì)話的 第一次查詢");OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);List<Order> orders = orderMapper.queryById(620898339119480832L);Order order = orders.get(0);order.setAmount(12L);// 進(jìn)行更新orderMapper.updateByPrimaryKey(order);// 同一個(gè)會(huì)話 第二次查詢System.out.println("第一次會(huì)會(huì)話的 第二次查詢");List<Order> orders2 = orderMapper.queryById(620898339119480832L);System.out.println(JSON.toJSONString(orders2));} }日志信息如下:
分析:第一次查詢打印了 sql 日志,然后進(jìn)行數(shù)據(jù)更新,最后進(jìn)行第二次查詢發(fā)現(xiàn)仍舊查詢數(shù)據(jù)庫,說明緩存已經(jīng)失效。
結(jié)論:同一個(gè)會(huì)話如果有更新操作則緩存清除。
4.3
? ?
導(dǎo)致臟數(shù)據(jù)
模擬思路:打開兩個(gè)會(huì)話,第一個(gè)會(huì)話查詢數(shù)據(jù)庫獲取數(shù)據(jù)后 ,接著第二個(gè)會(huì)話修改數(shù)據(jù),最后第一個(gè)會(huì)話再查詢數(shù)據(jù),那最后這次查詢?nèi)绻偷谝淮尾樵兿嗤?#xff0c;那說明一級(jí)緩存會(huì)導(dǎo)致臟數(shù)據(jù)問題。
代碼如下:
@Testpublic?void?testDirtyData()?throws?IOException?{SqlSession?sqlSession1?=?sqlSessionFactory.openSession(true);SqlSession?sqlSession2?=?sqlSessionFactory.openSession(true);try?{// 同一個(gè)會(huì)話 第一次查詢OrderMapper?orderMapper1?=?sqlSession1.getMapper(OrderMapper.class);OrderMapper?orderMapper2?=?sqlSession2.getMapper(OrderMapper.class);List<Order>?orders?=?orderMapper1.queryById(620898339119480832L);System.out.println("第一次會(huì)會(huì)話第一次查詢的結(jié)果"?+?orders);Order?order1?=?getOrder(orders);System.out.println("=========更新數(shù)據(jù)======");orderMapper2.updateByPrimaryKey(order1);sqlSession2.commit();List<Order>?orders1?=??orderMapper1.queryById(620898339119480832L);System.out.println("第二次查詢:"+JSON.toJSONString(orders1));}catch?(Exception?e){sqlSession1.close();sqlSession2.close();}}日志信息如下:
分析:第一個(gè)會(huì)話第一次查詢 amount 值是1212,第二會(huì)話將 amount 更改為666,第一個(gè)會(huì)話再次查詢數(shù)據(jù)獲取的 amount 值為1212,這說明第一個(gè)會(huì)話的第二次查詢命中緩存導(dǎo)致了臟數(shù)據(jù)問題。
結(jié)論:一級(jí)緩存在多會(huì)話中會(huì)導(dǎo)致臟數(shù)據(jù)。
解決方式:在配置一級(jí)緩存作用范圍的時(shí)候?qū)⑵湓O(shè)置為 STATEMENT,那么緩存僅對(duì)當(dāng)前執(zhí)行的語句有效,當(dāng)語句執(zhí)行完畢后,緩存就會(huì)被清空。
設(shè)置方式:
<settings><setting?name="localCacheScope"?value="STATEMENT"/> </settings>- EOF -
想要加入中生代架構(gòu)群的小伙伴,請(qǐng)?zhí)砑尤汉匣锶?strong>大白的微信
申請(qǐng)備注(姓名+公司+技術(shù)方向)才能通過哦!
工程師成長系列推薦
多隆:從工程師到阿里巴巴合伙人
2020-11-10
為什么說IT科技公司應(yīng)該留住35歲員工?
2020-11-05
工程師的基本功是什么?如何練習(xí)?聽美團(tuán)技術(shù)大咖怎么說
2020-10-19
美團(tuán)技術(shù)專家云鵬:寫給工程師的十條精進(jìn)原則!
2020-10-15
找CTO杜仲:再談中年危機(jī)和應(yīng)對(duì)策略
2020-10-10
Erik Dietrich:二十年的編程,教會(huì)我的五件事!
2020-09-22
Mobvista首席架構(gòu)師蔡超:工作感悟之失敗與成功,我的8點(diǎn)總結(jié)
2020-09-20
奈學(xué)教育CEO孫玄:成為一個(gè)有情懷的工程師,我的12點(diǎn)思考
2020-09-19
Netstars CTO陳斌:架構(gòu)師的成長之路
2020-09-17
左耳朵耗子:程序員如何把控自己的職業(yè)?
2020-08-22
RocketMQ 大神丁威親述參與開源社區(qū)的方式
2020-11-17
? ?END ? ?? #架構(gòu)師必備#點(diǎn)分享點(diǎn)點(diǎn)贊點(diǎn)在看總結(jié)
以上是生活随笔為你收集整理的一文彻底读懂优秀开源产品MyBatis一级缓存设计!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Eclispse中Run on Serv
- 下一篇: JeecgBoot 移动OA 新版本上线