业务应用程序中的常见要求是在特定数据更改时存储版本控制信息;当某事发生变化时,谁改变了它,改变了什么。在这篇博文中,我们将介绍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注释。@Audited
name
@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.auto
update
<property name="hibernate.hbm2ddl.auto" value="update" />
坚持不懈.xml
完成此配置后,Hibernate会自动创建“员工”和“公司”两个表。对于每个实体,它创建一个审核表,Envers 在其中跟踪更改。它还创建 REVINFO 表,该表跟踪修订号和发生更改时的时间戳。@Audited
每次要插入、更新或删除实体时,Envers 都会介入并通过在 REVINFO 表和相应的 AUD 表中插入新行来创建新的修订。@Audited
请注意,EMPLOYEE_AUD表包含 EMPLOYEE 表中每个字段的字段,而COMPANY_AUD表仅包含字段,这是因为我们只在 Company 类中批注了属性。AUD 表的 ID 字段是相应实体表的主键;这就是主键必须是不可变的原因。name
name
在 Employee 类中,我们启用了默认情况下禁用的withModifiedFlag选项 ()。您可以在EMPLOYEE_AUD表中看到此选项的效果。Envers 为每个属性添加了额外的布尔属性_MOD。此修改标志存储属性在给定修订时已更改的信息。@Audited(withModifiedFlag = true)
在公司类中,我们未启用此选项。因此,COMPANY_AUD表不包含NAME_MOD字段。
为了进行比较,如果不启用该选项,则EMPLOYEE_AUD表定义:
仅当您需要此信息时,才应启用,因为代价是附加字段会增加审核表的大小。有一个 Envers 查询 () 依赖于此附加信息,因此,如果计划使用此查询方法,则必须启用该选项。withModifiedFlag
forRevisionsOfEntityWithChange
自定义修订实体
请注意,默认情况下,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会自动设置它们。@RevisionEntity
newRevision
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