如何在Hibernate Search 6.x投影查询中高效获取Hibernate Envers版本号
实现思路
1. 将Envers修订号纳入Hibernate Search索引
要实现版本校验,核心是把实体对应的Envers最新修订号存入Hibernate Search索引,以下两种高效方案可选:
方式一:数据库公式映射+索引
在实体类中添加基于数据库查询的计算字段,直接映射该实体的最新Envers修订号,同时标记为可索引字段:
@Entity @Audited public class YourEntity { @Id private Long id; // 替换YourEntity_AUD为实际的审计表名,自动计算最新修订号 @Formula("(SELECT MAX(e.REV) FROM YourEntity_AUD e WHERE e.id = id)") @IndexedField(name = "revisionNumber") private Integer revisionNumber; // 其他实体字段... }
这种方式无需额外代码侵入,Hibernate会自动计算字段值,Hibernate Search索引时直接读取存储。注意需给审计表的id和REV字段建立联合索引,避免大表场景下的查询性能问题。
方式二:Envers事件监听主动设置修订号
若担心@Formula的查询开销,可通过Envers监听器在实体变更时主动获取修订号,存入临时字段后索引:
- 实现Envers的
RevisionListener,用ThreadLocal绑定当前事务的修订号:
public class CustomRevisionListener implements RevisionListener { private static final ThreadLocal<Integer> CURRENT_REV = new ThreadLocal<>(); @Override public void newRevision(Object revisionEntity) { RevInfo revInfo = (RevInfo) revisionEntity; CURRENT_REV.set(revInfo.getId()); } public static Integer getCurrentRevision() { return CURRENT_REV.get(); } public static void clear() { CURRENT_REV.remove(); } }
- 在Hibernate配置中指定监听器(以persistence.xml为例):
<property name="org.hibernate.envers.revision_listener" value="com.yourpackage.CustomRevisionListener"/>
- 实体类中添加临时索引字段,通过生命周期回调赋值:
@Entity @Audited public class YourEntity { @Id private Long id; @Transient @IndexedField(name = "revisionNumber") private Integer revisionNumber; @PostPersist @PostUpdate public void setRevisionNumber() { this.revisionNumber = CustomRevisionListener.getCurrentRevision(); CustomRevisionListener.clear(); } // 其他实体字段... }
该方案避免了索引时的额外数据库查询,性能更优,ThreadLocal确保了修订号与当前事务的绑定安全。
2. 投影查询时校验版本一致性
查询时同时投影索引中的修订号,与数据库中实体的最新修订号对比,判断是否同步:
// 1. 执行投影查询,获取数据和索引中的修订号 List<Tuple> results = searchSession.search(YourEntity.class) .where(f -> f.matchAll()) .select(f -> f.composite( f.field("id", Long.class), f.field("name", String.class), f.field("revisionNumber", Integer.class) )) .fetchHits(20); // 2. 校验版本,按需加载数据 AuditReader auditReader = AuditReaderFactory.get(entityManager); for (Tuple tuple : results) { Long id = tuple.get(0, Long.class); String projectedName = tuple.get(1, String.class); Integer indexedRev = tuple.get(2, Integer.class); // 查询数据库中实体的最新修订号 Number latestDbRev = auditReader.getRevisionNumberForEntity(id, YourEntity.class); if (indexedRev.equals(latestDbRev.intValue())) { // 索引与数据库同步,直接使用投影数据 useProjectedData(projectedName); } else { // 索引过时,从数据库加载最新数据 YourEntity latestEntity = entityManager.find(YourEntity.class, id); useLatestData(latestEntity); } }
3. 额外优化建议
- 增量索引配合:保持Hibernate Search默认的增量索引开启状态,仅在实体变更时更新索引,降低版本不一致概率。
- 批量校验:处理大量数据时,通过IN子句批量查询数据库中的修订号,减少单次查询开销。
- 修订号缓存:对频繁访问的实体,缓存其最新修订号,进一步减少数据库查询次数。
内容的提问来源于stack exchange,提问作者Rokko_11
相关产品推荐
相关产品推荐

