介紹
@Transaction 是 Spring 提供用來控制事務回滾/提交的一個註解,讓我們從編程式註解轉換到聲明式註解。在這裏就不做過多的撰述,今天主要來看下 @Transaction 裏面的屬性使用。
作用域
@Transaction 可以寫在類、接口、方法上
- 當標註在類上的時候:表示給該類所有的 public 方法添加上 @Transaction 註解
- 當標註在接口上的時候:Spring 建議不要在接口或者接口方法上使用該註解,因爲這只有在使用基於接口的代理時它纔會生效。像 CGLib 動態代理採用繼承的方式將會導致 @Transactional 註解失效
- 當標註在方法上的時候:事務的作用域就只在該方法上生效,並且如果類及方法上都配置 @Transaction 註解時,方法的註解會覆蓋類上的註解
屬性
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
可以看到 @Transaction 相關所有的屬性值
字段名 | 類型 | 含義 |
value | String | 主要用來指定不同的事務管理器 滿足在同一個系統中,存在不同的事務管理器 |
propagation | enum: Propagation | 可選的事務傳播行爲設置 |
isolation | enum: Isolation | 可選的事務隔離級別設置 |
readOnly | boolean | 讀寫或只讀事務,默認讀寫 |
timeout | int (in seconds granularity) | 事務超時時間設置 |
rollbackFor | Class對象數組,必須繼承自Throwable | 導致事務回滾的異常類數組 |
rollbackForClassName | 類名數組,必須繼承自Throwable | 導致事務回滾的異常類名字數組 |
noRollbackFor | Class對象數組,必須繼承自Throwable | 不會導致事務回滾的異常類數組 |
noRollbackForClassName | 類名數組,必須繼承自Throwable | 不會導致事務回滾的異常類名字數組 |
value
value 主要用來指定不同的事務管理器,滿足在同一個系統中,存在不同的事務管理器。如果在 Spring 中,配置了多個數據源聲明瞭多個事務管理器,可以通過該參數來進行指定事務管理器。
propagation
事務傳播行爲有一下7種,默認是 REQUIRED 傳播機制。
值 | 含義 |
REQUIRED |
如果當前存在事務,則加入該事務; 如果當前不存在事務,則創建一個新的事務; |
SUPPORTS |
如果當前存在事務,則加入該事務; 如果當前不存在事務,則以非事務的方式繼續運行; |
MANDATORY |
如果當前存在事務,則加入該事務; 如果當前不存在事務,則拋出異常; |
REQUIRES_NEW |
如果當前不存在事務,重新創建一個新的事務; 如果當前存在事務,則暫停當前的事務; |
NOT_SUPPORTED |
以非事務的方式運行 如果當前存在事務,則暫停當前的事務 |
NEVER |
以非事務的方式運行 如果當前存在事務,則拋出異常 |
NESTED |
如果當前存在事務,則在該事務內嵌套事務運行; 如果當前不存在事務,則創建一個新的事務; |
這裏比較容易引起疑問的是 REQUIRED 和 NESTED 有什麼差別?也是面試的重災區。
@Service
public class ServiceA{
@Autowired
private ServiceB serviceB;
@Transaction
public void A(){
try{
serviceB.B();
}catch(Exception e){
e.printStackTrace();
}
//僞代碼,執行數據庫修改操作
}
}
@Service
public class ServiceB{
@Transaction(propagation = Propagation.REQUIRED)
public void B(){
//僞代碼,執行數據庫修改操作
}
}
如上存在 ServiceA、ServiceB 兩個類和A、B兩個方法(這裏指的異常都是 RuntimeException 異常或其子類)
- 情況一:A方法中出現了異常,結果A、B方法修改操作都會被回滾
- 情況二:B方法中出現了異常,結果A、B方法修改操作都會被回滾
@Service
public class ServiceB{
@Transaction(propagation = Propagation.NESTED)
public void B(){
//僞代碼,執行數據庫修改操作
}
}
接下來將B方法的 propagation 修改爲 NESTED 事務傳播機制
- 情況一:A方法中出現了異常,結果A、B方法修改操作都會被回滾
- 情況二:B方法中出現了異常,結果B方法修改操作被回滾,A方法修改操作提交
通過如上案例,希望可以幫助大家掌握這兩個事務傳播機制的差異。
isolation
isolation 對應的事務隔離級別與 MySQL 一致,有不熟悉的同學可以另外瞭解一下。主要用來避免髒讀、不可重複讀以及幻讀的問題。
readOnly
設置爲 true:表示只讀,如果該方法內存在增、刪、改操作則會拋出異常;
設置爲false(默認):表示讀寫,增、刪、改、查操作都允許;
rollbackFor 和 rollbackForClassName
這兩個屬性都是用來指定回滾的異常類型
- rollbackFor:通過類進行指定,如@Transactional(rollbackFor = {Exception.class})
- rollbackForClassName:通過類名進行指定,如@Transactional(rollbackForClassName = {"java.lang.Exception"})
可能有同學會有疑問,爲什麼需要知道回滾的異常類型呢?不是默認有異常就回滾嘛?
Spring默認拋出了未檢查unchecked異常(繼承自 RuntimeException 的異常)或者 Error纔回滾事務;其他異常不會觸發回滾事務。如果在事務中拋出其他類型的異常,但卻期望 Spring 能夠回滾事務,就需要指定 rollbackFor 或者 rollbackForClassName 屬性。
noRollbackFor 和 noRollbackForClassName
與 rollbackFor 和 rollbackForClassName 相反,是用來指定不回滾的異常類型,使用方法一致。
手動回滾
spring 也提供了 @Transaction 對應的手動回滾方式
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
如果在代碼中一定要 catch 住異常記得使用手動回滾的方式或者重新拋出一個異常。
失效場景
接下來看一下場景的 @Transaction 註解失效的場景,希望大家以後能夠避免踩坑:
- @Transactional 應用在非 public 修飾的方法上
- @Transactional 註解屬性 propagation 設置問題
- @Transactional 註解屬性 rollbackFor、noRollbackFor 設置問題
- 同一個類中方法調用,導致 @Transactional 失效
- 異常被 catch 導致 @Transactional 失效
總結
本文重點闡述了 @Transaction 註解的屬性使用,幫助大家快速瞭解。希望能夠有所幫助!!!