
本文详解 spring `@transactional` 注解在 jpa 场景下如何实现异常触发的自动事务回滚,明确说明无需显式配置 `rollbackfor` 即可对运行时异常生效,并强调 entitymanager 必须由 spring 管理才能保证事务一致性。
在 Spring Boot + JPA 应用中,确保数据库操作的原子性是事务管理的核心目标。你提供的代码使用了 @Transactional 注解,但内部包裹了 try-catch 并吞掉了异常(e.getCause() 未抛出或记录),这实际上破坏了 Spring 的事务回滚机制——因为 Spring 只有在方法非正常退出(即抛出未捕获异常) 时,才会将当前事务标记为 rollback-only。
✅ 正确做法:移除无效 try-catch,依赖默认回滚策略
Spring 的 @Transactional 默认行为是:当方法抛出任何未被捕获的 RuntimeException 或其子类(如 IllegalArgumentException、NullPointerException、JPA 的 PersistenceException 等)时,自动触发回滚。而 JDBC 操作失败(如 SQL 语法错误、约束冲突、连接中断)所抛出的异常,在 Spring Data JPA 中通常会被包装为 RuntimeException(例如 DataAccessException 的子类),因此无需额外指定 rollbackFor = Exception.class。
以下是重构后的推荐写法:
@Transactional
public void deleteDadosSExecReenvCancelada(Long nuSeqConsecao) {
// 注意:不再使用 try-catch 包裹业务逻辑!
String deleteReenvGruSql = """
DELETE FROM sigpc_fnde.s_execucao_financ_gru_reenv
WHERE NU_SEQ_EXEC_FINANC_REENV IN (
SELECT NU_SEQ_EXECUCAO_FINANCEIRA
FROM sigpc_fnde.s_exec_financ_reenv
WHERE NU_SEQ_CONCESSAO = ?
)
""";
getEntityManager().createNativeQuery(deleteReenvGruSql)
.setParameter(1, nuSeqConsecao)
.executeUpdate();
String deleteReenvSql = """
DELETE FROM sigpc_fnde.s_exec_financ_reenv
WHERE NU_SEQ_CONCESSAO = ?
""";
getEntityManager().createNativeQuery(deleteReenvSql)
.setParameter(1, nuSeqConsecao)
.executeUpdate();
}⚠️ 关键前提:EntityManager 必须由 Spring 托管
事务上下文与 EntityManager 生命周期强绑定。若 getEntityManager() 返回的是手动创建的 EntityManager(如通过 emf.createEntityManager()),则它脱离 Spring 的事务管理器(PlatformTransactionManager),即使加了 @Transactional,也无法实现回滚。
✅ 正确注入方式(推荐使用构造器注入 + @PersistenceContext):
@Service
@RequiredArgsConstructor
public class ExecucaoService {
private final EntityManager entityManager; // Spring 自动注入受管 EntityManager
@Transactional
public void deleteDadosSExecReenvCancelada(Long nuSeqConsecao) {
// ... 上述无 try-catch 的实现
}
}? 提示:@PersistenceContext 是 JPA 标准注解,Spring 会将其解析为容器管理的、与当前事务同步的 EntityManager 实例(即 SharedEntityManagerProxy),确保 flush、clear 和回滚行为均符合事务预期。
❌ 常见误区澄清
- @Transactional(rollbackFor = Exception.class) 不是必须的:除非你明确需要对 IOException、SQLException 等检查型异常(checked exception)也触发回滚(此时需显式声明),否则默认策略已覆盖绝大多数 JPA 异常场景。
- catch (Exception e) { e.getCause(); } 是危险模式:它吞噬异常、阻止传播,导致 Spring 认为方法“成功完成”,从而提交事务——即使第二条 DELETE 已失败,第一条也可能已被持久化。
- 手动调用 entityManager.flush() 并非必需:executeUpdate() 本身不依赖一级缓存,直接执行 SQL;事务提交时自然保证所有变更原子生效或全部撤销。
✅ 最佳实践总结
| 项目 | 推荐方案 |
|---|---|
| 异常处理 | 避免空 catch;如需日志,应 log.error("删除执行信息失败", e); throw e; |
| 事务边界 | 方法粒度合理,避免过长事务;必要时用 @Transactional(propagation = Propagation.REQUIRES_NEW) 隔离子流程 |
| SQL 安全性 | 生产环境建议优先使用 JPQL 或 Repository 方法,原生 SQL 需严格校验表名/字段名及权限 |
| 测试验证 | 编写集成测试,主动模拟异常(如 mock EntityManager 抛出 PersistenceException),断言数据库状态未变更 |
遵循以上原则,你的 deleteDadosSExecReenvCancelada 方法即可在任意 SQL 执行失败时,自动、可靠地回滚整个事务,真正实现数据一致性保障。










