使用 JPA、Hibernate 和 Spring Data JPA 进行审计

1. 概述

在ORM的上下文中,数据库审计意味着跟踪和记录与持久实体相关的事件,或者只是实体版本控制。受 SQL 触发器的启发,这些事件是对实体的插入、更新和删除操作。数据库审核的好处类似于源版本控制提供的好处。

在本教程中,我们将演示将审核引入应用程序的三种方法。首先,我们将使用标准 JPA 实现它。接下来,我们将看看两个提供自己的审计功能的JPA扩展,一个由Hibernate提供,另一个由Spring Data提供。

下面是我们将在本例中使用的示例相关实体 BarFoo

 

2. JPA审计

JPA 没有显式包含审计 API,但我们可以使用实体生命周期事件来实现此功能。

2.1.@PrePersist,@PreUpdate@PreRemove

在 JPA 实体类中,我们可以指定一个方法作为回调,我们可以在特定实体生命周期事件期间调用该方法。由于我们对在相应的 DML 操作之前执行的回调感兴趣,因此 @PrePersist@PreUpdate和 @PreRemove回调注释可用于我们的目的:

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

内部回调方法应始终返回void,并且不带任何参数。它们可以具有任何名称和任何访问级别,但不应是静态的。

请注意,JPA 中的@Version注释与我们的主题并不严格相关;它与乐观锁定的关系大于与审计数据的关系。

2.2. 实现回调方法

不过,这种方法存在很大的限制。如 JPA2 规范 (JSR 317) 中所述:

通常,可移植应用程序的生命周期方法不应调用EntityManagerQuery操作、访问其他实体实例或修改同一持久性上下文中的关系。生命周期回调方法可以修改调用它的实体的非关系状态。

在没有审计框架的情况下,我们必须手动维护数据库模式和域模型。对于我们的简单用例,让我们向实体添加两个新属性,因为我们只能管理“实体的非关系状态”。操作属性将存储所执行操作的名称,时间戳属性用于操作时间戳

@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());
    }
     
}Copy

如果我们需要将这样的审计添加到多个类中,我们可以@EntityListeners来集中代码:

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

3. 冬眠者

使用Hibernate,我们可以利用拦截器和事件侦听器以及数据库触发器来完成审计。但是ORM框架提供了Envers,一个实现持久类的审计和版本控制的模块。

3.1. 开始使用 Envers

要设置 Envers,我们需要将hibernate-enversJAR 添加到我们的类路径中:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
    <version>${hibernate.version}</version>
</dependency>Copy

然后我们添加@Audited注释,要么在 an@Entity(审计整个实体)上,要么在 specific@Columns(如果我们只需要审计特定属性):

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

请注意,BarFoo 之间存在一对多关系。在这种情况下,我们要么还需要审核Foo@Audited在Foo上添加或者在Bar中对关系的属性进行设置@NotAudited

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

3.2. 创建审计日志表

有几种方法可以创建审核表:

  • hibernate.hbm2ddl.auto设置为创建,创建-删除更新,以便Envers可以自动创建它们
  • 使用 org.hibernate.tool.EnversSchemaGenerator以编程方式导出完整的数据库架构
  • 设置 Ant 任务以生成相应的 DDL 语句
  • 使用 Maven 插件从我们的映射(例如 Juplo)生成数据库模式以导出 Envers 模式(适用于 Hibernate 4 及更高版本)

我们将采用第一条路线,因为它是最直接的,但请注意,使用hibernate.hbm2ddl.auto在生产中并不安全。

在我们的例子中,bar_AUDfoo_AUD(如果我们也将Foo设置为@Audited)表应该自动生成。审核表使用两个字段从实体的表中复制所有审核字段,即 REVTYPE(值为:“0”表示添加,“1”表示更新,“2”表示删除实体)和REV

除此之外,默认情况下将生成一个名为REVINFO的额外表。它包括两个重要字段,REVREVTSTMP,并记录每个修订的时间戳。我们可以猜到,bar_AUD。修订foo_AUD。REV实际上是REVINFO.REV 的外键。

3.3. 配置环境

我们可以像配置任何其他 Hibernate 属性一样配置 Envers 属性。

