欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于Spring的@Transaction導(dǎo)致數(shù)據(jù)庫(kù)回滾全部生效問(wèn)題(又刪庫(kù)跑路)

 更新時(shí)間:2021年05月15日 10:54:49   作者:公眾號(hào)-JavaEdge  
使用@Transactional一鍵開(kāi)啟聲明式事務(wù), 這就真的事務(wù)生效了?過(guò)于信任框架總有“意外驚喜”。本文通過(guò)案例給大家詳解關(guān)于Spring的@Transaction導(dǎo)致數(shù)據(jù)庫(kù)回滾全部生效問(wèn)題,感興趣的朋友一起看看吧

1 前言

很多需要使用事務(wù)的場(chǎng)景,都只是在方法上直接添加個(gè)@Transactional注解


但是,你以為這真的夠了嗎?

事務(wù)如果未達(dá)到完美效果,在開(kāi)發(fā)和測(cè)試階段都難以被發(fā)現(xiàn),因?yàn)槟汶y以考慮到太多意外場(chǎng)景。但當(dāng)業(yè)務(wù)數(shù)據(jù)量發(fā)展,就可能導(dǎo)致大量數(shù)據(jù)不一致的問(wèn)題,就會(huì)造成前人栽樹(shù)后人踩坑,需要大量人力排查解決問(wèn)題和修復(fù)數(shù)據(jù)。

2 如何確認(rèn)Spring事務(wù)生效了?

使用@Transactional一鍵開(kāi)啟聲明式事務(wù), 這就真的事務(wù)生效了?過(guò)于信任框架總有“意外驚喜”。來(lái)看如下案例

領(lǐng)域?qū)?實(shí)體

領(lǐng)域服務(wù)

createUserError1調(diào)用private方法

createUserPrivate,被@Transactional注解。當(dāng)傳入的用戶名包含test則拋異常,讓用戶的創(chuàng)建操作失敗

getUserCount

用戶接口層

調(diào)用UserService#createUserError1

測(cè)試結(jié)果
即便用戶名不合法,用戶也能創(chuàng)建成功。刷新瀏覽器,多次發(fā)現(xiàn)有十幾個(gè)的非法用戶注冊(cè)。 @Transactional生效原則 public方法

除非特殊配置(比如使用AspectJ靜態(tài)織入實(shí)現(xiàn)AOP),@Transactional必須定義在public方法才生效。

因?yàn)镾pring的AOP,private方法無(wú)法被代理到,自然也無(wú)法動(dòng)態(tài)增強(qiáng)事務(wù)處理邏輯。

那簡(jiǎn)單,把createUserPrivate方法改為public不就行了。
但發(fā)現(xiàn)事務(wù)依舊未生效。

必須通過(guò)代理過(guò)的類(lèi)從外部調(diào)用目標(biāo)方法

要調(diào)用增強(qiáng)過(guò)的方法必然是調(diào)用代理后的對(duì)象。
嘗試修改UserService,注入一個(gè)self,然后再通過(guò)self實(shí)例調(diào)用標(biāo)記有 @Transactional 注解的createUserPublic方法。設(shè)置斷點(diǎn)可以看到,self是由Spring通過(guò)CGLIB方式增強(qiáng)過(guò)的類(lèi):

CGLIB通過(guò)繼承實(shí)現(xiàn)代理類(lèi),private方法在子類(lèi)不可見(jiàn),所以無(wú)法進(jìn)行事務(wù)增強(qiáng)。而this指針代表調(diào)用對(duì)象本身,Spring不可能注入this,所以通過(guò)this訪問(wèn)方法必然不是代理。
把this改為self,這時(shí)即可驗(yàn)證事務(wù)生效:非法的用戶注冊(cè)操作可回滾。

雖然在UserDomainService內(nèi)部注入自己調(diào)用自己的createUserPublic可正確實(shí)現(xiàn)事務(wù),但這不符常規(guī)。更合理的實(shí)現(xiàn)方式是,讓Controller直接調(diào)用之前定義的UserService的createUserPublic方法。

this/self/Controller調(diào)用UserDomainService

  • this自調(diào)用

無(wú)法走到Spring代理類(lèi)

  • 后兩種

調(diào)用的Spring注入的UserService,通過(guò)代理調(diào)用才有機(jī)會(huì)對(duì)createUserPublic方法進(jìn)行動(dòng)態(tài)增強(qiáng)。

推薦開(kāi)發(fā)時(shí)打開(kāi)Debug日志以了解Spring事務(wù)實(shí)現(xiàn)的細(xì)節(jié)。
比如JPA數(shù)據(jù)庫(kù)訪問(wèn),開(kāi)啟Debug日志:
logging.level.org.springframework.orm.jpa=DEBUG

