Hibernate Envers: Custom Revision Entity (Part 2)
The main goal of this article is to introduce a custom revision entity with Hibernate Envers.
Join the DZone community and get the full member experience.
Join For FreeIntroduction
In my previous article, I gave an overview of Hibernate Envers for auditing entities. The main goal of this article is to introduce a custom revision entity with Hibernate Envers.
Basic Concept
In my previous article, I wrote about how to implement auditing with Hibernate Envers. We used two entities: Employee and Department. Hibernate Envers internally creates a default revision entity with an id and tmpstmp column/properties. Now, we are going to add another property, refRevisionId
, to this table. That's why we now need another entity, which will be inherited from DefaultRevisionEntity
. And to listen to audit data and to add an entry to a custom revision table, we need a listener, which will be inherited from RevisionListener
.
Implementation
Since we already defined our entities (Employee and Department) in the previous article, we will now define a custom revision entity. As we are using a Maven project, we need to add the Hibernate Envers library to pom.xml.
@Entity
@RevisionEntity(CustomRevisionListener.class)
public class CustomRevisionEntity extends DefaultRevisionEntity {
public long getRefRevisionId() {
return refRevisionId;
}
public void setRefRevisionId(long refRevisionId) {
this.refRevisionId = refRevisionId;
}
long refRevisionId;
}
Here, we annotated this entity with @RevisionEntity
and also configured the listener. We added refRevisionId
as a new property. So when it will audit the actual entity, this extra property will add to the custom revision table. So the listener will be like this:
@Component
public class CustomRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
CustomRevisionEntity customRevisionEntity = (CustomRevisionEntity) revisionEntity;
EntityManagerFactory emf = ContextLookup.getApplicationContext().getBean(EntityManagerFactory.class);
AuditReader auditReader = AuditReaderFactory.get(emf.createEntityManager());
Number resultList = null;
Object obj = ContextLookup.getCurrentEntity();
if( Employee.class.isInstance(obj)) {
AuditQuery query = auditReader.createQuery()
.forRevisionsOfEntity(Department.class, false, true)
.addProjection(AuditEntity.revisionNumber().max());
resultList = (Number) query.getSingleResult();
}
customRevisionEntity.setRefRevisionId(resultList != null?resultList.longValue():0);
}
}
The newRevision()
method will customize the default revision entity with an extra property. ContextLookup class is an Aware class that will keep the application context of the main application. Otherwise, at the listener, we will lose the application context. ContextLookup class looks like this. You don't need to use this class if you don't need to use entitymanager on newRevision()
method in the CustomRevisionListener class.
@Component
public class ContextLookup implements ApplicationContextAware {
private static ApplicationContext sApplicationContext;
public static Object getCurrentEntity() {
return currentEntity;
}
public static void setCurrentEntity(Object currentEntity) {
ContextLookup.currentEntity = currentEntity;
}
private static Object currentEntity;
@Override
public void setApplicationContext( ApplicationContext aApplicationContext )
throws BeansException {
setContext( aApplicationContext );
}
public static void setContext( ApplicationContext aApplicationContext ) {
sApplicationContext = aApplicationContext;
}
protected static ApplicationContext getApplicationContext() {
return sApplicationContext;
}
public static Object getBean( String aName ) {
if ( sApplicationContext != null ) {
return sApplicationContext.getBean( aName );
}
return null;
}
public static <T> T getBean( Class<T> aClass ) {
if ( sApplicationContext != null ) {
return sApplicationContext.getBean( aClass );
}
return null;
}
}
At ContextLookup class, we see a property, currentEntity
, which will keep the current entity object that is being audited. For example, assume that Employee object is auditing, then the current entity will be Employee object. And we can set this object from the audit event listener.
public class CustomEnversPreInsertEventListenerImpl implements PreInsertEventListener {
@Override
public boolean onPreInsert(PreInsertEvent event) {
if(event.getEntity() instanceof Employee){
ContextLookup.setCurrentEntity(new Employee());
}
if(event.getEntity() instanceof Department){
ContextLookup.setCurrentEntity(new Department());
}
System.out.println("On Pre Insert event");
return false;
}
}
We can set it from different events, like during update, delete, or other operations. So when we insert entities, the database tables look like this:
You can clone the project from my git repo and run Application.java.
Opinions expressed by DZone contributors are their own.
Comments