在開發中,相信你們都使用過Spring的事務管理功能。那麼,你是否有了解過,Spring的事務傳播行爲呢?java
Spring中,有7種類型的事務傳播行爲。事務傳播行爲是Spring框架提供的一種事務管理方式,它不是數據庫提供的。不知道你們是否據說過「不要在service事務方法中嵌套事務方法,這樣會提交多個事務」的說法,其實這是不許確的。瞭解了事務傳播行爲以後,相信你就會明白!mysql
本文首發於頭條號【Happyjava】。Happy的掘金地址:juejin.im/user/5cc289…,Happy的我的博客:(blog.happyjava.cn)[blog.happyjava.cn]。歡迎轉載,但須保留此段聲明。spring
事務的傳播行爲,默認值爲 Propagation.REQUIRED。能夠手動指定其餘的事務傳播行爲,以下:sql
若是當前存在事務,則加入該事務,若是當前不存在事務,則建立一個新的事務。數據庫
若是當前存在事務,則加入該事務;若是當前不存在事務,則以非事務的方式繼續運行。bash
若是當前存在事務,則加入該事務;若是當前不存在事務,則拋出異常。app
從新建立一個新的事務,若是當前存在事務,延緩當前的事務。框架
以非事務的方式運行,若是當前存在事務,暫停當前的事務。ide
以非事務的方式運行,若是當前存在事務,則拋出異常。測試
若是沒有,就新建一個事務;若是有,就在當前事務中嵌套其餘事務。
數據庫表:
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工程,這裏就很少說了。
默認的事務傳播行爲是Propagation.REQUIRED,也就是說:若是當前存在事務,則加入該事務,若是當前不存在事務,則建立一個新的事務。
下面,咱們就驗證下前面說的「不要循環嵌套事務方法」的問題:
如今有兩個Service,以下:
@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插入用戶的方法。
@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的事務傳播行爲。
若是當前存在事務,則加入該事務;若是當前不存在事務,則以非事務的方式繼續運行。也就是說,該模式是否支持事務,看調用它的方法是否有事務支持。測試代碼以下:
@Transactional(propagation = Propagation.SUPPORTS)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
throw new RuntimeException();
}
複製代碼
public void insertWithoutTx() {
userService.insert();
}
複製代碼
調用的方法沒有開啓事務,運行結果:
運行報錯了,可是數據卻沒有回滾掉。說明了insert方法是沒有在事務中運行的。
若是當前存在事務,則加入該事務;若是當前不存在事務,則拋出異常。mandatory中文是強制性的意思,代表了被修飾的方法,必定要在事務中去調用,不然會拋出異常。
@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();
}
複製代碼
運行結果:
拋出了異常,提示沒有存在的事務。
這個理解起來可能會比較繞,官方的解釋是這樣子的:
Create a new transaction, and suspend the current transaction if one exists.
複製代碼
大意就是:從新建立一個新的事務,若是當前存在事務,延緩當前的事務。這個延緩,或者說掛起,可能理解起來比較難,下面經過例子來分析:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert() {
UserEntity user = new UserEntity();
user.setUsername("happyjava");
user.setPassword("123456");
userRepo.save(user);
}
複製代碼
這個insert方法的傳播行爲改成REQUIRES_NEW。
@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();
}
複製代碼
執行結果:
這種狀況,符合開始說的「不要循環嵌套事務方法」的說話,固然是否須要循環嵌套,仍是要看業務邏輯的。
Execute non-transactionally, suspend the current transaction if one exists.
複製代碼
以非事務的方式運行,若是當前存在事務,暫停當前的事務。這種方式與REQUIRES_NEW有所相似,可是NOT_SUPPORTED修飾的方法其自己是沒有事務的。這裏就不作代碼演示了。
以非事務的方式運行,若是當前存在事務,則拋出異常。
@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();
}
複製代碼
執行結果:
若是沒有事務,就新建一個事務;若是有,就在當前事務中嵌套其餘事務。
這個也是理解起來比較費勁的一個行爲。咱們一步一步分析。
外圍方法沒有事務:這種狀況跟REQUIRED是同樣的,會新建一個事務。
外圍方法若是存在事務:這種狀況就會嵌套事務。所謂嵌套事務,大意就是,外圍事務回滾,內嵌事務必定回滾,而內嵌事務能夠單獨回滾而不影響外圍主事務和其餘子事務。
因爲本人使用Spring Data JPA 進行的演示代碼,使用嵌套事務會提示:
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities 複製代碼
搜索了下,hibernate彷佛不支持這種事務傳播方式。因此這裏就不作演示了
事務傳播行爲,在開發中可能不會特別的留意到它(更多時候,咱們可能只是使用默認的方式),可是仍是須要對其要有所理解。但願本篇文章能讓你們明白Spring的7種事務傳播行爲。