• 使用 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回调注释可用于我们的目的:

    1. @Entity
    2. public class Bar {
    3. @PrePersist
    4. public void onPrePersist() { ... }
    5. @PreUpdate
    6. public void onPreUpdate() { ... }
    7. @PreRemove
    8. public void onPreRemove() { ... }
    9. }
    Copy

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

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

    2.2. 实现回调方法

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

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

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

    1. @Entity
    2. public class Bar {
    3. //...
    4. @Column(name = "operation")
    5. private String operation;
    6. @Column(name = "timestamp")
    7. private long timestamp;
    8. //...
    9. // standard setters and getters for the new properties
    10. //...
    11. @PrePersist
    12. public void onPrePersist() {
    13. audit("INSERT");
    14. }
    15. @PreUpdate
    16. public void onPreUpdate() {
    17. audit("UPDATE");
    18. }
    19. @PreRemove
    20. public void onPreRemove() {
    21. audit("DELETE");
    22. }
    23. private void audit(String operation) {
    24. setOperation(operation);
    25. setTimestamp((new Date()).getTime());
    26. }
    27. }
    Copy

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

    1. @EntityListeners(AuditListener.class)
    2. @Entity
    3. public class Bar { ... }
    Copy
    1. public class AuditListener {
    2. @PrePersist
    3. @PreUpdate
    4. @PreRemove
    5. private void beforeAnyOperation(Object object) { ... }
    6. }
    Copy

    3. 冬眠者

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

    3.1. 开始使用 Envers

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

    1. <dependency>
    2. <groupId>org.hibernate</groupId>
    3. <artifactId>hibernate-envers</artifactId>
    4. <version>${hibernate.version}</version>
    5. </dependency>
    Copy

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

    1. @Entity
    2. @Audited
    3. public class Bar { ... }
    Copy

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

    1. @OneToMany(mappedBy = "bar")
    2. @NotAudited
    3. private Set 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的值:

    1. Properties hibernateProperties = new Properties();
    2. hibernateProperties.setProperty(
    3. "org.hibernate.envers.audit_table_suffix", "_AUDIT_LOG");
    4. 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):

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

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

    1. AuditQuery query = reader.createQuery()
    2. .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,通用CRUD操作的接口。一旦我们创建并将存储库注入另一个组件,Spring Data 将自动提供实现,我们就可以添加审计功能了。

    4.1. 启用 JPA 审计

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

    1. @Configuration
    2. @EnableTransactionManagement
    3. @EnableJpaRepositories
    4. @EnableJpaAuditing
    5. public class PersistenceConfig { ... }
    Copy

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

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

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

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

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

    接下来,我们将添加两个新属性,用于将创建日期和上次修改日期存储到我们的 Bar实体。属性由相应的@CreatedDate@LastModifiedDate注释进行批注,并且其值会自动设置:

    1. @Entity
    2. @EntityListeners(AuditingEntityListener.class)
    3. public class Bar {
    4. //...
    5. @Column(name = "created_date", nullable = false, updatable = false)
    6. @CreatedDate
    7. private long createdDate;
    8. @Column(name = "modified_date")
    9. @LastModifiedDate
    10. private long modifiedDate;
    11. //...
    12. }
    Copy

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

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

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

    1. @Entity
    2. @EntityListeners(AuditingEntityListener.class)
    3. public class Bar {
    4. //...
    5. @Column(name = "created_by")
    6. @CreatedBy
    7. private String createdBy;
    8. @Column(name = "modified_by")
    9. @LastModifiedBy
    10. private String modifiedBy;
    11. //...
    12. }
    Copy

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

    1. public class AuditorAwareImpl implements AuditorAware<String> {
    2. @Override
    3. public String getCurrentAuditor() {
    4. // your custom logic
    5. }
    6. }
    Copy

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

    1. @EnableJpaAuditing(auditorAwareRef="auditorProvider")
    2. public class PersistenceConfig {
    3. //...
    4. @Bean
    5. AuditorAware<String> auditorProvider() {
    6. return new AuditorAwareImpl();
    7. }
    8. //...
    9. }
    Copy

    5. 结论

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

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

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

  • 相关阅读:
    java使用看门狗原理实现监听业务
    “操作系统不以 C 开头和结尾,C 不等于整个世界”
    Servlet的类和方法
    第九章、python中常用内置函数(方法)------解析函数eval与exec、过滤函数filter
    通过自学可以搭建量化交易模型吗?
    使用taichi 写了一个任意平台任意显卡推理的Linear
    大数据管理系统架构Hadoop
    debian11 安装 postgress 数据库 -- chatGPT
    onnx模型转换opset版本和固定动态输入尺寸
    java计算机毕业设计旅游管理系统源码+mysql数据库+系统+lw文档+部署
  • 原文地址:https://blog.csdn.net/allway2/article/details/127969064