You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 17中EntityManager.refresh抛出Entity not managed异常原因排查

问题分析与解决方案

核心原因

  1. 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时抛出异常。

  2. Hibernate 6.x的持久化上下文行为变化
    即使REQUIRES_NEW生效(方法为public),Hibernate 6.x在新事务加载同一ID的实体后,会自动将原事务中的实体实例detach,而Hibernate 5.x无此行为,这也是Java 17环境下报错的关键原因。

  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 10:57:11