<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          MyBatis的二級(jí)緩存,慎用!

          共 5219字,需瀏覽 11分鐘

           ·

          2023-06-25 23:43

          往期熱門文章:

          1、8種專坑同事的 SQL 寫法,性能降低100倍,不來看看?
          2、最近火起的 Bean Searcher 與 MyBatis Plus 到底有啥區(qū)別?
          3、別再重復(fù)造輪子了,一個(gè) Spring 注解輕松搞定循環(huán)重試功能!
          4、2023最新互聯(lián)網(wǎng)公司時(shí)長排行榜出爐!
          5、棄用 Nginx 后!它成為了最受歡迎 Web 服務(wù)器。。。


          為了增加查詢的性能,MyBatis 提供了二級(jí)緩存架構(gòu),分為一級(jí)緩存和二級(jí)緩存。


          這兩級(jí)緩存最大的區(qū)別就是:一級(jí)緩存是會(huì)話級(jí)別的,只要出了這個(gè) SqlSession,緩存就沒用了。而二級(jí)緩存可以跨會(huì)話,多個(gè)會(huì)話可以使用相同的緩存!


          一級(jí)緩存使用簡單,默認(rèn)就開啟。二級(jí)緩存需要手動(dòng)開啟,相對(duì)復(fù)雜,而且要注意的事項(xiàng)也多,否則可能有隱患。


          一級(jí)緩存


          應(yīng)用場景


          訂單表與會(huì)員表是存在一對(duì)多的關(guān)系,為了盡可能減少 join 查詢,進(jìn)行了分階段查詢。即先查詢出訂單表,再根據(jù) member_id 字段查詢出會(huì)員表,最后進(jìn)行數(shù)據(jù)整合。而如果訂單表中存在重復(fù)的 member_id,就會(huì)出現(xiàn)很多重復(fù)查詢。


          針對(duì)這種情況,MyBatis?通過一級(jí)緩存來解決:在同一次查詢會(huì)話(SqlSession)中如果出現(xiàn)相同的語句及參數(shù),就會(huì)從緩存中取出,不再走數(shù)據(jù)庫查詢。


          一級(jí)緩存只能作用于查詢會(huì)話中,所以也叫做會(huì)話緩存。


          生效的條件


          一級(jí)緩存要生效,必須滿足以下條件條件:


          • 必須是相同的會(huì)話

          • 必須是同一個(gè) mapper,即同一個(gè) namespace

          • 必須是相同的 statement,即同一個(gè) mapper 中的同一個(gè)方法

          • 必須是相同的 SQL 和參數(shù)

          • 查詢語句中間沒有執(zhí)行 session.clearCache() 方法

          • 查詢語句中間沒有執(zhí)行 insert/update/delete 方法(無論變動(dòng)記錄是否與緩存數(shù)據(jù)有無關(guān)系)


          與 SpringBoot 集成時(shí)一級(jí)緩存不生效原因




          因?yàn)橐患?jí)緩存是會(huì)話級(jí)別的,要生效的話,必須要在同一個(gè) SqlSession 中。但是與 SpringBoot 集成的 MyBatis,默認(rèn)每次執(zhí)行 SQL 語句時(shí),都會(huì)創(chuàng)建一個(gè)新的 SqlSession!所以一級(jí)緩存才沒有生效。


          當(dāng)調(diào)用 mapper 的方法時(shí),最終會(huì)執(zhí)行到 SqlSessionUtils 的 getSqlSession 方法,在這個(gè)方法中會(huì)嘗試在事務(wù)管理器中獲取 SqlSession,如果沒有開啟事務(wù),那么就會(huì) new 一個(gè) DefaultSqlSession。



          所以說,即便在同一個(gè)方法中,通過同一個(gè) mapper 連續(xù)調(diào)用兩次相同的查詢方法,也不會(huì)觸發(fā)一級(jí)緩存。


          解決與?SpringBoot?集成時(shí)一級(jí)緩存不生效問題


          在上面的代碼中也看到了,MyBatis?在查詢時(shí),會(huì)先從事務(wù)管理器中嘗試獲取 SqlSession,取不到才會(huì)去創(chuàng)建新的 SqlSession。所以可以猜測只要將方法開啟事務(wù),那么一級(jí)緩存就會(huì)生效。

          加上 @Transactional 注解,看下效果:



          沒錯(cuò),的確生效了。在代碼中可以看到,從事務(wù)管理器中,獲取到了 SqlSession:

          再看看源碼中是什么時(shí)候?qū)?SqlSession 設(shè)置到事務(wù)管理器中的。


          SqlSessionUtils 中,在獲取到 SqlSession 后,會(huì)調(diào)用 registerSessionHolder 方法注冊(cè) SessionHolder 到事務(wù)管理器:



          具體是在 TransactionSynchronizationManager 的 bindResource 方法中操作的,將 SessionHolder 保存到線程本地變量 (ThreadLocal) resources 中,這是每個(gè)線程獨(dú)享的:



          然后在下次查詢時(shí),就可以從這里取出此 SqlSession,使用同一個(gè) SqlSession 查詢,一級(jí)緩存就生效了。


          所以基本原理就是:如果當(dāng)前線程存在事物,并且存在相關(guān)會(huì)話,就從 ThreadLocal 中取出。如果沒有事務(wù),就重新創(chuàng)建一個(gè) SqlSession 并存儲(chǔ)到 ThreadLocal 當(dāng)中,共下次查詢使用。


          至于緩存查詢數(shù)據(jù)的地方,是在 BaseExecutor 中的 queryFromDatabase 方法中。執(zhí)行 doQuery 從數(shù)據(jù)庫中查詢數(shù)據(jù)后,會(huì)立馬緩存到 localCache(PerpetualCache類型) 中:



          二級(jí)緩存

          應(yīng)用場景

          業(yè)務(wù)系統(tǒng)中存在很多的靜態(tài)數(shù)據(jù)如,字典表、菜單表、權(quán)限表等,這些數(shù)據(jù)的特性是不會(huì)輕易修改但又是查詢的熱點(diǎn)數(shù)據(jù)。

          一級(jí)緩存針對(duì)的是同一個(gè)會(huì)話當(dāng)中相同 SQL,并不適合熱點(diǎn)數(shù)據(jù)的緩存場景。

          為了解決這個(gè)問題引入了二級(jí)緩存,它脫離于會(huì)話之外,多個(gè)會(huì)話可以使用相同的緩存。

          看一個(gè)例子:

          @RestController@RequestMapping("item")public class ItemController {
          @Autowired private ItemMapper itemMapper;
          @GetMapping("/{id}") public void getById(@PathVariable("id") Long id) { System.out.println("==================== begin ===================="); Item item = itemMapper.selectById(id); System.out.println(JSON.toJSONString(item)); }
          }

          當(dāng)發(fā)送兩次 get 請(qǐng)求時(shí)(兩個(gè)不同的會(huì)話),通過日志可以發(fā)現(xiàn)第二次查詢使用的是緩存。



          開啟的方法

          二級(jí)緩存需要手動(dòng)來開啟,MyBatis?默認(rèn)沒有開啟二級(jí)緩存。

          1)在 yaml 中配置 cache-enabled 為 true

          mybatis-plus:  configuration:    cache-enabled: true

          2)Mapper 接口上添加 @CacheNamespace 注解




          3)實(shí)體類實(shí)現(xiàn) Serializable 接口


          生效的條件


          • 當(dāng)會(huì)話提交或關(guān)閉之后才會(huì)填充二級(jí)緩存

          • 必須是同一個(gè) mapper,即同一個(gè)命名空間

          • 必須是相同的 statement,即同一個(gè) mapper 中的同一個(gè)方法

          • 必須是相同的 SQL 語句和參數(shù)

          • 如果 readWrite=true(默認(rèn)就是true),實(shí)體對(duì)像必須實(shí)現(xiàn) Serializable 接口


          緩存清除條件


          • 只有修改會(huì)話提交之后,才會(huì)執(zhí)行清空操作

          • xml 中配置的 update 不能清空 @CacheNamespace 中的緩存數(shù)據(jù)

          • 任何一種增刪改操作都會(huì)清空整個(gè) namespace 中的緩存


          源碼中是如何填充二級(jí)緩存的?


          在生效條件中提到了,二級(jí)緩存必須要在會(huì)話提交或關(guān)閉之后,才能生效!


          在查詢到結(jié)果后,會(huì)調(diào)用 SqlSession 的 commit 方法進(jìn)行提交(如果開啟事務(wù)的話,提交 SqlSession 走的不是這里了,但最終填充二級(jí)緩存的地方是一樣的):


          在此方法中,最終會(huì)調(diào)用到 TransactionalCache 的 flushPendingEntries 方法中填充二級(jí)緩存:

          SpringBoot?集成 MyBatis?的話,如果沒有開啟事務(wù),每次執(zhí)行查詢,都會(huì)創(chuàng)建新的 SqlSession,所以即使是在同一個(gè)方法中進(jìn)行查詢操作,那也是跨會(huì)話的。?


          查詢時(shí)如何使用二級(jí)緩存?


          在查詢的時(shí)候,最終會(huì)調(diào)用 MybatisCachingExecutor 的 query 方法,里面會(huì)從 TransactionalCacheManager 中嘗試根據(jù) key 獲取二級(jí)緩存的內(nèi)容。


          可以看到,這個(gè) key 很長,由 mapper、調(diào)用的查詢方法、SQL 等信息拼接而成,這也是為什么想要二級(jí)緩存生效,必須滿足前面所說的條件。

          如果能在二級(jí)緩存中查詢到,就直接返回了,不需要訪問數(shù)據(jù)庫。


          具體的調(diào)用層數(shù)實(shí)在太多,用到了裝飾者模式,最終是在 PerpetualCache 中獲取緩存的:



          打印日志是在 LoggingCache 中:



          為什么?MyBatis?默認(rèn)不開啟二級(jí)緩存?

          答案就是,不推薦使用二級(jí)緩存!

          二級(jí)緩存雖然能帶來一定的好處,但是有很大的隱藏危害!

          它的緩存是以 namespace(mapper) 為單位的,不同 namespace 下的操作互不影響。且 insert/update/delete 操作會(huì)清空所在 namespace 下的全部緩存。

          那么問題就出來了,假設(shè)現(xiàn)在有 ItemMapper 以及 XxxMapper,在 XxxMapper 中做了表關(guān)聯(lián)查詢,且做了二級(jí)緩存。此時(shí)在 ItemMapper 中將 item 信息給刪了,由于不同 namespace 下的操作互不影響,XxxMapper 的二級(jí)緩存不會(huì)變,那之后再次通過 XxxMapper 查詢的數(shù)據(jù)就不對(duì)了,非常危險(xiǎn)。

          來看一個(gè)例子:

          @Mapper@Repository@CacheNamespacepublic interface XxxMapper {
          @Select("select i.id itemId,i.name itemName,p.amount,p.unit_price unitPrice " + "from item i JOIN payment p on i.id = p.item_id where i.id = #{id}") List getPaymentVO(Long id);
          }

          @Autowiredprivate XxxMapper xxxMapper;
          @Testvoid test() { System.out.println("==================== 查詢PaymentVO ===================="); List voList = xxxMapper.getPaymentVO(1L); System.out.println(JSON.toJSONString(voList.get(0))); System.out.println("==================== 更新item表的name ==================== "); Item item = itemMapper.selectById(1); item.setName("java并發(fā)編程"); itemMapper.updateById(item); System.out.println("==================== 重新查詢PaymentVO ==================== "); List voList2 = xxxMapper.getPaymentVO(1L); System.out.println(JSON.toJSONString(voList2.get(0)));}


          上面的代碼,test() 方法中前后兩次調(diào)用了 xxxMapper.getPaymentVO 方法,因?yàn)闆]有加 @Transactional 注解,所以前后兩次查詢,是兩個(gè)不同的會(huì)話,第一次查詢完后,SqlSession 會(huì)自動(dòng) commit,所以二級(jí)緩存能夠生效;


          然后在中間進(jìn)行了 Item 表的更新操作,修改了下名稱;


          由于 itemMapper 與 xxxMapper 不是同一個(gè)命名空間,所以 itemMapper 執(zhí)行的更新操作不會(huì)影響到 xxxMapper 的二級(jí)緩存;


          再次調(diào)用 xxxMapper.getPaymentVO,發(fā)現(xiàn)取出的值是走緩存的,itemName 還是老的。但實(shí)際上 itemName 在上面已經(jīng)被改了!


          執(zhí)行日志如下:


          所以說,二級(jí)緩存的隱藏危害是比較大的,當(dāng)有表關(guān)聯(lián)時(shí),一個(gè)不注意就會(huì)出問題,不建議使用。

          轉(zhuǎn)自:xujingyiss,

          鏈接:blog.csdn.net/xujingyiss/article/details/123481116

          往期熱門文章:

          1、計(jì)算機(jī)會(huì)成為下一個(gè)土木嗎?
          2、干掉Maven和Gradle!推出更強(qiáng)更快更牛逼的新一代構(gòu)建工具,炸裂!
          3、大公司為什么禁止SpringBoot項(xiàng)目使用Tomcat?
          4、快速交付神器:阿里巴巴官方低代碼引擎開源了!
          5、為什么 Spring和IDEA 都不推薦使用 @Autowired 注解
          6、程序員的悲哀是什么?
          7、被問懵了:MySQL 自增主鍵一定是連續(xù)的嗎?
          8、點(diǎn)一下詳情系統(tǒng)掛了,CPU100%
          9、我說用count(*)統(tǒng)計(jì)行數(shù),面試官讓我回去等消息...
          10、世界第三大瀏覽器正在消亡

          瀏覽 85
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本爱爱激情网 | 成人毛片18女人毛片真水 | 人妻传媒 | 国产又黄又粗视频 | 美女国产一区二区 |