问题背景
[[记一次事务的坑: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
正常提交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
解决方案
- 捕获异常时,手动设置上层事务状态为 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);
}
- 修改事务传播机制,比如说将内层事务的传播方式指定为
@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 代替一下。 - 如果这个异常发生时,内层需要事务回滚的代码还没有执行,则可以
@Transactional(noRollbackFor = {内层抛出的异常}.class)
,指定内层也不为这个异常回滚。 - 取消内层方法的@Transactional 注解