Java 17中EntityManager.refresh抛出Entity not managed异常原因排查
核心原因
Spring事务注解在private方法上不生效
Spring的@Transactional依赖AOP代理实现,private方法无法被代理拦截,因此Propagation.REQUIRES_NEW的事务传播策略根本没有生效,所有操作实际处于同一个事务中。Java 8环境下Hibernate 5.x对实体状态的管理较为宽松,未触发异常;而Java 17通常搭配Spring Boot 3.x+Hibernate 6.x,Hibernate 6.x对实体托管状态的校验更严格,导致原实体被标记为非托管状态,调用refresh时抛出异常。Hibernate 6.x的持久化上下文行为变化
即使REQUIRES_NEW生效(方法为public),Hibernate 6.x在新事务加载同一ID的实体后,会自动将原事务中的实体实例detach,而Hibernate 5.x无此行为,这也是Java 17环境下报错的关键原因。Java 17模块系统的反射限制
Java 17的JPMS模块系统默认限制反射访问,Hibernate无法通过反射正确管理实体的托管状态,导致refresh时无法识别实体。
解决方案
1. 修复事务注解的生效问题
将Service B的updateFooInNewTransaction方法改为public,确保Spring能生成代理并应用REQUIRES_NEW策略:
// Service B @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateFooInNewTransaction(long id) { fooRepository.getById(id).setValue(...); }
2. 适配Hibernate 6.x的实体状态变化
在调用refresh前,先将实体合并回当前持久化上下文:
// Service A @Transactional public void handleFoo(Foo foo) { service.updateFooInNewTransaction(foo.getId()); foo = entityManager.merge(foo); // 合并实体到当前上下文 entityManager.refresh(foo); }
或者直接从Repository重新加载实体,替代refresh:
foo = fooRepository.findById(foo.getId()).orElseThrow();
3. 解决Java 17的反射权限问题
如果使用JPMS模块,在module-info.java中开放实体类所在模块给Hibernate:
opens com.yourpackage.entity to org.hibernate.core;
或者在测试启动时添加JVM参数,放宽反射限制:
--add-opens com.yourpackage.entity/org.hibernate.core=ALL-UNNAMED
4. 确保依赖版本兼容性
确认Spring Boot 3.x与Hibernate 6.x的版本匹配,避免依赖冲突(Spring Boot 3.x默认集成Hibernate 6.x,无需手动指定版本)。
内容的提问来源于stack exchange,提问作者Tuomas Toivonen

