ValidityAuditStrategy下ManyToOne关联查询出现NonUniqueResultException
在ValidityAuditStrategy下解决Entity关联查询的NonUniqueResultException问题
问题场景
现有两个开启审计的实体:
@Audited EntityA { @Column(name = "some_string") private String someString; @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "entity_b_id") @NotNull private EntityB entityB; } EntityB { @OneToMany(mappedBy = "entityB", fetch = FetchType.LAZY) @MapKey(name = "someString") private Map<String, EntityA> entityAs; }
使用ValidityAuditStrategy时,查询EntityA的修订版本会预加载关联的EntityB;若EntityB存在多个对应该EntityA修订版本的有效审计记录,会抛出NonUniqueResultException。关闭该策略后,查询会自动获取EntityA修订版本时间点及之前的最新EntityB版本,符合需求,但需要保留ValidityAuditStrategy的功能。
解决方案
1. 为关联字段单独指定审计策略
在EntityA的entityB字段上,通过@Audited注解的strategy参数覆盖全局的ValidityAuditStrategy,使用默认的DefaultAuditStrategy:
@Audited(strategy = DefaultAuditStrategy.class) @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "entity_b_id") @NotNull private EntityB entityB;
这样查询EntityA的修订版本时,关联的EntityB会采用默认策略的逻辑——获取对应时间点及之前的最新版本,而非所有有效版本,避免抛出NonUniqueResultException,同时全局仍保留ValidityAuditStrategy的审计跟踪能力。
2. 确保EntityB的审计记录无有效期重叠
若业务上不需要EntityB存在同一时间点的多个有效版本,检查并修复EntityB的审计数据:
- 确认
ValidityAuditStrategy的revend_timestamp字段已正确生成(需开启org.hibernate.envers.validity_store_revend_timestamp配置) - 清理或修正重叠的审计记录,保证任意时间点只有一个EntityB的有效版本
3. 自定义关联查询逻辑(备选)
若上述方法不适用,可通过AuditReader构建查询时显式过滤EntityB的版本:
AuditReader reader = AuditReaderFactory.get(entityManager); EntityA revision = reader.createQuery() .forEntitiesAtRevision(EntityA.class, revisionNumber) .traverseRelation("entityB", JoinType.INNER) .add(AuditEntity.revisionNumber().le(revisionNumber)) .addOrder(AuditEntity.revisionNumber().desc()) .setMaxResults(1) .getSingleResult();
此方法无需投影,直接获取完整的EntityA实体及对应正确版本的EntityB。
注意事项
- 验证Quarkus/Hibernate版本兼容性:上述注解参数在Hibernate 6.6+及Quarkus 3.20+版本中均支持,升级至3.27/7.1版本后仍可正常使用
- 若使用
@AuditJoinTable,需确保其配置与ValidityAuditStrategy的表结构匹配,否则无法生效
内容的提问来源于stack exchange,提问作者Traivor
相关产品推荐
相关产品推荐

