1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        為什么不推薦使用 MyBatis 二級(jí)緩存?

        共 5004字,需瀏覽 11分鐘

         ·

        2023-06-20 09:44

        程序員的成長(zhǎng)之路
        互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
        關(guān)注


        閱讀本文大概需要 6?分鐘。

        來(lái)自:blog.csdn.net/xujingyiss/article/details/123481116

        為了增加查詢的性能,mybatis 提供了二級(jí)緩存架構(gòu),分為一級(jí)緩存和二級(jí)緩存。
        這兩級(jí)緩存最大的區(qū)別就是:一級(jí)緩存是會(huì)話級(jí)別的,只要出了這個(gè) SqlSession,緩存就沒(méi)用了。而二級(jí)緩存可以跨會(huì)話,多個(gè)會(huì)話可以使用相同的緩存!
        一級(jí)緩存使用簡(jiǎn)單,默認(rèn)就開(kāi)啟。二級(jí)緩存需要手動(dòng)開(kāi)啟,相對(duì)復(fù)雜,而且要注意的事項(xiàng)也多,否則可能有隱患。

        一級(jí)緩存

        應(yīng)用場(chǎ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通過(guò)一級(jí)緩存來(lái)解決:在同一次查詢會(huì)話(SqlSession)中如果出現(xiàn)相同的語(yǔ)句及參數(shù),就會(huì)從緩存中取出,不再走數(shù)據(jù)庫(kù)查詢。
        一級(jí)緩存只能作用于查詢會(huì)話中,所以也叫做會(huì)話緩存。

        生效的條件

        一級(jí)緩存要生效,必須滿足以下條件條件:
        • 必須是相同的會(huì)話
        • 必須是同一個(gè) mapper,即同一個(gè) namespace
        • 必須是相同的 statement,即同一個(gè) mapper 中的同一個(gè)方法
        • 必須是相同的 sql 和參數(shù)
        • 查詢語(yǔ)句中間沒(méi)有執(zhí)行?session.clearCache()?方法
        • 查詢語(yǔ)句中間沒(méi)有執(zhí)行 insert/update/delete 方法(無(wú)論變動(dòng)記錄是否與緩存數(shù)據(jù)有無(wú)關(guān)系)

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

        因?yàn)橐患?jí)緩存是會(huì)話級(jí)別的,要生效的話,必須要在同一個(gè) SqlSession 中。但是與 springboot 集成的 mybatis,默認(rèn)每次執(zhí)行sql語(yǔ)句時(shí),都會(huì)創(chuàng)建一個(gè)新的 SqlSession!所以一級(jí)緩存才沒(méi)有生效。
        當(dāng)調(diào)用 mapper 的方法時(shí),最終會(huì)執(zhí)行到?SqlSessionUtils?的?getSqlSession?方法,在這個(gè)方法中會(huì)嘗試在事務(wù)管理器中獲取 SqlSession,如果沒(méi)有開(kāi)啟事務(wù),那么就會(huì) new 一個(gè)?DefaultSqlSession。
        所以說(shuō),即便在同一個(gè)方法中,通過(guò)同一個(gè) mapper 連續(xù)調(diào)用兩次相同的查詢方法,也不會(huì)觸發(fā)一級(jí)緩存。

        解決與springboot集成時(shí)一級(jí)緩存不生效問(wèn)題

        在上面的代碼中也看到了,mybatis 在查詢時(shí),會(huì)先從事務(wù)管理器中嘗試獲取?SqlSession,取不到才會(huì)去創(chuàng)建新的?SqlSession。所以可以猜測(cè)只要將方法開(kāi)啟事務(wù),那么一級(jí)緩存就會(huì)生效。
        加上?@Transactional?注解,看下效果:
        沒(méi)錯(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 中取出。如果沒(méi)有事務(wù),就重新創(chuàng)建一個(gè) SqlSession 并存儲(chǔ)到 ThreadLocal 當(dāng)中,共下次查詢使用。
        至于緩存查詢數(shù)據(jù)的地方,是在?BaseExecutor?中的?queryFromDatabase?方法中。執(zhí)行 doQuery 從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù)后,會(huì)立馬緩存到?localCache(PerpetualCache類型)?中:

        二級(jí)緩存

        應(yīng)用場(chǎ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ù)的緩存場(chǎng)景。
        為了解決這個(gè)問(wèn)題引入了二級(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ì)話),通過(guò)日志可以發(fā)現(xiàn)第二次查詢使用的是緩存

        開(kāi)啟的方法

        二級(jí)緩存需要手動(dòng)來(lái)開(kāi)啟,mybatis 默認(rèn)沒(méi)有開(kāi)啟二級(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 語(yǔ)句和參數(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)行提交(如果開(kāi)啟事務(wù)的話,提交 SqlSession 走的不是這里了,但最終填充二級(jí)緩存的地方是一樣的。):
        在此方法中,最終會(huì)調(diào)用到?TransactionalCache?的?flushPendingEntries?方法中填充二級(jí)緩存:
        springboot 集成 mybatis 的話,如果沒(méi)有開(kāi)啟事務(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 很長(zhǎng),由 mapper、調(diào)用的查詢方法、SQL 等信息拼接而成,這也是為什么想要二級(jí)緩存生效,必須滿足前面所說(shuō)的條件。
        如果能在二級(jí)緩存中查詢到,就直接返回了,不需要訪問(wèn)數(shù)據(jù)庫(kù)。
        具體的調(diào)用層數(shù)實(shí)在太多,用到了裝飾者模式,最終是在?PerpetualCache?中獲取緩存的:
        打印日志是在?LoggingCache?中:

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

        答案就是,不推薦使用二級(jí)緩存!
        二級(jí)緩存雖然能帶來(lái)一定的好處,但是有很大的隱藏危害!
        它的緩存是以?namespace(mapper)?為單位的,不同 namespace 下的操作互不影響。且 insert/update/delete 操作會(huì)清空所在?namespace?下的全部緩存。
        那么問(wèn)題就出來(lái)了,假設(shè)現(xiàn)在有?ItemMapper?以及?XxxMapper,在?XxxMapper?中做了表關(guān)聯(lián)查詢,且做了二級(jí)緩存。此時(shí)在?ItemMapper?中將 item 信息給刪了,由于不同 namespace 下的操作互不影響,XxxMapper?的二級(jí)緩存不會(huì)變,那之后再次通過(guò)?XxxMapper?查詢的數(shù)據(jù)就不對(duì)了,非常危險(xiǎn)。
        來(lái)看一個(gè)例子:

        @Mapper
        @Repository
        @CacheNamespace
        public?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);
        ?
        }
        ?
        ?
        @Autowired
        private?XxxMapper?xxxMapper;
        ?
        @Test
        void?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)闆](méi)有加?@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í)行日志如下:
        所以說(shuō),二級(jí)緩存的隱藏危害是比較大的,當(dāng)有表關(guān)聯(lián)時(shí),一個(gè)不注意就會(huì)出問(wèn)題,不建議使用。

        推薦閱讀:

        Java21的 main 方法聲明要變天了嗎?

        面試官:一千萬(wàn)的數(shù)據(jù),你是怎么查詢的?

        互聯(lián)網(wǎng)初中高級(jí)大廠面試題(9個(gè)G)

        內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬(wàn)并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!

        ?戳閱讀原文領(lǐng)??!? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??朕已閱?

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            精品少妇冒白浆喷潮免费视频 | 一本精品久久加勒比无码A片 | 亚洲中文字幕一 | 精品国产精品一区二区夜夜嗨 | 大力操逼 | wwwA片 | www.豆花成人在线免费看视频 | japanesefree暴力侵犯 | 美国一级特黄大片A片 | 女生抠逼视频 |