開(kāi)啟日志后再比較下在UserService中this調(diào)用、Controller中通過(guò)注入的UserService Bean調(diào)用createUserPublic的區(qū)別。

很明顯,this調(diào)用因沒(méi)走代理,事務(wù)沒(méi)有在createUserPublic生效,只在Repository的save生效:

// 在UserService中通過(guò)this調(diào)用public的createUserPublic
[23:04:30.748] [http-nio-45678-exec-5] [DEBUG] [o.s.orm.jpa.JpaTransactionManager:370 ] - 
Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: 
PROPAGATION_REQUIRED,ISOLATION_DEFAULT

[DEBUG] [o.s.orm.jpa.JpaTransactionManager       :370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
//在Controller中通過(guò)注入的UserService Bean調(diào)用createUserPublic
[10:10:47.750] [http-nio-45678-exec-6] [DEBUG] [o.s.orm.jpa.JpaTransactionManager       :370 ] - Creating new transaction with name [org.geekbang.time.commonmistakes.transaction.demo1.UserService.createUserPublic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

這種實(shí)現(xiàn)在Controller里處理異常顯得繁瑣,還不如直接把createUserWrong2@Transactional注解,然后在Controller中直接調(diào)用該方法。
這既能從外部(Controller中)調(diào)用UserService方法,方法又是public的能夠被動(dòng)態(tài)代理AOP增強(qiáng)。

小結(jié)

務(wù)必確認(rèn)調(diào)用被@Transactional注解標(biāo)記的方法被public修飾,并且是通過(guò)Spring注入的Bean進(jìn)行調(diào)用。

但有時(shí)因沒(méi)有正確處理異常,導(dǎo)致事務(wù)即便生效也不一定能回滾。

2 事務(wù)生效不代表能正確回滾

AOP實(shí)現(xiàn)事務(wù):使用try/catch包裹@Transactional注解的方法:

  • 當(dāng)方法出現(xiàn)異常并滿足一定條件,在catch里可設(shè)置事務(wù)回滾
  • 沒(méi)有異常則直接提交事務(wù) 一定條件

只有異常傳播出了被@Transactional注解的方法,事務(wù)才能回滾。

Spring的 TransactionAspectSupport#invokeWithinTransaction 就是在處理事務(wù)。觀察源碼得知,只有捕獲到異常后才能進(jìn)行后續(xù)事務(wù)處理:

默認(rèn)情況下,出現(xiàn)RuntimeException(非受檢異常)或Error,Spring才會(huì)回滾事務(wù)。

Spring的DefaultTransactionAttribute:

  • 受檢異常一般是業(yè)務(wù)異?;蝾?lèi)似另一種方法的返回值,出現(xiàn)這種異??赡軜I(yè)務(wù)還能完成,所以不會(huì)主動(dòng)回滾
  • 而Error或RuntimeException代表非預(yù)期結(jié)果,應(yīng)該回滾

 

事務(wù)無(wú)法正?;貪L的各種慘案 異常無(wú)法傳播出方法

受檢異常

注冊(cè)的同時(shí)會(huì)有一次文件讀,若讀文件失敗,希望用戶注冊(cè)的DB操作回滾。因讀文件拋的是受檢異常,createUserError2傳播出去的也是受檢異常


以上方法雖然避開(kāi)了事務(wù)不生效的坑,但因異常處理不當(dāng),導(dǎo)致異常時(shí)依舊不回滾事務(wù)。

修復(fù)回滾失敗bug 1 手動(dòng)設(shè)置讓當(dāng)前事務(wù)處回滾態(tài)

若希望自己捕獲異常并處理,可手動(dòng)設(shè)置讓當(dāng)前事務(wù)處回滾態(tài)

查看日志,事務(wù)確定回滾。

Transactional code has requested rollback:手動(dòng)請(qǐng)求回滾。

2 注解中聲明,期望所有Exception都回滾事務(wù) 突破默認(rèn)不回滾受檢異常的限制

查看日志,提示回滾:

該案例有DB操作、IO操作,在IO操作問(wèn)題時(shí)期望DB事務(wù)也回滾,以確保邏輯一致性。 小結(jié)

由于異常處理不正確,導(dǎo)致雖然事務(wù)生效,但出現(xiàn)異常時(shí)沒(méi)回滾。
Spring默認(rèn)只對(duì)被@Transactional注解的方法出現(xiàn)RuntimeExceptionError時(shí)回滾,所以若方法捕獲了異常,就需要通過(guò)手寫(xiě)代碼處理事務(wù)回滾。
若希望Spring針對(duì)其他異常也可回滾,可相應(yīng)配置@Transactional注解的rollbackFornoRollbackFor屬性覆蓋Spring的默認(rèn)配置。

有些業(yè)務(wù)可能包含多次DB操作,不一定希望將兩次操作作為一個(gè)事務(wù),這時(shí)就需仔細(xì)考慮事務(wù)傳播的配置。

3 事務(wù)傳播配置是否符合業(yè)務(wù)邏輯

案例

用戶注冊(cè):會(huì)插入一個(gè)主用戶到用戶表,還會(huì)注冊(cè)一個(gè)關(guān)聯(lián)的子用戶。期望將子用戶注冊(cè)的DB操作作為一個(gè)獨(dú)立事務(wù),即使失敗也不影響注冊(cè)主用戶的流程。

UserService:創(chuàng)建主、子用戶

SubUserService:使子用戶注冊(cè)失敗。期望子用戶注冊(cè)作為一個(gè)事務(wù)單獨(dú)回滾而不影響注冊(cè)主用戶

啟動(dòng)調(diào)用后查看日志:事務(wù)回滾了

不對(duì)呀!因?yàn)檫\(yùn)行時(shí)異常逃出被@Transactional注解的createUserWrong,Spring當(dāng)然會(huì)回滾事務(wù)。若期望主方法不回滾,應(yīng)捕獲子方法所拋的異常。

