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?
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.