面試官:聊聊spring的七種事務(wù)傳播行為?
??
大家好,這里是公眾號(hào):「java小杰要加油」 我們都知道 「spring」 有七種事務(wù)傳播行為,面試也經(jīng)常被問(wèn)道,不過(guò)他們長(zhǎng)得都太像啦,老虎老鼠傻傻分不清楚,今天我們就用這篇文章來(lái)徹底搞懂他們! 文中有「大量代碼」論證,建議「收藏在電腦端食用」
話不多說(shuō),直接開(kāi)車
spring的七種事務(wù)傳播行為
?以下事務(wù)傳播屬性都是打在B方法上的事務(wù)注解
?
「Propagation.REQUIRED:」 spring默認(rèn)的事務(wù)傳播行為,A方法調(diào)用B方法,如果A方法有事務(wù),則B方法加入到A方法中的事務(wù)中,否則B方法自己開(kāi)啟一個(gè)新事務(wù)
「Propagation.SUPPORTS:」 A方法調(diào)用B方法,如果A方法有事務(wù),則B方法加入到A方法中的事務(wù)中,否則B方法自己使用非事務(wù)方式執(zhí)行
「Propagation.MANDATORY:」 只能在存在事務(wù)的方法中被調(diào)用,A方法調(diào)用B方法,如果A方法沒(méi)事務(wù),則B方法會(huì)拋出異常
「Propagation.REQUIRES_NEW:」 A方法調(diào)用B方法,如果A方法有事務(wù),則B方法把A方法的事務(wù)掛起,B方法自己重新開(kāi)啟一個(gè)新事務(wù)
「Propagation.NOT_SUPPORTED:」 A方法調(diào)用B方法,如果A方法有事務(wù),則B方法掛起A方法中的事務(wù)中,否則B方法自己使用非事務(wù)方式執(zhí)行
「Propagation.NEVER:」 不支持事務(wù),A方法調(diào)用B方法,如果A方法有事務(wù),則B方法會(huì)拋出異常
「Propagation.NESTED:」 同 「Propagation.REQUIRED」,不過(guò)此傳播屬性還可以,「保存狀態(tài)節(jié)點(diǎn),從而避免所有嵌套事務(wù)都回滾」
我們看完了每個(gè)傳播屬性的一些解釋,腦子里應(yīng)該是還是蒙蒙的,下面來(lái)看下真正的代碼
實(shí)戰(zhàn)
Propagation.REQUIRED
spring 默認(rèn)的事務(wù)傳播屬性,A方法調(diào)用B方法,如果A方法有事務(wù),則B方法加入到A方法中的事務(wù)中,否則B方法自己開(kāi)啟一個(gè)新事務(wù)
A接口
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調(diào)用B接口的insertTest方法事務(wù)方法
BTestService.insertTest(test);
return i;
}
我們?cè)賮?lái)看下B接口
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 拋出異常
int a = 1 / 0 ;
return i;
}
假如說(shuō),我代碼這么寫(xiě)的話,那么這個(gè)數(shù)據(jù)庫(kù)里最終是會(huì)有什么數(shù)據(jù)呢?
@PostMapping("/update")
public Object updateTest(@RequestBody Test updateVO){
Integer flag = ATestService.updateTest(updateVO);
return flag;
}
原數(shù)據(jù)庫(kù)內(nèi)容

postman來(lái)一發(fā)看看

可以看到控制臺(tái)的結(jié)果是這樣的,他們共用一個(gè)事務(wù)(sqlSession是一樣的)

此時(shí)數(shù)據(jù)庫(kù)的內(nèi)容也并沒(méi)有發(fā)生變化,說(shuō)明A,B接口都回滾了

?這個(gè)時(shí)候就會(huì)出現(xiàn)一個(gè)常見(jiàn)的面試題:如果B方法拋出的異常被「A方法try catch」 捕獲了,那么A方法的操作還會(huì)回滾嗎?
?
答案是:「會(huì)回滾」
來(lái)看下測(cè)試代碼,我們?cè)贏方法中添加了捕獲B方法拋出異常的代碼
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
try {
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
}catch (Exception e){
System.out.println("A方法補(bǔ)獲了異常"+e.getMessage());
}
return i;
}
再次來(lái)一發(fā)postman,控制臺(tái)輸出測(cè)試結(jié)果如下
我們看數(shù)據(jù)庫(kù)數(shù)據(jù)也沒(méi)有變

?那么問(wèn)題又來(lái)了,如果「A沒(méi)有捕獲,B方法自己捕獲了異?!?/strong>,那么事務(wù)還會(huì)回滾嗎?答案是:「不會(huì)」
?
把B接口的代碼改一下
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = 0;
try {
i = testMapper.insert(updateVO);
// 拋出異常
int a = 1 / 0 ;
}catch (Exception e){
System.out.println("B方法補(bǔ)獲了異常"+e.getMessage());
}
return i;
}
同時(shí)把A方法的捕獲異常去掉
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
return i;
}
這個(gè)時(shí)候的結(jié)果是

數(shù)據(jù)庫(kù)的數(shù)據(jù)是
由此可見(jiàn),A和B兩個(gè)接口都生效了都操作數(shù)據(jù)庫(kù)了,都沒(méi)有回滾
A方法捕獲和B方法捕獲有什么區(qū)別嗎(指捕獲異常)
區(qū)別就是,A方法捕獲異常的話,B方法的事務(wù)注解會(huì)感知到異常的發(fā)生,從而回滾; 而B(niǎo)方法自己捕獲了,那么B方法的事務(wù)注解就不會(huì)感知到異常了,所以不會(huì)回滾
?只要理解了上面這個(gè)例子,我們以后各種異常/傳播屬性到底回滾不回滾就好分析啦!
?
Propagation.SUPPORTS
A方法調(diào)用B方法,如果A方法有事務(wù),則B方法加入到A方法中的事務(wù)中,否則B方法自己使用非事務(wù)方式執(zhí)行
我們把B接口的事務(wù)傳播屬性換成 Propagation.SUPPORTS
@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 拋出異常
int a = 1 / 0 ;
return i;
}
A方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
return i;
}
測(cè)試結(jié)果是
數(shù)據(jù)庫(kù)的值也沒(méi)有被改變 , 所以兩個(gè)操作都被回滾了
那我們把A方法的事務(wù)注解去掉后再看一下
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
return i;
}
測(cè)試結(jié)果是是
數(shù)據(jù)庫(kù)的值是
由此可見(jiàn),兩個(gè)操作都沒(méi)有被回滾,B方法是以非事務(wù)方式進(jìn)行的操作
Propagation.MANDATORY
?只能在存在事務(wù)的方法中被調(diào)用,A方法調(diào)用B方法,如果A方法沒(méi)事務(wù),則B方法會(huì)拋出異常
?
A接口如下
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
return i;
}
B接口如下
@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 拋出異常
int a = 1 / 0 ;
return i;
}
數(shù)據(jù)庫(kù)的值也沒(méi)有變,由此可見(jiàn),B方法的事務(wù)注解為 Propagation.MANDATORY 當(dāng)A方法沒(méi)事務(wù)時(shí),則直接報(bào)錯(cuò)。
Propagation.REQUIRES_NEW
A方法調(diào)用B方法,如果A方法有事務(wù),則B方法把A方法的事務(wù)掛起,B方法自己重新開(kāi)啟一個(gè)新事務(wù)
A方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
return i;
}
B方法
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 拋出異常
int a = 1 / 0 ;
return i;
}
結(jié)果是
其中可以發(fā)現(xiàn) 兩個(gè)接口的 DefaultSqlSession 不一樣,那么就表明,這兩個(gè)不是一個(gè)事務(wù),所以就是,當(dāng)A接口存在事務(wù)的時(shí)候,B接口將其掛起并且重新開(kāi)啟一個(gè)新的事務(wù)
??
B方法拋出了異常,那么A方法沒(méi)有捕獲的話,則A,B方法都會(huì)回滾 A方法捕獲了異常,則A方法不回滾
「還是那句話,如果在方法內(nèi)捕獲了異常,則此方法上的事務(wù)注解就感知不到這個(gè)異常的存在了,那么此方法的操作就不會(huì)回滾!」
Propagation.NOT_SUPPORTED
?A方法調(diào)用B方法,如果A方法有事務(wù),則B方法掛起A方法中的事務(wù)中,否則B方法自己使用非事務(wù)方式執(zhí)行
?
A接口
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
return i;
}
B接口
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 拋出異常
int a = 1 / 0 ;
return i;
}
測(cè)試結(jié)果是

數(shù)據(jù)庫(kù)的結(jié)果是

?我們可以看到,B接口生效了,確實(shí)插入了一條數(shù)據(jù),A接口沒(méi)有生效,沒(méi)有更改數(shù)據(jù),這是因?yàn)椋惓T贐接口內(nèi)拋出來(lái)了,由于B接口的事務(wù)傳播行為是 Propagation.NOT_SUPPORTED 則會(huì)掛起A接口的事務(wù),B接口以非事務(wù)情況操作(所以報(bào)錯(cuò)也不回滾),異常刨到了A接口內(nèi),A接口是有事務(wù)的,則會(huì)回滾,所以就沒(méi)有更改數(shù)據(jù)
?
Propagation.NEVER
不支持事務(wù),A方法調(diào)用B方法,如果A方法有事務(wù),則B方法會(huì)拋出異常
A接口
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
return i;
}
B接口
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NEVER)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 拋出異常
int a = 1 / 0 ;
return i;
}
結(jié)果是
數(shù)據(jù)庫(kù)也沒(méi)有被改變,
可見(jiàn),當(dāng)A接口有事務(wù)的情況下調(diào)用B接口,直接報(bào)錯(cuò)
Propagation.NESTED
同 「Propagation.REQUIRED」,不過(guò)此傳播屬性還可以,「保存狀態(tài)節(jié)點(diǎn),從而避免所有嵌套事務(wù)都回滾」
A接口
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
try {
// 調(diào)用insertTest方法事務(wù)方法
BTestService.insertTest(test);
}catch (Exception e){
System.out.println("A方法補(bǔ)獲了異常"+e.getMessage());
}
return i;
}
B接口
@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
@Override
public Integer insertTest(Test updateVO) {
System.out.println("B insertTest方法");
int i = testMapper.insert(updateVO);
// 拋出異常
int a = 1 / 0 ;
return i;
}
結(jié)果是

數(shù)據(jù)庫(kù)的變化如下
A接口的操作沒(méi)有回滾,B操作的回滾了,這就是因?yàn)椤皊avePoint”安全點(diǎn),在進(jìn)行B接口操作時(shí),當(dāng)前的狀態(tài)(A接口已經(jīng)操作完了)被保存至安全點(diǎn),B接口失敗的話,回滾只會(huì)回滾到這個(gè)安全點(diǎn)
?注:需要在A接口里try catch B接口的異常
?
這里是公眾號(hào):「java小杰要加油」,我們下期見(jiàn)
