问题背景

[[记一次事务的坑:Transaction rolled back because it has been marked as rollback-only]]

源码分析

@Nullable
	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

		// If the transaction attribute is null, the method is non-transactional.
		TransactionAttributeSource tas = getTransactionAttributeSource();
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
			Object retVal = null;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// aop对应目标方法报错
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
			// aop对应目标方法正常执行进行事务提交
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
			final ThrowableHolder throwableHolder = new ThrowableHolder();

			// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
			try {
				Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
					TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
					try {
						return invocation.proceedWithInvocation();
					}
					catch (Throwable ex) {
						if (txAttr.rollbackOn(ex)) {
							// A RuntimeException: will lead to a rollback.
							if (ex instanceof RuntimeException) {
								throw (RuntimeException) ex;
							}
							else {
								throw new ThrowableHolderException(ex);
							}
						}
						else {
							// A normal return value: will lead to a commit.
							throwableHolder.throwable = ex;
							return null;
						}
					}
					finally {
						cleanupTransactionInfo(txInfo);
					}
				});

				// Check result state: It might indicate a Throwable to rethrow.
				if (throwableHolder.throwable != null) {
					throw throwableHolder.throwable;
				}
				return result;
			}
			catch (ThrowableHolderException ex) {
				throw ex.getCause();
			}
			catch (TransactionSystemException ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
					ex2.initApplicationException(throwableHolder.throwable);
				}
				throw ex2;
			}
			catch (Throwable ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
				}
				throw ex2;
			}
		}
	}

异常aop: org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction ->org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing ->org.springframework.transaction.support.AbstractPlatformTransactionManager#rollback ->org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback

Pasted image 20230128172332
Pasted image 20230128172340

正常提交aop org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction ->org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning ->org.springframework.transaction.support.AbstractPlatformTransactionManager#commit ->org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback

@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);  
}
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");  
                  }
                  // 对当前事务设置RollbackOnly=true
                  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);  
   }  
}

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

解决方案

  1. 捕获异常时,手动设置上层事务状态为 rollback 状态
@Override  
@Transactional(rollbackFor = Exception.class)  
public void testRollbackOnly() {  
	String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();  
	logger.info("com.lin.app.service.impl.TransactionalServiceImpl.testRollbackOnly:" + transactionName);  
	try {  
		self().rollbackOnlyA();  
	} catch (Exception e) {  
		logger.error("com.lin.app.service.impl.TransactionalServiceImpl.testRollbackOnly.error", e);  
	}  
	if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {  
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();  
		logger.error("com.lin.app.service.impl.TransactionalServiceImpl.testRollbackOnly.isRollbackOnly");  
	}  
	Order order = new Order("ORDER" + "test");  
	orderMapper.insertSelective(order);  
}
  1. 修改事务传播机制,比如说将内层事务的传播方式指定为@Transactional(propagation= Propagation.NESTED),外层事务的提交和回滚能够控制嵌套的内层事务回滚;而内层事务报错时,只回滚内层事务,外层事务可以继续提交。但尝试Propagation.NESTED与 Hibernate JPA 一起使用将导致 Spring 异常:JpaDialect does not support savepoints - check your JPA provider's capabilities,这是因为 Hibernate JPA 不支持嵌套事务。可以考虑用 Propagation.REQUIRES_NEW 代替一下。
  2. 如果这个异常发生时,内层需要事务回滚的代码还没有执行,则可以@Transactional(noRollbackFor = {内层抛出的异常}.class),指定内层也不为这个异常回滚。
  3. 取消内层方法的@Transactional 注解