Search code examples
javahibernatejpahibernate-envers

Envers - insert actual value of @NotAudited field


We have an entity with a String property, that is subject to change very frequently, so we used @NotAudited on it. But this causes the column to be null in the audit table (understandably).

We want to insert the actual value of the String property to the audit table, if the entity (one of it's audited columns) change, but don't want to insert a new record into the audit table, if the said String property changes.

How can I do that?


Solution

  • What you describe is what we refer to as conditional auditing, which you can find details on inside the Envers documentation.

    The basic principle is that you'd continue to annotate the property with @Audited but determine whether any other property changed most likely during a PostUpdateEvent and if your specific property is all which changed, you wouldn't delegate the PostUpdateEvent to the Envers default listener implementation.

    Some pseudo code might look something like this:

    public class CustomEnversPostUpdateEventListener 
           extends EnversPostUpdateEventListenerImpl {
        public CustomEnversPostUpdateEventListener(EnversService enversService) {
          super( enversService );
        }
    
        @Override
        public void onPostUpdate(PostUpdateEvent event) {
          final String entityName = event.getPersister().getEntityName();
          if ( getEnversService().getEntityConfigurations().isVersioned( entityName ) ) {
             if ( MySpecialEntity.class.getName().equals( entityName ) ) {
               // Compare event.getState() against event.getOldState()
               // Determine if only your special String changed or not
               if ( !otherFieldsChangedBesidesSpecialProperty ) { 
                 return;
               }
             }
             // delegate to default implementation
             super.onPostUpdate( event );
          }
        }
    }
    

    The approach needed at this time of this write-up requires that you override the listener registration inside EnversIntegrator as well. Extending these listeners, requiring working knowledge of state comparison puts a significant burden on the user instead of offering a pluggable solution.

    A pluggable solution is the goal behind HHH-11326. What I'd like to introduce would be a JPA-like concept where an @AuditListener can be placed on the audited entity class and Envers would instead delegate to the listener implementation for this behavior.

    There is nothing set in stone yet about how this would work, so the following is simply a brain dump of my own personal thoughts on the matter and how it might look and play out code wise:

    public class MyEntityAuditListener extends AbstractAuditListener {
        @Override
        public boolean onPostUpdate(EnversPostUpdateEvent event) {
          // return true = allow the audit operation
          // return false = veto the audit operation
          if ( event.getPropertyChangeCount() != 1 ) {
            return true;
          }
          return !event.isPropertyChanged( "mySpecialString" );       
        }
    }
    
    @Entity
    @Audited
    @AuditListener(MyEntityAuditListener.class)
    public class MyEntity {
      // ...
      private string mySpecialString;
    }
    

    The nice part is that the EnversPostUpdateEvent allows us to abstract a lot of the Envers internals away, exposing a clean API that allows users to make easy decisions about whether or not to veto an audit operation without having to know much about how Hibernate events or the internals of Envers works.

    As the JIRA points out this new @AuditListener concept is something I plan to introduce in the next major release of Hibernate Envers 6.0 in the coming months.