使用 Hibernate Envers 进行实体审计

news2024/11/25 15:37:06

业务应用程序中的常见要求是在特定数据更改时存储版本控制信息;当某事发生变化时,谁改变了它,改变了什么。在这篇博文中,我们将介绍Hibernate Envers,它是Hibernate JPA库的一个组件,它为实体类提供了一个简单的审计/版本控制解决方案。Envers 可与 Hibernate 和 JPA 配合使用,您可以在 Hibernate 工作的任何地方使用 Envers。

Envers 将更改存储在特殊的审计表中,并提供多种查询方法来访问历史快照。

设置

若要开始使用 Envers,首先需要将库添加到项目的类路径中。在 Maven 托管文件中,添加此依赖项。

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

绒球.xml

接下来,使用 @Audited注释批注 Envers 应跟踪的实体类或实体属性。对于以下示例,我使用两个实体类:员工和公司。在 Employee 类中,我添加了类,Envers 跟踪此类中的所有属性。@Audited

@Entity
@Audited(withModifiedFlag = true)
public class Employee {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id;

  private String lastName;

  private String firstName;

  private String street;

  private String city;

  @ManyToOne
  private Company company;

员工.java

在公司类中,我只添加到属性。Envers 会跟踪此属性并忽略所有其他属性。Envers 还为添加到类但想要忽略特定属性的情况提供了@NotAudited注释。@Auditedname@Audited

@Entity
public class Company {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id;

  @Audited
  private String name;

  private String street;

  private String city;

  @OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<Employee> employees;

公司.java

确保实体使用不可变的唯一标识符(主键)。

在下面的示例项目中,我将休眠配置设置为。hibernate.hbm2ddl.autoupdate

      <property name="hibernate.hbm2ddl.auto" value="update" />

坚持不懈.xml

完成此配置后,Hibernate会自动创建“员工”和“公司”两个表。对于每个实体,它创建一个审核表,Envers 在其中跟踪更改。它还创建 REVINFO 表,该表跟踪修订号和发生更改时的时间戳。@Audited

每次要插入、更新或删除实体时,Envers 都会介入并通过在 REVINFO 表和相应的 AUD 表中插入新行来创建新的修订。@Audited

请注意,EMPLOYEE_AUD表包含 EMPLOYEE 表中每个字段的字段,而COMPANY_AUD表仅包含字段,这是因为我们只在 Company 类中批注了属性。AUD 表的 ID 字段是相应实体表的主键;这就是主键必须是不可变的原因。namename

在 Employee 类中,我们启用了默认情况下禁用的withModifiedFlag选项 ()。您可以在EMPLOYEE_AUD表中看到此选项的效果。Envers 为每个属性添加了额外的布尔属性_MOD。此修改标志存储属性在给定修订时已更改的信息。@Audited(withModifiedFlag = true)

在公司类中,我们未启用此选项。因此,COMPANY_AUD表不包含NAME_MOD字段。

为了进行比较,如果不启用该选项,则EMPLOYEE_AUD表定义:

仅当您需要此信息时,才应启用,因为代价是附加字段会增加审核表的大小。有一个 Envers 查询 () 依赖于此附加信息,因此,如果计划使用此查询方法,则必须启用该选项。withModifiedFlagforRevisionsOfEntityWithChange

自定义修订实体

请注意,默认情况下,Envers仅跟踪发生更改的日期和时间。但是,在多用户应用程序中,您通常还想知道谁进行了更改。

为此,我们需要创建自定义修订实体类。此类必须扩展DefaultRevisionEntity类,并且必须使用 and@RevisionEntity 进行批注。您可以添加任何您喜欢的属性,它们将与修订号和时间戳一起存储在 REVINFO 表中。@Entity

import javax.persistence.Entity;
import javax.persistence.Table;

import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;

@Entity
@Table(name = "REVINFO")
@RevisionEntity(CustomRevisionEntityListener.class)
public class CustomRevisionEntity extends DefaultRevisionEntity {

  private static final long serialVersionUID = 1L;

  private String username;

  public String getUsername() {
    return this.username;
  }

  public void setUsername(String username) {
    this.username = username;
  }
}

自定义修订实体.java

我们指定的侦听器必须实现RevisionListener接口。我们只需要实现一种方法:在此方法中,我们需要填写其他属性,在本例中为用户名。我们不必触摸修订号和时间戳,Envers会自动设置它们。@RevisionEntitynewRevision

import org.hibernate.envers.RevisionListener;

public class CustomRevisionEntityListener implements RevisionListener {

