Spring事务抛出异常后未回滚,请求排查问题原因
问题描述
我有一个MainService类,其processData方法未标注@Transactional注解:
@Service @RequiredArgsConstructor public class MainService { private final ServiceA serviceA; public void processData() { List<EntityA> list = serviceA.getList(); list.forEach(item -> { try { serviceA.doSomeDbOperations(item); } catch(Exception e) { // some processing here } finally { // some processing and DB interactions here } }); } }
我的需求是:当try块中调用的serviceA.doSomeDbOperations(item)执行抛出异常时,回滚该方法内的数据库变更。因此我为ServiceA的doSomeDbOperations方法添加了@Transactional注解:
@Service public class ServiceA { // dependencies @Transactional public void doSomeDbOperations(EntityA item) { // some logic here repositoryA.save(item); serviceB.deleteSomething(item); } }
其中serviceB.deleteSomething(input)方法可能抛出RuntimeException:
@Service public class ServiceB { // dependencies public void deleteSomething(EntityA item) { // some logic here if(condition) { throw new RuntimeException(); } } }
当前问题:当异常抛出时,try块中的数据库变更并未回滚,导致数据不一致。
问题分析
- 核心原因是异常被上层方法捕获,未传递到事务管理器:Spring的
@Transactional默认逻辑是,只有当事务方法抛出的异常未被该方法内部捕获,而是传播到事务管理器时,才会触发事务回滚。你的MainService.processData方法里用try-catch捕获了doSomeDbOperations抛出的异常,导致事务管理器无法感知到异常,因此不会执行回滚操作。 - 代码存在语法错误:
Throw new RuntimeException();应改为小写的throw,否则无法编译通过。
解决方案
针对这个问题,有两种常见的处理方式:
方式一:让异常传播到事务管理器
修改MainService的异常捕获逻辑,在处理完异常后重新抛出RuntimeException,让事务管理器感知到异常并触发回滚:
@Service @RequiredArgsConstructor public class MainService { private final ServiceA serviceA; public void processData() { List<EntityA> list = serviceA.getList(); list.forEach(item -> { try { serviceA.doSomeDbOperations(item); } catch(RuntimeException e) { // 保留你的异常处理逻辑,比如日志记录、告警等 throw e; // 重新抛出异常,触发事务回滚 } finally { // 原有的finally处理逻辑 } }); } }
方式二:手动标记事务回滚(适合必须在上层捕获异常的场景)
如果业务要求必须在MainService里捕获异常且不抛出,可以在ServiceA的方法里手动标记事务需要回滚:
@Service public class ServiceA { // dependencies @Transactional public void doSomeDbOperations(EntityA item) { try { // some logic here repositoryA.save(item); serviceB.deleteSomething(item); } catch(RuntimeException e) { // 手动标记当前事务为回滚状态 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 若需要上层处理异常,可继续抛出;不需要则仅保留标记逻辑 throw e; } } }
标记setRollbackOnly后,无论上层是否捕获异常,事务最终都会执行回滚。
补充说明
Spring事务默认的回滚规则是:仅对未被捕获的RuntimeException和Error触发回滚,检查型异常(非RuntimeException)不会触发默认回滚。如果需要对检查型异常也执行回滚,可以在@Transactional注解中指定rollbackFor属性,例如@Transactional(rollbackFor = Exception.class)。
内容的提问来源于stack exchange,提问作者MehdiB
相关产品推荐
相关产品推荐

