Spring注解@Transactional失效的場景分析
一、前言
emm,又又又踩坑啦。這次的需求主要是對逾期計算的需求任務(wù)進行優(yōu)化,現(xiàn)有的計算任務(wù)運行時間太長了。簡單描述下此次的問題:在項目中進行多個數(shù)據(jù)庫執(zhí)行操作時,我們期望的是將其整個封裝成一個事務(wù),要么全部成功,或者全部失敗,然而在自測異常場景時發(fā)現(xiàn),里面涉及的第一個數(shù)據(jù)狀態(tài)更新成功了,但是后面的數(shù)據(jù)在插入出現(xiàn)異常,后面查詢數(shù)據(jù)表發(fā)現(xiàn),該數(shù)據(jù)的狀態(tài)已經(jīng)被更新成功啦。
emmm,查看代碼發(fā)現(xiàn)確實是使用了@Transactional注解沒問啊。于是通過查詢網(wǎng)上相關(guān)資料發(fā)現(xiàn),在使用Spring中事務(wù)注解@Transactional時會存在幾種場景下該注解失效,即不能按照預(yù)期封裝成一個事務(wù)操作,于是對該注解進行學(xué)習(xí)并對相關(guān)失效場景進行分析,整理文章如下;
二、@Transactional注解失效場景實例驗證
1、@Transactional注解屬性
屬性 | 類型 | 描述 |
---|---|---|
value | String | 可選的限定描述符,指定使用的事務(wù)管理器 |
propagation | Enum:Propagation· | 可選的事務(wù)傳播行為設(shè)置 |
isolation | Enum:Isolation | 可選的事務(wù)隔離級別設(shè)置 |
readOnly | boolean | 讀寫或只讀事務(wù),默認讀寫 |
timeout | int | 事務(wù)超時時間設(shè)置 |
rollbackFor | Class對象數(shù)組,必須繼承自Throwable | 導(dǎo)致事務(wù)回滾的異常類數(shù)組 |
rollbackForClassName | 類名數(shù)組,必須繼承自Throwable | 導(dǎo)致事務(wù)回滾的異常類名字數(shù)組 |
noRollbackFor | Class對象數(shù)組,必須繼承自Throwable | 不會導(dǎo)致事務(wù)回滾的異常類數(shù)組 |
noRollbackForClassName | 類名數(shù)組,必須繼承自Throwable | 不會導(dǎo)致事務(wù)回滾的異常類名字數(shù)組 |
2、 propagation屬性
propagation代表事務(wù)的傳播行為,默認值為Propagation.REQUIRED
屬性 | 描述 |
---|---|
Propagation.REQUIRED | 若當(dāng)前存在事務(wù)則加入該事務(wù),若不存在則創(chuàng)建一個新事務(wù)(默認) |
Propagation.SUPPORTS | 若當(dāng)前存在事務(wù)則加入該事務(wù),若不存在則以非事務(wù)的方式繼續(xù)進行 |
Propagation.MANDATORY | 若當(dāng)前存在事務(wù)則加入該事務(wù),若不存在則拋出異常 |
Propagation.REQUIRES_NEW | 重新創(chuàng)建一個新的事務(wù),若當(dāng)前存在事務(wù)則暫定當(dāng)前事務(wù) |
Propagation.NOT_SUPPORTED | 以非事務(wù)的方式運行,若當(dāng)前存在事務(wù)則暫定當(dāng)前事務(wù) |
Propagation.NEVER | 以非事務(wù)的方式運行,若當(dāng)前存在事務(wù)則拋出異常 |
Propagation.NESTED | 與Propagation.REQUIRED效果一樣 |
3、 @Transactional注解使用場景?
@Transactional注解可以作用在接口、類、類方法中。
當(dāng)作用于類時,表示所有該類的public方法都配置相同的事務(wù)屬性信息。
當(dāng)作用于方法時,當(dāng)類配置了@Transactional注解,方法也配置了@Transactional,方法的事務(wù)會覆蓋類的事務(wù)配置信息。
當(dāng)作用于接口時,不推薦使用,因為在接口使用@Transactional并且配置了Spring AOP使用CGLib動態(tài)代理將會導(dǎo)致其失效。
4、 @Transactional注解失效場景?
- @Transactional注解作用在非public修飾的方法上,會失效。
失效原因:在Spring AOP代理時,TransactionInterceptor(事務(wù)攔截器)在目標(biāo)方法執(zhí)行前后進行攔截,DynamicAdvisedInterceptor(CglibAopProxy的內(nèi)部類)的Intercept方法或JDKDynamicAopProxy的invoke方法會間接調(diào)用AbstractFallbackTransationAttributeSource的computeTransactionAttribute方法,獲取@Transactional注解的事務(wù)配置信息。
1 protected TransactionAttribute computeTransactionAttribute(Method method, 2 Class<?> targetClass) { 3 // Don't allow no-public methods as required. 4 if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { 5 return null; 6 }
此方法會檢查目標(biāo)方法的修飾符是否為public,非public作用域則不會獲取@transactional的屬性配置信息。其中protected、private修飾的方法上使用 @Transactional注解,事務(wù)會失效但不會有任何報錯。
- @Transactional注解屬性propagation設(shè)置錯誤導(dǎo)致注解失效
失效原因:配置錯誤, PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER三種事務(wù)傳播方式不會發(fā)生回滾。
? 實例驗證:寫了一個demo進行測試。demo主要功能如下:執(zhí)行兩次數(shù)據(jù)庫插入操作,并在擴展信息字段中添加備注;
? 運行結(jié)果如下,構(gòu)造的單號不存在訂單查詢?yōu)榭沼|發(fā)異常,觀察數(shù)據(jù)庫發(fā)現(xiàn),第一次數(shù)據(jù)庫插入操作已經(jīng)執(zhí)行成功,故而驗證@Transactional注解失效;
- @Transactional注解屬性rollbackFor設(shè)置錯誤導(dǎo)致注解失效
rollbackFor可以指定能夠觸發(fā)事務(wù)回滾的異常類型。Spring默認拋出了unchecked異常(繼承自RuntimeException)或者Error才會回滾事務(wù)。若事務(wù)中拋出了其他類型的異常,但卻期望Spring能夠回滾事務(wù),就需要指定rollbackFor屬性,否則就會失效。
- 同一類中方法調(diào)用,導(dǎo)致@Transactional失效
比如類demo中有方法A和B,方法B中使用@Transactional注解,方法A沒有注解,但是demo類通過方法A調(diào)用方法B,像這種間接調(diào)用會導(dǎo)致方法B中的@Transactional事務(wù)注解失效。
失效原因:只有當(dāng)事務(wù)方法被當(dāng)前類以外的代碼調(diào)用時,才會有Spring生成的代理對象管理。(Spring AOP代理機制造成的)。
? 實例驗證:demo中構(gòu)造場景為在同一個類中,在test方法中添加@Transactional注解,querRiskScore方法中不添加該注解,然后在querRiskScore方法中調(diào)用test方法;觀察下多個插入操作是否會因為異常而中斷回滾;
? 運行結(jié)果如下,還是通過構(gòu)造的單號不存在訂單查詢?yōu)榭沼|發(fā)異常,觀察數(shù)據(jù)庫發(fā)現(xiàn),第一次數(shù)據(jù)庫插入操作已經(jīng)執(zhí)行成功,第二次數(shù)據(jù)插入操作失敗,并沒有因為異常而觸發(fā)事務(wù)操作,故而驗證@Transactional注解方法間的調(diào)用會失效;
- 多線程任務(wù)可能導(dǎo)致@Transaction案例失效
失效原因:線程不屬于Spring托管,故線程不能夠默認使用Spring的事務(wù),也不能獲取Spring注入的bean,在被Spring聲明式事務(wù)管理的方法內(nèi)開啟多線程,多線程內(nèi)的方法不被事務(wù)控制。
- 異常被方法內(nèi)catch捕獲導(dǎo)致@Transactional失效
比如B方法內(nèi)部拋了異常,而A方法此時try-catch了B方法的異常,則該事務(wù)不能正?;貪L。
失效原因:因為B方法中拋出異常以后,標(biāo)識當(dāng)前事務(wù)需要rollback,但是A方法中由于你手動的捕獲這個異常并進行處理,A方法認為當(dāng)前事務(wù)應(yīng)該正常commit,此時就出現(xiàn)前后不一致,會拋出org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only異常。
? 實例驗證:這個場景的本質(zhì)還是異常被捕獲導(dǎo)致無法正常的拋出,進而導(dǎo)致@Transactional注解無法正常工作,我簡化了下demo實例場景,構(gòu)造場景如下:在querRiskScore方法中添加@Transactional注解,然后在querRiskScore方法中對異常進行捕獲;觀察下多個插入操作是否會因為異常而中斷回滾;
? 運行結(jié)果如下,還是通過構(gòu)造的單號不存在訂單查詢?yōu)榭沼|發(fā)異常,但是我們在方法內(nèi)部對該異常進行捕獲,并未向上層拋出,我們期望的場景是兩次數(shù)據(jù)插入執(zhí)行失敗,但是觀察數(shù)據(jù)庫發(fā)現(xiàn),第一次數(shù)據(jù)庫插入操作已經(jīng)執(zhí)行成功,第二次數(shù)據(jù)插入執(zhí)行成功,與我們的預(yù)期結(jié)果不符,故而驗證@Transactional注解在方法中異常被捕獲的場景中會失效;
究其原因:Spring的事務(wù)是在調(diào)用業(yè)務(wù)方法之前開始的,業(yè)務(wù)方法執(zhí)行完畢之后才執(zhí)行commit 或 rollback,事務(wù)是否執(zhí)行取決于是否拋出runtime異常,如果拋出runtime exception并在你的業(yè)務(wù)方法中并沒有catch到的話,事務(wù)就會回滾。
三、“事務(wù)”知識回顧
1.什么是事務(wù)?
事務(wù)(Transaction)是由一系列對系統(tǒng)中數(shù)據(jù)進行訪問與更新的操作組成的一個程序執(zhí)行邏輯單元(Unit)。
通常我們所指的事務(wù)是指數(shù)據(jù)庫事務(wù),使用數(shù)據(jù)庫事務(wù)有以下兩處優(yōu)點:
當(dāng)多個應(yīng)用程序并發(fā)訪問數(shù)據(jù)庫時,事務(wù)可以在這些應(yīng)用程序之間提供一個隔離方法,以防止彼此的操作互相干擾。
事務(wù)為數(shù)據(jù)庫操作序列提供了一個從失敗恢復(fù)到正常狀態(tài)的方法,同時提供了數(shù)據(jù)庫即使在異常狀態(tài)下仍能保持數(shù)據(jù)一致性的方法。
2. 事務(wù)具有的特性?
原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性,簡稱事務(wù)的ACID特性。
- 原子性
事務(wù)的原子性是指事務(wù)必須是一個原子的操作序列單元,即事務(wù)中包含的各項操作在一次執(zhí)行過程中只會出現(xiàn)兩種狀態(tài):全部成功執(zhí)行、全部不執(zhí)行。任何一項操作失敗都將導(dǎo)致整個事務(wù)失敗,同時其他已經(jīng)被執(zhí)行的操作都將被撤銷并回滾,只打所有的操作全部成功,整個事務(wù)才算是成功完成。
- 一致性
事務(wù)的一致性是指事務(wù)的執(zhí)行不能破壞數(shù)據(jù)庫數(shù)據(jù)的完整性和一致性,一個事務(wù)在執(zhí)行之前和執(zhí)行之后,數(shù)據(jù)庫都必須處于一致性狀態(tài)。也就是說,事務(wù)執(zhí)行的結(jié)果必須是使數(shù)據(jù)庫從一個一致性狀態(tài)轉(zhuǎn)變到另一個一致性狀態(tài),因此當(dāng)數(shù)據(jù)庫只包含成功事務(wù)提交 的結(jié)果時,就能說數(shù)據(jù)庫處于一致性狀態(tài)。而如果數(shù)據(jù)庫系統(tǒng)在運行過程中發(fā)生故障, 有些事務(wù)尚未完成就被迫中斷,這些未完成的事務(wù)對數(shù)據(jù)庫所做的修改有一部分已寫入物理數(shù)據(jù)庫,這時數(shù)據(jù)庫就處于一種不正確的狀態(tài),或者說是不一致的狀態(tài)。
- 隔離性
事務(wù)的隔離性是指在并發(fā)環(huán)境中,并發(fā)的事務(wù)是相互隔離的,一個事務(wù)的執(zhí)行不能被其他事務(wù)干擾。也就是說,不同的事務(wù)并發(fā)操縱相同的數(shù)據(jù)時,每個事務(wù)都有各自完整的數(shù)據(jù)空間,即一個事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對其他并發(fā)事務(wù)是隔離的,并發(fā)執(zhí)行的 各個事務(wù)之間不能互相干擾。
- 持久性
事務(wù)一旦提交,其所做的修改就會永久保存到數(shù)據(jù)庫中,即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其有任何影響。需要注意的是,事務(wù)的持久性不能做到100%的持久,只能從事務(wù)本身的角度來保證永久性,而一些外部原因?qū)е聰?shù)據(jù)庫發(fā)生故障,如硬盤損壞,那么所有提交的數(shù)據(jù)可能都會丟失。
3. 什么是Spring中的事務(wù)?
Spring中同樣提供了很好的事務(wù)管理機制,主要分為編程式事務(wù)和聲明式事務(wù)。
- 編程式事務(wù)
是指在代碼中手動的管理事務(wù)的提交、回滾等操作,代碼侵入性比較強。編程式事務(wù)方式需要開發(fā)者在代碼中手動的管理事務(wù)的開啟、提交、回滾等操作。
public void test() { TransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def); try { // 事務(wù)操作 // 事務(wù)提交 transactionManager.commit(status); } catch (DataAccessException e) { // 事務(wù)提交 transactionManager.rollback(status); throw e; } }
- 聲明式事務(wù)
聲明式事務(wù)是基于AOP面向切面,它將具體業(yè)務(wù)和事務(wù)處理部分解耦,代碼侵入性很低,實際開發(fā)中比較常用。我們常用TX和AOP的xml配置文件方式和@Transactional注解方式。
?聲明式事務(wù)的優(yōu)點:
對代碼無侵入性,方法內(nèi)只需要寫業(yè)務(wù)邏輯,節(jié)省很多代碼量。
?聲明式事務(wù)的缺點:
1、聲明式事務(wù)粒度問題:聲明式事務(wù)的局限就是最小粒度要作用在方法上,且不適合耗時長、高并發(fā)場景。
2、聲明式事務(wù)容易被開發(fā)者忽略,當(dāng)事務(wù)嵌套的方法中存在RPC遠程調(diào)用、MQ發(fā)送、Redis更行、文件寫入等操作可能存在以下場景:
? 事務(wù)嵌套的方法中RPC調(diào)用成功了,但是本地事務(wù)回滾導(dǎo)致RPC調(diào)用無法回滾(暫不討論分布式事務(wù))。
?事務(wù)嵌套的方法中遠程調(diào)用會拉長整個事務(wù)周期,導(dǎo)致事務(wù)的數(shù)據(jù)庫連接一致被占用,類似操作過多會導(dǎo)致數(shù)據(jù)庫連接池耗盡。
3、聲明式事務(wù)使用錯誤會導(dǎo)致在某些場景下失效。
四、總結(jié)
以上就是Spring注解@Transactional失效的場景分析的詳細內(nèi)容,更多關(guān)于Spring注解@Transactional失效的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring定時任務(wù)使用及如何使用郵件監(jiān)控服務(wù)器
這篇文章主要介紹了Spring定時任務(wù)使用及如何使用郵件監(jiān)控服務(wù)器,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-07-07IDEA 設(shè)置顯示內(nèi)存的使用情況和內(nèi)存回收的方法
這篇文章主要介紹了IDEA 設(shè)置顯示內(nèi)存的使用情況和內(nèi)存回收的方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04Java報錯:Java.io.FileNotFoundException解決方法
這篇文章主要介紹了Java.io.FileNotFoundException的產(chǎn)生原因和解決方法,造成這個報錯的原因可能有文件路徑錯誤、文件被刪除或移動和權(quán)限問題,文中將解決的辦法介紹的非常詳細,需要的朋友可以參考下2024-12-12