最近,我一直在一个项目中工作,该项目需要审核所有数据库事务,包括用户名。为此,我一直在使用Hibernate ORM Envers,它旨在实现持久类的简单审计/版本控制。
为了在这篇文章中展示如何使用Hibernate Envers,我基于这个Spring boot JPA项目构建了一个示例项目。Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序,您可以“直接运行”。在这种情况下,它非常方便,因为它包括:嵌入式雄猫,“入门”POM以简化您的Maven配置,以及随时可用的REST Web服务。
这篇文章的结构分为 5 个简单的步骤,但是,最后 2 个步骤仅在您想将用户名(或任何其他信息)添加到修订版时才有用。
首先,您需要将 Hibernate Envers 依赖项添加到您的项目中。当我使用 Maven 来管理我的依赖项时,我需要做的就是将依赖项添加到我的pom文件中:
- <dependency>
- <groupId>org.hibernategroupId>
- <artifactId>hibernate-enversartifactId>
- <version>5.6.14.Finalversion>
- dependency>
为了能够审核实体表,您只需要有一个具有主键的实体并使用注释@Audited。您可以在类的顶部使用注释@Audited(这将审核类的所有字段),也可以仅在要审核的字段中使用注释。
添加@Audited注释后,您将看到将为每个实体创建一个后缀为“_AUD”的新表。此外,您还会发现已创建一个名为 REVINFO 的新表,其中包含所有修订信息。默认情况下,此表仅包含修订版的 id 和时间戳,尽管在第 4 节和第 5 节中,我将向您展示如何向此表添加更多数据,例如用户名。
下面是一个经过审核的 Java 类的示例:
- @Entity
- @Audited
- public class City implements Serializable {
-
- private static final long serialVersionUID = 1L;
-
- @Id
- @GeneratedValue(strategy = GenerationType.AUTO)
- private Long id;
-
- @Column
- private String name;
-
- @Column
- private String state;
-
- @Column
- private String country;
-
- @Column
- private String map;
-
- //getters and setters
-
- }
在这种情况下,我们将在数据库中自动拥有 2 个表,一个称为 City,另一个称为 City_AUD,其中包括所有 city 字段以及修订标识符和修订类型(创建、更新、删除)。
如果您已经在项目中配置了 Hibernate Envers,那么现在每次在数据库中添加或更新对象时,您都会看到另一行将添加到审核表中。现在我们想获得特定对象的不同修订。
Hibernate Envers包含一个名为Audit Reader的类,它使任何修订都更容易阅读。您只需要使用实体管理器对其进行初始化。然后,您可以使用 find 方法查找任何修订版本:
AuditReader reader = AuditReaderFactory.get(entityManager);
reader.find(class, object id, revision id);
下面是创建和更新 City 对象然后检查修订的示例:
- final static String WRONG_STATE = “Wrong State”;
- final static String OXFORDSHIRE_STATE = “Oxfordshire”;
-
- @PersistenceContext
- private EntityManager entityManager;
-
- @Autowired
- private Transactor transactor;
-
- long cityId;
-
- @Test
- public void testSaveAndUpdateCity(){
- transactor.perform(() -> {
- addCity();
- });
- transactor.perform(() -> {
- updateCity(cityId);
- });
-
- transactor.perform(() -> {
- checkRevisions(cityId);
- });
-
- }
-
- private void addCity(){
- City city = new City(“Oxford”, “UK”);
- city.setState(WRONG_STATE);
- entityManager.persist(city);
- entityManager.flush();
- cityId = city.getId();
- }
-
- private void updateCity(long cityId) {
- City updateCity = entityManager.find(City.class, cityId);
- updateCity.setState(OXFORDSHIRE_STATE);
- entityManager.persist(updateCity);
- entityManager.flush();
- }
-
- private void checkRevisions(long cityId){
- AuditReader reader = AuditReaderFactory.get(entityManager);
-
- City city_rev1 = reader.find(City.class, cityId, 1);
- assertThat(city_rev1.getState(), is(WRONG_STATE));
-
- City city_rev2 = reader.find(City.class, cityId, 2);
- assertThat(city_rev2.getState(), is(OXFORDSHIRE_STATE));
- }
事务处理器是一个自定义类,用于在单个事务中运行一段代码。正如您在checkRevisions函数中看到的,我们正在使用AuditReader来获取同一城市的不同修订。
AuditReader 具有更多方法,可让您更轻松地查找任何修订版,请查看API以获取更多信息。
如果您遵循了前 3 个步骤,您应该能够审核和阅读任何修订,对于某些项目,这就是您所需要的。但是,在其他情况下,您将需要审核更多信息,例如进行交易的人的用户名。为此,您需要实现以下 2 个类:
修订实体是包含您要在每个事务中审核的数据的类。此类必须包含修订号和修订时间戳,您可以手动添加这两个字段,也可以扩展已包含它们的 DefaultRevisionEntity 类。
下面是我的修订实体的示例,它扩展了 DefaultRevisionEntity 并具有用户名的属性:
- @Entity
- @RevisionEntity(UserRevisionListener.class)
- public class UserRevEntity extends DefaultRevisionEntity {
-
- private String username;
-
- public String getUsername() { return username; }
- public void setUsername(String username) { this.username = username; }
- }
请注意,该类包含 @RevisionEntity(UserRevisionListener.class) 注释。修订侦听器是您需要在其中指定如何将其他数据填充到修订中的类。此类需要实现 RevisionListener 接口,该接口只有一个称为 newRevin 的方法。
让我们看一个例子:
- public class UserRevisionListener implements RevisionListener {
-
- public final static String USERNAME = “Suay”;
-
- @Override
- public void newRevision(Object revisionEntity) {
- UserRevEntity exampleRevEntity = (UserRevEntity) revisionEntity;
- exampleRevEntity.setUsername(USERNAME);
- }
- }
在此示例中,我正在审核具有相同用户名(“SUAY”)的任何事务,但在实际环境中,您应该从用户服务或用于记录用户的任何其他方法获取用户名。
为了获取用户信息,我使用方法“forRevisionsOfEntity”运行查询,该方法将返回三元组对象:实体,实体修订信息,最后是修订类型。只有第二个对象是我感兴趣的对象,因为它包含用户名信息。请看以下示例:
- @Test
- public void testUserRevisions() throws Exception{
- transactor.perform(() -> {
- addCity();
- });
- transactor.perform(() -> {
- checkUsers();
- });
- }
-
- private void checkUsers(){
- AuditReader reader = AuditReaderFactory.get(entityManager);
- AuditQuery query = reader.createQuery()
- .forRevisionsOfEntity(City.class, false, false);
-
- //This return a list of array triplets of changes concerning the specified revision.
- // The array triplet contains the entity, entity revision information and at last the revision type.
- Object[] obj = (Object[]) query.getSingleResult();
-
- //In this case we want the entity revision information object, which is the second object of the array.
- UserRevEntity userRevEntity = (UserRevEntity) obj[1];
-
- String user = userRevEntity.getUsername();
- assertThat(user, is(UserRevisionListener.USERNAME));
-
- }
这篇文章中使用的所有代码都可以在github中找到。
如果你想运行这个项目,你只需运行SampleDataJpaApplication。