@Transactional嵌套事務失效異常Transaction rolled back because it has been marked as rollback-only


摘要:注解@Transactional嵌套事務失效,拋出異常ransaction rolled back because it has been marked as rollback-only,解決辦法內部事務開啟新事務。

問題描述

  有段事務嵌套的代碼,每次執行完都會報“Transaction rolled back because it has been marked as rollback-only”異常:


2022-04-02 07:01:47.810 [http-nio-7032-exec-1] INFO -c.c.i.impl.BarServiceImpl - 調用拋異常的方法
2022-04-02 07:01:47.819 [http-nio-7032-exec-1] INFO -c.c.i.impl.FarServiceImpl - 我拋出 runtime exception
2022-04-02 07:01:47.819 [http-nio-7032-exec-1] ERROR-c.c.i.impl.BarServiceImpl - 拋異常了,emo,
2022-04-02 07:01:47.819 [http-nio-7032-exec-1] INFO -c.c.i.impl.BarServiceImpl - 事務正常回滾?
2022-04-02 07:01:47.825 [http-nio-7032-exec-1] ERROR-c.c.i.m.a.w.ControllerExceptionAdvice - error
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:870)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:707)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

  更多事務失效的場景,請戳《注解@Transactional事務失效的常見場景》。

問題分析

  錯誤代碼如下:

@Slf4j
@Service
public class BarServiceImpl implements BarService {

    @Autowired
    private FarService farService;

    @Override
    @Transactional
    public void bar() {
        log.info("調用拋異常的方法");
        try {
            farService.far();
        } catch (Exception e) {
            log.error("拋異常了,emo,", e.getMessage());
        }
        log.info("事務正常回滾?");

    }
}

  FarService中far函數的實現如下:

@Slf4j
@Service
public class FarServiceImpl implements FarService {
    @Override
    @Transactional
    public void far() {
        log.info("我拋出 runtime exception");
        throw new NullPointerException("空指針了");
    }
}

  實現邏輯分析
1.兩個添加 @Transactional的方法有調用關系,在接口BarService調用了接口FarService;
2.far()報異常了,但沒有進行try catch捕獲;
3.在bar() 中進行了try catch捕獲。

  在這種情況下,外層事務(BarService)和內層事務(FarService)共用同一個事務,任何一個出現異常,都會在bar()執行完畢后回滾。因為far()報異常,會把當前事務標志成rollback-only,所以,bar()的事務也需要回滾。由於bar()方法捕捉異常后一直往下走,等方法結束commit的時候會報錯,提示:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

解決辦法

  小編Wiener在此提供三種解決策略:

1.被調用方法自己對異常做try catch處理。

2.修改事務傳播行為。如果希望內層事務回滾,但不影響外層事務提交,需要將內層事務新開一個事務,例如@Transactional的事務傳播行為修改為 @Transactional(propagation = Propagation.REQUIRES_NEW)

3.在外層事務不對內層事務的方法far()進行異常捕獲,這樣會自動拋出內層事務的錯誤,而不是報UnexpectedRollbackException。

源碼分析

  如果內部事務狀態是PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY,將會在外層事務中運行,回滾的時候,並不執行回滾,只是標記一下回滾狀態,當外層事務提交的時候,會先判斷ConnectionHolder中的回滾狀態,如果已經標記為回滾,則不會提交,而是外層事務進行回滾

  查看異常信息,我們知道UnexpectedRollbackException是從類AbstractPlatformTransactionManager.java 的 line 870 拋出的,源碼如下:

	/**
	 * Process an actual rollback.
	 * The completed flag has already been checked.
	 * @param status object representing the transaction
	 * @throws TransactionException in case of rollback failure
	 */
	private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
		try {
			boolean unexpectedRollback = unexpected;

			try {
				triggerBeforeCompletion(status);

				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Rolling back transaction to savepoint");
					}
					status.rollbackToHeldSavepoint();
				}
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction rollback");
					}
					doRollback(status);
				}
				else {
					// Participating in larger transaction
					if (status.hasTransaction()) {
						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
							}
							doSetRollbackOnly(status);
						}
						else {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
							}
						}
					}
					else {
						logger.debug("Should roll back transaction but cannot - no transaction available");
					}
					// Unexpected rollback only matters here if we're asked to fail early
					if (!isFailEarlyOnGlobalRollbackOnly()) {
						unexpectedRollback = false;
					}
				}
			}
			catch (RuntimeException | Error ex) {
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
				throw ex;
			}

			triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

			// Raise UnexpectedRollbackException if we had a global rollback-only marker
			if (unexpectedRollback) {
				throw new UnexpectedRollbackException(
						"Transaction rolled back because it has been marked as rollback-only");
			}
		}
		finally {
			cleanupAfterCompletion(status);
		}
	}

  由此可見,內部事務因為拋異常,已經把事務標記為rollback-only。而unexpectedRollback為true,則是由調用方傳入的,查看調用方AbstractPlatformTransactionManager.commit代碼可知,直接傳入了 processRollback(defStatus, true)

	@Override
	public final void commit(TransactionStatus status) throws TransactionException {
		if (status.isCompleted()) {
			throw new IllegalTransactionStateException(
					"Transaction is already completed - do not call commit or rollback more than once per transaction");
		}

		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
      // 如果在事務鏈中已經被標記回滾,那么不會嘗試提交事務,直接回滾
		if (defStatus.isLocalRollbackOnly()) {
			if (defStatus.isDebug()) {
				logger.debug("Transactional code has requested rollback");
			}
			processRollback(defStatus, false);
			return;
		}

		if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
			if (defStatus.isDebug()) {
				logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
			}
           // 進行事務回滾,並且拋出一個異常
			processRollback(defStatus, true);
			return;
		}
       // 沒有被標記為回滾,這里才真正判斷是否提交
		processCommit(defStatus);
	}

結束語

  工作中處處都需要學習,有時候看似簡單的一個異常問題排查,可以讓你深入學習后收獲各種知識。所以在學習中請不求甚解,用閱讀源碼的實際行動,打破坐吃山空的思維,不僅要了解這個知識點,也要熟悉為什么要這么做。如此以來,遇到異常問題的時候才可以避免像鐵拳打到棉花,無所着力。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM