1. 程式人生 > 其它 >事務傳播行為的失效(異常傳遞)

事務傳播行為的失效(異常傳遞)

技術標籤:javaspringaop

眾所周知,事務的傳播行為一共有7種,7種傳播行為具體有什麼特徵在這裡不再贅述,詳情參考https://zhuanlan.zhihu.com/p/256263914,本文主要對對於傳播行為失效進行探究,旨在對於事務的使用時對於細節的把控。

在這裡引出第一個問題:

1.為什麼我用 @Transactional(propagation = Propagation.REQUIRES_NEW)卻得到了意想不到的結果?

首先,由於spring中的事務底層採用的是jdk或cglib的動態代理,因此,不能在一個類中一個方法直接呼叫另一個方法,可以採用動態代理上下文的方式,在一個類中呼叫另一個採用事務的方法,具體參考

https://blog.csdn.net/xlgen157387/article/details/79026285,亦或者在當前類中注入當前類的物件,採用物件的方式呼叫。以下采用兩個類的方式進行呼叫。

程式碼如下:

 @Service
 public class TestTransactional {
     @Autowired
     private CustomerMapper customerMapper;
 
     @Autowired
     private TestTransactional2 testTransactional2;
 
     @Transactional(propagation =
Propagation.REQUIRED) public void methodA() { System.out.println("pre"); Customer customer = new Customer(); customer.setAddress("zzzzzzzz"); customer.setNickname("zzzzzzzz"); customer.setUsername("zzzzzzzz"); customer.
setPassword("zzzzzzzz"); customerMapper.insert(customer); testTransactional2.methodB(65); System.out.println("post"); } }
@Service
public class TestTransactional2 {
    @Autowired
    private CustomerMapper customerMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB(Integer id) {
        customerMapper.deleteById(id);
        throw new RuntimeException();
    }
}

methodA中事務採用Propagation.REQUIRED,在methodA呼叫methodB,methodB的事務傳播行為為Propagation.REQUIRES_NEW,按照Propagation.REQUIRES_NEW的特徵,方法B會開啟一個新的事務,方法A的事務會被掛起,如果方法B發生異常,方法B回滾,但是方法A不回滾,但是當方法B發生異常時,方法A也進行了回滾。如下:

在這裡插入圖片描述

可以發現65條資料沒有刪除,也沒有新增新的資料,說明方法B和方法A都發生了回滾。這是為什麼呢?

這事因為,方法B執行時丟擲了異常,而方法A中並沒有進行捕獲,造成方法A中也出現了異常,造成A的事務也進行了回滾。如果在方法A中加入try{}catch{},那麼問題就會迎刃而解。如下:

@Service
public class TestTransactional {
    @Autowired
    private CustomerMapper customerMapper;

    @Autowired
    private TestTransactional2 testTransactional2;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        System.out.println("pre");
        Customer customer = new Customer();
        customer.setAddress("zzzzzzzz");
        customer.setNickname("zzzzzzzz");
        customer.setUsername("zzzzzzzz");
        customer.setPassword("zzzzzzzz");
        customerMapper.insert(customer);
        try {
            testTransactional2.methodB(65);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("post");
    }

}

在這裡插入圖片描述

可以看出,方法B進行了回滾,方法A沒有進行回滾。

2.如果methodB的傳播採用@Transactional(propagation = Propagation.REQUIRED)時,我可以對異常進行捕獲嗎?

但是當方法B中事務傳播採用Propagation.REQUIRED(如果上下文中已經存在事務,那麼就加入到事務中執行,如果當前上下文中不存在事務,則新建事務執行)時,try{}catch{}會不會影響事務的執行呢?

@Service
public class TestTransactional2 {
@Autowired
private CustomerMapper customerMapper;

@Transactional(propagation = Propagation.REQUIRED)
public void methodB(Integer id) {
   customerMapper.deleteById(id);
   throw new RuntimeException();
   }
}

在這裡插入圖片描述

