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