  @Override
  public void newRevision(Object revisionEntity) {
    CustomRevisionEntity customRevisionEntity = (CustomRevisionEntity) revisionEntity;
    customRevisionEntity.setUsername(CurrentUser.INSTANCE.get());
  }
}

CustomRevisionEntityListener.java

对于此演示应用程序,我们将登录用户存储到 ThreadLocal 变量中。上面的侦听器提取它,我们将用户名设置为CurrentUser.INSTANCE.get()CurrentUser.INSTANCE.set(....)

public class CurrentUser {

  public static final CurrentUser INSTANCE = new CurrentUser();

  private static final ThreadLocal<String> storage = new ThreadLocal<>();

  public void logIn(String user) {
    storage.set(user);
  }

  public void logOut() {
    storage.remove();
  }

  public String get() {
    return storage.get();
  }
}

当前用户.java

完成此配置后,REVINFO 表现在包含一个新字段:用户名

我直接从 Envers 文档中复制了这三个类。访问此 URL 了解更多信息:Hibernate ORM 5.4.33.Final User Guide

例子

在本节中,我们将插入、更新和删除一些数据,并查看 Envers 如何存储更改。

修订版 1:插入

首先,用户“Alice”插入一家公司和两名员工。

    EntityManager em = JPAUtil.getEntityManagerFactory().createEntityManager();
    CurrentUser.INSTANCE.logIn("Alice");

    em.getTransaction().begin();
    Company company = new Company();
    company.setName("E Corp");
    company.setCity("New York City");
    company.setStreet(null);

    Set<Employee> employees = new HashSet<>();

    Employee employee = new Employee();
    employee.setCompany(company);
    employee.setLastName("Spencer");
    employee.setFirstName("Linda");
    employee.setStreet("High Street 123");
    employee.setCity("Newark");
    employees.add(employee);

    employee = new Employee();
    employee.setCompany(company);
    employee.setLastName("Ralbern");
    employee.setFirstName("Michael");
    employee.setStreet("57th Street");
    employee.setCity("New York City");
    employees.add(employee);

    company.setEmployees(employees);

    em.persist(company);
    em.getTransaction().commit();

主.java

请注意,您不必调用任何特殊的 Envers 方法。只需编写标准的JPA(或Hibernate)代码。在后台,Envers 侦听任何更新,并自动将审核信息插入数据库。

Envers 在 REVINFO 表中插入了一行新行,其中包含时间戳和用户名。由于我们在一个事务中插入了三个实体,因此只创建了一个 REVINFO 行。

Envers 还在新公司的COMPANY_AUD中插入了一个新行,REVTYPE 为 0 表示插入操作。此外,Envers 在EMPLOYEE_AUD表中插入了两行新行。因为在插入中,所有属性都已更改,所有_MOD字段都包含值 true。

REVINFO
+----+---------------+----------+
| ID |   TIMESTAMP   | USERNAME |
+----+---------------+----------+
|  1 | 1564997410711 | Alice    |
+----+---------------+----------+

COMPANY_AUD
+----+-----+---------+--------+
| ID | REV | REVTYPE |  NAME  |
+----+-----+---------+--------+
|  1 |   1 |       0 | E Corp |
+----+-----+---------+--------+

EMPLOYEE_AUD
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
| ID | REV | REVTYPE |      CITY      | CITY_MOD | FIRSTNAME | FIRSTNAME_MOD | LASTNAME | LASTNAME_MOD |      STREET      | STREET_MOD | COMPANY_ID | COMPANY_MOD |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  1 |   1 |       0 | New York City  |     TRUE | Michael   |          TRUE | Ralbern  |         TRUE | 57th Street      |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  2 |   1 |       0 | Newark         |     TRUE | Linda     |          TRUE | Spencer  |         TRUE | High Street 123  |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+

修订版 2:更新公司

在下一笔交易中,“Bob”将公司名称从“E Corp”更改为“EEE Corp”。

    CurrentUser.INSTANCE.logIn("Bob");

    em.getTransaction().begin();
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Company> q = cb.createQuery(Company.class);
    Root<Company> c = q.from(Company.class);
    ParameterExpression<String> p = cb.parameter(String.class);
    q.select(c).where(cb.equal(c.get("name"), p));

