Spring事務傳播中嵌套調用實現方法詳細介紹

前言

最近在使用Spring框架時遇到瞭一些問題,主要是Spring的事務傳播問題,一個不帶事務的方法調用帶事務的方法,有時候會出現不回滾的情況,所以寫瞭這篇文章來記錄一下。

7種傳播方式

我們先來看Spring事務的7中傳播方式以及對應的描述

屬性名稱 描述
PROPAGATION__REQUIRED REQUIRED 表示的是當前這個方法必須運行在一個事務環境中,如果當前方法已經處於事務環境中,就可以直接使用該方法,否則開啟一個新的事務
PROPAGATION_SUPPORTS SUPPORTS 如果當前方法處於事務環境中,就使用當前事務,否則不使用事務
PROPAGATION_MANDATORY MANDATORY 表示當前方法一定要處於事務環境中,否則就拋出異常
PROPAGATION_REQUIRES_NEW REQUIRES_NEW 當前方法需要運行在新的事務中。如果當前方法已在事務環境中,先暫停當前事務,在啟動新的事務方法後才執行該方法,如果當前方法不在事務環境中,就啟動一個新的事務後啟動執行該方法。
PROPAGATION_NOT_SUPPORTED NOT_SUPPORTED 不支持當前的事務,總是以非事務狀態執行。如果這個方法是事務方法,就先掛起這個事務方法,再執行這個方法
PROPAGATION_NEVER NEVER 不支持當前事務,如果是事務方法,則拋出異常
PROPAGATION__NESTED NESTED 如果當前執行的方法處於事務環境中,依舊會啟動一個事務,嵌套的事務也可以獨立於當前事務獨立回滾和提交,如果當前執行的方法不在事務環境中,也會啟動一個新事務。

註解式事務

在Spring中,我們常用@Transactional來標註一個事務方法,如果有點進去這個註解的源碼都可以看到Spring對於添加這個註解的方法,都會默認將這個方法的事務的傳播等級設置為REQUIRED,也就是是當前方法必須處於一個事務方法中,或者使用調用這個方法的事務行為。

下面我們來分析下這個註解在什麼情況下會失效,並且需要怎麼樣來去避免這種情況的發生。

事務的方法之間的調用

下面這個例子模仿的是一個帶事務的方法調用另外一個事務方法,在下面的這個方法報錯,查看當前事務有沒有進行回滾

@Override
    @Transactional(rollbackFor = Exception.class)
    public void saveWithDish(SetmealDto setmealDto){
        this.save(setmealDto);
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
    }
    @Transactional(rollbackFor = Exception.class)
    public void saveBa(List<SetmealDish> setmealDishes, SetmealDto setmealDto) throws Exception{
        setmealDishes = setmealDishes.stream().peek((item) -> item.setSetmealId(setmealDto.getId())).collect(Collectors.toList());
        setmealDishService.saveBatch(setmealDishes);
        int j = 2/0;
    }

根據事務的傳播等級來看,這種情況是肯定可以回滾的,但是如果是同一類中,像下面這種情況,同一個類中一個不帶事務的方法調用另外一個帶事務的方法,這種情況下它的事務會不會回滾呢?理論上我們覺得是會的,但是在測試的時候呢,我們發現這個事務並沒有進行回滾,也就是說,這個事務註解@Transantional沒有生效

@Override
    public void saveWithDish(SetmealDto setmealDto) throws Exception{
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        saveBa(setmealDishes, setmealDto);
    }
    @Transactional(rollbackFor = Exception.class)
    public void saveBa(List<SetmealDish> setmealDishes, SetmealDto setmealDto) throws Exception{
        this.save(setmealDto);
        setmealDishes = setmealDishes.stream().peek((item) -> item.setSetmealId(setmealDto.getId())).collect(Collectors.toList());
        setmealDishService.saveBatch(setmealDishes);
        int j = 2/0;
    }

雖然這裡我們報錯瞭,但是數據庫中還是新增瞭一條剛剛我們添加的一條數據,這樣可以說明,這是沒有添加事務的,也驗證瞭上面我們的方法。

下面我們來看情況上,當不同類之間類方法的調用,如果一個事務方法調用一個非事務方法,這樣非事務方法當然可以獲取到當前這個事務的,不會開啟一個新的事務。但是當一個非事務方法調用一個不同類的事務方法時,這樣會不會回滾呢,答案是會的,這邊我已經進行驗證過瞭。

註意事項

我們需要記住Spring的默認事務傳播等級是Required,在Spring掃描Bean時,會掃描這個方法是否帶有@Transactional註解,如果是包含的話,Spring會動態生成一個代理類(proxy),當這個方法被調用時,是由代理類來進行調用的,而在初始化時,同一個類下面,這個方法如果是沒有帶@Transactional註解調用一個@Transactional的方法的話,這個方法的調用是沒有經過代理類的,就不會啟動transactional,也就是在同一個類出現無效的現象出現

所以,解決的話,我們可以將這兩個方法分開到兩個不同的類中,所以我們可以知道在一個service類中,如果一個非事務方法調用一個帶事務的方法和事務方法之間的相互調用都不會開啟新的事務。

到此這篇關於Spring事務傳播中嵌套調用實現方法詳細介紹的文章就介紹到這瞭,更多相關Spring嵌套調用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: