@Transactional注解和Mybatis缓存问题(Mybatis 查询结果 List 对List修改后再次查询,结果与数据库不一致)
Mybatis 查詢結果 List 對List修改后再次查詢,結果與數據庫不一致
使用 Mybatis 查詢,結果為對象的 List ,修改List內的參數后,使用相同參數再次查詢,發現查詢結果與數據庫不一致,而是第一次查詢結果操作后的對象列表。
根據問題現象可以發現,相同查詢條件下,第二次查詢使用了第一次的查詢結果,而且兩次查詢是在同一方法的for循環內執行,第一次的對象肯定會被GC回收,所以應該有某種緩存機制存在,那么只可能是 Mybatis 實現了某種緩存機制。
以下為代碼舉例:
@Transactional public List<User> userList (){List<User> firstList = userDao.selectAll(); firstList.forEach(item -> item.setName("老王頭"));//修改完成之后,去 dao 中查詢用戶列表 然后返回return userDao.selectAll(); }查詢出來的數據先被緩存,然后修改列表時,修改的其實是緩存數據的引用
當再次查詢時,取緩存中的數據,由于緩存中的數據已經被修改
取出來的數據理所當然,已經是修改過了
復現代碼
@Overridepublic List<ProjectEntity> projectList() {//查詢列表final List<ProjectEntity> oneList = projectDao.findAll();oneList.forEach(item -> log.info("第一次查詢的ProjectName:{} \n", item.getProjectName()));//修改數據oneList.forEach(item -> item.setProjectName("貓貓身上有毛毛"));oneList.forEach(item -> log.info("修改后的ProjectName:{} \n", item.getProjectName()));//重新查詢List<ProjectEntity> secondList = projectDao.findAll();secondList.forEach(item -> log.info("重新查詢的ProjectName:{} \n", item.getProjectName()));return null;}輸出結果:
第一次查詢的ProjectName:小小橙
第一次查詢的ProjectName:大大橘
第一次查詢的ProjectName:花花牛
修改后的ProjectName:貓貓身上有毛毛
修改后的ProjectName:貓貓身上有毛毛
修改后的ProjectName:貓貓身上有毛毛
重新查詢的ProjectName:小小橙
重新查詢的ProjectName:大大橘
重新查詢的ProjectName:花花牛
為什么沒有復現,在看下邊代碼
@Transactional @Override public List<ProjectEntity> projectList() {//查詢列表final List<ProjectEntity> oneList = projectDao.findAll();oneList.forEach(item -> log.info("第一次查詢的ProjectName:{} \n", item.getProjectName()));//修改數據oneList.forEach(item -> item.setProjectName("貓貓身上有毛毛"));oneList.forEach(item -> log.info("修改后的ProjectName:{} \n", item.getProjectName()));//重新查詢List<ProjectEntity> secondList = projectDao.findAll();secondList.forEach(item -> log.info("重新查詢的ProjectName:{} \n", item.getProjectName()));return null; }輸出結果:
第一次查詢的ProjectName:小小橙
第一次查詢的ProjectName:大大橘
第一次查詢的ProjectName:花花牛
修改后的ProjectName:貓貓身上有毛毛
修改后的ProjectName:貓貓身上有毛毛
修改后的ProjectName:貓貓身上有毛毛
重新查詢的ProjectName:貓貓身上有毛毛
重新查詢的ProjectName:貓貓身上有毛毛
重新查詢的ProjectName:貓貓身上有毛毛
以下把 oneList 和 secondList 的 內存地址進行打印,進行對比
如果真的是緩存的話,那么第二次查詢應該不會再查詢數據庫了吧,我們把sql查詢日志也打印一遍
開啟事務和不開啟事務分別進行對比輸出的日志
執行結果
oneList 第一次查詢內存地址:114565630
oneList 將數據進行修改后的內存地址:114565630
secondList 剛創建后的內存地址:920320548
secondList 寫入數據后的內存地址:114565630
從打印出的日志可以看到:
先去數據庫查詢出 3條記錄放入 list
這時候打印出的 oneList : 114565630
然后將集合中數據進行修改,這時 oneList :114565630,可以看到雖然內容發生了改變,但是內存地址并沒有法師變化
然后聲明一個新的集合 secondlist: 920320548 很明顯,新創建的對象,跟 oneList 的內存地址不是同一個
然后見證奇跡的時候到了,重新執行 projectDao.findAll(); secondlist:114565630,“=” 賦值,內存地址發生變化, 并且沒有打印Sql日志
果然它出現了,重新調用了查詢,但是并沒有執行 slq 去數據庫查詢,而是直接去 114565630 這個內存地址取出來我們已經修改過的集合接下來把 @Transactional 去掉,在運行一下
//@Transactional @Override public List<ProjectEntity> projectList() {//查詢列表final List<ProjectEntity> oneList = projectDao.findAll();log.info("oneList 第一次查詢內存地址:{} \n",System.identityHashCode(oneList));//修改數據oneList.forEach(item -> item.setProjectName("貓貓身上有毛毛"));log.info("oneList 將數據進行修改后的內存地址:{} \n",System.identityHashCode(oneList));//先聲明一個對象List<ProjectEntity> secondList = new ArrayList<>();log.info("secondList 剛創建后的內存地址:{} \n",System.identityHashCode(secondList));//查詢數據庫 打印 hashcodesecondList = projectDao.findAll();log.info("secondList 寫入數據后的內存地址:{} \n",System.identityHashCode(secondList));return null; }oneList 第一次查詢內存地址:810898134
oneList 將數據進行修改后的內存地址:810898134
secondList 剛創建后的內存地址:599203108
secondList 寫入數據后的內存地址:1280730191
問題總結
使用 Mybatis 時,要結合具體場景注意緩存使用問題。
Mybatis 緩存機制簡介
MyBatis 有一級緩存和二級緩存,并且預留了集成第三方緩存的接口。
一級緩存
定義:一級緩存也叫本地緩存,MyBatis 的一級緩存是在會話(SqlSession)層面進行緩存的。MyBatis 的一級緩存是默認開啟的,不需要任何的配置。
一級緩存的缺點:使用一級緩存的時候,由于緩存不能跨會話共享,不同的會話之間對于相同的數據可能有不一樣的緩存。在有多個會話、或者分布式環境、或者本地對查詢結果進行了增刪改(本問題的場景)的情況下,會出現臟數據的問題。
一級緩存級別調整:MyBatis 一級緩存(MyBaits 稱其為 Local Cache)無法關閉,但是有兩種級別可選,如下所示:
| session | 級別的緩存(默認) 在同一個 sqlSession 內,對同樣的查詢將不再查詢數據庫,直接從緩存中獲取 |
| statement | 級別的緩存 每次查詢結束都會清掉一級緩存;將一級緩存的級別設為 statement 級別可避免臟數據問題 |
二級緩存
二級緩存是用來解決一級緩存不能跨會話共享的問題的,范圍是namespace 級別的,可以被多個SqlSession 共享(只要是同一個接口里面的相同方法,都可以共享),生命周期和應用同步。
如果 MyBatis 使用了二級緩存,并且你 Mapper 和 select 語句也配置使用了二級緩存,那么在執行select查詢的時候,MyBatis會先從二級緩存中取輸入,其次才是一級緩存,即MyBatis查詢數據的順序是:二級緩存 —> 一級緩存 —> 數據庫。
總結
以上是生活随笔為你收集整理的@Transactional注解和Mybatis缓存问题(Mybatis 查询结果 List 对List修改后再次查询,结果与数据库不一致)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1688关键词搜索api(附可用)
- 下一篇: Oracle入门(十三)之SQL的DML