面試官:聊聊 spring 的七種事務傳播行爲?
spring 的七種事務傳播行爲
以下事務傳播屬性都是打在 B 方法上的事務註解
-
「Propagation.REQUIRED:」 spring 默認的事務傳播行爲,A 方法調用 B 方法,如果 A 方法有事務,則 B 方法加入到 A 方法中的事務中,否則 B 方法自己開啓一個新事務
-
「Propagation.SUPPORTS:」 A 方法調用 B 方法,如果 A 方法有事務,則 B 方法加入到 A 方法中的事務中,否則 B 方法自己使用非事務方式執行
-
「Propagation.MANDATORY:」 只能在存在事務的方法中被調用,A 方法調用 B 方法,如果 A 方法沒事務,則 B 方法會拋出異常
-
「Propagation.REQUIRES_NEW:」 A 方法調用 B 方法,如果 A 方法有事務,則 B 方法把 A 方法的事務掛起,B 方法自己重新開啓一個新事務
-
「Propagation.NOT_SUPPORTED:」 A 方法調用 B 方法,如果 A 方法有事務,則 B 方法掛起 A 方法中的事務中,否則 B 方法自己使用非事務方式執行
-
「Propagation.NEVER:」 不支持事務,A 方法調用 B 方法,如果 A 方法有事務,則 B 方法會拋出異常
-
「Propagation.NESTED:」 同 「Propagation.REQUIRED」,不過此傳播屬性還可以,「保存狀態節點,從而避免所有嵌套事務都回滾」
我們看完了每個傳播屬性的一些解釋,腦子裏應該是還是濛濛的,下面來看下真正的代碼
實戰
Propagation.REQUIRED
- spring 默認的事務傳播屬性,A 方法調用 B 方法,如果 A 方法有事務,則 B 方法加入到 A 方法中的事務中,否則 B 方法自己開啓一個新事務
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);
// 調用B接口的insertTest方法事務方法
BTestService.insertTest(test);
return 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;
}
假如說,我代碼這麼寫的話,那麼這個數據庫裏最終是會有什麼數據呢?
@PostMapping("/update")
public Object updateTest(@RequestBody Test updateVO){
Integer flag = ATestService.updateTest(updateVO);
return flag;
}
原數據庫內容
postman 來一發看看
可以看到控制檯的結果是這樣的,他們共用一個事務(sqlSession 是一樣的)
此時數據庫的內容也並沒有發生變化,說明 A,B 接口都回滾了
這個時候就會出現一個常見的面試題:如果 B 方法拋出的異常被**「A 方法 try catch」** 捕獲了,那麼 A 方法的操作還會回滾嗎?
答案是:「會回滾」
來看下測試代碼,我們在 A 方法中添加了捕獲 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 {
// 調用insertTest方法事務方法
BTestService.insertTest(test);
}catch (Exception e){
System.out.println("A方法補獲了異常"+e.getMessage());
}
return i;
}
再次來一發 postman,控制檯輸出測試結果如下
那麼問題又來了, 如果**「A 沒有捕獲,B 方法自己捕獲了異常」**,那麼事務還會回滾嗎?答案是:**「不會」**
把 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方法補獲了異常"+e.getMessage());
}
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);
// 調用insertTest方法事務方法
BTestService.insertTest(test);
return i;
}
這個時候的結果是
數據庫的數據是
A 方法捕獲和 B 方法捕獲有什麼區別嗎(指捕獲異常)
-
區別就是, A 方法捕獲異常的話,B 方法的事務註解會感知到異常的發生,從而回滾;
-
而 B 方法自己捕獲了,那麼 B 方法的事務註解就不會感知到異常了,所以不會回滾
只要理解了上面這個例子,我們以後各種異常 / 傳播屬性到底回滾不回滾就好分析啦!
Propagation.SUPPORTS
- A 方法調用 B 方法,如果 A 方法有事務,則 B 方法加入到 A 方法中的事務中,否則 B 方法自己使用非事務方式執行
我們把 B 接口的事務傳播屬性換成 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);
// 調用insertTest方法事務方法
BTestService.insertTest(test);
return i;
}
測試結果是
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調用insertTest方法事務方法
BTestService.insertTest(test);
return i;
}
測試結果是是
由此可見,兩個操作都沒有被回滾,B 方法是以非事務方式進行的操作
Propagation.MANDATORY
只能在存在事務的方法中被調用,A 方法調用 B 方法,如果 A 方法沒事務,則 B 方法會拋出異常
A 接口如下
@Override
public Integer updateTest(Test updateVO) {
System.out.println("A updateTest方法");
int i = testMapper.updateByPrimaryKey(updateVO);
Test test =new Test("小杰",24);
// 調用insertTest方法事務方法
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;
}
Propagation.REQUIRES_NEW
- A 方法調用 B 方法,如果 A 方法有事務,則 B 方法把 A 方法的事務掛起,B 方法自己重新開啓一個新事務
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);
// 調用insertTest方法事務方法
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;
}
結果是
B 方法拋出了異常,那麼 A 方法沒有捕獲的話,則 A,B 方法都會回滾
A 方法捕獲了異常,則 A 方法不回滾
「還是那句話,如果在方法內捕獲了異常,則此方法上的事務註解就感知不到這個異常的存在了,那麼此方法的操作就不會回滾!」
Propagation.NOT_SUPPORTED
A 方法調用 B 方法,如果 A 方法有事務,則 B 方法掛起 A 方法中的事務中,否則 B 方法自己使用非事務方式執行
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);
// 調用insertTest方法事務方法
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;
}
測試結果是
數據庫的結果是
我們可以看到,B 接口生效了,確實插入了一條數據,A 接口沒有生效,沒有更改數據,這是因爲,異常在 B 接口內拋出來了,由於 B 接口的事務傳播行爲是 Propagation.NOT_SUPPORTED 則會掛起 A 接口的事務,B 接口以非事務情況操作(所以報錯也不回滾),異常刨到了 A 接口內,A 接口是有事務的,則會回滾,所以就沒有更改數據
Propagation.NEVER
- 不支持事務,A 方法調用 B 方法,如果 A 方法有事務,則 B 方法會拋出異常
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);
// 調用insertTest方法事務方法
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;
}
結果是
Propagation.NESTED
- 同 「Propagation.REQUIRED」,不過此傳播屬性還可以,「保存狀態節點,從而避免所有嵌套事務都回滾」
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 {
// 調用insertTest方法事務方法
BTestService.insertTest(test);
}catch (Exception e){
System.out.println("A方法補獲了異常"+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;
}
結果是
數據庫的變化如下
注:需要在 A 接口裏 try catch B 接口的異常
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Xaf5xaHJ7d6AbOy6Gh1ZZQ