Spring Data JPA事务异常:每次方法调用新建事务致回滚失效
看起来你遇到的核心问题是:明明给方法加了@Transactional,但第一个对象保存后抛异常却没法回滚,日志还显示每次save都新建事务提交。这大概率是事务代理没生效导致的,我给你拆解下常见原因和解决办法:
一、先排查最常见的「内部调用」坑
如果你的事务方法是被同一个类里的另一个非事务方法直接调用的,那Spring的动态代理就会被绕过,事务注解等于白加!比如这种情况:
@Service public class MyService { // 非事务方法 public void startSave() { testSaveMultiple(); // 直接内部调用,事务不生效! } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void testSaveMultiple() { repo.save(obj1); throw new NullPointerException(); // 抛异常但obj1已经被提交了 repo.save(obj2); } }
因为Spring的事务是基于代理实现的,只有通过代理对象调用方法时,才会触发事务逻辑。内部调用是直接用原对象调用,根本走不到代理那一步,所以testSaveMultiple的事务没生效,反而每个save方法自己的默认事务(Spring Data JPA的save自带@Transactional)会独立提交,导致obj1无法回滚。
解决办法:
- 拆分到不同类:把事务方法放到另一个Service类里,通过依赖注入调用;
- 获取自身代理对象调用:
@Service public class MyService implements ApplicationContextAware { private MyService selfProxy; private final MyRepo repo; public MyService(MyRepo repo) { this.repo = repo; } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { selfProxy = context.getBean(MyService.class); } public void startSave() { selfProxy.testSaveMultiple(); // 通过代理对象调用,事务生效 } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void testSaveMultiple() { repo.save(obj1); throw new NullPointerException(); repo.save(obj2); } }
二、检查事务代理的基础条件
如果不是内部调用的问题,那要确认这些基础配置有没有问题:
- 你的服务类是否被Spring管理:必须加
@Service/@Component注解,并且在Spring的扫描路径下(比如启动类的@SpringBootApplication扫描范围覆盖到这个类); - 类/方法不能是final的:如果你的服务类是final,或者事务方法是final,Spring的CGLIB代理(默认对无接口类用CGLIB)无法生成子类,事务注解会被直接忽略;
- 是否开启了事务管理:确保配置类加了
@EnableTransactionManagement注解(Spring Boot项目如果用了spring-boot-starter-data-jpa,会自动开启,但手动配置的话要注意)。
三、验证事务是否真的生效
你可以在事务方法里加一行日志,打印当前对象的类名:
System.out.println(this.getClass().getName());
如果输出的是类似com.example.MyService$$EnhancerBySpringCGLIB$$xxxx或者com.example.$Proxyxxx,说明代理生效了;如果是原类名,那肯定是代理没生效,回到前面的步骤排查。
只要代理生效了,你的@Transactional(rollbackFor = Exception.class)配置是没问题的——NullPointerException属于RuntimeException,即使不指定rollbackFor也会触发回滚,加上之后还能覆盖检查异常。这时候所有save操作都会加入同一个事务,一旦抛异常,整个事务会回滚,第一个对象也不会被保存。
内容的提问来源于stack exchange,提问作者Manish

