Auditing with Hibernate Envers
Java, Maven, JPA, Spring Boot, Hibernate, Audit
Envers is a core Hibernate model that works both with Hibernate and JPA.
Introduction
You can apply auditing to your JPA entities either by using Spring Data
or Hibernate Envers
. You can also combine both to provide a rich auditing infrastructure for your applications.
Hibernate Envers provides an easy auditing / versioning solution for your entity classes. Envers has the following core features:
- Auditing of all mappings defined by the JPA specification
- Auditing some Hibernate mappings that extend JPA, e.g. custom types and collections/maps of “simple” types like Strings, Integers.
- Logging data for each revision using a “revision entity”
- Querying historical snapshots of an entity and its associations
Prerequisites
Project Structure
At the end of this guide our folder structure will look similar to the following:
.
|__src/
| |__main/
| | |__java/
| | | |__com/
| | | | |__juliuskrah/
| | | | | |__audit/
| | | | | | |__Application.java
| | | | | | |__Customer.java
| | |__resources/
| | | |__db/
| | | | |__changelog/
| | | | | |__db.changelog-master.yaml
| | | |__application.yaml
| |__test/
| | |__java/
| | | |__com/
| | | | |__juliuskrah/
| | | | | |__audit/
| | | | | | |__ApplicationTests.java
|__pom.xml
What You Need to Get Started
To help readers be able to follow along and do hands-on, I have provided an initial code that you can download as zip/tar.gz. Go ahead download and extract, and import into your favorite IDE as a maven project. Run and confirm everything works.
Create the Required Auditing Classes
The first order of business is to create an Entity class and add the Hibernate auditing metadata:
file:
src/main/java/com/juliuskrah/audit/Customer.java
@Entity
@Audited
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
private String id;
private String name;
private String email;
private String item;
@CreationTimestamp
@Column(updatable = false)
private LocalDateTime createdDate;
@UpdateTimestamp
private LocalDateTime lastModifiedDate;
// Getters and Setters omitted for brevity
}
@Audited
is the primary Hibernate annotation for JPA auditing.
@Audited
when applied to an Entity class audits all persistent properties. You can also annotate only some
persistent properties with @Audited
. This will cause only these properties to be audited.
The audit (history) of an entity can be accessed using the AuditReader
interface, which can be obtained
having an open EntityManager
via the AuditReaderFactory
.
e.g.
AuditReader auditReader = AuditReaderFactory.get(entityManager);
List<Number> revisions = auditReader.getRevisions(ApplicationCustomer.class, 1L);
CustomTrackingRevisionEntity revEntity = auditReader.findRevision(
CustomTrackingRevisionEntity.class,
revisions.get( 0 )
);
Now, considering the previous Customer
entity, let’s see how Envers auditing works when inserting, updating,
and deleting the entity in question:
file:
src/test/java/com/juliuskrah/audit/ApplicationTests.java
@SpringBootTest
@SpringJUnitConfig
public class ApplicationTests {
@PersistenceUnit
private EntityManagerFactory emf;
private Customer customer;
private EntityManagerFactory entityManagerFactory() {
return this.emf;
}
@BeforeEach
public void init() {
this.customer = new Customer();
this.customer.setName("Julius");
this.customer.setEmail("juliuskrah@example.com");
}
@Test
public void testAudits() {
// Insert
doInJPA(this::entityManagerFactory, entityManager -> {
entityManager.persist(this.customer);
assertTrue(entityManager.contains(this.customer));
});
String id = this.customer.getId();
Customer firstRevision = doInJPA(this::entityManagerFactory, entityManager -> {
return AuditReaderFactory.get(entityManager).find(Customer.class, id, 1);
});
assertThat(firstRevision).isNotNull();
assertThat(firstRevision.getCreatedDate()).isNotNull();
assertThat(firstRevision.getCreatedDate()).isBefore(now());
assertThat(firstRevision.getName()).isSameAs("Julius");
// Retrieve and Update
doInJPA(this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.find(Customer.class, id);
customer.setItem("Biscuits");
customer.setName("Abeiku");
});
Customer secondRevision = doInJPA(this::entityManagerFactory, entityManager -> {
return AuditReaderFactory.get(entityManager).find(Customer.class, id, 2);
});
assertThat(secondRevision).isNotNull();
assertThat(secondRevision.getLastModifiedDate()).isNotNull();
assertThat(secondRevision.getLastModifiedDate()).isAfter(firstRevision.getCreatedDate());
assertThat(secondRevision.getName()).isSameAs("Abeiku");
// Delete
doInJPA(this::entityManagerFactory, entityManager -> {
Customer customer = entityManager.getReference(Customer.class, id);
entityManager.remove(customer);
});
Customer thirdRevision = doInJPA(this::entityManagerFactory, entityManager -> {
return AuditReaderFactory.get(entityManager).find(
Customer.class,
Customer.class.getName(),
id,
3,
true);
});
assertThat(thirdRevision).isNotNull();
assertThat(thirdRevision.getLastModifiedDate()).isNull();
assertThat(thirdRevision.getName()).isNull();
}
}
Unlike Spring Auditing, Hibernate Envers writes audit infomation in a different table, so delete infomation
is preserved. It writes into entity_table_AUD
(customer_AUD
in our case) by default if not explicitely
configured.
That’s all folks
Conclusion
In this post we looked at Hibernate Envers auditing infrastructure with minimal configuration leveraing auto configuration from Spring-Boot.
You can find the source to this guide in the github repository. Until the next post, keep doing cool things .