Hibernate Envers – Extend the standard revision


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


In the previous posts of this series, I showed you how to add Hibernate Envers to your project to write an audit log and how to use its query API to search in your audit log. These posts provide you an introduction to Hibernate Envers and allow you to write a basic audit log. But the default audit log information are not sufficient for a lot of applications.

Hibernate Envers audits what had happened but not who did it. The default revision entity doesn’t store any information of the user who performed the operations. If you also want to store user information, like the username or IP address, you have to use a custom entity to store the revision.

Create a custom revision entity

You just need to implement 2 classes to create and register a custom revision entity: the revision entity and a RevisionListener. Let’s have a look at the revision entity first.

The easiest way to implement your own revision entity is to extend the DefaultRevisionEntity as I do in the following code snippet. If you don’t want to do that, your entity needs to have at least 2 attributes:

  1. a revision number of type int/Integer or long/Long annotated with @RevisionNumber and
  2. a revision timestamp of type long/Long or java.util.Date annotated with @RevisionTimestamp.

In this example, I extend the DefaultRevisionEntity because I just want to store an additional userName attribute for each revision.

@Entity
@RevisionEntity(MyRevisionListener.class)
public class MyRevision extends DefaultRevisionEntity {

	private String userName;

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}
}

As you can see in the code snippet, you also need to annotate your revision entity with @RevisionEntity and provide an implementation of the RevisionListener interface as a parameter. The implementation of the RevisionListener interface is the second class you need to implement. It tells Hibernate Envers how to set the attributes of the revision entity. You can see an example of it in the following code snippet. You just need to implement the newRevision(Object revisionEntity) method which gets the newly instantiated revision entity as a parameter. The only thing you have to do is to set the additional attributes. In this example, I just need to set the userName attribute. The code snippet doesn’t show the code of the getUserName() method because it’s specific to your technology stack. Spring and all Java EE application server provide a way to get the current user. Please check your documentation to learn more about it.

public class MyRevisionListener implements RevisionListener {

	@Override
	public void newRevision(Object revisionEntity) {
		MyRevision rev = (MyRevision) revisionEntity;
		rev.setUserName(getUserName());
	}

	…
}

That’s all you have to do to create and register your custom revision entity. Hibernate will now persist it with all its attributes and you can use them to retrieve data from your audit log.

Use revision data in queries

Hibernate Envers provides a powerful query API that allows you to look at your log from a horizontal or vertical perspective. You can also define a set of expressions to retrieve the revisions or entities you’re looking for. I explained that in more detail in the previous post of this series.

When you create your AuditQuery, you can use the attributes of the revision entity in the same way as any attribute of an audited entity. The following code snippet shows an example in which I select the numbers of all revisions in which a user with userName “User 1” created, updated or deleted a Book entity.

AuditQuery q = auditReader.createQuery().forRevisionsOfEntity(Book.class, false, true);
q.addProjection(AuditEntity.revisionNumber());
q.add(AuditEntity.revisionProperty(“userName”).eq(“User 1”));
List<Number> revisionNumbers = q.getResultList();

Summary

Hibernate Envers’ default settings provide an easy way to create an audit log and to retrieve information from it. Unfortunately, it doesn’t store any information about the user who performed the audited operations.

From a framework point of view, this is the right approach. User authentication isn’t part of Hibernate, and it doesn’t have a generic way to retrieve this information. It can, therefore, also not depend on them.

But it also creates additional work for a lot of applications. In most cases, it’s not enough to document when someone changed which data. You also need to store who performed these operations.

As I showed you in today’s post, you can easily do that with a custom revision entity. You just need to provide your own entity and a RevisionListener to Hibernate Envers. Envers will then use it instead of the default entity, and you can store all the revision information you need.

2 Comments

  1. Avatar photo Lucas de Castro Oliveira says:

    Hello Thorben. You material is very good congratulations 🙂

    After studying a little but more, it has come to my knowledge that you can actually create another schema to store the _AUD tables. So the default application schema/catalog would have the tables currently used by the entities and there would be another schema only to keep track of the changes in the AUD table. I am doubt though which approach is considered a best practice: Leave the AUD tables to the same schema or create a separate schema for auditing data. What’s your opinion on this matter ? Thanks in advance!

    1. Avatar photo Thorben Janssen says:

      Hi Lucas,

      Both options are fine, and there doesn’t seem to be a well-established best practice.
      Personally, I like to separate the audit tables into a different schema.

      Regards,
      Thorben

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.