What?天天用Spring你竟然不知道事務的傳播性?
本文轉載自微信公眾號「故里學Java」,作者故里。轉載本文請聯(lián)系故里學Java公眾號。
在我們日常的開發(fā)中Spring是必備的技能,在面試的時候,這一塊的知識也會著重地問,雖然每天都在使用,但是稍不注意就會出問題,今天這篇文章我們來詳細的聊聊Spring的事務傳播性,助力金三銀四面試季。
什么是Spring事務傳播性?Spring事務傳播性是當多個包含事務的方法嵌套調用的時候,處理事務的規(guī)則。例如:兩個事務方法A、B,當方法A調用方法B的時候,方法B是合并到方法A的事務中還是開啟一個新的事務。如果是合并到方法A的事務中,那么當方法B回滾之后,方法A會不會回滾等等。Spring有幾種處理這種嵌套事務的方式?通過源碼我們發(fā)現(xiàn)有7種,定義在Propagation這個枚舉類中,接下來我們講詳細說一下每一種傳播行為都可以幫助我們處理什么樣的問題。
1、Propagation.REQUIRED
這種傳播行為是Spring默認的,當我們使用@Transactional注解且不指定傳播行為的時候就是使用這個,它指的是外層的調用方法如果開啟了事務,那么當前方法就合并到外層的事務中執(zhí)行,如果外層調用方法沒有開啟事務,就開啟一個事務執(zhí)行當前方法。
- //服務A
- @Service
- public class ServiceA {
- @Autowired
- private ServiceB serviceB;
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
- public void methodA() {
- //methodA 的業(yè)務操作
- System.out.println("methodA執(zhí)行業(yè)務");
- //調用服務B的methodB方法
- serviceB.methodB();
- }
- }
- //服務B
- @Service
- public class ServiceB {
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
- public void methodB() {
- System.out.println("methodB執(zhí)行業(yè)務");
- }
- }
我們的實例代碼,服務A的methodA方法調用了服務B的methodB方法,并且我們給methodA通過注解@Transactional加了一個事務,并定義了傳播性為REQUIRED。
methodA本身開啟了事務,methodB也開啟了事務,且事務的傳播性為REQUIRED,所以當methodA調用methodB的時候,methodB會合并到methodA開啟的事務中執(zhí)行。這個時候兩個方法是在一個事務中執(zhí)行的,當兩個方法都執(zhí)行成功后提交事務。
這個地方很多人就會犯迷糊啦,如果methodB在執(zhí)行過程中拋出了異常,那么methodB會回滾,那么methodA執(zhí)行的操作會回滾嗎?這里其實只要記住一點,這兩個操作是在同一個事務中,事務是原子性操作的,所以methodA也會回滾。
面試的時候還會進一步挖坑!如果methodA中使用try-catch捕獲了異常,那么methodA執(zhí)行的操作還會回滾嗎?
這里還是要牢記事務本身具有原子性,所以無論有沒有catch異常,都會回滾的。
2、Propagation.SUPPORTED
這個傳播行為是說,如果當前方法的調用方開啟了事務,那么當前方法就合并到外層事務中執(zhí)行,如果外層事務沒有開啟事務,那么當前方法也不會創(chuàng)建事務,就不開啟事務執(zhí)行。
- //服務A
- @Service
- public class ServiceA {
- @Autowired
- private ServiceB serviceB;
- public void methodA() {
- //methodA 的業(yè)務操作
- System.out.println("methodA執(zhí)行業(yè)務");
- //調用服務B的methodB方法
- serviceB.methodB();
- }
- }
- //服務B
- @Service
- public class ServiceB {
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTED)
- public void methodB() {
- System.out.println("methodB執(zhí)行業(yè)務");
- }
- }
我們看到,methodB開啟了事務,傳播性為SUPPORTED,methodA沒有開啟事務,那么methodA執(zhí)行的時候不會開啟事務,在調用methodB的時候,由于methodB開啟了事務,但傳播性為SUPPORTED,所以methodB也不會開啟事務,以非事務的方式運行。
如果methodA開啟了事務,那么methodB會合并到methodA的事務中執(zhí)行。
3、Propagation.MANDATORY
這個傳播行為是指,傳播性為MANDATORY的方法只能被開啟事務的方法調用,如果調用方?jīng)]有開啟事務就會拋出異常。
- //服務A
- @Service
- public class ServiceA {
- @Autowired
- private ServiceB serviceB;
- public void methodA() {
- //methodA 的業(yè)務操作
- System.out.println("methodA執(zhí)行業(yè)務");
- //調用服務B的methodB方法
- serviceB.methodB();
- }
- }
- //服務B
- @Service
- public class ServiceB {
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
- public void methodB() {
- System.out.println("methodB執(zhí)行業(yè)務");
- }
- }
我們的示例中,methodA沒有開啟事務,調用了開啟事務并且傳播性為MANDATORY的methodB,這時,執(zhí)行methodA的業(yè)務操作時不開啟事務,在調用服務B的methodB方法的時候,就會拋出異常:
- IllegalTransactionStateException(
- "No existing transaction found for transaction marked with propagation 'mandatory'")
4、Propagation.REQUIRES_NEW
這個傳播行為是指,每次都會開啟一個新的事務來執(zhí)行當前方法。比如調用放methodA開啟了事務,在methodA中調用開啟了事務且傳播性為REQUIRES_NEW的方法methodB,那么在methodA會開啟一個事務執(zhí)行自己的業(yè)務代碼,在調用methodB的時候的時候會先掛起methodA的事務,然后開啟一個新的事務執(zhí)行methodB,在methodB的事務提交后,會恢復methodA的事務繼續(xù)執(zhí)行。
- //服務A
- @Service
- public class ServiceA {
- @Autowired
- private ServiceB serviceB;
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
- public void methodA() {
- //methodA 的業(yè)務操作
- System.out.println("methodA執(zhí)行業(yè)務");
- //調用服務B的methodB方法
- try{
- serviceB.methodB();
- } catch (Exception e){
- }
- }
- }
- //服務B
- @Service
- public class ServiceB {
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
- public void methodB() {
- System.out.println("methodB執(zhí)行業(yè)務");
- }
- }
我們的實例代碼中,methodA開啟了事務,傳播性為REQUIRED,所以在執(zhí)行的時候,methodA會開啟一個事務A,然后執(zhí)行methodA的業(yè)務,在調用methodB的時候,由于methodB開啟了事務,且事務傳播性為REQUIRES_NEW,,所以這個時候就先掛起事務A,重新開啟一個事務B來執(zhí)行methodB,在methodB執(zhí)行完提交事務后,會恢復事務A的執(zhí)行,最后再提交事務A。
這個地方面試的時候可能會問到,methodB在執(zhí)行的過程中出現(xiàn)了異常整個過程會發(fā)生什么變化?
我們根據(jù)上邊的調用圖分析,在methodB執(zhí)行過程中拋出異常,事務B會回滾,如果methodA中調用methodB的時候catch住了異常,并沒有向外排除,那么methodA不會回滾,如果methodA中沒有處理異常,那么methodA也會回滾。
5、Propagation.NOT_SUPPORTED
這個傳播性就是不支持事務,如果調用方開啟了事務,那么在執(zhí)行的時候會先掛起調用方的事務,以非事務的方式執(zhí)行當前的業(yè)務,在執(zhí)行完之后,再恢復調用方的事務繼續(xù)執(zhí)行。
- //服務A
- @Service
- public class ServiceA {
- @Autowired
- private ServiceB serviceB;
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
- public void methodA() {
- //methodA 的業(yè)務操作
- System.out.println("methodA執(zhí)行業(yè)務");
- //調用服務B的methodB方法
- serviceB.methodB();
- }
- }
- //服務B
- @Service
- public class ServiceB {
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
- public void methodB() {
- System.out.println("methodB執(zhí)行業(yè)務");
- }
- }
在我們的實例代碼中,methodA開啟了事務,傳播性為REQUIRED,methodB的傳播性為NOT_SUPPORTED,在執(zhí)行的過程中,methodA會開啟一個事務A,在調用methodB的時候,會先掛起methodA的事務A,然后以非事務的方式執(zhí)行methodB的業(yè)務,在methodB執(zhí)行完之后,恢復事務A,最后提交事務A。整個過程如下圖:
6、Propagation.NEVER
這個傳播性和前一種傳播性都是不支持事務,但是不同的是這種傳播性是調用方如果開啟了事務,那么在執(zhí)行當前方法的時候就會拋出異常。下邊還是通過一個示例來看:
- //服務A
- @Service
- public class ServiceA {
- @Autowired
- private ServiceB serviceB;
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
- public void methodA() {
- //methodA 的業(yè)務操作
- System.out.println("methodA執(zhí)行業(yè)務");
- //調用服務B的methodB方法
- serviceB.methodB();
- }
- }
- //服務B
- @Service
- public class ServiceB {
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
- public void methodB() {
- System.out.println("methodB執(zhí)行業(yè)務");
- }
- }
示例中我們看到,methodA開啟了事務,傳播性為REQUIRED,methodB的傳播性為NEVER,那么在methodA調用methodB的時候,就會拋出如下異常:
- IllegalTransactionStateException(
- "Existing transaction found for transaction marked with propagation 'never'")
7、Propagation.NESTED
這個傳播性和REQUIRED很相似,都是當調用方?jīng)]有開啟事務時,就開啟一個新的事務,如果調用方開啟了事務就合并到調用方的事務中執(zhí)行,不同的地方就是NESTED這種傳播行為可以保存狀態(tài)點,當事務回滾的時候,可以回滾到某一個地方,從而避免了嵌套事務全部回滾的情況。
- //服務A
- @Service
- public class ServiceA {
- @Autowired
- private ServiceB serviceB;
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
- public void methodA() {
- //methodA 的業(yè)務操作
- System.out.println("methodA執(zhí)行業(yè)務");
- //
- try{
- serviceB.methodB();
- }catch(Exception e) {
- }
- //methodA在methodB之后的業(yè)務操作...
- update();
- }
- }
- //服務B
- @Service
- public class ServiceB {
- @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
- public void methodB() {
- System.out.println("methodB執(zhí)行業(yè)務");
- }
- }
在這個示例中,我們可以看到,在methodA執(zhí)行的時候,如果沒有開啟事務,會先開啟一個事務,然后執(zhí)行methodA的業(yè)務操作;在實行調用服務B的methodB的時候,由于其傳播行為NESTED,所以會創(chuàng)建一個savepoint,用于標記methodA執(zhí)行的業(yè)務操作。
然后methodB的業(yè)務操作是在methodA的事務中進行的,當methodB拋出異常時,methodB中的業(yè)務操作會回滾掉,methodA執(zhí)行的業(yè)務操作并不會回滾,因為在執(zhí)行methodB之前創(chuàng)建了savepoint,methodB只會回滾到這個savepoint點之前。
這個地方注意的是,methodB回滾以后,對于methodA在methodB之后的業(yè)務操作是會被提交的,并不受methodB回滾的影響。
最后
我們常用的事務傳播行為其實只有兩種,分別是REQUIRED和REQUIRED_NEW。其余五種傳播行為只需要了解即可,可以在面試的時候展示一下知識面。