前言
在開發(fā)中敢订,相信大家都使用過Spring的事務(wù)管理功能。那么厅缺,你是否有了解過蔬顾,Spring的事務(wù)傳播行為呢?
Spring中湘捎,有7種類型的事務(wù)傳播行為诀豁。事務(wù)傳播行為是Spring框架提供的一種事務(wù)管理方式,它不是數(shù)據(jù)庫提供的窥妇。不知道大家是否聽說過“不要在service事務(wù)方法中嵌套事務(wù)方法舷胜,這樣會提交多個事務(wù)”的說法,其實(shí)這是不準(zhǔn)確的活翩。了解了事務(wù)傳播行為之后烹骨,相信你就會明白!
原創(chuàng)聲明
本文首發(fā)于頭條號【Happyjava】材泄。Happy的掘金地址:https://juejin.im/user/5cc2895df265da03a630ddca沮焕,Happy的個人博客:(http://blog.happyjava.cn)[http://blog.happyjava.cn]。歡迎轉(zhuǎn)載拉宗,但須保留此段聲明峦树。
Spring中七種事務(wù)傳播行為
事務(wù)的傳播行為,默認(rèn)值為 Propagation.REQUIRED旦事】可以手動指定其他的事務(wù)傳播行為,如下:
- Propagation.REQUIRED
如果當(dāng)前存在事務(wù)姐浮,則加入該事務(wù)谷遂,如果當(dāng)前不存在事務(wù),則創(chuàng)建一個新的事務(wù)单料。
- Propagation.SUPPORTS
如果當(dāng)前存在事務(wù)埋凯,則加入該事務(wù);如果當(dāng)前不存在事務(wù)扫尖,則以非事務(wù)的方式繼續(xù)運(yùn)行白对。
- Propagation.MANDATORY
如果當(dāng)前存在事務(wù),則加入該事務(wù)换怖;如果當(dāng)前不存在事務(wù)甩恼,則拋出異常。
- Propagation.REQUIRES_NEW
重新創(chuàng)建一個新的事務(wù),如果當(dāng)前存在事務(wù)条摸,延緩當(dāng)前的事務(wù)悦污。
- Propagation.NOT_SUPPORTED
以非事務(wù)的方式運(yùn)行,如果當(dāng)前存在事務(wù)钉蒲,暫停當(dāng)前的事務(wù)切端。
- Propagation.NEVER
以非事務(wù)的方式運(yùn)行,如果當(dāng)前存在事務(wù)顷啼,則拋出異常踏枣。
- Propagation.NESTED
如果沒有,就新建一個事務(wù)钙蒙;如果有茵瀑,就在當(dāng)前事務(wù)中嵌套其他事務(wù)。
準(zhǔn)備工作
數(shù)據(jù)庫表:
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(默認(rèn)的事務(wù)傳播行為)
默認(rèn)的事務(wù)傳播行為是Propagation.REQUIRED,也就是說:如果當(dāng)前存在事務(wù)扛施,則加入該事務(wù)鸿捧,如果當(dāng)前不存在事務(wù),則創(chuàng)建一個新的事務(wù)煮嫌。
下面笛谦,我們就驗(yàn)證下前面說的“不要循環(huán)嵌套事務(wù)方法”的問題:
現(xiàn)在有兩個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恳邀,循環(huán)十次調(diào)用參數(shù)方法懦冰。并且第十次拋出異常。調(diào)用inserBatch方法谣沸,查看結(jié)果:
@Test
public void insertBatchTest() {
userService2.inserBatch();
}
結(jié)果如下:
數(shù)據(jù)庫中沒有記錄:
這也證明了“如果當(dāng)前存在事務(wù)刷钢,則加入該事務(wù)”的概念。如果以后還碰到有人說不要循環(huán)嵌套事務(wù)的話乳附,可以叫他回去好好看看Spring的事務(wù)傳播行為内地。
SUPPORTS
如果當(dāng)前存在事務(wù),則加入該事務(wù)赋除;如果當(dāng)前不存在事務(wù)阱缓,則以非事務(wù)的方式繼續(xù)運(yùn)行。也就是說举农,該模式是否支持事務(wù)荆针,看調(diào)用它的方法是否有事務(wù)支持。測試代碼如下:
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();
}
調(diào)用的方法沒有開啟事務(wù),運(yùn)行結(jié)果:
運(yùn)行報錯了航背,但是數(shù)據(jù)卻沒有回滾掉喉悴。說明了insert方法是沒有在事務(wù)中運(yùn)行的。
MANDATORY
如果當(dāng)前存在事務(wù)玖媚,則加入該事務(wù)箕肃;如果當(dāng)前不存在事務(wù),則拋出異常今魔。mandatory中文是強(qiáng)制性的意思突雪,表明了被修飾的方法,一定要在事務(wù)中去調(diào)用涡贱,否則會拋出異常咏删。
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();
}
調(diào)用:
@Test
public void insertWithoutTxTest() {
userService2.insertWithoutTx();
}
運(yùn)行結(jié)果:
拋出了異常,提示沒有存在的事務(wù)问词。
REQUIRES_NEW
這個理解起來可能會比較繞督函,官方的解釋是這樣子的:
Create a new transaction, and suspend the current transaction if one exists.
大意就是:重新創(chuàng)建一個新的事務(wù),如果當(dāng)前存在事務(wù)激挪,延緩當(dāng)前的事務(wù)辰狡。這個延緩,或者說掛起垄分,可能理解起來比較難宛篇,下面通過例子來分析:
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("初次調(diào)用");
user.setPassword("123456");
userRepo.save(user);
for (int i = 0; i < 10; i++) {
if (i == 9) {
throw new RuntimeException();
}
userService.insert();
}
}
inserBatch擁有事務(wù)薄湿,然后后面循環(huán)調(diào)用的insert方法也有自己的事務(wù)叫倍。根據(jù)定義,inserBatch的事務(wù)會被延緩豺瘤。具體表現(xiàn)就是:后面的10次循環(huán)的事務(wù)在每次循環(huán)結(jié)束之后都會提交自己的事務(wù)吆倦,而inserBatch的事務(wù),要等循環(huán)方法走完之后再提交坐求。但由于第10次循環(huán)會拋出異常蚕泽,則inserBatch的事務(wù)會回滾,既數(shù)據(jù)庫中不會存在:“初次調(diào)用”的記錄:
測試代碼:
@Test
public void insertBatchTest() {
userService2.inserBatch();
}
執(zhí)行結(jié)果:
這種情況桥嗤,符合開始說的“不要循環(huán)嵌套事務(wù)方法”的說話须妻,當(dāng)然是否需要循環(huán)嵌套,還是要看業(yè)務(wù)邏輯的泛领。
NOT_SUPPORTED
Execute non-transactionally, suspend the current transaction if one exists.
以非事務(wù)的方式運(yùn)行荒吏,如果當(dāng)前存在事務(wù),暫停當(dāng)前的事務(wù)师逸。這種方式與REQUIRES_NEW有所類似司倚,但是NOT_SUPPORTED修飾的方法其本身是沒有事務(wù)的豆混。這里就不做代碼演示了。
NEVER
以非事務(wù)的方式運(yùn)行动知,如果當(dāng)前存在事務(wù)皿伺,則拋出異常。
@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();
}
執(zhí)行結(jié)果:
NESTED
如果沒有事務(wù)盒粮,就新建一個事務(wù)鸵鸥;如果有,就在當(dāng)前事務(wù)中嵌套其他事務(wù)丹皱。
這個也是理解起來比較費(fèi)勁的一個行為妒穴。我們一步一步分析。
外圍方法沒有事務(wù):這種情況跟REQUIRED是一樣的摊崭,會新建一個事務(wù)讼油。
外圍方法如果存在事務(wù):這種情況就會嵌套事務(wù)。所謂嵌套事務(wù)呢簸,大意就是矮台,外圍事務(wù)回滾,內(nèi)嵌事務(wù)一定回滾根时,而內(nèi)嵌事務(wù)可以單獨(dú)回滾而不影響外圍主事務(wù)和其他子事務(wù)瘦赫。
由于本人使用Spring Data JPA 進(jìn)行的演示代碼,使用嵌套事務(wù)會提示:
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
搜索了下蛤迎,hibernate似乎不支持這種事務(wù)傳播方式确虱。所以這里就不做演示了
總結(jié)
事務(wù)傳播行為,在開發(fā)中可能不會特別的留意到它(更多時候替裆,我們可能只是使用默認(rèn)的方式)校辩,但是還是需要對其要有所理解。希望本篇文章能讓大家明白Spring的7種事務(wù)傳播行為扎唾。