天天看點

Spring 雙層事務,我抛出的異常去哪了?

系統 A 調用系統 B 執行資料同步,系統 B 傳回了錯誤提示,系統 A 需要将前邊儲存的復原掉,同時把錯誤資訊向上抛。

大緻代碼如下

@Service("noteService")
public class NoteServiceImpl implements NoteService {

    @Resource
    private SearchService searchService;


    @Transactional(rollbackFor = Throwable.class)
    @Override
    public CommonResponse<NoteEntity> save(NoteEntity note) {
        // 一系列 DB 操作

        try {
            searchService.sync(note);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return CommonResponse.success(entity);
    }

}

@Service("searchService")
public class SearchServiceImpl implements SearchService {

    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void sync(NoteEntity note) {
        // 一系列 DB 操作

        throw new RuntimeException("同步異常! [XXX]");
    }

}

@SpringBootTest
public class NoteTests {

    @Resource
    private NoteService noteService;

    @Test
    public void saveNote() {
        NoteEntity entity = new NoteEntity();
        entity.setTitle("念奴嬌赤壁懷古");
        entity.setContent("大江東去,浪淘盡,千古風流人物。故壘西邊,人道是:三國周郎赤壁。。。");
        entity.setTags("蘇轼,宋代");
        entity.setCategory("蘇轼詩詞");

        try {
            noteService.save(entity);
        } catch (Exception e) {
            e.printStackTrace();
            // FIXME 我想在這裡拿到的是 同步異常! [XXX]
            // FIXME 但是這裡拿到的是 Transaction silently rolled back because it has been marked as rollback-only
            System.out.println(">>>>>>>>>> " + e.getMessage());
        }
    }

}      

事出有因

代碼曆史久遠,為何這樣寫已無從追溯。

納悶了一會兒,看到雙層事務,就想起了 Spring事務傳播機制,前邊了解得比較膚淺。

沒有特殊的配置,自然是走預設的事務傳播機制了,也就是 Propagation.REQUIRED。

國際慣例,列出事務傳播機制:

1、PROPAGATION_REQUIRED
目前沒事務,則建立事務;存在事務,就加入該事務,這是最常用的設定。

2、PROPAGATION_SUPPORTS
目前存在事務,就加入事務,目前不存在事務,就以非事務方式執行。

3、PROPAGATION_MANDATORY
目前存在事務,就加入事務;目前不存在事務,就抛出異常。

4、PROPAGATION_REQUIRES_NEW
無條件建立新事務。

5、PROPAGATION_NOT_SUPPORTED
以非事務方式執行,如果目前存在事務,就将目前事務挂起。

6、PROPAGATION_NEVER
以非事務方式運作,如果存在事務,就抛出異常。

7、PROPAGATION_NESTED
開始執行事務前,先儲存一個savepoint,當發生異常時,就復原到savepoint;沒有異常時,跟着外部事務一起送出或復原。      

具體原因

1、看了上邊的事務傳播機制,繼續細化問題,内外層共用一個事務,内層抛出異常,會導緻整個事務失敗。

2、繼續分析,外層邏輯進行了 try catch,就導緻内層的異常無法繼續向上抛出,外層事務會繼續送出。

3、事務送出時,進行事務狀态的判斷,就發現這個事務是失敗的,需要復原,是以抛出了 Transaction silently rolled back because it has been marked as rollback-only 的異常。

怎麼解決?

銀彈自然是沒有的,根據業務場景選擇合适的方案。

1、目前這種場景,直接把外層邏輯中的 try catch 去掉即可。異常直接向上抛,事務就不會繼續送出,調用方拿到的就是一手的異常;

2、如果内層不是核心邏輯,記錄個日志啥的,可以把内層事務配置為 @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW), 無論如何,都建立新的事務,外層事務不受内層事務影響。但是有個問題,外層事務失敗了,内層事務還是把記錄入庫了,有可能産生髒資料;

3、如果外層事務失敗了,内層事務也不能送出,那就可以使用 @Transactional(rollbackFor = Throwable.class, propagation = Propagation.NESTED)。 注意:hibernate/jpa 不支援嵌套事務 NESTED,可用 JdbcTemplate 代替。

近期熱文推薦:

1.Java 15 正式釋出, 14 個新特性,重新整理你的認知!!

2.終于靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!

3.我用 Java 8 寫了一段邏輯,同僚直呼看不懂,你試試看。。

4.吊打 Tomcat ,Undertow 性能很炸!!

5.《Java開發手冊(嵩山版)》最新釋出,速速下載下傳!

覺得不錯,别忘了随手點贊+轉發哦!