最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring?data?jpa緩存機(jī)制使用總結(jié)

 更新時(shí)間:2023年12月01日 08:44:30   作者:qq_34485381  
這篇文章主要介紹了Spring?data?jpa緩存機(jī)制使用總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

Spring data jpa緩存機(jī)制

Spring data jpa  的使用讓我們操作數(shù)據(jù)庫(kù)變得非常簡(jiǎn)單,開(kāi)發(fā)人員只需要編寫(xiě)repository接口,Spring將自動(dòng)提供實(shí)現(xiàn),尤其是基礎(chǔ)的的CURD 操作,為我們封裝好的同時(shí)也做了一些性能上的優(yōu)化。

但也正因?yàn)槿绱?,這些基礎(chǔ)的操作的背后并不是那么簡(jiǎn)單,稍有不慎就會(huì)得到我們意料之外的結(jié)果,接下來(lái)列舉一些工作中遇到的問(wèn)題。

一、案例

項(xiàng)目中遇到過(guò)這樣一個(gè)問(wèn)題,repository繼承了CrudRepository接口,直接使用save(S entity) 方法進(jìn)行數(shù)據(jù)保存,但是因?yàn)槟硞€(gè)字段的唯一約束沖突了,導(dǎo)致保存失敗并拋出了異常,但是save方法后的代碼邏輯卻執(zhí)行了,將數(shù)據(jù)保存到redis,這導(dǎo)致了數(shù)據(jù)庫(kù)和redis數(shù)據(jù)不一致。

代碼代碼大概是這樣子:

    @Override
    @Transactional
    public void save(SomeThingVo vo){
        SomeThingEntity entity = new SomeThingEntity();
        BeanUtils.copyProperties(vo,entity);
        //保存至數(shù)據(jù)庫(kù)
        someThingRepository.save(entity);
        //緩存
        cacheSomeThing(entity);
         //做一些其他事
        doSomeThingElse();
    }

然后對(duì)這個(gè)操作進(jìn)行了debug,發(fā)現(xiàn)到save方法結(jié)束,是沒(méi)有拋出異常的,然后繼續(xù)進(jìn)行保存redis等操作,直到方法結(jié)束才拋出了異常。

這時(shí)注意到了@Transactional注解加在了這個(gè)方法之上,那就是事務(wù)提交時(shí)才會(huì)報(bào)出 唯一約束沖突的異常,再聯(lián)想到Spring data Jpa的是用Hibernate實(shí)現(xiàn)的 , Hibernate是有緩存機(jī)制的,猜想不使用jpa自帶的save方法,就可以在保存時(shí)直接拋異常,而不執(zhí)行之后的代碼,然后進(jìn)行嘗試,的確如此;還有一種解決方式是使用saveAndFlush方法,立馬將緩存中的實(shí)體bean刷入數(shù)據(jù)庫(kù)。

二、分析

Hibernate緩存包括兩大類:一級(jí)緩存和二級(jí)緩存。

一級(jí)緩存又稱為“Session的緩存”,它是內(nèi)置的,不能被卸載(不能被卸載的意思就是這種緩存不具有可選性,必須有的功能,不可以取消session緩存)。由于Session對(duì)象的生命周期通常對(duì)應(yīng)一個(gè)數(shù)據(jù)庫(kù)事務(wù)或者一個(gè)應(yīng)用事務(wù),因此它的緩存是事務(wù)范圍的緩存在第一級(jí)緩存中,持久化類的每個(gè)實(shí)例都具有唯一的OID。我們使用@Transactional 注解時(shí),JpaTransactionManager會(huì)在開(kāi)啟事務(wù)前打開(kāi)一個(gè)session,將事務(wù)綁定在這個(gè)session上,事務(wù)結(jié)束session關(guān)閉,所以后續(xù)內(nèi)容將以粗略以事務(wù)作為一級(jí)緩存的生存時(shí)段。

二級(jí)緩存又稱為“SessionFactory的緩存”,由于SessionFactory對(duì)象的生命周期和應(yīng)用程序的整個(gè)過(guò)程對(duì)應(yīng),因此二級(jí)緩存是進(jìn)程范圍或者集群范圍的緩存,有可能出現(xiàn)并發(fā)問(wèn)題,因此需要采用適當(dāng)?shù)牟l(fā)訪問(wèn)策略。第二級(jí)緩存是可選的,是一個(gè)可配置的插件,在默認(rèn)情況下,SessionFactory不會(huì)啟用這個(gè)插件,二級(jí)緩存應(yīng)用場(chǎng)景局限性比較大,適用于數(shù)據(jù)要求的實(shí)時(shí)性和準(zhǔn)確性不高、變動(dòng)很少的情況,此次我們僅針對(duì)一級(jí)緩存進(jìn)行詳細(xì)說(shuō)明。

我們使用CrudRepository.save() 方法保存或更新對(duì)象的流程如下

  

從上圖可以看出每次save方法執(zhí)行時(shí)都會(huì)用主鍵向數(shù)據(jù)庫(kù)發(fā)起一次查詢,來(lái)判斷是更新還是插入,此時(shí)spring data jpa 不會(huì)立馬向數(shù)據(jù)庫(kù)發(fā)送命令,而是將這條數(shù)據(jù)保存在一級(jí)緩存之中,然后返回緩存中實(shí)體對(duì)象,接下來(lái)繼續(xù)執(zhí)行后續(xù)的代碼。

如果想更新這條數(shù)據(jù)的值,可以直接修改這個(gè)實(shí)體對(duì)象,jpa會(huì)在事前提交之前的某個(gè)點(diǎn)(具體后面會(huì)說(shuō)明)自動(dòng)將這些變更的數(shù)據(jù)保存至數(shù)據(jù)庫(kù),并且在事務(wù)期間查詢這條數(shù)據(jù)都是優(yōu)先從緩存中獲取數(shù)據(jù)。

一級(jí)緩存的作用還是很明顯的,在整個(gè)事務(wù)中,在對(duì)同一條數(shù)據(jù)進(jìn)行了保存更新查詢操作都會(huì)以盡量少地請(qǐng)求數(shù)據(jù)庫(kù)的方式進(jìn)行優(yōu)化,降低了網(wǎng)絡(luò)io開(kāi)銷。     

三、聯(lián)想

有利就有弊,就像第一部分描述的,因?yàn)檠舆t提交 ,數(shù)據(jù)的正確性驗(yàn)證(數(shù)據(jù)庫(kù)限制方面,比如約束)并沒(méi)有立馬執(zhí)行,有時(shí)候完全是我們不能承受的,我們想要的效果并不是這樣。

接下來(lái)設(shè)想一下其他場(chǎng)景:

1、何時(shí)會(huì)將數(shù)據(jù)提交至數(shù)據(jù)庫(kù)?

實(shí)際上這中情況是不存在的。

測(cè)試代碼和結(jié)果如下:

 @Transactional(rollbackFor = {Exception.class})
   public SomeThingEntity save(SomeThingVo vo) {
       SomeThingEntity entity = new SomeThingEntity();
       BeanUtils.copyProperties(vo,entity);
       SomeThingEntity someThingEntity = someThingRepository.save(entity);
       log.info("保存方法結(jié)束");
       String code = "GOODS_" + someThingEntity.getCode() ;
       someThingEntity.setCode(code);
       log.info("開(kāi)始查找");
      SomeThingEntity searchThing = someThingRepository.searchByCode(code);
      log.info("查找結(jié)果:{}" , searchThing);
      SomeThingEntity getThing = someThingRepository.getOne(someThingEntity.getId());
      log.info("執(zhí)行了一次JPA查詢\n\r" +
              "someThingEntity == getThing : {}\n\r" +
              "searchThing == getThing :{}" , someThingEntity == getThing , searchThing == getThing );
      return someThingEntity;
   }


打印日志:

1 Hibernate: select somethinge0_.id as id1_3_0_, somethinge0_.code as code2_3_0_, somethinge0_.description as descript3_3_0_, somethinge0_.price as price4_3_0_ from tb_something somethinge0_ where somethinge0_.id=?

2 保存方法結(jié)束

3 開(kāi)始查找

4 Hibernate: insert into tb_something (code, description, price, id) values (?, ?, ?, ?)

5 Hibernate: update tb_something set code=?, description=?, price=? where id=?

6 Hibernate: select somethinge0_.id as id1_3_, somethinge0_.code as code2_3_, somethinge0_.description as descript3_3_, somethinge0_.price as price4_3_ from tb_something somethinge0_ where somethinge0_.code=?

7 查找結(jié)果:SomeThingEntity(id=5, code=GOODS_005, price=100, description=書(shū)包)

8 執(zhí)行了一次JPA查詢

9 someThingEntity == getThing : true

10 searchThing == getThing :true

11 Hibernate: update tb_something set code=?, description=?, price=? where id=?

從日志可見(jiàn):

  • save()方法執(zhí)行時(shí)只打印了一個(gè)查詢sql       
  • someThingRepository.searchByCode()方法執(zhí)行前各打印了一條插入sql和更新sql
  • someThingRepository.searchByCode() 進(jìn)行了查詢  
  • getOne()并沒(méi)有打印sql,直接獲取緩存中的對(duì)象

最后比對(duì)這些實(shí)體都是同一個(gè)對(duì)象,即緩存中的對(duì)象。

