关于Spring Boot:如何添加JPA和Hibernate Envers审计

·  阅读 14
关于Spring Boot:如何添加JPA和Hibernate Envers审计

在这篇博客中,我们将演示如何为你的Spring Boot应用程序添加基本审计。

1.0 简介

之前的博客中,我们演示了如何创建Spring Boot应用程序,添加Flyway迁移,以及实现认证/授权。

在这篇博客中,我们将探讨如何为应用程序添加审计功能。

所以问题是 "什么是审计,为什么它很重要?"

在任何应用程序中,审计意味着我们跟踪和记录所有业务对象的每一个变化,即跟踪每个插入、更新和删除操作。
基本上,它涉及跟踪三件事

  • 执行了什么操作?
  • 谁做的?
  • 什么时候做的?

审计帮助我们维护历史记录,这可以帮助我们跟踪用户活动。

你可以按照本博客中的步骤,使用任何Spring Boot应用程序。然而,在这篇博客中,我们将从我们在上一篇博客中建立的基础应用程序开始,该应用程序中已经建立了flyway集成和认证。
你可以在github.com/chatterjees…中找到本博客的起始基础代码。

我们的应用程序中已经有了一个Customer 实体,我们将为这个实体添加审计。

2.0 JPA审计

这是最简单的审计形式。
在这里,我们不跟踪所做的改变,而只是跟踪谁创建或修改了一个业务实体,以及何时完成的。

具体来说,我们将跟踪每个业务对象的以下额外字段

  • 创建时间
  • 创建者
  • 修改时
  • 修改时间

2.1 在数据库中添加新的审计列

让我们先把创建/修改列添加到数据库的Customer 表中。

ALTER TABLE `customer`
ADD COLUMN `created_by` VARCHAR(50) NOT NULL,
ADD COLUMN `updated_by` VARCHAR(50) NULL,
ADD COLUMN `created_on` DATETIME NOT NULL,
ADD COLUMN `updated_on` DATETIME NULL;
复制代码

你可以直接在你的数据库上运行下面的脚本,或者你可以把它添加到你的Flyway/Liquibase脚本中(如果你正在使用它们或任何其他类似的数据库版本管理工具)。

2.2 在实体类中添加审计字段

现在我们已经将字段添加到数据库中,让我们将字段添加到我们的Customer 实体中。
添加以下代码。

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

    @Column(name = "updated_by")
    @LastModifiedBy
    private String updatedBy;

    @Column(name = "created_on")
    @CreatedDate
    private Date createdOn;

    @Column(name = "updated_on")
    @LastModifiedDate
    private Date updatedOn;
复制代码
  • 注释。 我们已经为所有的审计字段添加了来自org.springframework.data.annotation 的注解

    • @CreatedBy /@LastModifiedBy:将一个字段声明为代表最近创建/修改包含该字段的实体的委托人的字段

    • @CreatedDate/ @LastModifiedDate宣布一个字段为代表包含该字段的实体被创建/最近被修改的日期。

  • 日期字段

    • 在我们的例子中,我们使用了java.util.Date
    • 该字段可以是以下任何一种类型 -Joda-Time,DateTime, legacy JavaDateCalendar, JDK8日期和时间类型,以及longLong

如果你不想在你的每个实体类上添加这些字段和注解,你的实体也可以从以下方面扩展你的实体org.springframework.data.jpa.domain.AbstractAuditable

2.3 填充登录的用户信息

创建和修改日期将由JPA自动填充。

然而我们必须在created_byupdated_by 列中提供要坚持的细节。

2.3.1 实现AuditAware[T]。

我们要做的第一件事是实现AuditAware<T> ,其中TCreatedBy/ModifiedBy字段的类型(对我们来说是String )。

  • 创建一个新的类AuditAwareImpl ,实现AuditAware<String>
  • 实现方法 -public Optional getCurrentAuditor()
  • 在方法getCurrentAuditor() 中添加逻辑,以返回当前登录用户的用户名
    • 我们可以获取当前的SecurityContext ,从中获取当前的Principal ,然后从其中获取用户的详细信息。
public class AuditAwareImpl implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.ofNullable(SecurityContextHolder.getContext())
                .map(SecurityContext::getAuthentication)
                .filter(Authentication::isAuthenticated)
                .map(Authentication::getPrincipal)
                .map(User.class::cast)
                .map(User::getUsername);
    }
}
复制代码

