Spring配置Propagation.REQUIRES_NEW的内部事务未持久化问题咨询
问题根因
该现象的核心原因是BarService使用的getSubscriberRepo2持有的EntityManager未绑定到REQUIRES_NEW创建的新事务上下文,完全匹配你给出的日志和代码特征:
- 移除
Propagation.REQUIRES_NEW时,BarService的事务直接加入外层事务,和FooService共用同一个EntityManager实例,两个Repository的操作都被纳入同一个事务,提交时统一持久化,因此两条记录都能正常入库。 - 加上
Propagation.REQUIRES_NEW时,Spring会挂起外层事务的EntityManager,新创建一个独立的EntityManager绑定到内层事务上下文,但你的getSubscriberRepo2是手动创建的实例,注入的是固定的外层EntityManager引用,不是Spring代理的、可动态获取当前事务上下文的EntityManager:- 调用
repo.save(sub)保存bar@bar.com时,操作的是已经被挂起的外层EntityManager,不会被纳入内层新事务,也不会触发主键生成逻辑,所以日志中打印的Bar记录ID为null - 内层事务提交时,仅处理新的EntityManager中的持久化操作,而新的EntityManager中没有任何待提交的数据,相当于提交了空事务,没有任何数据写入数据库
- 外层事务恢复后,你保存在被挂起的EntityManager中的Bar实体,会因为持久化上下文状态问题被丢弃,最终不会被持久化
- 调用
验证方法
你可以在BarService的repo.save(sub)之后新增代码,对比当前事务绑定的EntityManager和Repository持有的EntityManager是否为同一个实例,即可直接确认问题:
// 类中注入EntityManagerFactory @PersistenceUnit private EntityManagerFactory emf; // save执行后新增日志 EntityManager currentTxEm = emf.createEntityManager().getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(sub); // 或者通过反射获取repo持有的EntityManager实例打印对比
如果两个实例ID不一致,即可100%确认是该问题。
解决方案
- 放弃手动创建Repository实例的逻辑,所有Repository都交给Spring Data JPA自动扫描管理,通过
@Autowired注入,Spring会自动为Repository注入代理的EntityManager,动态获取当前线程绑定的事务上下文实例。 - 如果必须手动实现Repository,注入EntityManager时使用
@PersistenceContext注解,不要用@Autowired直接注入固定的EntityManager实例,@PersistenceContext默认会注入代理对象,可动态关联当前事务的EntityManager。 - 如果是多数据源场景,需要为第二个数据源配置独立的事务管理器,并且在BarService的
@Transactional注解中通过transactionManager属性指定对应的事务管理器。 - 临时验证可以在BarService的save操作之后手动调用
repo.flush(),如果抛出「没有活动事务」相关异常,可直接确认EntityManager未绑定到当前内层事务。
内容的提问来源于stack exchange,提问作者AlexG
相关产品推荐
相关产品推荐