將代碼中someThingRepository.searchByCode方法改為其他讀寫(xiě)語(yǔ)句,嘗試多次,得出以下結(jié)論:

(1)未提交至數(shù)據(jù)庫(kù)的操作會(huì)在下次請(qǐng)求到數(shù)據(jù)庫(kù)時(shí)一起提交至數(shù)據(jù)庫(kù)執(zhí)行

(2)在事務(wù)提交前存在未提交的數(shù)據(jù),會(huì)提交至數(shù)據(jù)庫(kù)執(zhí)行

2、實(shí)體對(duì)象加入緩存后

我們寫(xiě)sql更新數(shù)據(jù),再用自己的sql獲取這條數(shù)據(jù),得到的是緩存中的數(shù)據(jù)還是更新后的數(shù)據(jù)

這次測(cè)試代碼和結(jié)果如下:

@Transactional(rollbackFor = {Exception.class})
 public SomeThingEntity save(SomeThingVo vo) {
       SomeThingEntity entity = new SomeThingEntity();
       BeanUtils.copyProperties(vo,entity);
       SomeThingEntity someThingEntity = someThingRepository.save(entity);
       log.info("開(kāi)始更新");
       Integer fenPrice = entity.getPrice() * 100;
       someThingRepository.updatePriceByCode(someThingEntity.getCode(),fenPrice);
      //Session session = (Session) entityManger.getDelegate();
      //session.clear();
      SomeThingEntity searchThing = someThingRepository.searchByCode(someThingEntity.getCode());
     log.info("searchThing = {}",searchThing);
     log.info("searchThing == someThingEntity {}",searchThing == someThingEntity);
     //someThingEntity.setDescription("");
     return someThingEntity;
 }

