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

Hibernate Envers 5.x迁6时AuditReader#find抛NPE问题求助

Hibernate Envers 6.x迁移:自定义RevisionListener调用AuditReader#find时的NullPointerException问题

问题场景

从Hibernate Envers 5.x迁移至6.x版本后,项目中存在多对多关联场景(通过带额外字段的中间实体DocumentAuthorEmployee实现,两侧均为@ManyToOne关联),所有实体均标记@Audited(withModifiedFlag = true)。

实现自定义RevisionListener用于对比实体的前后版本差异时,在监听器中调用AuditReader#find方法触发以下NullPointerException:

java.lang.NullPointerException: Cannot invoke "org.hibernate.envers.internal.entities.EntityConfiguration.getRelationDescription(String)" because "entCfg" is null
    at org.hibernate.envers.internal.entities.EntitiesConfigurations.getRelationDescription(EntitiesConfigurations.java:101)
    at org.hibernate.envers.internal.entities.mapper.relation.ToOneIdMapper.isIgnoreNotFound(ToOneIdMapper.java:226)
    at org.hibernate.envers.internal.entities.mapper.relation.ToOneIdMapper.nullSafeMapToEntityFromMap(ToOneIdMapper.java:171)
    ...
    at org.hibernate.envers.internal.reader.AuditReaderImpl.find(AuditReaderImpl.java:89)
    at com.github.csaadaam.envers.manytoone.npe.listener.RevisionListener.entityChanged(RevisionListener.java:20)

原因分析

该问题源于Hibernate Envers 6.0对ToOneIdMapper类的逻辑变更:

  • 5.x版本中,代码会先检查关联实体是否被审计(if ( !referencedEntity.isAudited())),若已审计则跳过相关逻辑。
  • 6.x版本中,会尝试从版本数据的map中获取$type$字段来确定referencingEntityName,当该字段不存在时会导致referencingEntityName为null。后续在EntitiesConfigurations#getRelationDescription方法中,无法获取对应的EntityConfiguration,最终抛出NullPointerException。

相关核心代码片段:

// EntitiesConfigurations.java
final EntityConfiguration entCfg;
if ( isVersioned( entityName ) ) {
    entCfg = get( entityName );
}
else {
    entCfg = getNotVersionEntityConfiguration( entityName );
}
// 当entityName为null时,entCfg为null,后续调用getRelationDescription会抛NPE

解决方案

1. 延迟差异计算到事务提交后

RevisionListener的entityChanged方法执行时机是在事务提交前,此时Envers的审计数据尚未完全持久化,内部配置也未就绪。建议将版本对比逻辑延迟到事务提交后执行:

  • 若使用Spring框架,可通过@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)监听事务完成事件,在事件处理方法中调用AuditReader获取历史版本。
  • 原生JPA环境可使用TransactionSynchronizationRegistry注册同步回调。

2. 显式标记关联字段的审计属性

在中间实体的@ManyToOne字段上显式添加@Audited注解,确保Envers正确识别关联实体的审计配置:

@Audited(withModifiedFlag = true)
@Entity
public class DocumentAuthorEmployee {
    // ...其他字段

    @ManyToOne
    @JoinColumn(name = "document_id")
    @Audited
    private Document document;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "employee_id")
    @Audited
    private Employee employee;

    // ...getter/setter
}

3. 使用AuditQuery替代AuditReader#find

改用Envers的查询API获取历史版本,避免find方法的内部逻辑问题:

// 在合适的时机(事务提交后)执行
AuditReader auditReader = AuditReaderFactory.get(entityManager);
AuditQuery query = auditReader.createQuery()
        .forEntitiesAtRevision(Document.class, currentRevision - 1)
        .add(AuditEntity.id().eq(documentId));
Document previousVersion = (Document) query.getSingleResult();

4. 升级至最新Envers版本

检查Hibernate Envers的官方issue库,确认该问题是否已被修复。尝试升级到最新的6.x稳定版本(如6.4.x),新版本可能已修复该类内部配置引用的问题。

内容的提问来源于stack exchange,提问作者csaadaam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 03:24:59