可以發現,try{}catch{}並沒有影響Propagation.REQUIRED的特徵,因為,方法B使用的事務是方法A中的事務,方法A與方法B公用一個事務,如果方法B發生了異常,即使方法A中進行了捕獲,方法A也會發生回滾。所以,try{}catch{}也應用於propagation = Propagation.REQUIRED時。

3.如果方法B中事務傳播採用Propagation.NOT_SUPPORTED時,我需要進行異常捕獲嗎?

Propagation.NOT_SUPPORTED,當前級別的特點就是上下文中存在事務,則掛起事務,執行當前邏輯,結束後恢復上下文的事務。

@Service
public class TestTransactional {
    @Autowired
    private CustomerMapper customerMapper;

    @Autowired
    private TestTransactional2 testTransactional2;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        System.out.println("pre");
        Customer customer = new Customer();
        customer.setAddress("zzzzzzzz");
        customer.setNickname("zzzzzzzz");
        customer.setUsername("zzzzzzzz");
        customer.setPassword("zzzzzzzz");
        customerMapper.insert(customer);
        testTransactional2.methodB(65);
        System.out.println("post");
    }

}
@Service
public class TestTransactional2 {
    @Autowired
    private CustomerMapper customerMapper;

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void methodB(Integer id) {
        customerMapper.deleteById(id);
        throw new RuntimeException();
    }
}

當方法A沒有進行異常捕獲時,得到了意想不到的結果:

在這裡插入圖片描述

方法B中刪除成功,方法A進行了回滾,按照Propagation.NOT_SUPPORTED的特性,應該是方法B沒有事務的執行,不會影響方法A中的事務,但是,結果顯示,方法B丟擲的異常影響了方法A中的事務,因此,方法A中一定要進行異常的捕獲。

4.如果方法B中事務傳播採用Propagation.NESTED時,我需要進行異常捕獲嗎?

Propagation.NESTED,巢狀是子事務套在父事務中執行,子事務是父事務的一部分,在進入子事務之前,父事務建立一個回滾點,叫save point,然後執行子事務,這個子事務的執行也算是父事務的一部分,然後子事務執行結束,父事務繼續執行。

如果子事務回滾,會發生什麼?

父事務會回滾到進入子事務前建立的save point,然後嘗試其他的事務或者其他的業務邏輯,父事務之前的操作不會受到影響,更不會自動回滾。

如果父事務回滾,會發生什麼?

父事務回滾,子事務也會跟著回滾!為什麼呢,因為父事務結束之前,子事務是不會提交的,我們說子事務是父事務的一部分,正是這個道理。那麼:

@Service
public class TestTransactional {
    @Autowired
    private CustomerMapper customerMapper;

    @Autowired
    private TestTransactional2 testTransactional2;

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        System.out.println("pre");
        Customer customer = new Customer();
        customer.setAddress("zzzzzzzz");
        customer.setNickname("zzzzzzzz");
        customer.setUsername("zzzzzzzz");
        customer.setPassword("zzzzzzzz");
        customerMapper.insert(customer);
        testTransactional2.methodB(65);
        System.out.println("post");
    }

}

@Service
public class TestTransactional2 {
    @Autowired
    private CustomerMapper customerMapper;

    @Transactional(propagation = Propagation.NESTED)
    public void methodB(Integer id) {
        customerMapper.deleteById(id);
        throw new RuntimeException();
    }
}

當方法A中沒有對異常進行捕獲時,結果如下:

在這裡插入圖片描述

可以發現,方法B和方法A都進行了回滾,按照NESTED特性,應該方法A不會進行回滾而是回到了開始執行方法B的儲存的point,但是方法A進行了回滾。當方法A中對異常進行捕獲時,結果就會與預期一致。

在這裡插入圖片描述

綜上所述:無論方法B中傳播行為是何種方式,方法A中都要對異常進行捕獲,否則會發生出乎意料的結果,而且這種錯誤還很難發現,雖然這是運用事務時的細節問題,但是在一些專案中總是這些細節造成重大的損失,因此,在我們做專案時在邏輯嚴謹的同時也要對一些細節進行把握,增強程式碼的健壯性。