天天看點

spring中,多個service發生嵌套,事務是怎麼樣的?前言報錯原因解決方案

前言

最近在項目中發現了一則報錯:“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,外層事務可以繼續送出。

附:事務傳播方式

@see org.springframework.transaction.annotation.Propagation

事務傳播方式 說明
PROPAGATION_REQUIRED 如果目前沒有事務,就建立一個事務,如果已經存在一個事務中,加入到這個事務中。這是預設的傳播方式
PROPAGATION_SUPPORTS 支援目前事務,如果目前沒有事務,就以非事務方式執行
PROPAGATION_MANDATORY 使用目前的事務,如果目前沒有事務,就抛出異常
PROPAGATION_REQUIRES_NEW 建立事務,如果目前存在事務,把目前事務挂起
PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作,如果目前存在事務,就把目前事務挂起
PROPAGATION_NEVER 以非事務方式執行,如果目前存在事務,則抛出異常
支援目前事務,如果目前沒有事務,就以非事務方式執行。
PROPAGATION_NESTED 如果目前存在事務,則在嵌套事務内執行。如果目前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。