修正方案

subUserService#createSubUserWithExceptionError包上catch,這樣外層主方法createUserError2就不會(huì)出現(xiàn)異常

啟動(dòng)后查看日志注意到:

  • 對(duì)createUserError2開(kāi)啟異常處理
  • 子方法因出現(xiàn)運(yùn)行時(shí)異常,標(biāo)記當(dāng)前事務(wù)為回滾
  • 主方法捕獲異常并打印create sub user error
  • 主方法提交事務(wù)

但Controller出現(xiàn)一個(gè)UnexpectedRollbackException,異常描述提示最終該事務(wù)回滾了且為靜默回滾:因createUserError2本身并無(wú)異常,只不過(guò)提交后發(fā)現(xiàn)子方法已把當(dāng)前事務(wù)設(shè)為回滾,無(wú)法完成提交。

明明無(wú)異常發(fā)生,但事務(wù)也不一定可提交
因?yàn)橹鞣椒ㄗ?cè)主用戶的邏輯和子方法注冊(cè)子用戶的邏輯為同一事務(wù),子邏輯標(biāo)記了事務(wù)需回滾,主邏輯自然也無(wú)法提交。
那么修復(fù)方式就明確了,獨(dú)立子邏輯的事務(wù),即修正SubUserService注冊(cè)子用戶方法,為注解添加propagation = Propagation.REQUIRES_NEW設(shè)置REQUIRES_NEW事務(wù)傳播策略。即執(zhí)行到該方法時(shí)開(kāi)啟新事務(wù),并掛起當(dāng)前事務(wù)。
創(chuàng)建一個(gè)新事務(wù),若存在則暫停當(dāng)前事務(wù)。類(lèi)似同名的EJB事務(wù)屬性。
注:實(shí)際事務(wù)暫停不會(huì)對(duì)所有事務(wù)管理器外的開(kāi)箱。 這特別適于org.springframework.transaction.jta.JtaTransactionManager ,這就需要javax.transaction.TransactionManager被提供給它(這是服務(wù)器特定的標(biāo)準(zhǔn)Java EE)

主方法無(wú)變化,依舊需捕獲異常,防止異常外泄導(dǎo)致主事務(wù)回滾,重命名為createUserRight

修正后再查看日志

Creating new transaction with name createUserRight

對(duì)createUserRight開(kāi)啟主方法事務(wù)
createMainUser finish
創(chuàng)建主用戶完成
Suspending current transaction, creating new transaction with name createSubUserWithExceptionRight
主事務(wù)掛起,開(kāi)啟新事務(wù),即對(duì)createSubUserWithExceptionRight創(chuàng)建子用戶的邏輯
Initiating transaction rollback
子方法事務(wù)回滾
Resuming suspended transaction after completion of inner transaction
子方法事務(wù)完成,繼續(xù)主方法之前掛起的事務(wù)
create sub user error:invalid status
主方法捕獲到了子方法的異常
Committing JPA transaction on EntityManager
主方法的事務(wù)提交了,隨后我們?cè)贑ontroller里沒(méi)看到靜默回滾異常

小結(jié)

若方法涉及多次DB操作,并希望將它們作為獨(dú)立事務(wù)進(jìn)行提交或回滾,即需考慮細(xì)化配置事務(wù)傳播方式,即配置@Transactional注解的Propagation屬性。