2.3.2 启用JPA审计

接下来我们将创建一个Configuration ,启用JPA审计,同时为AuditAware<String> Bean提供一个实现。

  • 创建一个新的配置类
  • 为这个类添加注解@EnableJpaAuditing
  • 创建一个方法来返回AuditorAware<String> 的Bean - 并返回一个我们在上一步创建的AuditAwareImpl 类的新实例。
@Configuration
@EnableJpaAuditing(auditorAwareRef = "customAuditProvider")
public class JpaConfig {

    @Bean
    public AuditorAware<String> customAuditProvider() {
        return new AuditAwareImpl();
    }
}
复制代码

最后一个配置是将一个EntityListener 类与你的审计实体联系起来。Spring已经为我们提供了一个这样的监听器----。AuditingEntityListener

@Entity
@EntityListeners(AuditingEntityListener.class) // add this line
public class Customer {
  ....
  ....
}
复制代码

2.4 测试JPA的审计。

现在我们已经添加了所有需要的实现,是时候测试它了。
我们将使用Rest客户端测试curl

所用的例子.或测试你已经在spring boot应用程序中配置了认证/授权,并且它在数据库中有两个用户

- admin01@tw.com(密码:admin01@123#)
- admin02@tw.com(密码:admin02@123#)

然而,这些用户并不是强制性的。
你可以通过改变下面例子中的curl语句,用你自己的用户组进行测试。

以用户身份*(admin01@tw.com)*登录,并使用以下命令创建一个客户

curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
复制代码

现在以另一个用户*(admin02@tw.com)*的身份登录,更新同一个客户,并改变姓氏。(改变PUT url末尾的id,以匹配创建的客户的id)

curl -i -u admin02@tw.com:admin02@123# -X PUT -H "Content-Type: application/json" http://localhost:8080/customer/1 -d '{"firstName":"John","lastName":"Doe01","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
复制代码

转到数据库并运行sql --select * from customer where email_address = 'johndoe@gmail.com'

你可以看到审计细节被填充到客户表中。

3.0 Hibernate Envers审计

尽管JPA审计为基本的审计提供了简单的配置,但它只提供以下信息

  • 实体是什么时候创建的,谁创建了它。
  • 实体最后一次修改是什么时候,谁修改了它。

它并没有为你提供一个实体的所有修改/更新的细节,例如一个客户实体可能被修改了5次。通过JPA审计,我没有办法找出在这5次更新中,实体的哪些地方被修改了,以及谁做了这些修改。

因此,Hibernate Envers ,它为一个实体提供完整的 审计历史为一个实体提供完整的信息。

3.1 Hibernate Envers如何在数据库中存储审计信息

  • Envers创建了一个 REVINFO
    • 这个表在应用程序的所有实体中都是通用的。
    • 它包含了每个实体的修订时间以及由谁完成的细节。
    • 默认情况下,它包含两列
      • REV- 主键列 - 修订版ID/修订版号
      • REVTSTMP- 修订版创建的时间戳。
      • 此外,我们还可以在这个表中以自定义列的形式存储用户信息。
  • 基本上,Envers为每个需要审计的实体创建一个'AUDIT'表。
    • 例如,如果我们想审计表的变化,Envers将创建一个存储在数据库中的 CUSTOMER表的变化,Envers将创建审计历史记录存储在一个 CUSTOMER_AUD表。(表的名字后缀为 _AUD)
    • 这个 *_AUD该表拥有与主实体表相同的所有列。
    • 此外,它还有另外两列
      • REV: Revision ID - Foriegn key 参照的是REVINFO.REV
      • REVTYPE: 有三个可能的数字值来表示对实体的操作类型 -0 (添加),1 (更新), 和2 (删除)

Hibernate为Enver表和列的名称提供了一些自定义功能。我们将在我们的应用程序中进行以下定制,以提高表和列名称的可读性。

  • 表的后缀 _AUD将被改成 _AUDIT...例如,CUSTOMER 的审计表现在将是 CUSTOMER_AUDIT.所有审计表的以下列也将被重新命名
    • REV列将被重命名为 REVISION_ID
    • REVTYPE列将被重命名为 REVISION_TYPE
  • REVINFO表将被重新命名为 REVISION_INFO.此外,修订表中的以下列将被重新命名
    • REV列将被重命名为 REVISION_ID
    • REVTSTMP将被重命名为 REV_TIMESTAMP

总结一下,这就是AUDIT表的ER图的模样。

你可能已经注意到,在ID列中没有从审计表到主实体表的外来键引用。

这样做的原因是,在某些情况下,我们可能希望在审计表中保留删除的记录。在这种情况下,由于主实体表中的记录已经被删除,所以外国键引用将不起作用。

3.2 在数据库中添加审计表

我们已经看到了审计表和修订信息表的模样。让我们在数据库中创建这些表。

你可以直接在你的数据库中运行下面的脚本,也可以把它添加到你的Flyway/Liquibase脚本中(如果你正在使用它们或者其他类似的数据库版本管理工具)。

CREATE TABLE `revision_info` (
    `revision_id` INTEGER PRIMARY KEY AUTO_INCREMENT,
    `rev_timestamp` BIGINT(20) NOT NULL,
    `user` VARCHAR(50) NOT NULL
);

CREATE TABLE `customer_audit` (
  `revision_id` INTEGER NOT NULL,
  `id` BIGINT(20) NOT NULL,
  `revision_type` TINYINT NOT NULL,
  `email_address` VARCHAR(255),
  `first_name` VARCHAR(255),
  `last_name` VARCHAR(255),
  `created_by` VARCHAR(50),
  `updated_by` VARCHAR(50),
  `created_on` DATETIME,
  `updated_on` DATETIME,
  PRIMARY KEY (`revision_id`, `id`),
  CONSTRAINT `idfk_customer_revinfo_rev_id`
	FOREIGN KEY (`revision_id`) REFERENCES `revision_info` (`revision_id`)
);

CREATE TABLE `address_audit` (
  `revision_id` INTEGER NOT NULL,
  `id` BIGINT(20) NOT NULL,
  `revision_type` TINYINT NOT NULL,
  `city` VARCHAR(255),
  `country` VARCHAR(255),
  `customer_id` BIGINT(20),
  `state_code` VARCHAR(255),
  `street_address` VARCHAR(255),
  `zip_code` VARCHAR(255),
  PRIMARY KEY (`revision_id`, `id`),
  CONSTRAINT `idfk_address_revinfo_rev_id`
	FOREIGN KEY (`revision_id`) REFERENCES `revision_info` (`revision_id`)
);
复制代码

3.3 启用审计

在本节中,我们将通过失败的第一种方法来添加审计。
这意味着我们将一步一步地添加配置/代码,之后启动应用程序,看看应用程序是启动还是失败。如果失败了,我们将修复它并再次重复循环,直到我们得到一个工作的应用程序。

这将帮助我们了解为什么需要一些配置/代码,以及如果我们错过了这些配置/代码可能会出现什么错误。

3.3.1 在build.gradle中添加依赖性

第一个要做的配置是在build.gradle中添加Hibernate Envers的依赖性。build.gradle

compile('org.hibernate:hibernate-envers:5.4.25.Final')
复制代码

3.3.2 添加@Audited注解

现在将@Audited 注解添加到所有你想审计的实体中。

@Audited
public class Customer {
  ....
}
复制代码
@Audited
public class Address {
  ....
}
复制代码

3.3.3 在application.yaml中添加配置

让我们尝试启动该应用程序。

你会得到以下错误

org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [address_aud]

OR

org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [customer_aud]
复制代码

这是因为Hibernate Envers期望审计表为 *_AUD表。
然而,正如你所看到的,我们已经将我们的表后缀自定义为 _AUDITtable。这意味着Envers正在搜索 customer_aud table, address_aud表,但我们有 customer_auditaddress_audit表。此外,我们还定制了一些列 -- REV, REVTSMTPREVTYPE.

失败的原因是我们需要告诉Hibernate Envers关于这些定制的内容。我们可以通过添加以下属性来做到这一点application.yaml

spring:
  jpa:
    properties:
      org:
        hibernate:
          envers:
            audit_table_suffix: _AUDIT
            revision_field_name: REVISION_ID
            revision_type_field_name: REVISION_TYPE
复制代码

现在,如果你再次尝试启动应用程序,你会得到下面的错误

Schema-validation: missing table [customer_address_audit]
复制代码

为了修复这个错误,在 实体类中的字段,添加一个 @NotAudited注释到Customer 实体类中的字段addresses

    @NotAudited
    @OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, targetEntity = Address.class, cascade = CascadeType.ALL)
    @JoinColumn(name = "customer_id")
    private List<Address> addresses;
复制代码

3.3.4 配置一个RevisionEntity

让我们再次尝试启动该应用程序。
这一次你会得到以下错误。

org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [revinfo]
复制代码

我们已经创建了一个表 REVISION_INFO而不是 REVINFO.

但是我们不能通过application.yaml 中的配置来改变表的名称。要做到这一点,我们需要创建一个RevisionEntity 类。

Envers 已经提供了一个类 - ,我们将扩展这个类。org.hibernate.envers.DefaultRevisionEntity

@Entity
@Table(name = "revision_info")
@RevisionEntity
public class AuditRevisionEntity extends DefaultRevisionEntity {

    @Column(name = "user")
    private String user;
}
复制代码
  • 我们创建了一个类 AuditRevisionEntity延伸自 DefaultRevisionEntity.
  • 添加@Table 注解,并提供表的名称。revision_info
  • 添加注解 @RevisionEntity,这表明这是Hibernate Envers的RevisionInfo 实体。
  • 添加一个字段为 user的字段,我们已经在revison_info 表中添加了这个字段。

让我们尝试再次启动应用程序。这次我们会得到以下错误。

org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [hibernate_sequence]
复制代码
  • Hibernate要求这个ID列有以下注释------。 @GeneratedValue(strategy = GenerationType.***AUTO***)
  • 解决这个问题的最简单方法是将生成策略从 GenerationType.***AUTO***改为 GenerationType.***IDENTITY***
  • 然而,这个错误是由于org.hibernate.envers.DefaultRevisionEntity 中的ID字段,我们扩展了这个字段。

由于我们不能改变DefaultRevisionEntity类,我们将在application.yaml中添加以下配置来解决这个问题。

spring:
  jpa:
    hibernate:
      use-new-id-generator-mappings: false
复制代码

让我们再次尝试启动应用程序。
这次你会得到以下错误,因为Envers希望在修订信息表中有idtimestamp 列。

Schema-validation: missing column [id] in table [revision_info]
复制代码

由于我们已经将列重命名为revision_idrev_timestamp ,我们需要在我们的RevisionEntity 类中添加配置。

只需将以下注解添加到 AuditRevisionEntity类。

@AttributeOverrides({
    @AttributeOverride(name = "timestamp", column = @Column(name = "rev_timestamp")),
    @AttributeOverride(name = "id", column = @Column(name = "revision_id"))
})
public class AuditRevisionEntity extends DefaultRevisionEntity {
  ...
  ...
}
复制代码

现在你应该可以成功启动你的应用程序了。

现在你知道了在配置Envers审计时可能出现的各种错误,以及如何解决它们。

注意:不需要将 spring-data-envers库到你的项目中。Hibernate Envers库已经足够了。

3.4 在Revision表中填入登录用户信息

现在我们已经设置好了一切,我们的应用程序也在运行。
让我们试着创建一个新的客户。

curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
复制代码

你会得到以下错误

Caused by: java.sql.SQLException: Field 'user' doesn't have a default value
复制代码

这是因为Envers试图将数据审核到revision_info 表中,并将null 放到user 列中。
这是因为Envers不知道如何获取登录用户的信息并填充到这个user 列中。

让我们添加代码来添加登录的用户信息。

  • 创建一个实现以下功能的类org.hibernate.envers.RevisionListener
  • 在新的类中实现方法------。public void newRevision(Object revisionEntity)
    • 在这个方法中,从Spring的SecurityContext 中获取当前用户(类似于我们在AuditorAwareImpl 中的做法)。
    • 并将这个用户设置在AuditRevisionEntity
public class AuditRevisionListener implements RevisionListener {

    @Override
    public void newRevision(Object revisionEntity) {

        String currentUser = Optional.ofNullable(SecurityContextHolder.getContext())
                .map(SecurityContext::getAuthentication)
                .filter(Authentication::isAuthenticated)
                .map(Authentication::getPrincipal)
                .map(User.class::cast)
                .map(User::getUsername)
                .orElse("Unknown-User");

        AuditRevisionEntity audit = (AuditRevisionEntity) revisionEntity;
        audit.setUser(currentUser);

    }
}
复制代码

接下来在RevisionEntity 类中添加以下注解----。 AuditRevisionEntity

@RevisionEntity(AuditRevisionListener.class)
复制代码

3.5 测试Hibernate Envers审计

现在让我们测试一下这个应用程序,看看审计工作是否正常。

以用户身份*(admin01@tw.com)*登录,使用下面的命令创建一个客户

curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}, {"streetAddress":"2240 Bridge Parkway","city":"San Francisco","stateCode":"CA","zipCode":"94065","country":"USA"}]}'
复制代码

