You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 15:54:16