如何解决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的生效逻辑上:
B.methodB()开启事务T1后,E.methodE()默认以REQUIRED传播行为加入T1,两者共用同一个事务。- 处理
d的逻辑如果抛出RuntimeException(比如空指针、Hibernate操作异常),即使被catch块捕获,Hibernate会自动将当前事务标记为rollback-only——这是Hibernate的底层行为,不受Spring的dontRollbackOn直接控制。 - 当事务最终提交时,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.
相关产品推荐
相关产品推荐

