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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Mybatis缓存探索,查询集合后修改内容,再次执行sql查询结果发现是被修改过的

發布時間:2023/12/3 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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:貓貓身上有毛毛
  • Ok 已經復現了,接下來驗證我們的猜測,把 oneList 和 secondList 的 內存地址進行打印,進行對比
  • 如果真的是緩存的話,那么第二次查詢應該不會再查詢數據庫了吧,我們把sql查詢日志也打印一遍
  • 開啟事務和不開啟事務分別進行對比輸出的日志
  • Go Now !
  • 修改后的代碼

    @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

    簡單分析一下

  • 第一次查詢數據,記錄內存地址
  • 講數據進行修改,內存地址并未發生變化,變化的知識內存內的數據
  • 新建一對象,會聲明新的內存地址
  • 然后查詢數據庫,看到了 sql 已經被打印,并且賦值后的內存地址也跟第一次查詢的內存地址不一樣
  • 好,打完收工

    總結

    以上是生活随笔為你收集整理的Mybatis缓存探索,查询集合后修改内容,再次执行sql查询结果发现是被修改过的的全部內容,希望文章能夠幫你解決所遇到的問題。

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