<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 二級緩存?

          共 5004字,需瀏覽 11分鐘

           ·

          2023-06-20 09:44

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


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

          來自:blog.csdn.net/xujingyiss/article/details/123481116

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

          一級緩存

          應(yīng)用場景

          訂單表與會員表是存在一對多的關(guān)系,為了盡可能減少join查詢,進行了分階段查詢。即先查詢出訂單表,再根據(jù)member_id字段查詢出會員表,最后進行數(shù)據(jù)整合。而如果訂單表中存在重復(fù)的member_id,就會出現(xiàn)很多重復(fù)查詢。
          針對這種情況,mybatis通過一級緩存來解決:在同一次查詢會話(SqlSession)中如果出現(xiàn)相同的語句及參數(shù),就會從緩存中取出,不再走數(shù)據(jù)庫查詢。
          一級緩存只能作用于查詢會話中,所以也叫做會話緩存。

          生效的條件

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

          與springboot集成時一級緩存不生效原因

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

          解決與springboot集成時一級緩存不生效問題

          在上面的代碼中也看到了,mybatis 在查詢時,會先從事務(wù)管理器中嘗試獲取?SqlSession,取不到才會去創(chuàng)建新的?SqlSession。所以可以猜測只要將方法開啟事務(wù),那么一級緩存就會生效。
          加上?@Transactional?注解,看下效果:
          沒錯,的確生效了。在代碼中可以看到,從事務(wù)管理器中,獲取到了 SqlSession:
          再看看源碼中是什么時候?qū)?SqlSession 設(shè)置到事務(wù)管理器中的。
          SqlSessionUtils?中,在獲取到?SqlSession?后,會調(diào)用?registerSessionHolder方法注冊?SessionHolder?到事務(wù)管理器:
          具體是在?TransactionSynchronizationManager?的?bindResource?方法中操作的,將?SessionHolder?保存到線程本地變量(ThreadLocal) resources?中,這是每個線程獨享的。
          然后在下次查詢時,就可以從這里取出此 SqlSession,使用同一個 SqlSession 查詢,一級緩存就生效了。
          所以基本原理就是:如果當(dāng)前線程存在事物,并且存在相關(guān)會話,就從 ThreadLocal 中取出。如果沒有事務(wù),就重新創(chuàng)建一個 SqlSession 并存儲到 ThreadLocal 當(dāng)中,共下次查詢使用。
          至于緩存查詢數(shù)據(jù)的地方,是在?BaseExecutor?中的?queryFromDatabase?方法中。執(zhí)行 doQuery 從數(shù)據(jù)庫中查詢數(shù)據(jù)后,會立馬緩存到?localCache(PerpetualCache類型)?中:

          二級緩存

          應(yīng)用場景

          業(yè)務(wù)系統(tǒng)中存在很多的靜態(tài)數(shù)據(jù)如,字典表、菜單表、權(quán)限表等,這些數(shù)據(jù)的特性是不會輕易修改但又是查詢的熱點數(shù)據(jù)。
          一級緩存針對的是同一個會話當(dāng)中相同SQL,并不適合這情熱點數(shù)據(jù)的緩存場景。
          為了解決這個問題引入了二級緩存,它脫離于會話之外,多個會話可以使用相同的緩存。
          看一個例子:

          @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 請求時(兩個不同的會話),通過日志可以發(fā)現(xiàn)第二次查詢使用的是緩存

          開啟的方法

          二級緩存需要手動來開啟,mybatis 默認(rèn)沒有開啟二級緩存。
          1)在 yaml 中配置?cache-enabled?為 true

          mybatis-plus:
          ??configuration:
          ????cache-enabled:?true

          2)Mapper 接口上添加?@CacheNamespace?注解
          3)實體類實現(xiàn)?Serializable?接口

          生效的條件

          • 當(dāng)會話提交或關(guān)閉之后才會填充二級緩存
          • 必須是同一個 mapper,即同一個命名空間
          • 必須是相同的 statement,即同一個 mapper 中的同一個方法
          • 必須是相同的 SQL 語句和參數(shù)
          • 如果?readWrite=true(默認(rèn)就是true),實體對像必須實現(xiàn)?Serializable?接口

          緩存清除條件

          • 只有修改會話提交之后,才會執(zhí)行清空操作
          • xml 中配置的 update 不能清空?@CacheNamespace?中的緩存數(shù)據(jù)
          • 任何一種增刪改操作都會清空整個?namespace?中的緩存

          源碼中是如何填充二級緩存的?

          在生效條件中提到了,二級緩存必須要在會話提交或關(guān)閉之后,才能生效!
          在查詢到結(jié)果后,會調(diào)用 SqlSession 的 commit 方法進行提交(如果開啟事務(wù)的話,提交 SqlSession 走的不是這里了,但最終填充二級緩存的地方是一樣的。):
          在此方法中,最終會調(diào)用到?TransactionalCache?的?flushPendingEntries?方法中填充二級緩存:
          springboot 集成 mybatis 的話,如果沒有開啟事務(wù),每次執(zhí)行查詢,都會創(chuàng)建新的 SqlSession,所以即使是在同一個方法中進行查詢操作,那也是跨會話的。

          查詢時如何使用二級緩存?

          在查詢的時候,最終會調(diào)用?MybatisCachingExecutor?的 query 方法,里面會從?TransactionalCacheManager?中嘗試根據(jù) key 獲取二級緩存的內(nèi)容。
          可以看到,這個 key 很長,由 mapper、調(diào)用的查詢方法、SQL 等信息拼接而成,這也是為什么想要二級緩存生效,必須滿足前面所說的條件。
          如果能在二級緩存中查詢到,就直接返回了,不需要訪問數(shù)據(jù)庫。
          具體的調(diào)用層數(shù)實在太多,用到了裝飾者模式,最終是在?PerpetualCache?中獲取緩存的:
          打印日志是在?LoggingCache?中:

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

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

          @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?方法,因為沒有加?@Transactional?注解,所以前后兩次查詢,是兩個不同的會話,第一次查詢完后,SqlSession?會自動 commit,所以二級緩存能夠生效;
          然后在中間進行了 Item 表的更新操作,修改了下名稱;
          由于?itemMapper?與?xxxMapper?不是同一個命名空間,所以?itemMapper?執(zhí)行的更新操作不會影響到?xxxMapper?的二級緩存;
          再次調(diào)用?xxxMapper.getPaymentVO,發(fā)現(xiàn)取出的值是走緩存的,itemName?還是老的。但實際上?itemName?在上面已經(jīng)被改了!
          執(zhí)行日志如下:
          所以說,二級緩存的隱藏危害是比較大的,當(dāng)有表關(guān)聯(lián)時,一個不注意就會出問題,不建議使用。

          推薦閱讀:

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

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

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

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

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

          瀏覽 36
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美成人无码A片免费一区澳门 | 五月天淫秽网站 | 国产三级电影在线观看 | 亚洲黄视频 | 亚洲黄色视频网站在线观看视频 |