Hibernate Enver未审计软删除字段?如何在删除时记录该字段
首先咱们得先搞清楚问题根源:你用@SQLDelete实现软删除(把deleted设为当前时间),但Enver是在Hibernate触发删除事件的节点捕获实体状态的——这时候你的@SQLDelete还没执行,实体里的deleted字段还是null,所以审计表Person_HISTORY里这个字段自然是空的。
下面给你几个实用的解决方案,按推荐程度排序:
方案一:用@PreRemove提前设置deleted字段(最推荐)
直接在Person实体里加一个@PreRemove注解的方法,在Hibernate触发删除操作之前手动把deleted字段设为当前时间。这样Enver捕获实体状态时,就能拿到已经赋值的字段,存入审计表。
修改后的实体类代码:
@Entity @Table(name = "person") @SQLDelete(sql = "UPDATE person SET deleted = NOW() WHERE id = ?", check = ResultCheckStyle.COUNT) @Where(clause = "deleted is NULL") @Audited public class Person { @Id private UUID id; private OffsetDateTime deleted; // 新增PreRemove方法,删除前设置deleted字段 @PreRemove public void beforeRemove() { this.deleted = OffsetDateTime.now(); } // 省略其他字段、getter和setter }
这个方案的好处是:不需要改业务代码的删除逻辑,也不用额外配置Enver,只加几行代码就能解决问题。而且你的@SQLDelete还是用NOW()保证数据库层面的时间一致性,和代码里的OffsetDateTime.now()只要时区一致就完全匹配。
方案二:手动更新实体再执行删除(适合小项目)
如果不想改实体代码,也可以调整业务层的删除逻辑:不要直接调用entityManager.remove(person),而是先手动设置deleted字段,再执行更新操作。
示例代码:
// 原来的删除逻辑:entityManager.remove(person); // 改成下面的更新逻辑 person.setDeleted(OffsetDateTime.now()); entityManager.merge(person);
这样Enver会把这次更新操作记录到审计表,deleted字段就会被正确存储。缺点是需要修改所有涉及删除Person的业务代码,适合项目规模较小的情况。
方案三:自定义Enver RevisionListener(适合复杂场景)
如果上面两种方案都不满足需求,可以自定义一个EntityTrackingRevisionListener,在Enver记录审计数据的节点,手动补全deleted字段的值。
- 先写自定义的Listener类:
import org.hibernate.envers.EntityTrackingRevisionListener; import org.hibernate.envers.RevisionType; public class PersonAuditRevisionListener implements EntityTrackingRevisionListener { @Override public void newRevision(Object revisionEntity) { // 这里可以初始化版本实体的属性,比如设置版本创建时间等(可选) } @Override public void entityChanged(Class<?> entityClass, String entityName, Object entityId, RevisionType revisionType, Object revisionEntity) { // 只处理Person实体的删除事件 if (Person.class.equals(entityClass) && RevisionType.DEL.equals(revisionType)) { Person person = Person.class.cast(entity); // 如果deleted字段还是null,手动设置为当前时间 if (person.getDeleted() == null) { person.setDeleted(OffsetDateTime.now()); } } } }
- 然后在Enver的配置里指定这个Listener:
properties.put("org.hibernate.envers.revision_listener", "com.yourpackage.PersonAuditRevisionListener");
这个方案灵活性最高,但配置稍复杂,适合需要对审计逻辑做更多自定义的场景。
内容的提问来源于stack exchange,提问作者Viet

