javascript
springboot mybatis 事务_SpringBoot 下 Mybatis 的缓存
“IT魔幻屋”致力于讓你遇見更好的自己!
說起 mybatis,作為 Java 程序員應該是無人不知,它是常用的數據庫訪問框架。與 Spring 和 Struts 組成了 Java Web 開發的三劍客— SSM。當然隨著 Spring Boot 的發展,現在越來越多的企業采用的是 SpringBoot + mybatis 的模式開發,我們公司也不例外。而 mybatis 對于我也僅僅停留在會用而已,沒想過怎么去了解它,更不知道它的緩存機制了,直到那個生死難忘的 BUG。故事的背景比較長,但并不是啰嗦,只是讓讀者知道這個 BUG 觸發的場景,加深記憶。在遇到類似問題時,可以迅速定位。
先說下故事的前提,為了防止用戶在動態中輸入特殊字符,用戶的動態都是編碼后發到后臺,而后臺在存入到 DB 表之前會解碼以方便在 DB 中查看以及上報到搜索引擎。在查詢用戶動態的時候先從 DB 表中讀取并在后臺做一次編碼再傳到前端,前端再解碼就可以正常展示了。流程如下圖:
有一天后端預發環境發布完畢后,用戶的動態頁面有的動態顯示正常,而有的卻是被編碼過的。看到現象后的第一個反應就是有問題的動態被編碼了兩次,但是編碼操作只會在 service 層的 findById 中有。理論不會在上層犯這種低級錯誤。話不多說便開始排查新增加的代碼,發現只要進入了新增加代碼中的某個 if 分支則被編碼了兩次。分支中除了再次調用 findById(必要性不討論),也無其他特殊代碼了。百思不得其解后請教了旁邊的老司機,老司機說可能是 mybatis 緩存。于是看了下我代碼,將編碼的操作從 findById 中移出來后再次發布到預發,正常了,心想老司機不愧是老司機。本次 BUG 觸發的有兩個條件需要注意:
- 整個操作過程都在一個函數中,而函數上面加了 @Transactional 的注解(對 mybatis 來說是在同一個 SESSION 中)
- 一般只會調用 findByIdy 一次,如果進入分支則會調用兩次 (第一次調用后做了編碼后被緩存,第二次從緩存讀后繼續被編碼)
便開始谷歌 mybatis 的緩存機制,搜到了一篇非常不錯的文章《 聊聊 mybatis 的緩存機制 》,推薦大家看一下。但是這篇文章講到了源碼,涉及的比較深。而且并沒講 SpringBoot 下 mybatis 下的緩存知識點,遂作此篇,以作補充。
緩存的配置
SpringBoot + mybatis 環境搭建很簡單而且網上一堆教程,這里不班門弄斧了,記得在項目中將 mytatis 的源碼下載下來即可。mybaits 一共有兩級緩存:一級緩存的配置 key 是 localCacheScope,而二級緩存的配置 key 是 cacheEnabled,從名字上可以得出以下信息:
- 一級緩存是本地或者說局部緩存,它不能被關閉,只能配置緩存范圍。SESSION 或者 STATEMENT。
- 二級緩存才是 mybatis 的正統,功能會更強大些。
先來看下在 SpringBoot中 如何配置 mybatis 緩存的相關信息。默認情況下 SpringBoot 下的 mybatis 一級緩存為 SESSION 級別,二級緩存也是打開的,可以在 mybatis 源碼中的 org.apache.ibatis.session.Configuration.class 文件中看到(idea中打開),如下圖:
也可以通過以下測試程序查看緩存開啟情況:
@RunWith(SpringRunner.class)@SpringBootTestpublic class LearnApplicationTests { private SqlSessionFactory factory; @Before public void setUp() throws Exception { InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml"); factory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void showDefaultCacheConfiguration() { System.out.println("一級緩存范圍: " + factory.getConfiguration().getLocalCacheScope()); System.out.println("二級緩存是否被啟用: " + factory.getConfiguration().isCacheEnabled()); }}如果要設置一級緩存的緩存級別和開關二級緩存,在 mybatis-config.xml (當然也可以在 application.xml/yml 中配置)加入如下配置即可:
但需要注意的是二級緩存 cacheEnabled 只是個總開關,如果要讓二級緩存真正生效還需要在 mapper xml 文件中加入 。一級緩存只在同一 SESSION 或者 STATEMENT 之間共享,二級緩存可以跨 SESSION,開啟后它們默認具有如下特性:
- 映射文件中所有的 select 語句將被緩存
- 映射文件中所有的 insert/update/delete 語句將刷新緩存
一二級緩存同時開啟的情況下,數據的查詢順序是 二級緩存 -> 一級緩存 -> 數據庫。一級緩存比較簡單,而二級緩存可以設置更多的屬性,只需要在 mapper 的 xml 文件中的 中配置即可,具體如下:
觸發緩存
Controller 中調用兩次 getOne,代碼如下:
@RequestMapping("/getUser")public UserEntity getUser(Long id) { //第一次調用 UserEntity user1=userMapper.getOne(id); //第二次調用 UserEntity user2=userMapper.getOne(id); return user1;}調用: http://localhost:8080/getUser?id=1,打印結果如下:
從圖中的 1/2/3/4 可以看出每次 mapper 層的一次接口調用如 getOne 就會創建一個 session,并且在執行完畢后關閉 session。所以兩次調用并不在一個 session 中,一級緩存并沒有發生作用。開啟事務,Controller 層代碼如下:
@RequestMapping("/getUser")@Transactional(rollbackFor = Throwable.class)public UserEntity getUser(Long id) { //第一次調用 UserEntity user1=userMapper.getOne(id); //第二次調用 UserEntity user2=userMapper.getOne(id); return user1;}由于在同一個事務中,雖然調用了 select 操作兩次但是只執行了一次 sql ,緩存發揮了作用。這就跟一開始我遇到的那個 BUG 場景一樣:同一 session 且 select 調用 > 1 次。如果在兩次調用中間插入 update 操作,緩存會立即失效。只要 session 中有 insert、update 和 delete 語句,該 session 中的緩存會立即被刷新。但是注意這只是在同一 session 之間。不同 session 之間如 session1 和 session2,session1 里的 insert/update/delete 并不會影響 session 2 下的緩存,這在高并發或者分布式的情況下會產生臟數據。所以建議將一級緩存級別調成 statement。
再次將(1)中的無事務和有事務的代碼分別執行一遍,打印結果始終如下:
配置成 SATEMENT 后,一級緩存相當于被關閉了。STATEMENT 級別暫時不好模擬,但是我猜測 STATEMENT 級別即在同一執行 sql 的接口中(如上面的 getOne 中)緩存,出了 getOne 緩存即失效。
Controller 中去掉 @Transactional 注解代碼如下:
@RequestMapping("/getUser")public UserEntity getUser(Long id) { UserEntity user1=userMapper.getOne(id); UserEntity user2=userMapper.getOne(id); return user1;}當然二級緩存開關保證打開,在 mapper xml 文件中加入 ,整個文件代碼如下:
<?xml version="1.0" encoding="UTF-8" ?> id, name, sex SELECT FROM users WHERE id = #{id};執行 http://localhost:8080/getUser?id=1,打印結果如下:
從圖中紅框可以看出第二次查詢命中緩存,0.5 是命中率。再次執行
http://localhost:8080/getUser?id=1
打印結果如下:
這次一次 sql 也沒執行了,緩存命中率上升到 0.75了,所以說二級緩存全局緩存。但它的緩存范圍也是有限的,一級緩存在同一個 session 中。二級緩存雖然可以跨 session 但也只能在同一 namespace 中,所謂 namespace 即 mapper xml 文件。具體實驗請看《聊聊 mybatis 的緩存機制》中的關于二級緩存的實驗 4 和 5。再看下二級緩存配置對二級緩存的影響,為了明顯的看出效果,只改如下配置:
/****/@RequestMapping("/getUser")public UserEntity getUser(Long id, Long id2) { //第一個對象 1 System.out.println("================緩存對象 1================="); UserEntity user1 = userMapper.getOne(id); //另一個對象 2 System.out.println("========緩存對象 2,剔除緩存中的對象 1======="); UserEntity user2=userMapper.getOne(id2); user2 = userMapper.getOne(id2); //再次讀取第一個對象 System.out.println("==========緩存被剔除,執行查詢 sql==========="); user1 = userMapper.getOne(id); //暫停 5s try { sleep(5000); }catch (Exception e){ e.printStackTrace(); } System.out.println("============5s 后再次查詢對象 2============="); user2 = userMapper.getOne(id2); return user1;}執行 http://localhost:8080/getUser?id=1&id2=2 最后打印的結果如下:
可以看出二級緩存只能緩存一個對象且 5s 后就失效了,配置生效。緩存配置中還有一個重要的配置 type,該配置可以配置第三方的 cache,特別在高并發和分布式情況下。當然,使用更專業的分布式緩存才是王道,例如 redis 等。
關注“IT魔幻屋”,學習更多前沿技術,來這里你將遇見更好的自己!
總結
以上是生活随笔為你收集整理的springboot mybatis 事务_SpringBoot 下 Mybatis 的缓存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xml发生错误_WEB之web.xml详
- 下一篇: python测试运行快捷键_Python