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

如何解决Spring事务UnexpectedRollbackException:事务标记为仅回滚异常

事务回滚异常问题排查与修复

我采用Controller-Service-Dao架构搭建后端服务,代码结构如下。运行时抛出org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only异常,日志显示E.methodE()的try-catch块捕获到错误,Hibernate日志显示删除操作已执行成功,但最终提交时事务被回滚。我已经在事务注解中设置了dontRollbackOn={Exception.class},想知道怎么修复这个问题,是不是事务注解用错了?


代码结构

1. Controller层

@RestController
public class A {
    @Autowired
    B b;

    @GetMapping("/path")
    public ResponseEntity methodA() throws Exception {
        return ResponseEntity.ok(b.methodB());
    }
}

2. 服务层B

@Service
public class B {
    @Autowired
    E e;
    @Transactional(dontRollbackOn = {Exception.class})
    public Map methodB() throws Exception {
        return e.methodE();
    }
}

2.a. 服务层E

@Service
public class E {
    @Autowired
    C c;
    @Transactional(dontRollbackOn = {Exception.class})
    public Map methodE() throws Exception {
        List<D> ds = c.inquiry();
        for (D d : ds) {
              
            try { // 在此处理每个d }
            catch(Exception ex) {d.setStatus("FAIL!");}
            c.save(d);
        }
        return new HashMap(){{ put("deleted_rows", c.delete()); }};
    }
}

3. Dao层C

@Component
public class C {
    public List<D> inquiry() {
        Session session = sessionFactory.getCurrentSession();
        return session.createQuery("from D").list();
    };
    public void save(D d){
        Session session = sessionFactory.getCurrentSession();
        session.save(d);
    };

    public int delete() {
        sessionFactory.getCurrentSession().createQuery("delete from D where createdDate < :today")
            .setParameter("today", today).executeUpdate();
    }
}

问题根源

核心问题出在事务传播机制和dontRollbackOn的生效逻辑上:

  1. B.methodB()开启事务T1后,E.methodE()默认以REQUIRED传播行为加入T1,两者共用同一个事务。
  2. 处理d的逻辑如果抛出RuntimeException(比如空指针、Hibernate操作异常),即使被catch块捕获,Hibernate会自动将当前事务标记为rollback-only——这是Hibernate的底层行为,不受Spring的dontRollbackOn直接控制。
  3. 当事务最终提交时,Spring发现事务已被标记为必须回滚,会强制触发回滚并抛出UnexpectedRollbackException,导致已执行的删除操作也被回滚。

修复方案

方案1:让E层方法开启独立事务

修改E.methodE()的事务注解,设置传播行为为REQUIRES_NEW,使其创建独立事务,与B层事务隔离:

@Transactional(dontRollbackOn = {Exception.class}, propagation = Propagation.REQUIRES_NEW)
public Map methodE() throws Exception {
    // 原有代码不变
}

注意:这种方式下两层操作不在同一事务中,需自行评估数据一致性风险。

方案2:避免事务被标记为rollback-only

在处理d的逻辑中,将RuntimeException转换为Checked Exception抛出,避免Hibernate自动标记事务:

try {
    // 处理d的业务逻辑
} catch (RuntimeException ex) {
    d.setStatus("FAIL!");
    // 将运行时异常转为受检异常抛出
    throw new Exception("处理单条数据失败", ex);
}

需确保所有可能触发Hibernate标记的RuntimeException都被捕获转换。

方案3:移除B层事务注解

如果B层仅作为调用入口、无独立数据库操作,可以直接去掉B层的@Transactional,让事务控制仅在E层生效,避免跨方法的事务状态冲突。


额外注意点

  • dontRollbackOn仅控制Spring层面的事务回滚逻辑,无法干预Hibernate对事务状态的底层标记。
  • 优先排查处理d的逻辑中抛出的异常类型,RuntimeException是触发事务标记的核心诱因。

内容的提问来源于stack exchange,提问作者Dhana D.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 00:55:13