    TypedQuery<Company> query1 = em.createQuery(q);
    query1.setParameter(p, "E Corp");

    company = query1.getSingleResult();
    company.setName("EEE Corp");

    em.getTransaction().commit();

主.java

Envers 创建一个新的修订版本,并在COMPANY_AUD表中插入一个新行。REVTYPE = 1 表示更新操作。

REVINFO
+----+---------------+----------+
| ID |   TIMESTAMP   | USERNAME |
+----+---------------+----------+
|  1 | 1564997410711 | Alice    |
+----+---------------+----------+
|  2 | 1564997410849 | Bob      |
+----+---------------+----------+

COMPANY_AUD
+----+-----+---------+----------+
| ID | REV | REVTYPE |   NAME   |
+----+-----+---------+----------+
|  1 |   1 |       0 | E Corp   |
+----+-----+---------+----------+
|  1 |   2 |       1 | EEE Corp |
+----+-----+---------+----------+

修订版 3:新员工

“鲍勃”插入新员工:珍妮特·罗宾逊

    CurrentUser.INSTANCE.logIn("Bob");

    em.getTransaction().begin();
    employee = new Employee();
    employee.setCompany(company);
    employee.setLastName("Robinson");
    employee.setFirstName("Janet");
    employee.setCity("Greenwich");
    employee.setStreet("Walsh Ln 10");
    company.getEmployees().add(employee);
    em.getTransaction().commit();

主.java

REVINFO
+----+---------------+----------+
| ID |   TIMESTAMP   | USERNAME |
+----+---------------+----------+
|  1 | 1564997410711 | Alice    |
+----+---------------+----------+
|  2 | 1564997410849 | Bob      |
+----+---------------+----------+
|  3 | 1564997410858 | Bob      |
+----+---------------+----------+


EMPLOYEE_AUD
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
| ID | REV | REVTYPE |      CITY      | CITY_MOD | FIRSTNAME | FIRSTNAME_MOD | LASTNAME | LASTNAME_MOD |      STREET      | STREET_MOD | COMPANY_ID | COMPANY_MOD |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  1 |   1 |       0 | New York City  |     TRUE | Michael   |          TRUE | Ralbern  |         TRUE | 57th Street      |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  2 |   1 |       0 | Newark         |     TRUE | Linda     |          TRUE | Spencer  |         TRUE | High Street 123  |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  3 |   3 |       0 | Greenwich      |     TRUE | Janet     |          TRUE | Robinson |         TRUE | Walsh Ln 10      |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+

Revision 4: Update Employee

"Alice" updates the street and city of the employee Linda Spencer

    CurrentUser.INSTANCE.logIn("Alice");

    em.getTransaction().begin();
    TypedQuery<Employee> query2 = createEmployeeQuery(em, "Linda", "Spencer");
    employee = query2.getSingleResult();
    employee.setStreet("101 W 91st St");
    employee.setCity("New York City");
    em.getTransaction().commit();

Main.java

REVINFO
+----+---------------+----------+
| ID |   TIMESTAMP   | USERNAME |
+----+---------------+----------+
|  1 | 1564997410711 | Alice    |
+----+---------------+----------+
|  2 | 1564997410849 | Bob      |
+----+---------------+----------+
|  3 | 1564997410858 | Bob      |
+----+---------------+----------+
|  4 | 1564997410873 | Alice    |
+----+---------------+----------+

在这里,我们看到只有 CITY_MOD 和 STREET_MOD 设置为 true,因为这是我们在代码中更改的唯一两个属性。

EMPLOYEE_AUD
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
| ID | REV | REVTYPE |      CITY      | CITY_MOD | FIRSTNAME | FIRSTNAME_MOD | LASTNAME | LASTNAME_MOD |      STREET      | STREET_MOD | COMPANY_ID | COMPANY_MOD |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  1 |   1 |       0 | New York City  |     TRUE | Michael   |          TRUE | Ralbern  |         TRUE | 57th Street      |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  2 |   1 |       0 | Newark         |     TRUE | Linda     |          TRUE | Spencer  |         TRUE | High Street 123  |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  3 |   3 |       0 | Greenwich      |     TRUE | Janet     |          TRUE | Robinson |         TRUE | Walsh Ln 10      |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  2 |   4 |       1 | New York City  |     TRUE | Linda     |         FALSE | Spencer  |        FALSE | 101 W 91st St    |       TRUE |          1 |       FALSE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+

修订版 5:删除员工

爱丽丝删除了员工迈克尔·拉尔伯恩

