Spring Boot 中使用 @Transactional 註解配置事務管理
事務管理是應用系統開發中必不可少的一部分。Spring 為事務管理提供了豐富的功能支援。Spring 事務管理分為程式設計式和宣告式的兩種方式。程式設計式事務指的是通過編碼方式實現事務;宣告式事務基於 AOP,將具體業務邏輯與事務處理解耦。宣告式事務管理使業務程式碼邏輯不受汙染, 因此在實際使用中宣告式事務用的比較多。宣告式事務有兩種方式,一種是在配置檔案(xml)中做相關的事務規則宣告,另一種是基於@Transactional
註解的方式。本文將著重介紹基於@Transactional
註解的事務管理。
需要明確幾點:
- 預設配置下Spring 只會回滾執行時、未檢查異常(繼承自 RuntimeException 的異常)或者 Error。
@Transactional
註解只能應用到 public 方法才有效。參考這裡 Method visibility and @Transactional
以下的示例使用的是 mybatis,所以 spring boot 會自動配置一個DataSourceTransactionManager
,我們只需在方法(或者類)加上@Transactional
註解,就自動納入 Spring 的事務管理了。
簡單的使用方法
只需在方法加上@Transactional
註解就可以了。
如下有一個儲存使用者的方法,加入@Transactional
註解,使用預設配置,丟擲異常之後,事務會自動回滾,資料不會插入到資料庫。
@Transactional
@Override
public void save() {
User user = new User("服部半藏");
userMapper.insertSelective(user);
if (true) {
throw new RuntimeException("save 拋異常了");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
我們可以從日誌裡面看出這些資訊
@Transactional 註解的屬性介紹
下面分別介紹一下@Transactional
的幾個屬性。
value 和 transactionManager 屬性
它們兩個是一樣的意思。當配置了多個事務管理器時,可以使用該屬性指定選擇哪個事務管理器。
propagation 屬性
事務的傳播行為,預設值為 Propagation.REQUIRED。
可選的值有:
-
Propagation.REQUIRED
如果當前存在事務,則加入該事務,如果當前不存在事務,則建立一個新的事務。
-
Propagation.SUPPORTS
如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續執行。
-
Propagation.MANDATORY
如果當前存在事務,則加入該事務;如果當前不存在事務,則丟擲異常。
-
Propagation.REQUIRES_NEW
重新建立一個新的事務,如果當前存在事務,暫停當前的事務。
-
Propagation.NOT_SUPPORTED
以非事務的方式執行,如果當前存在事務,暫停當前的事務。
-
Propagation.NEVER
以非事務的方式執行,如果當前存在事務,則丟擲異常。
-
Propagation.NESTED
和 Propagation.REQUIRED 效果一樣。
這些概念理解起來實在是有點兒抽象,後文會用程式碼示例解釋說明。
isolation 屬性
事務的隔離級別,預設值為 Isolation.DEFAULT。
可選的值有:
-
Isolation.DEFAULT
使用底層資料庫預設的隔離級別。
-
Isolation.READ_UNCOMMITTED
- Isolation.READ_COMMITTED
- Isolation.REPEATABLE_READ
- Isolation.SERIALIZABLE
timeout 屬性
事務的超時時間,預設值為-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。
readOnly 屬性
指定事務是否為只讀事務,預設值為 false;為了忽略那些不需要事務的方法,比如讀取資料,可以設定 read-only 為 true。
rollbackFor 屬性
用於指定能夠觸發事務回滾的異常型別,可以指定多個異常型別。
noRollbackFor 屬性
丟擲指定的異常型別,不回滾事務,也可以指定多個異常型別。
@Transactional 的 propagation 屬性程式碼示例
比如如下程式碼,save 方法首先呼叫了 method1 方法,然後丟擲了異常,就會導致事務回滾,如下兩條資料都不會插入資料庫。
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
method1();
User user = new User("服部半藏");
userMapper.insertSelective(user);
if (true) {
throw new RuntimeException("save 拋異常了");
}
}
public void method1() {
User user = new User("宮本武藏");
userMapper.insertSelective(user);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
現在有需求如下,就算 save 方法的後面拋異常了,也不能影響 method1 方法的資料插入。或許很多人的想法如下,給 method1 頁加入一個新的事務,這樣 method1 就會在這個新的事務中執行,原來的事務不會影響到新的事務。比如 method1 方法上面再加入註解 @Transactional,設定 propagation 屬性為 Propagation.REQUIRES_NEW,程式碼如下。
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
method1();
User user = new User("服部半藏");
userMapper.insertSelective(user);
if (true) {
throw new RuntimeException("save 拋異常了");
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
User user = new User("宮本武藏");
userMapper.insertSelective(user);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
執行之後,發現然並卵,資料也是沒有插入資料庫。怎麼肥四,看起來很不科學。我們先來看看日誌內容。
從日誌內容可以看出,其實兩個方法都是處於同一個事務中,method1 方法並沒有建立一個新的事務。
這就得看看Spring 官方文件了。
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
大概意思:在預設的代理模式下,只有目標方法由外部呼叫,才能被 Spring 的事務攔截器攔截。在同一個類中的兩個方法直接呼叫,是不會被 Spring 的事務攔截器攔截,就像上面的 save 方法直接呼叫了同一個類中的 method1方法,method1 方法不會被 Spring 的事務攔截器攔截。可以使用 AspectJ 取代 Spring AOP 代理來解決這個問題,但是這裡暫不討論。
為了解決這個問題,我們可以新建一個類。
@Service
public class OtherServiceImpl implements OtherService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void method1() {
User user = new User("風魔小太郎");
userMapper.insertSelective(user);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
然後在 save 方法中呼叫 otherService.method1 方法
@Autowired
private OtherService otherService;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
otherService.method1();
User user = new User("服部半藏");
userMapper.insertSelective(user);
if (true) {
throw new RuntimeException("save 拋異常了");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
這下,otherService.method1 方法的資料插入成功,save 方法的資料未插入,事務回滾。
繼續看一下日誌內容
從日誌可以看出,首先建立了 save 方法的事務,由於 otherService.method1 方法的 @Transactional 的 propagation 屬性為 Propagation.REQUIRES_NEW ,所以接著暫停了 save 方法的事務,重新建立了 otherService.method1 方法的事務,接著 otherService.method1 方法的事務提交,接著 save 方法的事務回滾。這就印證了只有目標方法由外部呼叫,才能被 Spring 的事務攔截器攔截。
還有幾個示例如下。
接著把 save 方法的 @Transactional 註解去掉,otherService.method1 的 @Transactional 註解保持不變,從日誌就可以看出,只會建立一個 otherService.method1 方法的事務,兩條資料都會插入。
@Autowired
private OtherService otherService;
// @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
otherService.method1();
User user = new User("服部半藏");
userMapper.insertSelective(user);
if (true) {
throw new RuntimeException("save 拋異常了");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
接著把 save 方法的 @Transactional 註解去掉,save 方法改為呼叫內部的 method1 方法,從日誌就可以看出,完全沒有建立任何事務,兩條資料都會插入。
// @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
method1();
User user = new User("服部半藏");
userMapper.insertSelective(user);
if (true) {
throw new RuntimeException("save 拋異常了");
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
User user = new User("宮本武藏");
userMapper.insertSelective(user);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
這樣,其他的幾個 propagation 屬性值也就比較好理解了。
@Transactional 事務實現機制
在應用系統呼叫聲明瞭@Transactional
的目標方法時,Spring Framework 預設使用 AOP 代理,在程式碼執行時生成一個代理物件,根據@Transactional
的屬性配置資訊,這個代理物件決定該宣告@Transactional
的目標方法是否由攔截器TransactionInterceptor
來使用攔截,在TransactionInterceptor
攔截時,會在目標方法開始執行之前建立並加入事務,並執行目標方法的邏輯, 最後根據執行情況是否出現異常,利用抽象事務管理器AbstractPlatformTransactionManager
操作資料來源DataSource
提交或回滾事務。
Spring AOP 代理有CglibAopProxy
和JdkDynamicAopProxy
兩種,以CglibAopProxy
為例,對於CglibAopProxy
,需要呼叫其內部類的DynamicAdvisedInterceptor
的 intercept 方法。對於JdkDynamicAopProxy
,需要呼叫其 invoke 方法。
正如上文提到的,事務管理的框架是由抽象事務管理器AbstractPlatformTransactionManager
來提供的,而具體的底層事務處理實現,由PlatformTransactionManager
的具體實現類來實現,如事務管理器DataSourceTransactionManager
。不同的事務管理器管理不同的資料資源DataSource
,比如DataSourceTransactionManager
管理 JDBC 的Connection
。
原始碼地址
參考資料
結語
由於本人知識和能力有限,文中如有沒說清楚的地方,希望大家能在評論區指出,以幫助我將博文寫得更好。