傳入?yún)?shù):{id=20,code='GOODS_020",price=100,description="書(shū)包"}

打印日志:

1Hibernate: select somethinge0_.id as id1_3_0_, somethinge0_.code as code2_3_0_, somethinge0_.description as descript3_3_0_, somethinge0_.price as price4_3_0_ from tb_something somethinge0_ where somethinge0_.id=?

2 開(kāi)始更新

3 Hibernate: insert into tb_something (code, description, price, id) values (?, ?, ?, ?)

4 Hibernate: update tb_something set price=? where code=?

5 Hibernate: select * from tb_something  where code = ? 

6 searchThing = SomeThingEntity(id=20, code=GOODS_020, price=100, description=書(shū)包)

7 searchThing == someThingEntity true

數(shù)據(jù)庫(kù)結(jié)果:{id=20,code='GOODS_020",price=10000,description="書(shū)包"}

從日志中可見(jiàn):

someThingRepository.updatePriceByCode(someThingEntity.getCode(),fenPrice) 執(zhí)行打印了相關(guān)更新sql(第4行日志),目的  將price由100 改為10000

我們的查詢方法向數(shù)據(jù)庫(kù)發(fā)起了查詢;

打印的結(jié)果不是我們更新后的結(jié)果,price仍然為100;

查詢的結(jié)果對(duì)象和緩存中的對(duì)象比較,是同一個(gè)對(duì)象;  

測(cè)試說(shuō)明:

執(zhí)行我們的查詢方法后,jpa返回給我們的仍然是緩存中的值,這樣子的話我們?cè)谶@個(gè)事務(wù)中怎么查詢都拿不到我們變更后的值! jpa不會(huì)根據(jù)我們的update方法自動(dòng)刷新緩存,后邊查詢出來(lái)的數(shù)據(jù)也不會(huì)覆蓋緩存中的數(shù)據(jù)。

那么一些同學(xué)可能會(huì)把一個(gè)事務(wù)涵蓋內(nèi)容的比較多,在頂層的service就加了@Transactional ,就可能在一些操作上進(jìn)入了這樣的場(chǎng)景,在緩存存在的情況,手動(dòng)update,后續(xù)有去查詢使用,最終使用了錯(cuò)誤的數(shù)據(jù)。

如果非要在當(dāng)前事務(wù)中查詢到正確數(shù)據(jù)的話,那就手動(dòng)清除session中的緩存吧(上述代碼中 10、11行)。

另外,放開(kāi)上述代碼中的15行,最終保存在數(shù)據(jù)庫(kù)的結(jié)果為 {id=20,code='GOODS_020",price=100,description=""} ,price的值會(huì)被緩存中的覆蓋。

總結(jié)

Spring data jpa 的這些操作都是簡(jiǎn)單常用而又容易忽視的,我們?cè)谑褂脮r(shí)要考慮一下是否得當(dāng)。

對(duì)于這樣的緩存機(jī)制我們要做的是 將事務(wù)控制在合適的范圍,將不需要在事務(wù)中執(zhí)行的內(nèi)容就移出去;在需要sql明確執(zhí)行好的情況,就主要避開(kāi)使用會(huì)延遲提交的方法。

規(guī)范的代碼和設(shè)計(jì)是質(zhì)量的一個(gè)重要保證之一。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java中的泛型和泛型通配符詳解

    Java中的泛型和泛型通配符詳解

    這篇文章主要介紹了Java中的泛型和泛型通配符詳解,泛型的作用就是在編譯的時(shí)候能夠檢查類型安全,并且所有的強(qiáng)制轉(zhuǎn)換都是自動(dòng)和隱式的在沒(méi)有泛型的情況的下,通過(guò)對(duì)類型Object的引用來(lái)實(shí)現(xiàn)參數(shù)的“任意化”,需要的朋友可以參考下
    2023-07-07
  • Java常用工具類—集合排序

    Java常用工具類—集合排序

    這篇文章主要介紹了Java集合排序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Spring配置文件和mybatis詳解

    Spring配置文件和mybatis詳解

    MyBatis是一款優(yōu)秀的持久層框架,支持自定義SQL、存儲(chǔ)過(guò)程、高級(jí)映射以及多種配置方式,本文給大家介紹Spring配置文件和mybatis的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧
    2025-10-10
  • springboot+WebMagic+MyBatis爬蟲(chóng)框架的使用

    springboot+WebMagic+MyBatis爬蟲(chóng)框架的使用

    本文是對(duì)spring boot+WebMagic+MyBatis做了整合,使用WebMagic爬取數(shù)據(jù),然后通過(guò)MyBatis持久化爬取的數(shù)據(jù)到mysql數(shù)據(jù)庫(kù)。具有一定的參考價(jià)值,感興趣的可以了解一下
    2021-08-08
  • ?java中Thread.sleep()的具體使用

    ?java中Thread.sleep()的具體使用

    本文主要介紹了?java中Thread.sleep()的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • SpringBoot連接Microsoft SQL Server實(shí)現(xiàn)登錄驗(yàn)證

    SpringBoot連接Microsoft SQL Server實(shí)現(xiàn)登錄驗(yàn)證

    本文主要介紹了SpringBoot連接Microsoft SQL Server實(shí)現(xiàn)登錄驗(yàn)證,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-05-05
  • java去除集合中重復(fù)元素示例分享 java去除重復(fù)

    java去除集合中重復(fù)元素示例分享 java去除重復(fù)

    這篇文章主要介紹了java去除集合中重復(fù)元素示例,大家參考使用吧
    2014-01-01
  • Java實(shí)現(xiàn)發(fā)起HTTP請(qǐng)求的四種方法實(shí)現(xiàn)與適用場(chǎng)景

    Java實(shí)現(xiàn)發(fā)起HTTP請(qǐng)求的四種方法實(shí)現(xiàn)與適用場(chǎng)景

    Java中發(fā)起HTTP請(qǐng)求有多種方式,包括原生庫(kù)、第三方庫(kù)及現(xiàn)代API,以下是常見(jiàn)的幾種實(shí)現(xiàn)方法,本文介紹了它們的具體實(shí)現(xiàn)代碼以及性能適用場(chǎng)景,感興趣的可以了解下
    2025-06-06
  • java實(shí)現(xiàn)滑動(dòng)驗(yàn)證解鎖

    java實(shí)現(xiàn)滑動(dòng)驗(yàn)證解鎖

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)滑動(dòng)驗(yàn)證解鎖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • Java中JUC包(java.util.concurrent)下的常用子類

    Java中JUC包(java.util.concurrent)下的常用子類

    相信大家已經(jīng)對(duì)并發(fā)機(jī)制中出現(xiàn)的很多的常見(jiàn)知識(shí)點(diǎn)進(jìn)行了總結(jié),下面這篇文章主要給大家介紹了關(guān)于Java中JUC包(java.util.concurrent)下的常用子類的相關(guān)資料,文中通過(guò)圖文以及示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12

最新評(píng)論

天门市| 洱源县| 闵行区| 阳春市| 金山区| 汉寿县| 苏尼特左旗| 屏边| 交城县| 广平县| 马边| 措勤县| 涟源市| 方正县| 宜君县| 海南省| 麻江县| 铁岭市| 汝城县| 襄汾县| 綦江县| 吉木萨尔县| 韩城市| 壶关县| 栾川县| 扶绥县| 大城县| 葵青区| 息烽县| 安西县| 东城区| 棋牌| 司法| 城口县| 旅游| 乌兰县| 香格里拉县| 汉阴县| 雅安市| 灵山县| 马尔康县|