1. 概述

在 ORM 的世界里,数据库审计(Auditing)指的是对持久化实体相关的事件进行追踪和记录,也可以理解为实体版本管理。这个概念借鉴了 SQL 中的触发器机制,主要关注实体的插入(insert)、更新(update)和删除(delete)操作。审计的好处类似于代码版本控制,可以帮我们追踪变更历史、排查问题、满足合规性需求。

本文将介绍三种实现审计的方式:

  • 使用标准 JPA 生命周期回调
  • 使用 Hibernate Envers(Hibernate 提供的审计模块)
  • 使用 Spring Data JPA 内置的审计支持

我们以 BarFoo 两个实体为例,其关系结构如下:

Screenshot_4


2. 使用 JPA 实现审计

虽然 JPA 本身没有专门的审计 API,但我们可以通过实体生命周期回调(Lifecycle Callbacks)来实现类似功能。

2.1. @PrePersist、*@PreUpdate* 和 @PreRemove

在 JPA 实体类中,我们可以通过注解标记回调方法,使其在特定生命周期事件中被调用。常用的三个注解是:

@Entity
public class Bar {
      
    @PrePersist
    public void onPrePersist() { ... }
      
    @PreUpdate
    public void onPreUpdate() { ... }
      
    @PreRemove
    public void onPreRemove() { ... }
      
}

⚠️ 注意事项:

  • 回调方法必须返回 void,且不能有参数
  • 方法名和访问修饰符可以随意,但不能是 static

📌 一个小提醒:@Version 注解主要用于乐观锁,和审计没有直接关系。

2.2. 实现回调方法

虽然回调机制看起来方便,但 JPA 2 规范(JSR 317)明确指出:

生命周期回调方法不应该调用 EntityManager 或 Query 操作,也不应该访问其他实体或修改关系。

因此,我们只能修改实体的非关系状态。为了实现审计,我们可以手动在实体中添加两个字段:operationtimestamp,记录操作类型和时间戳:

@Entity
public class Bar {
     
    //...
     
    @Column(name = "operation")
    private String operation;
     
    @Column(name = "timestamp")
    private long timestamp;
     
    // standard setters and getters for the new properties
     
    @PrePersist
    public void onPrePersist() {
        audit("INSERT");
    }
     
    @PreUpdate
    public void onPreUpdate() {
        audit("UPDATE");
    }
     
    @PreRemove
    public void onPreRemove() {
        audit("DELETE");
    }
     
    private void audit(String operation) {
        setOperation(operation);
        setTimestamp((new Date()).getTime());
    }
     
}

✅ 如果多个实体都需要审计,可以使用 @EntityListeners 将审计逻辑集中管理:

@EntityListeners(AuditListener.class)
@Entity
public class Bar { ... }
public class AuditListener {
    
    @PrePersist
    @PreUpdate
    @PreRemove
    private void beforeAnyOperation(Object object) { ... }
    
}

3. 使用 Hibernate Envers 实现审计

Hibernate 提供了 Envers 模块,用于对持久化实体进行审计和版本控制。相比手动实现,它更强大、更灵活。

3.1. 引入 Envers

首先添加依赖:

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-envers</artifactId>
    <version>6.4.4.Final</version>
</dependency>

然后在实体上添加 @Audited 注解:

@Entity
@Audited
public class Bar { ... }

如果实体之间有关联关系,比如 BarFoo 是一对多关系,要么在 Foo 上也加 @Audited,要么在 Bar 的关系字段上加 @NotAudited

@OneToMany(mappedBy = "bar")
@NotAudited
private Set<Foo> fooSet;

3.2. 审计表的生成

Envers 会自动为我们生成审计表,有以下几种方式:

✅ 自动建表(开发环境推荐):

  • 设置 hibernate.hbm2ddl.autocreatecreate-dropupdate

❌ 不推荐用于生产环境

举个例子,Bar 会被生成 bar_AUD 表,包含所有字段,外加两个字段:

  • REV:修订号,外键指向 REVINFO
  • REVTYPE:操作类型(0=新增,1=更新,2=删除)

此外,还会生成 REVINFO 表,记录每次修订的时间戳。

3.3. 配置 Envers

Envers 的配置和其他 Hibernate 属性一样。例如,修改审计表后缀:

Properties hibernateProperties = new Properties(); 
hibernateProperties.setProperty(
  "org.hibernate.envers.audit_table_suffix", "_AUDIT_LOG"); 
sessionFactory.setHibernateProperties(hibernateProperties);

更多配置项可参考 Envers 官方文档

3.4. 查询历史数据

通过 AuditReader 可以查询历史版本:

AuditReader reader = AuditReaderFactory.get(session);

查询特定修订版本的实体:

AuditQuery query = reader.createQuery()
  .forEntitiesAtRevision(Bar.class, 2)

查询某个实体的所有历史版本:

AuditQuery query = reader.createQuery()
  .forRevisionsOfEntity(Bar.class, true, true);

排序和过滤:

query.addOrder(AuditEntity.revisionNumber().desc());

4. 使用 Spring Data JPA 实现审计

Spring Data JPA 是 JPA 的增强层,提供了更简洁的接口抽象和内置审计支持。

4.1. 启用 JPA 审计

在配置类上添加注解:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@EnableJpaAuditing
public class PersistenceConfig { ... }

4.2. 添加 Spring 的实体监听器

使用 Spring 提供的 AuditingEntityListener 来监听实体变更:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar { ... }

4.3. 记录创建和修改时间

通过 @CreatedDate@LastModifiedDate 自动填充时间字段:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
    
    //...
    
    @Column(name = "created_date", nullable = false, updatable = false)
    @CreatedDate
    private long createdDate;

    @Column(name = "modified_date")
    @LastModifiedDate
    private long modifiedDate;
    
    //...
    
}

📌 建议将这些字段抽到一个 @MappedSuperclass 基类中复用。

4.4. 记录操作人(Spring Security 集成)

如果项目使用了 Spring Security,可以通过 @CreatedBy@LastModifiedBy 自动记录操作人:

@Column(name = "created_by")
@CreatedBy
private String createdBy;

@Column(name = "modified_by")
@LastModifiedBy
private String modifiedBy;

这些字段会自动从 SecurityContext 中获取当前用户信息。

如果需要自定义逻辑,可以实现 AuditorAware<T> 接口:

public class AuditorAwareImpl implements AuditorAware<String> {
 
    @Override
    public String getCurrentAuditor() {
        // your custom logic
    }

}

然后在配置类中启用:

@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig {
    
    @Bean
    AuditorAware<String> auditorProvider() {
        return new AuditorAwareImpl();
    }
    
}

5. 总结

本文介绍了三种审计实现方式:

JPA 原生方式:使用生命周期回调,适合轻量级场景,但限制较多,删除操作无法记录。

Hibernate Envers:功能强大,支持完整的增删改审计,适合复杂业务场景。

Spring Data JPA:使用注解简化审计逻辑,支持 Spring Security 集成,但同样无法审计删除操作。

📌 实际项目中,推荐使用 Hibernate Envers 或 Spring Data JPA,避免手动维护审计字段。

完整代码示例可参考 GitHub 仓库


原始标题:Auditing with JPA, Hibernate, and Spring Data JPA