例如,让我们将审计表后缀(默认为“_AUD”)更改为“_AUDIT_LOG”。以下是我们如何设置相应属性org.hibernate.envers.audit_table_suffix的值:

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

可用属性的完整列表可以在Envers 文档中找到。

3.4. 访问实体历史记录

我们可以以类似于通过休眠条件 API 查询数据的方式查询历史数据。我们可以使用AuditReader界面访问实体的审计历史记录,我们可以通过打开的EntityManager会话通过AuditReaderFactory 获取:

AuditReader reader = AuditReaderFactory.get(session);Copy

Envers 提供AuditQueryCreator(由AuditReader.createQuery() 返回)以创建特定于审计的查询。以下行将返回在修订版 #2 修改的所有柱线实例(其中bar_AUDIT_LOG。修订版 = 2):

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

以下是我们如何查询Bar 的修订版。这将导致获取所有状态的所有已审核Bar实例的列表:

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

如果第二个参数为 false,则结果与REVINFO表联接。否则,仅返回实体实例。最后一个参数指定是否返回已删除的 Bar实例。

然后,我们可以使用AuditEntity工厂类指定约束:

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

4. 春季数据JPA

Spring Data JPA是一个框架,通过在JPA提供程序的顶部添加额外的抽象层来扩展JPA。该层支持通过扩展 Spring JPA 存储库接口来创建 JPA 存储库。

出于我们的目的,我们可以扩展CrudRepository<T,ID扩展Serializable>,通用CRUD操作的接口。一旦我们创建并将存储库注入另一个组件,Spring Data 将自动提供实现,我们就可以添加审计功能了。

4.1. 启用 JPA 审计

首先,我们希望通过注释配置启用审核。为了做到这一点,我们在@Configuration类中添加@EnableJpaAuditing

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

4.2. 添加 Spring 的实体回调侦听器

正如我们已经知道的,JPA 提供了@EntityListeners注释来指定回调侦听器类。Spring Data 提供了自己的 JPA 实体侦听器类,AuditingEntityListener。因此,让我们指定Bar实体的侦听器:

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

现在,我们可以在持久化和更新Bar实体时捕获侦听器的审核信息。

4.3. 跟踪创建和上次修改日期

接下来,我们将添加两个新属性,用于将创建日期和上次修改日期存储到我们的 Bar实体。属性由相应的@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;
    
    //...
    
}Copy

通常,我们将属性移动到基类(注释@MappedSuperClass),所有受审计实体都将扩展该基类。在我们的示例中,为了简单起见,我们将它们直接添加到Bar中。

4.4. 使用 Spring 安全性审核更改的作者

如果我们的应用程序使用 Spring 安全性,我们可以跟踪何时进行更改以及更改者:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
    
    //...
    
    @Column(name = "created_by")
    @CreatedBy
    private String createdBy;

    @Column(name = "modified_by")
    @LastModifiedBy
    private String modifiedBy;
    
    //...
    
}Copy

@CreatedBy@LastModifiedBy批注的列填充有创建或上次修改实体的主体的名称。该信息来自SecurityContext 的身份验证实例。如果我们想自定义设置为注释字段的值,我们可以实现AuditorAware<T>接口:

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

}Copy

为了将应用程序配置为使用AuditorAwareImpl查找当前主体,我们声明了一个AuditorAware类型的 bean,使用AuditorAwareImpl 的实例进行初始化,并将 Bean 的名称指定为auditorAwareRef参数的值@EnableJpaAuditing

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

5. 结论

在本文中,我们研究了实现审核功能的三种方法:

  • 纯 JPA 方法是最基本的,包括使用生命周期回调。但是,我们只允许修改实体的非关系状态。这使得@PreRemove回调对我们的目的毫无用处,因为我们在方法中所做的任何设置都将与实体一起删除。
  • Envers是Hibernate提供的成熟审计模块。它是高度可配置的,并且缺乏纯JPA实现的缺陷。因此,它允许我们审核删除操作,因为它记录到实体表以外的表中。
  • Spring Data JPA 方法抽象了使用 JPA 回调的工作,并为审计属性提供了方便的注释。它也准备好与Spring Security集成。缺点是它继承了 JPA 方法的相同缺陷,因此无法审核删除操作。

本文的示例在GitHub 存储库中提供。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值