前言
最近在項目中發現了一則報錯:“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類似的操作。 |