    CurrentUser.INSTANCE.logIn("Alice");

    em.getTransaction().begin();
    TypedQuery<Employee> query3 = createEmployeeQuery(em, "Michael", "Ralbern");
    employee = query3.getSingleResult();
    employee.getCompany().getEmployees().remove(employee);
    em.remove(employee);
    em.getTransaction().commit();

主.java

REVINFO
+----+---------------+----------+
| ID |   TIMESTAMP   | USERNAME |
+----+---------------+----------+
|  1 | 1564997410711 | Alice    |
+----+---------------+----------+
|  2 | 1564997410849 | Bob      |
+----+---------------+----------+
|  3 | 1564997410858 | Bob      |
+----+---------------+----------+
|  4 | 1564997410873 | Alice    |
+----+---------------+----------+
|  5 | 1564997410892 | Alice    |
+----+---------------+----------+

在这里,我们看到 REVTYPE 2,它表示 DELETE 操作。对于删除操作,所有属性都设置为 NULL。

EMPLOYEE_AUD
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
| ID | REV | REVTYPE |      CITY      | CITY_MOD | FIRSTNAME | FIRSTNAME_MOD | LASTNAME | LASTNAME_MOD |      STREET      | STREET_MOD | COMPANY_ID | COMPANY_MOD |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  1 |   1 |       0 | New York City  |     TRUE | Michael   |          TRUE | Ralbern  |         TRUE | 57th Street      |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  2 |   1 |       0 | Newark         |     TRUE | Linda     |          TRUE | Spencer  |         TRUE | High Street 123  |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  3 |   3 |       0 | Greenwich      |     TRUE | Janet     |          TRUE | Robinson |         TRUE | Walsh Ln 10      |       TRUE |          1 |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  2 |   4 |       1 | New York City  |     TRUE | Linda     |         FALSE | Spencer  |        FALSE | 101 W 91st St    |       TRUE |          1 |       FALSE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
|  1 |   5 |       2 | NULL           |     TRUE | NULL      |          TRUE | NULL     |         TRUE | NULL             |       TRUE |       NULL |        TRUE |
+----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+

查询

存储审核/版本信息是故事的一面,但我们还需要一种方法来在需要时访问此信息。为此,Envers 提供了几种方法来查询审计表。

查询方法的主要入口点是类AuditReader。创建实例,并将实体管理器实例作为参数传递。AuditReaderFactory.get

    EntityManager em = JPAUtil.getEntityManagerFactory().createEntityManager();
    AuditReader reader = AuditReaderFactory.get(em);

主查询.java

getRevision() 方法返回一个修订号列表,在该修订号处修改了实体。该方法需要实体类和实体的主键作为参数。