现在以另一个用户*(admin02@tw.com)*的身份登录,更新同一个客户,并改变姓氏和删除其中一个地址。(改变PUT url末尾的id,以匹配创建的客户的id)

curl -i -u admin02@tw.com:admin02@123# -X PUT -H "Content-Type: application/json" http://localhost:8080/customer/1 -d '{"firstName":"John","lastName":"Doe01","emailAddress":"johndoe@gmail.com","addresses":[{"id":1, "streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
复制代码

现在让我们到数据库中去验证一下审计历史是否被正确填入。

运行sql -select * from revision_info;
这将给你提供关于有多少次修订和由谁创建的信息。

接下来看看客户审计表 -select * from customer_audit

你可以看到,在修订1中,客户John Doe被创建,而修订2中,它的姓氏被更新。

最后让我们看一下地址审计表------。select * from address_audit;

我们可以从上面看到,在修订版1中,有两个地址被添加。而在修订版2中,其中一个地址被删除。

被删除的条目作为NULL 值存储在审计表中。
如果你想保留旧的值,而不是NULL ,那么请将以下属性设置为 "true"。application.yaml

org.hibernate.envers.store_data_at_delete: true

3.6 通过REST API从应用程序中获取审计信息

3.6.1 创建一个AuditReader的bean

