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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

@Transactional注解和Mybatis缓存问题(Mybatis 查询结果 List 对List修改后再次查询,结果与数据库不一致)

發布時間:2023/12/3 数据库 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 @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查詢日志也打印一遍
開啟事務和不開啟事務分別進行對比輸出的日志

@Transactional@Overridepublic 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 第一次查詢內存地址: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修改后再次查询,结果与数据库不一致)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。