UnexpectedRollbackException解決方案

碼魘發表於2019-01-19

前言

最近在專案中發現了一則報錯:“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。根據報錯資訊來看是spring框架中的事務管理報錯:事務回滾了,因為它被標記為回滾狀態。

報錯原因

多層巢狀事務中,如果使用了預設的事務傳播方式,當內層事務丟擲異常,外層事務捕捉並正常執行完畢時,就會報出rollback-only異常。
spring框架是使用AOP的方式來管理事務,如果一個被事務管理的方法正常執行完畢,方法結束時spring會將方法中的sql進行提交。如果方法執行過程中出現異常,則回滾。spring框架的預設事務傳播方式是PROPAGATION_REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。
在專案中,一般我們都會使用預設的傳播方式,這樣無論外層事務和內層事務任何一個出現異常,那麼所有的sql都不會執行。在巢狀事務場景中,內層事務的sql和外層事務的sql會在外層事務結束時進行提交或回滾。如果內層事務丟擲異常e,在內層事務結束時,spring會把事務標記為“rollback-only”。這時如果外層事務捕捉了異常e,那麼外層事務方法還會繼續執行程式碼,直到外層事務也結束時,spring發現事務已經被標記為“rollback-only”,但方法卻正常執行完畢了,這時spring就會丟擲“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。
程式碼示例如下:

Class ServiceA {
    @Resource(name = "serviceB")
    private ServiceB b;
    
    @Transactional
    public void a() {
        try {
            b.b()
        } catch (Exception ignore) {
        }
    }
}

Class ServiceB {
    @Transactional
    public void b() {
        throw new RuntimeException();
    }
}

當呼叫a()時,就會報出“rollback-only”異常。

解決方案

  • 如果希望內層事務丟擲異常時中斷程式執行,直接在外層事務的catch程式碼塊中丟擲e.
  • 如果希望程式正常執行完畢,並且希望外層事務結束時全部提交,需要在內層事務中做異常捕獲處理。
  • 如果希望內層事務回滾,但不影響外層事務提交,需要將內層事務的傳播方式指定為PROPAGATION_NESTED。注:PROPAGATION_NESTED基於資料庫savepoint實現的巢狀事務,外層事務的提交和回滾能夠控制嵌內層事務,而內層事務報錯時,可以返回原始savepoint,外層事務可以繼續提交。

在我的專案中之所以會報“rollback-only”異常的根本原因是程式碼風格不一致的原因。外層事務對錯誤的處理方式是返回true或false來告訴上游執行結果,而內層事務是通過丟擲異常來告訴上游(這裡指外層事務)執行結果,這種差異就導致了“rollback-only”異常。雖然最後事務依然是回滾了,不影響程式對sql的處理,但外層事務的上游本期望返回true和false,卻收到了UnexpectedRollbackException異常,(╯ ̄Д ̄)╯︵ ┻━┻。

附:事務傳播方式

@see org.springframework.transaction.annotation.Propagation

事務傳播方式 說明
PROPAGATION_REQUIRED 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是預設的傳播方式
PROPAGATION_SUPPORTS 支援當前事務,如果當前沒有事務,就以非事務方式執行
PROPAGATION_MANDATORY 使用當前的事務,如果當前沒有事務,就丟擲異常
PROPAGATION_REQUIRES_NEW 新建事務,如果當前存在事務,把當前事務掛起
PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起
PROPAGATION_NEVER 以非事務方式執行,如果當前存在事務,則丟擲異常
PROPAGATION_SUPPORTS 支援當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_NESTED 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

相關文章