Hibernate Envers提供了两个方法来获取一个实体的审计信息。

  • forRevisionsOfEntity- 这个方法用于获取一个实体所经历的所有修订变化。
  • forRevisionsOfEntityWithChanges- 这是为了获取修订变化,以及每个修订中哪些字段和数据被改变的信息。

你将需要一个类的实例 org.hibernate.envers.AuditReader的实例,以创建 AuditQuery来获取审计信息

添加以下配置类来创建一个Bean的 org.hibernate.envers.AuditReader

@Configurationpublic class AuditConfiguration {    private final EntityManagerFactory entityManagerFactory;    AuditConfiguration(EntityManagerFactory entityManagerFactory) {        this.entityManagerFactory = entityManagerFactory;    }    @Bean    AuditReader auditReader() {        return AuditReaderFactory.get(entityManagerFactory.createEntityManager());    }}
复制代码

3.6.2 在客户服务中创建一个方法来获取客户的审计信息

现在我们已经创建了一个AuditReader 的bean,继续将AuditReader的依赖关系自动连接到CustomerService 类中。

public class CustomerService {
    ...
    ...
    @Autowired
    private AuditReader auditReader;
    ...
}
复制代码

现在在CustomerService ,添加一个方法来获取客户实体的审计修订细节

public List<?> getRevisions(Long id, boolean fetchChanges) {
        AuditQuery auditQuery = null;

        if(fetchChanges) {
            auditQuery = auditReader.createQuery()
                    .forRevisionsOfEntityWithChanges(Customer.class, true);
        }
        else {
            auditQuery = auditReader.createQuery()
                    .forRevisionsOfEntity(Customer.class, true);
        }
        auditQuery.add(AuditEntity.id().eq(id));
        return auditQuery.getResultList();
    }
