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

本文章向大家介绍@Transactional嵌套事务失效异常Transaction rolled back because it has been marked as rollback-only,主要包括@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);
	}

结束语

  工作中处处都需要学习,有时候看似简单的一个异常问题排查,可以让你深入学习后收获各种知识。所以在学习中请不求甚解,用阅读源码的实际行动,打破坐吃山空的思维,不仅要了解这个知识点,也要熟悉为什么要这么做。如此以来,遇到异常问题的时候才可以避免像铁拳打到棉花,无所着力。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当出现 "Transaction rolled back because it has been marked as rollback-only" 的异常时,通常是因为事务在某个方法中被标记为只能回滚,但后续的代码却试图提交该事务,从而导致异常的抛出。 一个例子是在方法A中捕获了方法B抛出的异常,并继续执行后续的代码,最后正常提交事务。然而,由于AB使用同一个事务,在方法B执行时,该事务会被标记为rollback-only,然后方法A继续使用该事务并执行事务提交的操作,因此最终会抛出异常。 要解决这个问题,可以在方法A中将异常继续往上抛出,而不是在catch块中处理异常,这样事务就会正确地回滚。例如,在方法A的catch块中将异常重新抛出,或者将异常声明为方法A抛出的异常之一。 另外,还可以通过在方法B中使用@Transactional注解来确保方法B在出现异常事务正确回滚,以避免这个异常的出现。 总之,"Transaction rolled back because it has been marked as rollback-only" 异常通常是由于事务在被标记为rollback-only后继续提交引起的,可以通过将异常继续往上抛出或使用@Transactional注解来解决这个问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [COS——R.log](https://download.csdn.net/download/ktc7000/4424623)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Transaction rolled back because it has been marked as rollback-only](https://blog.csdn.net/hingli/article/details/118415444)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值