• 使用 Hibernate Envers 进行实体审计


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

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

    设置

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

    1. <dependency>
    2. <groupId>org.hibernategroupId>
    3. <artifactId>hibernate-enversartifactId>
    4. <version>5.4.6.Finalversion>
    5. dependency>

    绒球.xml

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

    1. @Entity
    2. @Audited(withModifiedFlag = true)
    3. public class Employee {
    4. @Id
    5. @GeneratedValue(strategy = GenerationType.IDENTITY)
    6. private int id;
    7. private String lastName;
    8. private String firstName;
    9. private String street;
    10. private String city;
    11. @ManyToOne
    12. private Company company;

    员工.java

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

    1. @Entity
    2. public class Company {
    3. @Id
    4. @GeneratedValue(strategy = GenerationType.IDENTITY)
    5. private int id;
    6. @Audited
    7. private String name;
    8. private String street;
    9. private String city;
    10. @OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval = true)
    11. private Set 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

    1. import javax.persistence.Entity;
    2. import javax.persistence.Table;
    3. import org.hibernate.envers.DefaultRevisionEntity;
    4. import org.hibernate.envers.RevisionEntity;
    5. @Entity
    6. @Table(name = "REVINFO")
    7. @RevisionEntity(CustomRevisionEntityListener.class)
    8. public class CustomRevisionEntity extends DefaultRevisionEntity {
    9. private static final long serialVersionUID = 1L;
    10. private String username;
    11. public String getUsername() {
    12. return this.username;
    13. }
    14. public void setUsername(String username) {
    15. this.username = username;
    16. }
    17. }

    自定义修订实体.java

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

    1. import org.hibernate.envers.RevisionListener;
    2. public class CustomRevisionEntityListener implements RevisionListener {
    3. @Override
    4. public void newRevision(Object revisionEntity) {
    5. CustomRevisionEntity customRevisionEntity = (CustomRevisionEntity) revisionEntity;
    6. customRevisionEntity.setUsername(CurrentUser.INSTANCE.get());
    7. }
    8. }

    CustomRevisionEntityListener.java

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

    1. public class CurrentUser {
    2. public static final CurrentUser INSTANCE = new CurrentUser();
    3. private static final ThreadLocal storage = new ThreadLocal<>();
    4. public void logIn(String user) {
    5. storage.set(user);
    6. }
    7. public void logOut() {
    8. storage.remove();
    9. }
    10. public String get() {
    11. return storage.get();
    12. }
    13. }

    当前用户.java

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

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

    例子

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

    修订版 1:插入

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

    1. EntityManager em = JPAUtil.getEntityManagerFactory().createEntityManager();
    2. CurrentUser.INSTANCE.logIn("Alice");
    3. em.getTransaction().begin();
    4. Company company = new Company();
    5. company.setName("E Corp");
    6. company.setCity("New York City");
    7. company.setStreet(null);
    8. Set employees = new HashSet<>();
    9. Employee employee = new Employee();
    10. employee.setCompany(company);
    11. employee.setLastName("Spencer");
    12. employee.setFirstName("Linda");
    13. employee.setStreet("High Street 123");
    14. employee.setCity("Newark");
    15. employees.add(employee);
    16. employee = new Employee();
    17. employee.setCompany(company);
    18. employee.setLastName("Ralbern");
    19. employee.setFirstName("Michael");
    20. employee.setStreet("57th Street");
    21. employee.setCity("New York City");
    22. employees.add(employee);
    23. company.setEmployees(employees);
    24. em.persist(company);
    25. em.getTransaction().commit();

    主.java

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

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

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

    1. REVINFO
    2. +----+---------------+----------+
    3. | ID | TIMESTAMP | USERNAME |
    4. +----+---------------+----------+
    5. | 1 | 1564997410711 | Alice |
    6. +----+---------------+----------+
    7. COMPANY_AUD
    8. +----+-----+---------+--------+
    9. | ID | REV | REVTYPE | NAME |
    10. +----+-----+---------+--------+
    11. | 1 | 1 | 0 | E Corp |
    12. +----+-----+---------+--------+
    13. EMPLOYEE_AUD
    14. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
    15. | ID | REV | REVTYPE | CITY | CITY_MOD | FIRSTNAME | FIRSTNAME_MOD | LASTNAME | LASTNAME_MOD | STREET | STREET_MOD | COMPANY_ID | COMPANY_MOD |
    16. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
    17. | 1 | 1 | 0 | New York City | TRUE | Michael | TRUE | Ralbern | TRUE | 57th Street | TRUE | 1 | TRUE |
    18. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
    19. | 2 | 1 | 0 | Newark | TRUE | Linda | TRUE | Spencer | TRUE | High Street 123 | TRUE | 1 | TRUE |
    20. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+

    修订版 2:更新公司

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

    1. CurrentUser.INSTANCE.logIn("Bob");
    2. em.getTransaction().begin();
    3. CriteriaBuilder cb = em.getCriteriaBuilder();
    4. CriteriaQuery q = cb.createQuery(Company.class);
    5. Root c = q.from(Company.class);
    6. ParameterExpression p = cb.parameter(String.class);
    7. q.select(c).where(cb.equal(c.get("name"), p));
    8. TypedQuery query1 = em.createQuery(q);
    9. query1.setParameter(p, "E Corp");
    10. company = query1.getSingleResult();
    11. company.setName("EEE Corp");
    12. em.getTransaction().commit();

    主.java

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

    1. REVINFO
    2. +----+---------------+----------+
    3. | ID | TIMESTAMP | USERNAME |
    4. +----+---------------+----------+
    5. | 1 | 1564997410711 | Alice |
    6. +----+---------------+----------+
    7. | 2 | 1564997410849 | Bob |
    8. +----+---------------+----------+
    9. COMPANY_AUD
    10. +----+-----+---------+----------+
    11. | ID | REV | REVTYPE | NAME |
    12. +----+-----+---------+----------+
    13. | 1 | 1 | 0 | E Corp |
    14. +----+-----+---------+----------+
    15. | 1 | 2 | 1 | EEE Corp |
    16. +----+-----+---------+----------+

    修订版 3:新员工

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

    1. CurrentUser.INSTANCE.logIn("Bob");
    2. em.getTransaction().begin();
    3. employee = new Employee();
    4. employee.setCompany(company);
    5. employee.setLastName("Robinson");
    6. employee.setFirstName("Janet");
    7. employee.setCity("Greenwich");
    8. employee.setStreet("Walsh Ln 10");
    9. company.getEmployees().add(employee);
    10. em.getTransaction().commit();

    主.java

    1. REVINFO
    2. +----+---------------+----------+
    3. | ID | TIMESTAMP | USERNAME |
    4. +----+---------------+----------+
    5. | 1 | 1564997410711 | Alice |
    6. +----+---------------+----------+
    7. | 2 | 1564997410849 | Bob |
    8. +----+---------------+----------+
    9. | 3 | 1564997410858 | Bob |
    10. +----+---------------+----------+
    11. EMPLOYEE_AUD
    12. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
    13. | ID | REV | REVTYPE | CITY | CITY_MOD | FIRSTNAME | FIRSTNAME_MOD | LASTNAME | LASTNAME_MOD | STREET | STREET_MOD | COMPANY_ID | COMPANY_MOD |
    14. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
    15. | 1 | 1 | 0 | New York City | TRUE | Michael | TRUE | Ralbern | TRUE | 57th Street | TRUE | 1 | TRUE |
    16. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
    17. | 2 | 1 | 0 | Newark | TRUE | Linda | TRUE | Spencer | TRUE | High Street 123 | TRUE | 1 | TRUE |
    18. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+
    19. | 3 | 3 | 0 | Greenwich | TRUE | Janet | TRUE | Robinson | TRUE | Walsh Ln 10 | TRUE | 1 | TRUE |
    20. +----+-----+---------+----------------+----------+-----------+---------------+----------+--------------+------------------+------------+------------+-------------+

    Revision 4: Update Employee

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

    1. CurrentUser.INSTANCE.logIn("Alice");
    2. em.getTransaction().begin();
    3. TypedQuery query2 = createEmployeeQuery(em, "Linda", "Spencer");
    4. employee = query2.getSingleResult();
    5. employee.setStreet("101 W 91st St");
    6. employee.setCity("New York City");
    7. em.getTransaction().commit();

    Main.java

    1. REVINFO
    2. +----+---------------+----------+
    3. | ID | TIMESTAMP | USERNAME |
    4. +----+---------------+----------+
    5. | 1 | 1564997410711 | Alice |
    6. +----+---------------+----------+
    7. | 2 | 1564997410849 | Bob |
    8. +----+---------------+----------+
    9. | 3 | 1564997410858 | Bob |
    10. +----+---------------+----------+
    11. | 4 | 1564997410873 | Alice |
    12. +----+---------------+----------+

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

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

    修订版 5:删除员工

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

    1. CurrentUser.INSTANCE.logIn("Alice");
    2. em.getTransaction().begin();
    3. TypedQuery query3 = createEmployeeQuery(em, "Michael", "Ralbern");
    4. employee = query3.getSingleResult();
    5. employee.getCompany().getEmployees().remove(employee);
    6. em.remove(employee);
    7. em.getTransaction().commit();

    主.java

    1. REVINFO
    2. +----+---------------+----------+
    3. | ID | TIMESTAMP | USERNAME |
    4. +----+---------------+----------+
    5. | 1 | 1564997410711 | Alice |
    6. +----+---------------+----------+
    7. | 2 | 1564997410849 | Bob |
    8. +----+---------------+----------+
    9. | 3 | 1564997410858 | Bob |
    10. +----+---------------+----------+
    11. | 4 | 1564997410873 | Alice |
    12. +----+---------------+----------+
    13. | 5 | 1564997410892 | Alice |
    14. +----+---------------+----------+

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

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

    查询

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

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

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

    主查询.java

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

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

    主查询.java

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

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

    主查询.java

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

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

    主查询.java

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

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

    主查询.java

    应用程序打印以下输出。

    1. 1
    2. Mon Aug 05 07:46:25 CEST 2019
    3. Alice
    4. E Corp
    5. null
    6. ------------------------------------------------
    7. 2
    8. Mon Aug 05 07:46:25 CEST 2019
    9. Bob
    10. EEE Corp
    11. null

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

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

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

    主查询.java

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

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

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

    主查询.java

    审计查询

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

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

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

    主查询.java

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

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

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

    主查询.java

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

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

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

    主查询.java

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

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

    1. query = reader.createQuery().forEntitiesAtRevision(Employee.class, 5);
    2. 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 作为第三个参数来更改此设置。

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

    主查询.java

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

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

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

    1. query = reader.createQuery().forEntitiesModifiedAtRevision(Employee.class, 1);
    2. for (Employee e : (List) query.getResultList()) {
    3. System.out.println(e.getId() + ": " + e.getLastName() + " " + e.getFirstName());
    4. }
    5. // 1: Ralbern Michael
    6. // 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.

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

    MainQuery.java

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

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

    MainQuery.java

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

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

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

    1. query = reader.createQuery().forRevisionsOfEntity(Employee.class, false, true);
    2. // query.add(AuditEntity.id().eq(1));
    3. List results = query.getResultList();
    4. for (Object[] result : results) {
    5. Employee employee = (Employee) result[0];
    6. CustomRevisionEntity revEntity = (CustomRevisionEntity) result[1];
    7. RevisionType revType = (RevisionType) result[2];
    8. System.out.println("Revision : " + revEntity.getId());
    9. System.out.println("Revision Date: " + revEntity.getRevisionDate());
    10. System.out.println("User : " + revEntity.getUsername());
    11. System.out.println("Type : " + revType);
    12. System.out.println(
    13. "Employee : " + employee.getLastName() + " " + employee.getFirstName());
    14. System.out.println("------------------------------------------------");
    15. }

    主查询.java

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

    1. Revision : 1
    2. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    3. User : Alice
    4. Type : ADD
    5. Employee : Ralbern Michael
    6. ------------------------------------------------
    7. Revision : 1
    8. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    9. User : Alice
    10. Type : ADD
    11. Employee : Spencer Linda
    12. ------------------------------------------------
    13. Revision : 3
    14. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    15. User : Bob
    16. Type : ADD
    17. Employee : Robinson Janet
    18. ------------------------------------------------
    19. Revision : 4
    20. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    21. User : Alice
    22. Type : MOD
    23. Employee : Spencer Linda
    24. ------------------------------------------------
    25. Revision : 5
    26. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    27. User : Alice
    28. Type : DEL
    29. Employee : null null

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

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

    主查询.java

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

    1. Revision : 3
    2. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    3. User : Bob
    4. Type : ADD
    5. Employee : Robinson Janet

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

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

    1. query = reader.createQuery().forRevisionsOfEntityWithChanges(Employee.class, true);
    2. results = query.getResultList();
    3. for (Object[] result : results) {
    4. Employee employee = (Employee) result[0];
    5. CustomRevisionEntity revEntity = (CustomRevisionEntity) result[1];
    6. RevisionType revType = (RevisionType) result[2];
    7. Set properties = (Set) result[3];
    8. System.out.println("Revision : " + revEntity.getId());
    9. System.out.println("Revision Date: " + revEntity.getRevisionDate());
    10. System.out.println("User : " + revEntity.getUsername());
    11. System.out.println("Type : " + revType);
    12. System.out.println("Changed Props: " + properties);
    13. System.out.println(
    14. "Employee : " + employee.getLastName() + " " + employee.getFirstName());
    15. System.out.println("------------------------------------------------");
    16. }

    主查询.java

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

    1. Revision : 1
    2. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    3. User : Alice
    4. Type : ADD
    5. Changed Props: []
    6. Employee : Ralbern Michael
    7. ------------------------------------------------
    8. Revision : 1
    9. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    10. User : Alice
    11. Type : ADD
    12. Changed Props: []
    13. Employee : Spencer Linda
    14. ------------------------------------------------
    15. Revision : 3
    16. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    17. User : Bob
    18. Type : ADD
    19. Changed Props: []
    20. Employee : Robinson Janet
    21. ------------------------------------------------
    22. Revision : 4
    23. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    24. User : Alice
    25. Type : MOD
    26. Changed Props: [city, street]
    27. Employee : Spencer Linda
    28. ------------------------------------------------
    29. Revision : 5
    30. Revision Date: Mon Aug 05 07:46:25 CEST 2019
    31. User : Alice
    32. Type : DEL
    33. Changed Props: []
    34. Employee : null null

    我对Envers的概述到此结束。

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

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

  • 相关阅读:
    应用安全四十二:SSO安全
    平板用电容笔还是触控笔?实惠的ipad平替电容笔推荐
    字典的基本概念
    局域网综合设计-----计算机网络
    springboot的多模块开发
    【技术分享】NetLogon于域内提权漏洞(CVE-2020-1472)
    java计算机毕业设计车辆保险平台系统研究与设计源码+mysql数据库+系统+lw文档+部署
    基于深度学习的AI绘画为何突然一下子火了?
    web框架与Django
    备战蓝桥杯---贪心刷题1
  • 原文地址:https://blog.csdn.net/allway2/article/details/128116061