Transaction rolled back because it has been marked as rollback-only

一、 問(wèn)題描述

兩個(gè)使用@Transaction注解的Service,A和B,在A中引入了B的方法用于更新數(shù)據(jù) ,當(dāng)A中捕捉到B中有異常時(shí),回滾動(dòng)作正常執(zhí)行,但是當(dāng)return時(shí)則出現(xiàn) org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 異常。
代碼示例:

@Service
public class ServiceA {
  @Autowired
  private ServiceB serviceB;

  @Transactional
  public void methodA() {
    try{
      serviceB.methodB();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

@Service
public class serviceB {
  @Transactional
  public void methodB() {
    throw new RuntimeException();
  }
}

二、問(wèn)題分析

@Transactional的默認(rèn)方式為@Transactional(propagation= Propagation.REQUIRED):如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒(méi)有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。

在這種情況下,外層事務(wù)(ServiceA)和內(nèi)層事務(wù)(ServiceB)就是一個(gè)事務(wù),任何一個(gè)出現(xiàn)異常,都會(huì)在methodA執(zhí)行完畢后回滾。
??
如果內(nèi)層事務(wù)B拋出異常e(沒(méi)有catch,繼續(xù)向外層拋出),在內(nèi)層事務(wù)結(jié)束時(shí),spring會(huì)把事務(wù)B標(biāo)記為“rollback-only”。
這時(shí)外層事務(wù)A發(fā)現(xiàn)了異常e,如果外層事務(wù)A catch了異常并處理掉,那么外層事務(wù)A的方法會(huì)繼續(xù)執(zhí)行代碼,直到外層事務(wù)也結(jié)束。
外層事務(wù)A結(jié)束后想commit,因?yàn)檎=Y(jié)束沒(méi)有向外拋異常,但是內(nèi)外層事務(wù)AB是同一個(gè)事務(wù),事務(wù)B(同時(shí)也是事務(wù)A)已經(jīng)被內(nèi)層方法標(biāo)記為“rollback-only”,需要回滾,無(wú)法commit。

因此spring就拋出了異常org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only,意思是“事務(wù)已經(jīng)被標(biāo)記為回滾,無(wú)法提交”。

三、解決方法

  1. methodB和methodA放在同一個(gè)service中(這個(gè)不大現(xiàn)實(shí), 也不符合代碼規(guī)范);

  2. 外層事務(wù)不使用try-catch代碼塊, 讓其自然拋出異常;

  3. 在內(nèi)層事務(wù)中做異常捕獲處理,并且不向外拋異常;

  4. 在內(nèi)層事務(wù)中做手動(dòng)回滾, 代碼示例:

  // 回滾整個(gè)方法
  TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  
  // 回滾指定的一段操作
  // 設(shè)置回滾點(diǎn)
  Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
  // 回滾到回滾點(diǎn)
  TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
  1. 推薦:如果希望內(nèi)層事務(wù)回滾,但不影響外層事務(wù)提交,需要將內(nèi)層事務(wù)的傳播方式指定為@Transactional(propagation= Propagation.NESTED),外層事務(wù)的提交和回滾能夠控制嵌套的內(nèi)層事務(wù)回滾;而內(nèi)層事務(wù)報(bào)錯(cuò)時(shí),只回滾內(nèi)層事務(wù),外層事務(wù)可以繼續(xù)提交。。

  2. 如果這個(gè)異常發(fā)生時(shí),內(nèi)層需要事務(wù)回滾的代碼還沒(méi)有執(zhí)行,則可以@Transactional(noRollbackFor = {內(nèi)層拋出的異常}.class),指定內(nèi)層也不為這個(gè)異?;貪L。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容