Versioning Entity with Hibernate Envers

The Envers project aims to enable easy auditing/versioning of persistent classes. This can simplify storing and retrieving historical sets of data from the DB.
Similarly to Subversion, Envers has a concept of revisions. Basically, one transaction is one revision (unless the transaction didn’t modify any audited entities).
 
As the revisions are global, having a revision number, you can query for various entities at that revision, retrieving a view of the database at that revision.The Project Envers is available as standalone project, however, since the release 3.5 of Hibernate it is included as Hibernate module too.
Let’s see a practical example:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.envers.Audited;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;


@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Audited
@Builder
public class Customer implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String firstName;
    private String lastName;
}

As you can see, that is an Entity Bean which uses Lombok project to reduce boilerplate coding but also contains the @org.hibernate.envers.Audited annotation, which tells Envers to automatically track changes to this entity.

To get it working, you have to add the hibernate-envers dependency to your pom.xml file:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
    <version>${hibernate.version}</version>
</dependency>
Please note that instead of annotating the whole class, you can annotate only some persistent properties with @Audited. This will cause only these properties to be audited.

With the @Audited annotation in place, Hibernate will then create a new revision for each transaction and create a new record in the audit table for each create, update or delete operation performed on an audited Entity.

You can then retrieve and query historical data without much effort. Basically, one transaction is one revision. As the revisions are global, having a revision number, you can query for various entities at that revision, retrieving a (partial) view of the database at that revision. You can find a revision number having a date, and the other way round, you can get the date at which a revision was commited.

For above entity will be auto-generated audit table:

create table Customer_AUD (
    id bigint not null,
    REV integer not null,
    REVTYPE tinyint,
    firstName varchar(255),
    lastName varchar(255),
    primary key (id, REV)
)

The REVTYPE column value is taken from the RevisionType Enum:

| Database column value | RevisionType
| ----------------------|-------------
| 0                     | ADD
| 1                     | MOD
| 2                     | DEL
  • ADD – indicates that the entity was added (persisted) at that revision.
  • DEL – indicates that the entity was deleted (removed) at that revision.
  • MOD – indicates that the entity was modified (one or more of its fields) at that revision.

Accessing Entity History

You can access the audit (history) of an entity using the AuditReader interface, which you can obtain from an open EntityManager:

AuditReader reader = AuditReaderFactory.get(entityManager);
Customer oldCustomer = auditReader.find(Customer.class, customerId, rev)

That returns the Customer entity with the given primary key, with the data it contained at the given revision. If the entity didn’t exist at this revision, null is returned.

You can also get a list of revisions at which an entity was modified:

List<Number> revisions = auditReader.getRevisions(Customer.class, customerId);

And you can  retrieve the date (when the revision was created) using the getRevisionDate method:

Map<Number, Date> revisionDatesMap = revisions.stream().collect(Collectors.toMap(Function.identity(), auditReader::getRevisionDate));

For additional information about Envers, visit the Enver Project documentation at: https://envers.jboss.org/

 

Found the article helpful? if so please follow us on Socials