使用 Hibernate Envers 进行实体审计

news/2024/4/20 1:25:31/文章来源:https://blog.csdn.net/allway2/article/details/128116061

业务应用程序中的常见要求是在特定数据更改时存储版本控制信息;当某事发生变化时,谁改变了它,改变了什么。在这篇博文中,我们将介绍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;@ManyToOneprivate Company company;

员工.java

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

@Entity
public class Company {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private int id;@Auditedprivate 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 {@Overridepublic 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.luyixian.cn/news_show_39184.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

期中考试【Verilog】

期中考试【Verilog】前言推荐期中考试一. 单选题&#xff08;共10题&#xff09;二. 填空题&#xff08;共5题&#xff09;三. 简答题&#xff08;共3题&#xff09;四. 其它&#xff08;共4题&#xff09;最后前言 编写于2022/11/30 13:30 以下内容源自Verilog期中试题 仅供…

GeoServer服务迁移出现 EncryptionOperationNotPossibleException 错误的解决方案

目录1.前言2.GeoServer服务迁移一般流程3.遇到问题4.原因分析5.解决办法6.根本原因分析7.总结1.前言 这几天我在迁移 GeoServer 服务的时候发现&#xff0c;报了一个错&#xff0c;EncryptionOperationNotPossibleException &#xff0c;这个错误的大概意思是加密操作不可用异常…

Faster RCNN全文翻译

Abstract—State-of-the-art【最先进的】 object detection networks depend on region proposal algorithms to hypothesize【假设、推测】 object locations.Advances like SPPnet [1] and Fast R-CNN [2] have reduced the running time of these detection networks, expos…

Redis集群方案备忘录

文章目录哨兵模式官方Redis ClusterJedis&#xff08;客户端分片&#xff09;Codis&#xff08;代理分片&#xff09;哨兵模式 优点 哨兵模式是基于主从模式的&#xff0c;解决可主从模式中master故障不可以自动切换故障的问题。缺点 &#xff08;1&#xff09;是一种中心化的…

一些跨平台技术方案的经验参考

今天就站在一个小开发的视角分享一下一个小项目是如何进行跨平台方案选型的 本系列文章先站在公司的的角度对产品技术选型进行分析&#xff0c;然后再根据我们项目实际开发经验进行汇总&#xff0c;供大家参考。 目前大前端技术也非常丰富&#xff0c;可以实现&#xff0c;一…

【uniapp】利用Vuex实现购物车功能

实战项目名称&#xff1a;实现购物车功能 文章目录一、实战步骤1. 先编辑store.js文件2. 定义方法和基本的结构3. 编写SETSHPPING二、在项目中调用1. 触发相应的mutations2. 利用computed计算数量和总价的方法提示&#xff1a;本实战内容大部分为具体实现的思路&#xff0c;界面…

FRED应用:激光二极管的模拟

简介 当提及模拟激光二极管时&#xff0c;FRED软件具有极大的灵活性。在这篇应用笔记中&#xff0c;将会描述简单到详细的激光光源模型。最基本的模型是高斯TEM0,0模。更高级的模型包括在束腰上偏移和发散中的像散光束。激光也可以使用其M2因子表示。最后&#xff0c;可以创…

猿如意开发工具|Sublime Text(4126)

文章目录 一、猿如意是什么&#xff1f; 二、如何使用猿如意下载安装Sublime Text 三、总结 一、猿如意是什么&#xff1f; 猿如意是一款面向开发者的辅助开发工具箱&#xff0c;包含了效率工具、开发工具下载&#xff0c;教程文档&#xff0c;代码片段搜索&#xff0c;全网搜…

Azure DevOps Server 用户组加入 Azure AD Domain Service 管理用户

一&#xff0c;引言 今天我们继续讲解 Azure DevOps Server 的内容&#xff0c;对于管理用户组除了在 Azure DevOps Server 服务器上添加管理员方式外&#xff0c;还有没有其他方式&#xff0c;Azure DevOps 需要加入Azure ADDS 服务域后&#xff0c;Azure DevOps Server 的管理…

oh-my-zsh 为 ls 命令自定义颜色

ls 命令默认显示的颜色是&#xff1a; 白色&#xff1a; 表示普通文件 蓝色&#xff1a; 表示目录 绿色&#xff1a; 表示可执行文件 红色&#xff1a; 表示压缩文件 蓝绿色&#xff1a; 链接文件 红色闪烁&#xff1a;表示链接的文件有问题 黄色&#xff1a; 表示设备文件 灰…

深入理解SR-IOV和IO虚拟化

一、背景 SR-IOV&#xff08;Single Root I/O Virtualization&#xff09;是由PCI-SIG组织定义的PCIe规范的扩展规范《Single Root I/O Virtualization and Sharing Specification》&#xff0c;目的是通过提供一种标准规范&#xff0c;为VM&#xff08;虚拟机&#xff09;提供…

ProcessDB实时/时序数据库——ODBC之连接数据库

目录 前言 一、安装ProcessDB-ODBC驱动 1.下载ProcessDB-ODBC驱动 2.安装ProcessDB-ODBC驱动 二、配置ProcessDB数据源 三、JAVA连接ProcessDB数据库 前言 ProcessDB实时/时序数据库支持ODBC连接数据库&#xff0c;接下来将和大家分享下如何使用ODBC操作ProcessDB实时/时…

基于Java Web的传智播客crm企业管理系统的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

tinymce富文本编辑器做评论区

今天分享一下tinymce富文本编辑器做评论区的全过程。 文章目录一、介绍1.最终效果2.功能介绍3.主要项目包版本介绍&#xff1a;二、每个功能的实现1.自定义toolbar的功能区①对应的样式以及意义②对应的代码实现【忽略了一切非实现该功能的代码】2.展示、收起评论区①对应的样式…

软件测试面试技巧:如何提高面试通过率?这3点一定要做到

对于想要进入到软件测试岗的新手人员来说&#xff0c;面试这一关是非常重要的&#xff0c;它直接关系着你的去留&#xff0c;也关系后续的期待遇问题&#xff0c;那么&#xff0c;有没有什么技巧可以帮忙提高面试通过率呢&#xff1f; 关于这一问题答案&#xff0c;我从这几个方…

Java自动化测试调试中遇到的问题

前言:记录下遇到的问题 Java自动化测试在调试中遇到的问题总结 1、遇到的Waring:must be unique [WARNING] dependencies.dependency.(groupId:artifactId:type:classifier) must be unique: com.vertica.jdbc:vertica-jdbc:jar -> duplicate declaration of version 10.…

数商云供应链管理系统助力化工行业企业实现客户订单管理可视化

订单管理是现代企业商务业务的重要组成部分&#xff0c;可以帮助企业解决订单管理低效、混乱等问题。随着产业互联网时代的到来&#xff0c;越来越多企业放弃传统费时费力的手动操作&#xff0c;开始应用数字化的管理工具来提高企业订单管理的水平。这里以化工行业企业为例&…

RabbitMQ 快速入门七种简单模式

RabbitMQ 快速入门七种简单模式起步七种模式项目依赖1、"Hello World!"(1) Connection 方式(2) RabbitTemplate 方式2、Work Queues生产者消费者3、Publish/Subscribe关系绑定生产者消费者4、Routing消费者生产者5. Topics消费者生产者6、RPC7、Publisher Confirms起…

09 更真实的云原生:Kubeadm实际搭建多节点的Kubernetes集群

文章目录1. 前言2. 什么是 kubeadm&#xff1f;2.1 kubeadm 介绍2.2 kubeadm 原理3. kubeadm 实验环境的架构是什么样的?3.1 Master 节点3.2 Worker 节点3.3 辅助节点3.4 安装前的准备工作3.4.1 修改主机名3.4.2 修改 Docker 配置3.4.3 修改网络设置3.4.4 修改交换分区4. 安装…

Android -- 每日一问:如何理解 Android 中的 Context,它有什么用?

经典回答 官方文档对于 Context 的解释&#xff1a; Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and cla…