Mybatis缓存探索,查询集合后修改内容,再次执行sql查询结果发现是被修改过的
起因
事情是這樣的,昨天晚上同學跟我說遇到一個問題,大概是這么描述的
A:"我寫了個神奇的BUG 我從數據庫查詢一個列表 list1 "
A: “然后對這個列表進行了篩選,刪掉了一部分數據”
A:" 結果我 return 的時候又重新去數據庫查詢了一遍 "
A:"神奇的是返回的結果是我篩選后的結果,要不是同事要 復制我的代碼用,到現在都沒發現這個問題 "
我:”怎么可能,一定是你們弄錯了,給我看你代碼“
A:復現代碼ing… 執行… 結果顯示并沒有這種現象
我:“你看吧,一定是你弄錯了,mybatis要是有這種bug早就沒人用了”
A:“不是的,昨天我和張三,李四,都看到了,明天給你看我們之前的代碼”
第二天。。。
A:“代碼… 輸出結果… 你看吧,我就說有這個問題”
我:陷入沉思…
A:“好像跟緩存有關系”
他的代碼大概是這樣的
@Override @Transactional public List<User> userList (){//去數據庫查詢列表List<User> firstList = userDao.selectAll(); //把所有用戶名修改成 "老王頭"firstList.forEach(item -> item.setName("老王頭"));//修改完成之后,去 dao 中查詢用戶列表 然后返回return userDao.selectAll(); }然后我就開始思考,什么情況下會出現這種情況,想出兩個情況可能會有這個情況
首先第一種,緩存,理論上會有可能出現這種情況,但是他的代碼已經頒到生產環境,并且運行了N天,應該可以排除先執行正確代碼之后緩存的現象
ps:不知道引用傳遞,百度一下,很多文章的
然后第二種,引用傳遞,按理說,第二次查詢之后直接返回,并沒有跟 第一個集合有什么交道
再次陷入沉思…
繼續觀察推測,大膽猜想,兩張情況都有可能造成這種現象,但是目前來看,兩種情況都不滿足
那么,如果是兩個情況結合起來呢!
查詢出來的數據先被緩存,然后修改列表時,修改的其實是緩存數據的引用
當再次查詢時,取緩存中的數據,由于緩存中的數據已經被修改
取出來的數據理所當然,已經是修改過了
OK,推理完成,那么接下來要進行論證了,畢竟紙上談兵,不如真槍實彈練一練,開始驗證
首先復現代碼
@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;}讓我們來看下輸出結果
2021-05-14 13:42:23.393 第一次查詢的ProjectName:小小橙 2021-05-14 13:42:23.393 第一次查詢的ProjectName:大大橘 2021-05-14 13:42:23.393 第一次查詢的ProjectName:花花牛 2021-05-14 13:42:23.394 修改后的ProjectName:貓貓身上有毛毛 2021-05-14 13:42:23.394 修改后的ProjectName:貓貓身上有毛毛 2021-05-14 13:42:23.394 修改后的ProjectName:貓貓身上有毛毛 2021-05-14 13:42:23.406 重新查詢的ProjectName:小小橙 2021-05-14 13:42:23.406 重新查詢的ProjectName:大大橘 2021-05-14 13:42:23.406 重新查詢的ProjectName:花花牛!!! Why??? 為什么沒有復現
秉著 代碼不具有隨機性 想法,再看一下
果然任何是一個細節都是關鍵的,對比后發現,A同學的方法是開啟了事務的,那么接下來我們修改代碼繼續測試
@Transactional@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;}讓我們來看下輸出結果
2021-05-14 13:50:46.001 第一次查詢的ProjectName:小小橙 2021-05-14 13:50:46.001 第一次查詢的ProjectName:大大橘 2021-05-14 13:50:46.001 第一次查詢的ProjectName:花花牛 2021-05-14 13:50:46.002 修改后的ProjectName:貓貓身上有毛毛 2021-05-14 13:50:46.002 修改后的ProjectName:貓貓身上有毛毛 2021-05-14 13:50:46.002 修改后的ProjectName:貓貓身上有毛毛 2021-05-14 13:50:46.002 重新查詢的ProjectName:貓貓身上有毛毛 2021-05-14 13:50:46.002 重新查詢的ProjectName:貓貓身上有毛毛 2021-05-14 13:50:46.002 重新查詢的ProjectName:貓貓身上有毛毛修改后的代碼
@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;}執行結果
2021-05-14 14:05:50.240 【測試開始】:2021-05-14 14:05:50 050 2021-05-14 14:05:50.274 ==> Preparing: select id, code, project_name, ji_zu_lei_xing, address, employee_code, employee_name, data_count, create_time, create_name, update_time, update_name, del_flag from project 2021-05-14 14:05:50.384 ==> Parameters: 2021-05-14 14:05:50.401 <== Total: 3 2021-05-14 14:05:50.402 oneList 第一次查詢內存地址:114565630 2021-05-14 14:05:50.402 oneList 將數據進行修改后的內存地址:114565630 2021-05-14 14:05:50.402 secondList 剛創建后的內存地址:920320548 2021-05-14 14:05:50.403 secondList 寫入數據后的內存地址:114565630 2021-05-14 14:05:50.406 【測試結束】:2021-05-14 14:05:50 050 【耗時】:0s167ms從打印出的日志可以看到:
先去數據庫查詢出 3條記錄放入 list
這時候打印出的 oneList : 114565630
然后將集合中數據進行修改,這時 oneList :114565630,可以看到雖然內容發生了改變,但是內存地址并沒有法師變化
然后聲明一個新的集合 secondlist: 920320548 很明顯,新創建的對象,跟 oneList 的內存地址不是同一個
然后見證奇跡的時候到了,重新執行 projectDao.findAll(); secondlist:114565630,“=” 賦值,內存地址發生變化, 并且沒有打印Sql日志
果然它出現了,重新調用了查詢,但是并沒有執行 slq 去數據庫查詢,而是直接去 114565630 這個內存地址取出來我們已經修改過的集合
到這基本上就確定我們的想法是正確的,查詢到的數據被緩存再內存中,我們修改集合時,因為對象是引用傳遞,我們每次修改的都是內存中的對象,當我們再次查詢相同的sql語句,并沒有去數據庫查詢,而是直接去內存地址取數據,所以就看到了我們明明打印的事直接調用查詢的方法,卻得到了我們處理過的數據
你以為文章到這里就結束了?不不不,還有最后一點要確認,
到底是不是 @Transactional 惹的禍
好,我們把 @Transactional 去掉,在運行一下
作為一個優秀的程序員一定要親眼所見在下定論,上代碼,看日志
//@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;} 2021-05-14 14:18:50.753 【測試開始】:2021-05-14 14:18:50 050 2021-05-14 14:18:50.780 ==> Preparing: select id, code, project_name, ji_zu_lei_xing, address, employee_code, employee_name, data_count, create_time, create_name, update_time, update_name, del_flag from project 2021-05-14 14:18:50.885 ==> Parameters: 2021-05-14 14:18:50.902 <== Total: 3 2021-05-14 14:18:50.903 oneList 第一次查詢內存地址:810898134 2021-05-14 14:18:50.903 oneList 將數據進行修改后的內存地址:810898134 2021-05-14 14:18:50.903 secondList 剛創建后的內存地址:599203108 2021-05-14 14:18:50.903 ==> Preparing: select id, code, project_name, ji_zu_lei_xing, address, employee_code, employee_name, data_count, create_time, create_name, update_time, update_name, del_flag from project 2021-05-14 14:18:50.903 ==> Parameters: 2021-05-14 14:18:50.906 <== Total: 3 2021-05-14 14:18:50.907 secondList 寫入數據后的內存地址:1280730191 2021-05-14 14:18:50.909 【測試結束】:2021-05-14 14:18:50 050 【耗時】:0s157ms簡單分析一下
好,打完收工
總結
以上是生活随笔為你收集整理的Mybatis缓存探索,查询集合后修改内容,再次执行sql查询结果发现是被修改过的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TypeScript看完就会了
- 下一篇: 1688关键词搜索api(附可用)