Spring Boot JPA - Hibernate Envers
2020-08-04
Coding
Spring Boot
Spring
Java
Hibernate
JPA
👋 ‍️‍️阅读
❤️ 喜欢
💬 评论

Spring Boot JPA - Hibernate Envers

虽然Spring Data JPA有默认的Auditing功能,但是功能还是相对单薄。

而Hibernate Envers是Hibernate提供的完整Auditing方案,可以记录Entity的历史版本和版本信息。 可以帮助找回丢失数据,检查修改历史以及数据分析。

Hibernate Envers和Spring Data JPA无缝集成,只需要简单地加上注解就可以工作。

Quick Start

首先需要添加依赖项

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
</dependency>

接着,在你的Entity类上打上@Audited,就大功告成了。 现在一个最简单的审计功能已经可以正常工作

@Data
@Entity
@Audited //HL
@Table(name = "users")
public class UserEntity {
  @Id
  @Column(name = "id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  int id;

  @Column(name = "name")
  String name;

  @Column(name = "age")
  int age;
}

如果你开启了spring.jpa.hibernate.ddl-auto,则Hibernate会自动创建以下审计表

  • users_AUD, 其包含了users表的所有列以及额外的REVREVTYPE
    • REV,外联向REVINFO
    • REVTYPE,0 = create, 1 = update, 2 = delete
  • REVINFO,其包含了每个版本的详细信息,默认只有idtimestamp两列,即记录了版本时间

在Audit开启后,当我们通过JPA操作entity的时候,就会自动记录版本和实体。

例子:

@Autowired UserRepo repo;

UserEntity user = new UserEntity();
user.setName("Dean");
user.setAge(25);
user = repo.save(user);

user.setAge(26);
user = repo.save(user);

repo.delete(user);

SELECT * FROM USERS

no rows

SELECT * FROM USERS_AUD

IDREVREVTYPEAGENAME
11025Dean
12126Dean
132nullnull

SELECT * FROM REVINFO

REVREVTSTMP
11596521917324
21596521917356
31596521917371

追踪属性变化

上面创建的是最简单的审计表,如果你还需要快速定位属性的修改,你可以尝试开启属性追踪。

你可以在想要追踪的属性或Entity上加上@Audited(withModifiedFlag=true)或者 配置参数org.hibernate.envers.global_with_modified_flag=true以全局启用该功能。

启用的属性会在_AUD表中自动创建<column>_MOD列,其会记录属性是否在此版本中被修改。

进行和上面例子同样的操作,我们会得到:

SELECT * FROM USERS_AUD

IDREVREVTYPEAGEAGE_MODNAMENAME_MOD
11025TRUEDeanTRUE
12126TRUEDeanFALSE
132nullTRUEnullTRUE

查询

Envers提供了AuditReader来进行查询,你可以通过AuditReaderFactory.get来从一个EntityManager中创建AuditReader

创建查询:

AuditQuery query = getAuditReader()
    .createQuery()
    .forEntitiesAtRevision(MyEntity.class, revisionNumber);

添加条件:

query.add(AuditEntity.property("address").eq(relatedEntityInstance));
// or
query.add(AuditEntity.relatedId("address").eq(relatedEntityId));

获得结果:

List personsAtAddress = getAuditReader().createQuery()
    .forEntitiesAtRevision(Person.class, 12)
    .addOrder(AuditEntity.property("surname").desc())
    .add(AuditEntity.relatedId("address").eq(addressId))
    .setFirstResult(4)
    .setMaxResults(2)
    .getResultList();

当然,我们还可以进行更多复杂的查询,从版本查实体,从实体查版本,等等,这里不一一介绍。

自定义版本实体

默认的RevisionEntity,即REVINFO表对应的实体类,只提供了时间戳这一个属性。 这通常是不足以满足业务的,一个最为典型的场景就是需要记录是谁进行了修改。 所以我们需要自定义RevisionEntity以添加username字段。

这一自定义也非常简便,我们只需要继承DefaultRevisionEntity, 然后加上@RevisionEntity指向自定义的RevisionListener即可。

@Entity
@RevisionEntity(AuthRevisionListener.class)
@Table(name = "REVINFO")
public class AuthRevisionEntity extends DefaultRevisionEntity {
  @Column(name = "username")
  private String username;
}

然后在自定义的listener里设置username的值。

@Component
public class AuthRevisionListener implements RevisionListener {
  public static String user = "foo";

  @Override
  public void newRevision(Object revisionEntity) {
    AuthRevisionEntity e = (AuthRevisionEntity) revisionEntity;
    // If you are using spring-security, you can set it as authentication
    // Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    e.setUsername(user);
  }
}

现在稍微修改我们的测试代码以模拟多用户操作

UserEntity user = new UserEntity();
user.setName("Dean");
user.setAge(25);
user = repo.save(user);

user.setAge(26);
user = repo.save(user);

AuthRevisionListener.user = "bar"; //HL
repo.delete(user);

运行测试代码,我们会看到不一样的REVINFO

SELECT * FROM REVINFO

IDTIMESTAMPUSERNAME
11596525766803foo
21596525766844foo
31596525766858bar

Reference


Copyright © 2020-2022 Dean Xu. All Rights reserved.