{{announcement.body}}
{{announcement.title}}

Hibernate Envers: Custom Revision Entity (Part 2)

DZone 's Guide to

Hibernate Envers: Custom Revision Entity (Part 2)

The main goal of this article is to introduce a custom revision entity with Hibernate Envers.

· Database Zone ·
Free Resource

Introduction

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:

Image title

You can clone the project from my git repo and run Application.java

Topics:
hibernate envers ,tutorial ,hibernate ,hibernate 5 ,auditing ,database

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}