4 總結(jié)

若要針對(duì)private方法啟用事務(wù),動(dòng)態(tài)代理方式的AOP不可行,需要使用靜態(tài)織入方式的AOP,也就是在編譯期間織入事務(wù)增強(qiáng)代碼,可以配置Spring框架使用AspectJ來(lái)實(shí)現(xiàn)AOP。

以上就是關(guān)于Spring的@Transaction導(dǎo)致數(shù)據(jù)庫(kù)回滾全部生效問(wèn)題(又刪庫(kù)跑路)的詳細(xì)內(nèi)容,更多關(guān)于Spring @Transaction數(shù)據(jù)庫(kù)回滾的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 特殊數(shù)據(jù)結(jié)構(gòu)之使用Java實(shí)現(xiàn)單調(diào)棧示例

    特殊數(shù)據(jù)結(jié)構(gòu)之使用Java實(shí)現(xiàn)單調(diào)棧示例

    這篇文章主要為大家介紹了特殊數(shù)據(jù)結(jié)構(gòu)之使用Java實(shí)現(xiàn)單調(diào)棧示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 一步步帶你入門(mén)Java中File類(lèi)

    一步步帶你入門(mén)Java中File類(lèi)

    java.io.File類(lèi)是文件和目錄路徑名的抽象表示形式,主要用于文件和目錄的創(chuàng)建、查找和刪除等操作,下面這篇文章主要給大家介紹了關(guān)于入門(mén)Java中File類(lèi)的相關(guān)資料,需要的朋友可以參考下
    2022-03-03
  • mybatis的if判斷integer問(wèn)題

    mybatis的if判斷integer問(wèn)題

    這篇文章主要介紹了mybatis的if判斷integer問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java項(xiàng)目之java+springboot+ssm實(shí)現(xiàn)理財(cái)管理系統(tǒng)設(shè)計(jì)

    Java項(xiàng)目之java+springboot+ssm實(shí)現(xiàn)理財(cái)管理系統(tǒng)設(shè)計(jì)

    這篇文章主要介紹了Java項(xiàng)目java+springboot+ssm實(shí)現(xiàn)理財(cái)管理系統(tǒng)設(shè)計(jì),使用了當(dāng)前較為流行的spring boot,spring,spring mvc,mybatis,shiro框架分頁(yè)處理使用了pagehelper進(jìn)行操作,需要的朋友可以參考一下
    2022-03-03
  • Java程序員編程性能優(yōu)化必備的34個(gè)小技巧(總結(jié))

    Java程序員編程性能優(yōu)化必備的34個(gè)小技巧(總結(jié))

    這篇文章主要介紹了Java程序員編程性能優(yōu)化必備的34個(gè)小技巧(總結(jié)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-07-07
  • 從一道面試題看你對(duì)java的理解程度

    從一道面試題看你對(duì)java的理解程度

    這篇文章主要給大家介紹了關(guān)于如何從一道面試題看你對(duì)java的理解程度的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起看看吧
    2018-09-09
  • SpringBoot自動(dòng)裝配原理詳細(xì)解析

    SpringBoot自動(dòng)裝配原理詳細(xì)解析

    這篇文章主要介紹了SpringBoot自動(dòng)裝配原理詳細(xì)解析,一個(gè)對(duì)象交給Spring來(lái)管理的三種方式 @Bean @Compoment @Import,
    @Bean主要在@Configuration中,通過(guò)方法進(jìn)行注入相關(guān)的Bean,@Compoent與@Service歸為一類(lèi),在類(lèi)上加注入對(duì)應(yīng)的類(lèi),需要的朋友可以參考下
    2024-01-01
  • SpringBoot開(kāi)發(fā)項(xiàng)目,引入JPA找不到findOne方法的解決

    SpringBoot開(kāi)發(fā)項(xiàng)目,引入JPA找不到findOne方法的解決

    這篇文章主要介紹了SpringBoot開(kāi)發(fā)項(xiàng)目,引入JPA找不到findOne方法的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Myeclipse清理項(xiàng)目緩存的幾大方法

    Myeclipse清理項(xiàng)目緩存的幾大方法

    今天小編就為大家分享一篇關(guān)于Myeclipse清理項(xiàng)目緩存的幾大方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • Spring中的retry重試組件詳解

    Spring中的retry重試組件詳解

    這篇文章主要介紹了Spring中的retry重試組件詳解,Retry重試組件是一個(gè)處理重試邏輯的工具,可以在出現(xiàn)異?;蚴∏闆r下自動(dòng)進(jìn)行重試操作,從而提高程序的穩(wěn)定性和可靠性,需要的朋友可以參考下
    2023-10-10

最新評(píng)論