复制代码

如果fetchChanges boolean为false,这个方法将只获取修订信息,否则它将返回所有的修订信息以及被修改的细节。

3.6.3 为客户审计暴露一个Rest API

像这样公开一个Rest API

@GetMapping(path = "/{id}/revisions", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getRevisions(@PathVariable(name = "id") String customerId,
           @RequestParam(value = "fetchChanges", required = false) boolean fetchChanges) {
        
    List results = customerService.getRevisions(Long.valueOf(customerId), fetchChanges);
    return ResponseEntity.ok(results);
}
复制代码

现在让我们点击API来获取审计信息。点击下面的URL来获取客户身份2的审计修订。

curl -i -u admin02@tw.com:admin02@123# \
  -X GET -H "Content-Type: application/json" \
   http://localhost:8080/customer/2/revisions
复制代码

你会得到下面的错误,因为应用程序不能序列化。AuditRevisionEntity

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer
复制代码

为了解决这个问题,在AuditRevisionEntity类中添加以下注解

@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class AuditRevisionEntity extends DefaultRevisionEntity {
   ....
}
复制代码

现在,如果你再次点击相同的URL,你应该得到id=2的客户的修订列表(如下所示)。

[
  {
    "id": 1,
    "timestamp": 1608084027455,
    "user": "admin01@tw.com",
    "revisionDate": "2020-12-16T02:00:27.455+0000"
  },
  {
    "id": 2,
    "timestamp": 1608084069397,
    "user": "admin02@tw.com",
    "revisionDate": "2020-12-16T02:01:09.397+0000"
  }
]
复制代码

3.6.4 获取带有修改细节的审计报告

在上面的例子中,我们只得到了关于对象何时被改变的信息。我们没有得到谁改变了它的信息。

要做到这一点,请点击带有查询参数的相同URLfetchChanges=true

curl -i -u admin02@tw.com:admin02@123# \
  -X GET -H "Content-Type: application/json" \
   http://localhost:8080/customer/2/revisions\?fetchChanges\=true
复制代码

不幸的是,你会得到以下错误

{
  "statusCode": 400,
  "errorMessage": "The specified entity [com.dev.springdemo.customer.model.Customer] does not support or use modified flags."
}
复制代码

这是因为我们没有配置我们的客户实体来跟踪哪些字段被改变。

修改Customer 实体上的@Audited 注解,并添加属性withModifiedFlag = true

@Audited(withModifiedFlag = true)
public class Customer implements Serializable {
   ...
}
复制代码

现在,如果你重新运行代码,你将再次得到错误(类似下面的内容)

Schema-validation: missing column [email_address_mod] in table [customer_audit]
复制代码

Envers可以通过为*_audit 表中的每个实体字段添加_mod 列来跟踪字段的变化。

因此,对于客户的每一个字段,我们需要在customer_audit 表中添加一个相应的boolean 列,并在列名的后面加上_mod

例如,对于customer 表中的email_address 列,在customer_audit 表中添加一个email_address_mod 字段。

因此,为了在customer_audit 表中添加所有的*_mod 列,运行下面的 sql 脚本。

ALTER TABLE `customer_audit`
ADD COLUMN `created_by_mod` TINYINT(1) NULL,
ADD COLUMN `created_on_mod` TINYINT(1) NULL ,
ADD COLUMN `email_address_mod` boolean NULL,
ADD COLUMN `first_name_mod` boolean NULL,
ADD COLUMN `last_name_mod` boolean NULL,
ADD COLUMN `updated_by_mod` boolean NULL,
ADD COLUMN `updated_on_mod` boolean NULL;
复制代码

现在我们终于可以测试它了。通过创建和更新客户,创建一些新的审计条目。
现在,如果你用chagnes获取修订版,你会得到一个类似这样的输出。

[
  [
    {
      "id": 4,
      "firstName": "Clark",
      "lastName": "Kent",
      "emailAddress": "superman@gmail.com",
      "addresses": null,
      "createdBy": "admin01@tw.com",
      "updatedBy": "admin01@tw.com",
      "createdOn": "2020-12-16T03:25:01.000+0000",
      "updatedOn": "2020-12-16T03:25:01.000+0000"
    },
    {
      "id": 3,
      "timestamp": 1608089100811,
      "user": "admin01@tw.com",
      "revisionDate": "2020-12-16T03:25:00.811+0000"
    },
    "ADD",
    []
  ],
  [
    {
      "id": 4,
      "firstName": "Super",
      "lastName": "Man",
      "emailAddress": "superman@gmail.com",
      "addresses": null,
      "createdBy": "admin01@tw.com",
      "updatedBy": "admin02@tw.com",
      "createdOn": "2020-12-16T03:25:01.000+0000",
      "updatedOn": "2020-12-16T03:25:49.000+0000"
    },
    {
      "id": 4,
      "timestamp": 1608089148899,
      "user": "admin02@tw.com",
      "revisionDate": "2020-12-16T03:25:48.899+0000"
    },
    "MOD",
    [
      "firstName",
      "lastName",
      "updatedBy",
      "updatedOn"
    ]
  ]
]
复制代码

这个项目的全部代码可以在github.com/chatterjees…

要检查这个发布标签,请运行以下命令。

git clonegithub.com/chatterjees…
cd spring-boot-app
git checkout tags/v4.0 -b v4.0-with-auditing

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改