








transactionStatus.setRollbackOnly()引起UnexpectedRollbackException异常@Transactional
public Result<Void> updateChannelLevy(ChannelLevyDTO dto) {
// ... 前置操作
boolean updated = update(dto);
if (!updated) { // 假设 update 返回失败
// 1. 手动将事务标记为回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// 2. 返回错误结果(注意:没有抛出异常!)
return Result.error("操作失败");
}
return Result.success();
// 3. 方法正常结束,Spring 会尝试提交事务
}
执行上面代码,在update为false的情况下,程序会抛出异常:UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
stacktrace:
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:632)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:386)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
at com.levy.backendapi.levy.LevyService$$EnhancerBySpringCGLIB$$80203775.updateChannelLevy(<generated>)
症结在于其中所调用的setRollbackOnly()方法。
Spring 的事务管理是基于异常的:
RuntimeException → 自动回滚setRollbackOnly() 的作用transactionStatus.setRollbackOnly();这个方法的真正含义是:
标记事务必须回滚(无论之后发生什么)。
Spring事务管理的源码,可查看 TransactionAspectSupport 类中的 invokeWithinTransaction() 方法,其中检查回滚标记并抛出 UnexpectedRollbackException的逻辑在 AbstractPlatformTransactionManager.commit() 方法中。示意代码见下方:
// Spring 事务管理的伪代码逻辑
try {
// 开启事务
// 执行业务方法
Object result = method.invoke(...);
// 如果事务被标记为回滚,抛出 UnexpectedRollbackException
if (transactionStatus.isRollbackOnly()) {
throw new UnexpectedRollbackException("Transaction rolled back...");
}
// 否则提交事务
commitTransaction();
} catch (RuntimeException e) {
// 如果抛出 RuntimeException,回滚事务
rollbackTransaction();
}
从源码可知,当方法正常返回(没有抛出异常)时,Spring 会尝试提交事务,如果发现事务已被标记为回滚,则会导致 UnexpectedRollbackException。
上面的Java方法向 Spring 发送了两个矛盾的信号:
| 信号 | 含义 | 代码行为 |
|---|---|---|
| 返回正常结果 | 告诉 Spring:"业务执行成功,可以提交事务" | 返回 Result 对象(没有异常) |
| 标记回滚 | 告诉 Spring:"需要回滚事务" | 调用 setRollbackOnly() |
而Spring 的困惑是:
于是,Spring 选择抛出一个 UnexpectedRollbackException 来报告这个矛盾。
@Transactional
public Result<Void> updateChannelLevy(ChannelLevyDTO dto) {
boolean updated = update(dto);
if (!updated) {
// 抛出异常,Spring 自动回滚
throw new BusinessException("操作失败: " + result.getMsg());
}
return Result.success();
}
外层调用方:
try {
Result<Void> result = levyService.updateChannelLevy(dto);
return result;
} catch (BusinessException e) {
// 转换异常为业务结果
return Result.error(e.getMessage());
}
public Result<Void> updateChannelLevy(ChannelLevyDTO dto) {
return transactionTemplate.execute(status -> {
boolean updated = update(dto);
if (!updated) {
status.setRollbackOnly(); // 标记回滚
return Result.error("操作失败"); // 返回错误结果
}
return Result.success();
});
}
复杂业务,需要细粒度控制事务的,可采用编程式事务。 简单业务操作,直接使用声明式事务即可,代码更简洁。
@Service
public class TransactionServiceDemo {
@Autowired
private TransactionTemplate transactionTemplate;
public Result<Void> processing() {
return transactionTemplate.execute(status -> {
// 步骤1
if (!performStep1()) {
status.setRollbackOnly();
return Result.error("步骤1失败");
}
// 步骤2
try {
performStep2();
} catch (BusinessException e) {
status.setRollbackOnly();
throw e; // 重新抛出异常
}
return Result.success();
});
}
}
setRollbackOnly(),避免UnexpectedRollbackException异常UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 的出现是因为:
setRollbackOnly() 不仅适用于编程式事务,也适用于声明式事务,但:
在编程式事务中:是主要且推荐的回滚控制方式
TransactionStatus 参数直接访问UnexpectedRollbackException在声明式事务中:是备选方案。通常建议使用异常驱动回滚。
TransactionAspectSupport.currentTransactionStatus() 获取UnexpectedRollbackException推荐策略:
setRollbackOnly()setRollbackOnly()我的实践建议:如果业务需要精细的事务控制,考虑使用编程式事务+ setRollbackOnly();如果业务相对简单,使用声明式事务+异常驱动更为简洁。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。