最近,我一直在一个项目中工作,该项目需要审核所有数据库事务,包括用户名。为此,我一直在使用Hibernate ORM Envers,它旨在实现持久类的简单审计/版本控制。
为了在这篇文章中展示如何使用Hibernate Envers,我基于这个Spring boot JPA项目构建了一个示例项目。Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序,您可以“直接运行”。在这种情况下,它非常方便,因为它包括:嵌入式雄猫,“入门”POM以简化您的Maven配置,以及随时可用的REST Web服务。
这篇文章的结构分为 5 个简单的步骤,但是,最后 2 个步骤仅在您想将用户名(或任何其他信息)添加到修订版时才有用。
首先,您需要将 Hibernate Envers 依赖项添加到您的项目中。当我使用 Maven 来管理我的依赖项时,我需要做的就是将依赖项添加到我的pom文件中:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.6.14.Final</version>
</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。