前言
在開發中,相信大家都使用過Spring的事務管理功能。那么,你是否有了解過,Spring的事務傳播行為呢?
Spring中,有7種類型的事務傳播行為。事務傳播行為是Spring框架提供的一種事務管理方式,它不是數據庫提供的。不知道大家是否聽說過“不要在service事務方法中嵌套事務方法,這樣會提交多個事務”的說法,其實這是不准確的。了解了事務傳播行為之后,相信你就會明白!
原創聲明
本文首發於頭條號【Happyjava】。Happy的掘金地址:https://juejin.im/user/5cc2895df265da03a630ddca,Happy的個人博客:(http://blog.happyjava.cn)[http://blog.happyjava.cn]。歡迎轉載,但須保留此段聲明。
Spring中七種事務傳播行為
事務的傳播行為,默認值為 Propagation.REQUIRED。可以手動指定其他的事務傳播行為,如下:
- Propagation.REQUIRED
如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。
- Propagation.SUPPORTS
如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行。
- Propagation.MANDATORY
如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常。
- Propagation.REQUIRES_NEW
重新創建一個新的事務,如果當前存在事務,延緩當前的事務。
- Propagation.NOT_SUPPORTED
以非事務的方式運行,如果當前存在事務,暫停當前的事務。
- Propagation.NEVER
以非事務的方式運行,如果當前存在事務,則拋出異常。
- Propagation.NESTED
如果沒有,就新建一個事務;如果有,就在當前事務中嵌套其他事務。
准備工作
數據庫表:
CREATE TABLE `t_user` (
`id` int(11) NOT NULL,
`password` varchar(255) DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
一個整合了Spring Data JPA的SpringBoot工程,這里就不多說了。
REQUIRED(默認的事務傳播行為)
默認的事務傳播行為是Propagation.REQUIRED,也就是說:如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。
下面,我們就驗證下前面說的“不要循環嵌套事務方法”的問題:
現在有兩個Service,如下:
UserService.java
@Service
public class UserService {
@Autowired
private UserRepo userRepo;
@Transactional(propagation = Propagation.REQUIRED)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
}
這里很簡單,就一個insert插入用戶的方法。
UserService2.java
@Service
public class UserService2 {
@Autowired
private UserService userService;
@Transactional
public void inserBatch() {
for (int i = 0; i < 10; i++) {
if (i == 9) {
throw new RuntimeException();
}
userService.insert();
}
}
}
注入UserService,循環十次調用參數方法。並且第十次拋出異常。調用inserBatch方法,查看結果:
@Test
public void insertBatchTest() {
userService2.inserBatch();
}
結果如下:
數據庫中沒有記錄:
這也證明了“如果當前存在事務,則加入該事務”的概念。如果以后還碰到有人說不要循環嵌套事務的話,可以叫他回去好好看看Spring的事務傳播行為。
SUPPORTS
如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行。也就是說,該模式是否支持事務,看調用它的方法是否有事務支持。測試代碼如下:
UserService
@Transactional(propagation = Propagation.SUPPORTS)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
throw new RuntimeException();
}
UserService2
public void insertWithoutTx() {
userService.insert();
}
調用的方法沒有開啟事務,運行結果:
運行報錯了,但是數據卻沒有回滾掉。說明了insert方法是沒有在事務中運行的。
MANDATORY
如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常。mandatory中文是強制性的意思,表明了被修飾的方法,一定要在事務中去調用,否則會拋出異常。
UserService.java
@Transactional(propagation = Propagation.MANDATORY)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
UserService2.java
public void insertWithoutTx() {
userService.insert();
}
調用:
@Test
public void insertWithoutTxTest() {
userService2.insertWithoutTx();
}
運行結果:
拋出了異常,提示沒有存在的事務。
REQUIRES_NEW
這個理解起來可能會比較繞,官方的解釋是這樣子的:
Create a new transaction, and suspend the current transaction if one exists.
大意就是:重新創建一個新的事務,如果當前存在事務,延緩當前的事務。這個延緩,或者說掛起,可能理解起來比較難,下面通過例子來分析:
UserService.java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
這個insert方法的傳播行為改為REQUIRES_NEW。
UserService2.java
@Transactional
public void inserBatch() {
UserEntity user = new UserEntity();
user.setUsername("初次調用");
user.setPassword("123456");
userRepo.save(user);
for (int i = 0; i < 10; i++) {
if (i == 9) {
throw new RuntimeException();
}
userService.insert();
}
}
inserBatch擁有事務,然后后面循環調用的insert方法也有自己的事務。根據定義,inserBatch的事務會被延緩。具體表現就是:后面的10次循環的事務在每次循環結束之后都會提交自己的事務,而inserBatch的事務,要等循環方法走完之后再提交。但由於第10次循環會拋出異常,則inserBatch的事務會回滾,既數據庫中不會存在:“初次調用”的記錄:
測試代碼:
@Test
public void insertBatchTest() {
userService2.inserBatch();
}
執行結果:
這種情況,符合開始說的“不要循環嵌套事務方法”的說話,當然是否需要循環嵌套,還是要看業務邏輯的。
NOT_SUPPORTED
Execute non-transactionally, suspend the current transaction if one exists.
以非事務的方式運行,如果當前存在事務,暫停當前的事務。這種方式與REQUIRES_NEW有所類似,但是NOT_SUPPORTED修飾的方法其本身是沒有事務的。這里就不做代碼演示了。
NEVER
以非事務的方式運行,如果當前存在事務,則拋出異常。
@Transactional(propagation = Propagation.NEVER)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
@Transactional
public void insertWithTx() {
userService.insert();
}
執行結果:
NESTED
如果沒有事務,就新建一個事務;如果有,就在當前事務中嵌套其他事務。
這個也是理解起來比較費勁的一個行為。我們一步一步分析。
外圍方法沒有事務:這種情況跟REQUIRED是一樣的,會新建一個事務。
外圍方法如果存在事務:這種情況就會嵌套事務。所謂嵌套事務,大意就是,外圍事務回滾,內嵌事務一定回滾,而內嵌事務可以單獨回滾而不影響外圍主事務和其他子事務。
由於本人使用Spring Data JPA 進行的演示代碼,使用嵌套事務會提示:
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
搜索了下,hibernate似乎不支持這種事務傳播方式。所以這里就不做演示了
總結
事務傳播行為,在開發中可能不會特別的留意到它(更多時候,我們可能只是使用默認的方式),但是還是需要對其要有所理解。希望本篇文章能讓大家明白Spring的7種事務傳播行為。