Published on

Hibernate Envers: Comprehensive Guide to Entity Versioning

Hibernate Envers - Versioning

Hibernate envers is a Java library for maintaining versioning of our entity class. It listens to the changes made on the entity and stores it into the audit table.

What is Hibernate Envers ?

Hibernate enver provides us an easy way to store history data for maintaining versioning. i.e Recording the changes of data from one consistent state to another. This approach of maintaining data history is known as CDC (Change Data Capture).
There are several techniques for CDC. Hibernate envers is based on the Application-level trigger.

# Database triggers

# Application-level triggers

  • Emulates database triggers at the application level. eg- Hibernate envers
    • Advantage: don't need to mind database specific syntax for triggers
    • Disadvantage: Can't log data changes that don't flow through the application

# Transaction log
RDBMS uses its own way of maintaining a transaction log

  • Oracle - GoldenGate
  • SQL Server - built-in CDC
  • MySQL - 3rd party solutions, like LinkedIn DataBus

# Others

  • Debezium
    • open source project by RedHat.
    • connectors available for Oracle, MySQL, PostgreSQL, and MongoDB
    • Also support propagation to Kafka

Enver in Spring

Setting up the dependency in pom.xml

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-envers</artifactId>
   <version>5.2.5.Final</version>
</dependency>

Envers Table

By Default Hibernate enver uses REVINFO (Revision Table) table to log data for revision. This includes keeping track of the revision number and timestamp.
The default query for REVINFO looks like:-

CREATE TABLE revinfo (
 rev integer NOT NULL,
 revtstmp bigint,
 CONSTRAINT revinfo_pkey PRIMARY KEY (rev)
)

To add more data fields to the revinfo table we can modify default provided by hibernate envers by creating custom revision entity class.

1. Envers Configuration

  1. Create custom revision entity class adding additional fields to it. (This configuration isn't required - If we dont need custom configuration of AuditEnversInfo class)
@Entity
@RevisionEntity(CustomRevisionListener.class)
// Custom Class adding additional fields to the envers default "reverifo" table
public class AuditEnversInfo extends DefaultRevisionEntity {

   private String source;
   private long userId;

   // Getter and Setter
}
  1. Set value to the added additional fields.
public class CustomRevisionListener implements RevisionListener {

   // Setting value to the added addtional fields - our custom revision entity "auditEnversInfo".
   @Override
   public void newRevision(Object revisionEntity) {
       AuditEnversInfo auditEnversInfo = (AuditEnversInfo) revisionEntity;
       auditEnversInfo.setSource("In application");
       auditEnversInfo.setUserId(1L);
   }
}

2. Setting Entity class for Audit

In all entities that need to be audited specify which entity or attribute to be audited using annotation @Audited

@Entity
@Audited
public class Customer {
  // ..
 }

@Audited creates Audit table for each entity. The structure looks like:

Envers table overview

As from figure, the Entity Audit Table ( customer_aud ) contains:

  • Primary key same as the original entity
  • contains all audited fields declared using @Audited annotation.
  • revision number (used with primary id column to create combined primary key)
  • revision type (type of operation performed on entity) i.e 0- Added, 1- Updated, 2- Deleted

Envers stores data into the Audit table by listening to the operations - create, update and delete.

NOTE

If you are using Native SQL or JPA's CriteriaUpdate/CriteriaDelete operations to manipulate database records, Envers will not pick up those changes. This is because Hibernate does not raise an event for bulk or stateless operations, preventing Envers from auditing those changes.

3. Retrieving data from the revision table

To Read the Information we can use getRevisions() method passing our entity class.

AuditReader auditReader = AuditReaderFactory.get(em);
List<Number> revisionNumbers = auditReader.getRevisions(Entity.class, e.getId());

Lets say we have records of Employess with managed revision details i.e @Audited.

List<Employee> revisionsEmployees = AuditReaderFactory.get(em)
                .createQuery()
                .forRevisionsOfEntity(Asset.class, true, true)
                .add(AuditEntity.id().eq(id))
                .getResultList();

For more info on Hibernate Envers and Audit See: