一、作用于接口、接口方法、類以及類方法上
1??當(dāng)作用于類上時,該類的所有 public 方法將都具有該類型的事務(wù)屬性。
2??當(dāng)作用在方法級別時會覆蓋類級別的定義。
3??當(dāng)作用在接口和接口方法時則只有在使用基于接口的代理時它才會生效,也就是 JDK 動態(tài)代理,而不是 Cglib 代理。如果正在使用基于類的代理(也就是 CGLIB 代理)時,那么事務(wù)的設(shè)置將不能被基于類的代理所識別,而且對象也將不會被事務(wù)代理所包裝,因?yàn)樽⒔馐遣荒芾^承的。
Spring 的建議是在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實(shí)現(xiàn)的任何接口上。
4??當(dāng)在 protected、private 或者默認(rèn)可見性的方法上使用 @Transactional 時是不會生效的,也不會拋出任何異常。
5??默認(rèn)情況下,只有來自外部的方法調(diào)用才會被 AOP 代理捕獲,也就是,類內(nèi)部方法調(diào)用本類內(nèi)部的其他方法并不會引起事務(wù)行為,即使被調(diào)用方法使用 @Transactional 進(jìn)行修飾。
二、@Transactional 配置事務(wù)失效的場景
1??@Transactional 應(yīng)用在非 public 修飾的方法上。
注意:protected、private 修飾的方法上使用 @Transactional,雖然事務(wù)無效,但不會有任何報(bào)錯。
2??@Transactional 屬性 propagation 設(shè)置錯誤。
這種失效是由于配置錯誤,若是錯誤的配置以下三種 propagation,事務(wù)將不會發(fā)生回滾:
①@Transactional(propagation=Propagation.SUPPORTS)
:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則以非事務(wù)的方式繼續(xù)運(yùn)行。 ②@Transactional(propagation=Propagation.NOT_SUPPORTED)
:以非事務(wù)方式運(yùn)行,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。
③@Transactional(propagation=Propagation.NEVER)
:以非事務(wù)方式運(yùn)行,如果當(dāng)前存在事務(wù),則拋出異常。
3??@Transactional 屬性 rollbackFor 設(shè)置錯誤。
rollbackFor 可以指定能夠觸發(fā)事務(wù)回滾的異常類型。Spring 默認(rèn)拋出了未檢查 unchecked 異常(繼承自 RuntimeException 的異常)或者 Error 才回滾事務(wù);其他異常不會觸發(fā)回滾事務(wù)。如果在事務(wù)中拋出其他類型的異常,但卻期望 Spring 能夠回滾事務(wù),就需要指定 rollbackFor 屬性。
4??同一個類中方法調(diào)用,導(dǎo)致 @Transactional 失效。
一個類 Test,它的方法 A(未聲明注解事務(wù)),調(diào)用本類的方法 B(聲明有注解事務(wù)。不論是 public 的還是 private 的)。外部調(diào)用方法 A 之后,方法 B 的事務(wù)是不會起作用的。
5??如果異常被try{}catch{}
了,事務(wù)就不回滾。如果想讓事務(wù)回滾必須再往外拋try{}catch{throw new RunTimeException()}
。
如果 B 方法內(nèi)部拋了異常,而 A 方法此時 try catch 了 B 方法的異常,該事務(wù)不能正?;貪L。會拋出異常:
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
因?yàn)楫?dāng) B 中拋出了一個異常以后,B 標(biāo)識當(dāng)前事務(wù)需要 rollback。但是由于 A 手動捕獲該異常并進(jìn)行處理,A 認(rèn)為當(dāng)前事務(wù)應(yīng)該正常 commit。此時就出現(xiàn)了前后不一致,也就是因?yàn)檫@樣,拋出了前面的UnexpectedRollbackException
。
Spring 的事務(wù)是在調(diào)用業(yè)務(wù)方法之前開始的,業(yè)務(wù)方法執(zhí)行完畢之后才執(zhí)行 commit/rollback,事務(wù)是否執(zhí)行取決于是否拋出 RuntimeException。如果拋出 RuntimeException,并在業(yè)務(wù)方法中沒有 catch 的話,事務(wù)會回滾。
在業(yè)務(wù)方法中一般不需要 catch 異常,如果非要 catch 一定要throw new RuntimeException()
,或者注解中指定拋異常類型@Transactional(rollbackFor = Exception.class)
,否則會導(dǎo)致事務(wù)失效,數(shù)據(jù) commit 造成數(shù)據(jù)不一致,所以有些時候 try catch 反倒會畫蛇添足。
6??數(shù)據(jù)庫引擎不支持事務(wù)
事務(wù)能否生效,數(shù)據(jù)庫引擎是否支持事務(wù)是關(guān)鍵。常用的 MySQL 數(shù)據(jù)庫默認(rèn)使用支持事務(wù)的 innodb 引擎。一旦數(shù)據(jù)庫引擎切換成不支持事務(wù)的 MyISAM,那事務(wù)就從根本上失效了。
三、@Transactional 的可用參數(shù)
1??readOnly
該屬性用于設(shè)置當(dāng)前事務(wù)是否為只讀事務(wù),設(shè)置為 true 表示只讀,false 則表示可讀寫,默認(rèn)值為 false。
理解:
如果一次執(zhí)行單條查詢語句,則沒有必要啟用事務(wù)支持,數(shù)據(jù)庫默認(rèn)支持 SQL 執(zhí)行期間的讀一致性。
如果一次執(zhí)行多條查詢語句,例如統(tǒng)計(jì)查詢,報(bào)表查詢,在這種場景下,多條查詢 SQL 必須保證整體的讀一致性。否則,在前條 SQL 查詢之后,后條 SQL 查詢之前,數(shù)據(jù)被其他用戶改變,則該次整體的統(tǒng)計(jì)查詢將會出現(xiàn)讀數(shù)據(jù)不一致的狀態(tài),此時,應(yīng)該啟用事務(wù)支持。
【注意是一次執(zhí)行多次查詢來統(tǒng)計(jì)某些信息,這時為了保證數(shù)據(jù)整體的一致性,要用只讀事務(wù)】
2??rollbackFor
該屬性用于設(shè)置需要進(jìn)行回滾的異常類數(shù)組,當(dāng)方法中拋出指定異常數(shù)組中的異常時,則進(jìn)行事務(wù)回滾。例如:
- 指定單一異常類:
@Transactional(rollbackFor=RuntimeException.class)
- 指定多個異常類:
@Transactional(rollbackFor={RuntimeException.class,BusinessException.class})
3??rollbackForClassName
該屬性用于設(shè)置需要進(jìn)行回滾的異常類名稱數(shù)組,當(dāng)方法中拋出指定異常名稱數(shù)組中的異常時,則進(jìn)行事務(wù)回滾。例如:
- 指定單一異常類名稱:
@Transactional(rollbackForClassName="RuntimeException")
- 指定多個異常類名稱:
@Transactional(rollbackForClassName={"RuntimeException","BusnessException"})
4??noRollbackFor
該屬性用于設(shè)置不需要進(jìn)行回滾的異常類數(shù)組,當(dāng)方法中拋出指定異常數(shù)組中的異常時,不進(jìn)行事務(wù)回滾。
5??timeout
該屬性用于設(shè)置事務(wù)的超時秒數(shù),默認(rèn)值為 -1,表示永不超時。
6??propagation
該屬性用于設(shè)置事務(wù)傳播行為。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED)
四、基于注解@Transactional的事務(wù)
Spring 的事務(wù)基礎(chǔ)架構(gòu)代碼將默認(rèn)地只在拋出 RuntimeException 和 unchecked exceptions 時才標(biāo)識事務(wù)回滾。也就是說,當(dāng)拋出 RuntimeException 或其子類例的實(shí)例時(error 也一樣)默認(rèn)標(biāo)識事務(wù)回滾。從事務(wù)方法中拋出的 Checked exceptions 將不被標(biāo)識進(jìn)行事務(wù)回滾。
1??@Transactional 的異??刂疲J(rèn)是 unChecked Exception 回滾;Checked Exception 不回滾。
2??如果配置了rollbackFor 和 noRollbackFor 且兩個都是用同樣的異常,那么遇到該異常,還是回滾。
3??rollbackFor 和 noRollbackFor 配置也許不會涵蓋所有異常,對于遺漏的按照 unChecked Exception 回滾,Check ed Exception 不回滾。
4??讓 checked 例外也回滾:
在整個方法前加上@Transactional(rollbackFor=Exception.class)
5??讓 unchecked 例外不回滾:
@Transactional(notRollbackFor=RunTimeException.class)
6??不需要事務(wù)管理的(只查詢的)方法:
@Transactional(propagation=Propagation.NOT_SUPPORTED)
注意:
1??@Transactional 標(biāo)識的方法,處理過程盡量的簡單。尤其是帶鎖的事務(wù)方法,能不放在事務(wù)里面的最好不要放在事務(wù)里面??梢詫⒊R?guī)的數(shù)據(jù)庫查詢操作放在事務(wù)前面進(jìn)行,而事務(wù)內(nèi)進(jìn)行增、刪、改、加鎖查詢等操作。