Hibernate 5.3+调用getHibernateLazyInitializer获取ID报EntityNotFoundException
Hibernate 5.3+ 结合Envers使用时,AuditedReader加载的Proxy调用
getIdentifier()抛出EntityNotFoundException问题 问题描述
升级至Hibernate 5.3+版本后,调用由Hibernate Envers的AuditedReader加载的HibernateProxy对象的getHibernateLazyInitializer().getIdentifier()方法时,抛出javax.persistence.EntityNotFoundException异常。数据库中存在对应ID的实体记录,调试时代码可正常返回结果,但正常运行时抛出异常。已尝试升级Hibernate版本未解决,当前环境为JBoss 7.4 EAP补丁11,依赖版本:
- hibernate-core-5.3.29.Final-redhat-00001
- hibernate-envers-5.3.28.Final-redhat-00001
- javax.persistence.api
相关代码示例
@Entity @Audited public class Person { @Id private Long id; @Column(name="surname") private String name; @JoinColumn(name="country_id", foreignKey = @javax.persistence.ForeignKey(name="FK_country_id")) @ManyToOne(cascade = { CascadeType.REFRESH }, fetch = FetchType.EAGER) @Fetch(FetchMode.SELECT) private Country country; } @Entity @Audited public class Country { @Id public Long id; @Column(name="countryName") public String countryName; } // 从Envers审计表的Person对象中获取Country的ID private static Object getId(Object personObject, Field field) throws IllegalAccessException { Object countryObject = field.get(personObject); if (countryObject instanceof HibernateProxy) { return ((HibernateProxy)countryObject).getHibernateLazyInitializer().getIdentifier(); } return null; }
报错堆栈信息
javax.persistence.EntityNotFoundException: Unable to find entity.Country with id 10 at org.hibernate@5.3.29.Final-redhat-00001//org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:163) at org.hibernate@5.3.29.Final-redhat-00001//org.hibernate.proxy.AbstractLazyInitializer.checkTargetState(AbstractLazyInitializer.java:291) at org.hibernate@5.3.29.Final-redhat-00001//org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:186) at org.hibernate@5.3.29.Final-redhat-00001//org.hibernate.proxy.AbstractLazyInitializer.getIdentifier(AbstractLazyInitializer.java:95)
解决方向与方案
1. 统一Hibernate Core与Envers版本
当前依赖中hibernate-core(5.3.29)和hibernate-envers(5.3.28)版本不一致,可能引发内部API兼容性问题。将hibernate-envers升级至与core相同的版本(5.3.29.Final-redhat-00001),再验证问题是否解决。
2. 绕过Proxy直接读取审计表中的关联ID
Envers会在审计表中存储关联实体的外键值(如person_aud表中的country_id字段),无需通过Proxy对象获取ID:
- 通过反射直接读取Person审计对象中对应外键的字段(而非
country关联对象); - 使用Envers的查询API(
AuditQuery)直接查询审计数据中的关联ID,例如:AuditQuery query = reader.createQuery() .forEntitiesAtRevision(Person.class, revisionNumber) .add(AuditEntity.id().eq(personId)); Person person = (Person) query.getSingleResult(); // 直接读取country_id字段,需确保字段可访问 Field countryIdField = Person.class.getDeclaredField("country_id"); countryIdField.setAccessible(true); Long countryId = (Long) countryIdField.get(person);
3. 避免Proxy初始化触发的查询
Hibernate 5.3+中,AbstractLazyInitializer.getIdentifier()会触发Proxy初始化(检查目标实体状态),若此时EntityManager已关闭或上下文异常,会导致查询失败。可通过以下方式规避:
- 使用内部API读取标识符(不推荐,依赖Hibernate内部实现):通过反射读取
AbstractLazyInitializer中的identifier字段,跳过初始化检查:if (countryObject instanceof HibernateProxy) { LazyInitializer initializer = ((HibernateProxy) countryObject).getHibernateLazyInitializer(); Field identifierField = AbstractLazyInitializer.class.getDeclaredField("identifier"); identifierField.setAccessible(true); return identifierField.get(initializer); } - 确保操作在EntityManager上下文内:调用
getIdentifier()时,确保AuditedReader对应的EntityManager处于打开状态,避免上下文关闭后触发Proxy初始化。
4. 调整Envers关联实体的加载策略
修改实体类的关联配置,避免Envers生成Proxy对象:
- 将
@Fetch(FetchMode.SELECT)改为@Fetch(FetchMode.JOIN),让Envers在加载Person审计数据时直接关联查询Country,避免生成Proxy; - 检查Envers配置参数
org.hibernate.envers.proxy.enabled,若设置为false可禁用Proxy生成(需验证Hibernate 5.3是否支持该参数)。
内容的提问来源于stack exchange,提问作者Skyverian
相关产品推荐
相关产品推荐