    List<Number> revisions = reader.getRevisions(Company.class, 1);
    for (Number rev : revisions) {
      System.out.println(rev);

主查询.java

使用getRevisionDate(),我们可以访问修订日期 (REVINFO.时间戳)。

      Date revisionDate = reader.getRevisionDate(rev);
      System.out.println(revisionDate);

主查询.java

要访问修订表中的自定义用户名字段,我们需要调用findRevision() 并将自定义修订实体类的类和修订号作为参数传递。

      CustomRevisionEntity revision = reader.findRevision(CustomRevisionEntity.class,
          rev);
      String username = revision.getUsername();
      System.out.println(username);

主查询.java

使用find(),我们通过主键和给定的修订获得一个实体。

      Company comp = reader.find(Company.class, 1, rev);
      String name = comp.getName();
      String street = comp.getStreet();
      System.out.println(name);
      System.out.println(street);

主查询.java

应用程序打印以下输出。

1
Mon Aug 05 07:46:25 CEST 2019
Alice
E Corp
null
------------------------------------------------
2
Mon Aug 05 07:46:25 CEST 2019
Bob
EEE Corp
null

公司在修订版 1(插入)和修订版 2(更新名称)中进行了更改。请注意,我们从中获取的公司实例的街道属性为 null,因为我们只审核该属性。find()name

您还可以调用未更改给定实体类的修订号。在修订版 5 中,我们删除了一名员工。这就是公司在该修订时的状态。find()find()

    Company comp = reader.find(Company.class, 1, 5);
    String name = comp.getName();
    System.out.println(name); // output: EEE Corp

主查询.java

该库还提供了将 Date 对象而不是修订号作为第三个参数的变体。然后,这将返回该特定日期状态的实体。find()

另一个有用的方法是getRevisionNumberForDate()。此方法返回在给定日期当天或之前创建的最高修订号。

    Calendar cal = Calendar.getInstance();
    Number revNumber = reader.getRevisionNumberForDate(cal.getTime());
    System.out.println(revNumber); // output: 5

主查询.java

审计查询

让我们看一下更高级的查询,即使用 AuditReader.createQuery() 访问的AuditQuery类的所有成员。

forEntitiesAtRevision()查询返回给定类在特定修订版的所有实体。在第一个示例中,我们希望修订版 1 中的所有 Employee 对象。我们取回了在修订版 1 中插入的两个实例。

    AuditQuery query = reader.createQuery().forEntitiesAtRevision(Employee.class, 1);
    query.add(AuditEntity.relatedId("company").eq(1));
    for (Employee e : (List<Employee>) query.getResultList()) {
      System.out.println(e.getId() + ": " + e.getLastName() + " " + e.getFirstName());
    }
    // 1: Ralbern Michael
    // 2: Spencer Linda

主查询.java

所有返回 AuditQuery 实例的查询都可以使用 themethod进一步限制。在上面的示例中,我们仅获取与主键为 1 的公司相关的员工。add()

如果我们使用修订版 2 运行查询,即使我们没有更改修订版 2 中与员工相关的任何内容,我们也会返回相同的两个员工实例。该类像方法一样返回给定修订版中实体的状态,实体在此修订版中是否更改并不重要。find()

    query = reader.createQuery().forEntitiesAtRevision(Employee.class, 2);

主查询.java

    // 1: Ralbern Michael
    // 2: Spencer Linda

当我们查询修订版 5 时,我们看到输出发生了变化。因为我们在修订版 3 中插入了一名新员工,并在修订版 5 中删除了一名员工。

    query = reader.createQuery().forEntitiesAtRevision(Employee.class, 5);

主查询.java

    // 3: Robinson Janet
    // 2: Spencer Linda

为了演示另一个 where 子句,这里有一个例子,我们只希望姓氏等于“Spencer”的实体。

    query = reader.createQuery().forEntitiesAtRevision(Employee.class, 5);
    query.add(AuditEntity.property("lastName").eq("Spencer"));

主查询.java

    // 2: Spencer Linda

您还可以将多个 where 子句与AuditEntity.or()AuditEntity.and()

query.add(AuditEntity.or(AuditEntity.property("lastName").eq("Spencer"), AuditEntity.property("lastName").eq("Robinson")));

forEntitiesAtRevision()默认情况下不返回已删除的实体。您可以通过传递 true 作为第三个参数来更改此设置。

    query = reader.createQuery().forEntitiesAtRevision(Employee.class,
        Employee.class.getName(), 5, true);
    for (Employee e : (List<Employee>) query.getResultList()) {
      System.out.println(e.getId() + ": " + e.getLastName() + " " + e.getFirstName());
    }
    // 3: Robinson Janet
    // 2: Spencer Linda
    // 1: null null

主查询.java

请注意,除已删除实体的主键外,所有属性均为 null。

下一个方法是forEntitiesModifiedAtRevision(),它只返回在给定修订中受影响的实体。与所有 AuditQuery 一样,您可以进一步限制结果query.add()

当我们查询修订版 1 时,我们会返回两名员工,因为我们在此修订版中插入了他们。

    query = reader.createQuery().forEntitiesModifiedAtRevision(Employee.class, 1);
    for (Employee e : (List<Employee>) query.getResultList()) {
      System.out.println(e.getId() + ": " + e.getLastName() + " " + e.getFirstName());
    }
    // 1: Ralbern Michael
    // 2: Spencer Linda

MainQuery.java

When we query revision 2, we get back an empty list, because, in revision 2, we changed the company and didn't change any employee.

    query = reader.createQuery().forEntitiesModifiedAtRevision(Employee.class, 2);
    for (Employee e : (List<Employee>) query.getResultList()) {
      System.out.println(e.getId() + ": " + e.getLastName() + " " + e.getFirstName());
    }
    // empty

MainQuery.java

In revision 5, we deleted an employee, so we get back only this deleted entity.

    query = reader.createQuery().forEntitiesModifiedAtRevision(Employee.class, 5);
    for (Employee e : (List<Employee>) query.getResultList()) {
      System.out.println(e.getId() + ": " + e.getLastName() + " " + e.getFirstName());
    }
    // 1: null null

MainQuery.java

forRevisionsOfEntity() 返回修订列表,在该列表中修改了给定的实体类。结果是一个包含实体类 (0)、修订实体 (1) 和修订类型 (2) 的三元素数组列表

如果将第二个布尔参数设置为 true,则该方法将返回实体类的列表,而不是包含三元素数组的列表。

第三个布尔参数指定查询是否应返回已删除的实体 (true) 或不返回 (false)。

    query = reader.createQuery().forRevisionsOfEntity(Employee.class, false, true);
    // query.add(AuditEntity.id().eq(1));
    List<Object[]> results = query.getResultList();
    for (Object[] result : results) {
      Employee employee = (Employee) result[0];
      CustomRevisionEntity revEntity = (CustomRevisionEntity) result[1];
      RevisionType revType = (RevisionType) result[2];

      System.out.println("Revision     : " + revEntity.getId());
      System.out.println("Revision Date: " + revEntity.getRevisionDate());
      System.out.println("User         : " + revEntity.getUsername());
      System.out.println("Type         : " + revType);
      System.out.println(
          "Employee     : " + employee.getLastName() + " " + employee.getFirstName());

      System.out.println("------------------------------------------------");
    }

主查询.java

上面代码的输出。请注意,修订版 2 未列出,因为我们仅在该特定修订版中更改了公司。

Revision : 1
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Alice
Type : ADD
Employee : Ralbern Michael
------------------------------------------------
Revision : 1
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Alice
Type : ADD
Employee : Spencer Linda
------------------------------------------------
Revision : 3
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Bob
Type : ADD
Employee : Robinson Janet
------------------------------------------------
Revision : 4
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Alice
Type : MOD
Employee : Spencer Linda
------------------------------------------------
Revision : 5
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Alice
Type : DEL
Employee : null null

另一个有用的条款是,通过它,我们可以将修订限制为仅由一个特定用户的更改引起的修订。AuditEntity.revisionProperty

    query = reader.createQuery().forRevisionsOfEntity(Employee.class, false, true);
    query.add(AuditEntity.revisionProperty("username").eq("Bob"));

主查询.java

“鲍勃”在修订版 3 中只更新了一名员工

Revision : 3
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Bob
Type : ADD
Employee : Robinson Janet

我们在此博客文章中介绍的最后一个查询方法是forRevisionsOfEntityWithChanges()。此方法的工作方式与 相同。唯一的区别是此方法返回一个四元素数组:实体类 (0)、修订实体 (1)、修订类型 (2) 以及在此修订 (3) 中更改的一组属性名称。forRevisionsOfEntity()

如果要在应用程序中使用此查询,则必须启用 withModifiedFlag 标志 ()。@Audited(withModifiedFlag = true)

    query = reader.createQuery().forRevisionsOfEntityWithChanges(Employee.class, true);
    results = query.getResultList();
    for (Object[] result : results) {
      Employee employee = (Employee) result[0];
      CustomRevisionEntity revEntity = (CustomRevisionEntity) result[1];
      RevisionType revType = (RevisionType) result[2];
      Set<String> properties = (Set<String>) result[3];

      System.out.println("Revision     : " + revEntity.getId());
      System.out.println("Revision Date: " + revEntity.getRevisionDate());
      System.out.println("User         : " + revEntity.getUsername());
      System.out.println("Type         : " + revType);
      System.out.println("Changed Props: " + properties);
      System.out.println(
          "Employee     : " + employee.getLastName() + " " + employee.getFirstName());

      System.out.println("------------------------------------------------");
    }

主查询.java

请注意,具有已更改属性的集仅包含修订类型为 MOD(更新)的值。

Revision : 1
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Alice
Type : ADD
Changed Props: []
Employee : Ralbern Michael
------------------------------------------------
Revision : 1
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Alice
Type : ADD
Changed Props: []
Employee : Spencer Linda
------------------------------------------------
Revision : 3
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Bob
Type : ADD
Changed Props: []
Employee : Robinson Janet
------------------------------------------------
Revision : 4
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Alice
Type : MOD
Changed Props: [city, street]
Employee : Spencer Linda
------------------------------------------------
Revision : 5
Revision Date: Mon Aug 05 07:46:25 CEST 2019
User : Alice
Type : DEL
Changed Props: []
Employee : null null  

我对Envers的概述到此结束。

请参阅官方文档以了解有关Envers:Hibernate ORM 5.4.33.Final User Guide 的更多信息

这篇博文提供的源代码托管在GitHub上:
blog2019/envers at master · ralscha/blog2019 · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/48538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux网络配置实战】服务器Network静态路由配置

【Linux网络配置实战】服务器Network静态路由配置一、环境介绍1.环境规划2.实验目的二、检查各节点IP地址1.检查server01服务器上2.检查server02服务器网卡3.检查route01上的网卡三、在route01上启动IP包转发四、查看当前两节点互通情况1.查看server01和server02连通状态2.查看…

新手小白可以做什么互联网项目,副业项目应该怎么选择

现在网上的信息这么冗杂&#xff0c;有没有可靠的副业项目呢&#xff1f;怎样才能找到适合自己的副业呢&#xff1f; 说实话&#xff0c;在网上找副业并不难&#xff0c;搜索一下就会出来很多&#xff0c;但新手小白不知道如何选择&#xff0c;导致焦虑&#xff0c;一个重要的…

helm2.0安装及部署

一、helm简介 Helm是Deis (https://deis.com/) 开发的一个用于kubernetes的包管理器。每个包称为一个Chart&#xff0c;一个Chart是一个目录&#xff08;一般情况下会将目录进行打包压缩&#xff0c;形成name-version.tgz格式的单一文件&#xff0c;方便传输和存储&#xff09…

Linux 如何设置代理

安装部署 clash 是一款用 Go 语言开发的软件&#xff0c;所以我可以直接下载预编译的版本进行部署。 下载地址&#xff1a;https://github.com/Dreamacro/clash/releases/download/v1.8.0/clash-linux-amd64-v1.8.0.gz软件的作者提供了多种架构下预编译的二进制文件&#xff0…

67 - 经典问题解析五(指针的判别 构造中的异常)

---- 整理自狄泰软件唐佐林老师课程 1. 问题一 编写程序判断一个变量是不是指针&#xff1f; 1.1 指针的判别 C中仍然支持C语言中的可变参数函数C编译器的 匹配调用 优先级&#xff1a;重载函数 > 函数模板 > 变参函数 #include <iostream> #include <strin…

16-JavaSE基础巩固项目:拼图小游戏

阶段项目-拼图小游戏 一、项目介绍 1、目的 锻炼逻辑思维能力&#xff0c;让我们知道前面学习的知识点在实际开发中的应用场景。 1、为了学习一个新知识&#xff1a;GUI GUI全称&#xff1a;Graphical User Interface&#xff08;又称图形用户接口&#xff09;是指采用图形化…

三维地质建模数据处理

三维地质建模计算在地质工程、地球物理、矿产勘查等领域获得了广泛的应用&#xff0c;常用软件包括GOCAD、Surpac、XModel、DMine等。通过三维地质建模&#xff0c;既可以表达空间几何对象&#xff0c;也可以表现空间属性分布&#xff0c;进而实现地下三维空间可视化、地质解释…

win11 L2TP连接尝试失败,因为安全层在初始化与远程计算机的协商时遇到了一个处理错误

连接公司内网遇到的问题。。。。修改了&#xff08;不是翻墙&#xff0c;审核一下&#xff09; 在所有情况之前先尝试用手机热点试一下&#xff0c;排除网络问题&#xff0c; 今天遇到就移动的热点WiFi登不上公司内网&#xff0c;电信联通都行 所以先试试换个运营商热点看看…

springboot项结构分析

三. SpringBoot 结构 3.1.SpringBoot 工作原理 Spring boot应用程序采用各种Starters启动器,入口类是包含SpringBootApplication注解和main方法的类,然后使用ComponentScan注解自动扫描项目中的所有组件,并且Spring Boot会根据EnableAutoConfiguration注解将项目中的依赖项自…

redis基础1——发展历程+源码安装及配置+基本常识

文章目录一、NOSQL概述1.1 单机Mysql的演进1.2 什么是Nosql1.3 Nosql的四大分类1.3.1 KV键值对型1.3.2 文档型数据库&#xff08;bson格式&#xff0c;和json一样&#xff09;1.3.3 列存储数据库1.3.4 图数据库二、redis安装与配置2.1 redis概述2.2 Windows安装2.2.1 安装至win…

【项目实战:核酸检测平台】第四章 冲锋陷阵

项目实战&#xff1a;核酸检测平台第四章 冲锋陷阵 摘要&#xff1a;战争&#xff0c;冲在最前面的永远是最危险的人群&#xff0c;新冠之战&#xff0c;冲在最前的则是医护人员、防疫工作者。 核酸检测平台的采集人员APP做为先头部队的重要武器&#xff0c;一定要做的好用、…

JVM之运行时数据区 PC、虚拟机栈、本地方法栈

JVM之运行时数据区 PC、虚拟机栈、本地方法栈PC寄存器线程回顾寄存器实践面试使用PC寄存器存储字节码指令地址有什么用&#xff1f;为什么使用PC寄存器记录当前线程的执行地址PC寄存器为什么会被设定为线程私有虚拟机栈虚拟机栈出现背景简介栈可能出现的异常栈中存储着什么运行…

二、【redux】redux 完整版求和Demo

文章目录1、添加count_action.js1.1、项目结构变化1.2、CODE1.2.1、count_action.js1.2.2、Count_Redux.jsx2、添加constant.js2.1、项目结构变化2.2、CODE2.2.1、constant.js2.2.2、count_action.js2.2.3、count_reducer.js本示例补全 上一章 redux mini版示例&#xff0c;使用…

Codeforces Round #721 (Div. 2) C. Sequence Pair Weight

翻译&#xff1a; 序列的权值定义为具有相同值(&#x1d44e;&#x1d456;&#x1d44e;&#x1d457;)的无序索引对(&#x1d456;&#xff0c;&#x1d457;)(这里&#x1d456;<&#x1d457;)的数量。例如&#xff0c;序列&#x1d44e;[1,1,2,2,1]的权值为4。具有相同…

5-UI自动化-三大切换,iframe如何定位,窗口新开、alert弹窗如何进行元素定位

5-UI自动化-三大切换&#xff0c;iframe如何定位&#xff0c;窗口新开、alert弹窗如何进行元素定位新开一个窗口如何定位元素switch_to方法iframe定位元素alert弹窗如何定位元素上篇介绍4-UI自动化-selenium三大等待操作 web测试过程中有没有遇到以下问题&#xff1a; 1、测试…

中概股回暖,B站打开向上通道

“回来了&#xff0c;我感觉他们都回来了。”周星驰《少林足球》中这句经典台词&#xff0c;最近成为了中概股投资者的口头禅。 财报季临近尾声&#xff0c;好消息已经层出不穷。中概互联网指数KWEB在11月的涨幅超过30%&#xff0c;不少个股从低位大幅反弹&#xff0c;其中就包…

C/C++家族族谱管理系统

C/C家族族谱管理系统 课题名称: 家族族谱管理 主要目标: 通过训练&#xff0c;强化学生对树结构、二叉树结构的表示及操作算法的掌握和灵活运用 3.具体要求: 要求设计实现具有下列功能的家谱管理系统: (1) 输入文件以存放最初家谱中各成员的信息&#xff0c;成员的信息中…

[Java] 从内存的角度去理解ThreadLocal如何把不同线程间的访问隔离开来?ThreadLocal的内存泄露问题是什么?如何避免?

文章目录前言前置知识&#xff1a;堆内存与栈内存普通数据结构类和ThreadLocal存取数据的不同&#xff1f;结合源码来看ThreadLocal如何实现的1. ThreadLocal类get方法2. ThreadLocal类的getMap(Thread)方法3. Thread类的threadLocals属性4. ThreadLocal类的setInitialValue()方…

win11该文件没有与之关联的应用怎么办

win11用户在使用电脑的时候遇到了“该文件没有与之关联的应用”的提示&#xff0c;这是怎么回事呢&#xff1f;应该怎么办呢&#xff1f;出现这个情况应该是注册表被误删了&#xff0c;大家需要新建一个文本文档&#xff0c;然后输入下文提供的指令&#xff0c;之后将其重命名为…

亚信科技亮相南京软博会,数智赋能百行千业

11月23至25日&#xff0c;主题为“软件赋能 数智转型”的2022中国&#xff08;南京&#xff09;国际软件产品和信息服务交易博览会在南京国际博览中心盛大启幕。“数智化全栈能力提供商”亚信科技携“云网边端”产品体系&#xff0c;5G、人工智能、边缘AI、机器人流程自动化、数…