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