Hibernate envers creating a record when no changes

与世无争的帅哥 提交于 2021-02-07 10:20:03

问题


I've been searching a way to make envers not recording any entity that I merged when there were no modification since last record. I turns out that this should be Envers' normal behavior (no audit if there are no modifications).

Entities only have the @Audited annotations, but they keep being audited even when there is no change since last audit. This is the configuration of my persitence.xml:

<property name="org.hibernate.envers.revision_field_name" value="revision" />
<property name="org.hibernate.envers.revision_type_field_name" value="revision_type" />
<property name="org.hibernate.envers.revision_on_collection_change" value="false"/>
<property name="org.hibernate.envers.store_data_at_delete" value="true"/>

I have found this Hibernate Envers: Auditing an object, calling merge on it, gives an audit record EVERY time even with no change? but there is no answer.

Some of my equals()/hascode() methods are only testing for IDs (the primary keys), but I didn't find out any topic on how this could be related.

I'v also seen taht there is a new parameter to see which field changed, but I don't think that's related to my problem too.

I'm using Postgresql, if that matters.

Any ideas for this behavior ? The only solution I have for the moment is to get the entity through the entityManager and compare them (I'll use some reflection based API if it comes to this).


回答1:


The problem wasn't from the application, but from the code itself. Our entites has a field "lastUpdateDate", which was set at the current date on every merge(). The comparaison is done by envers after the merge, so this field has change since last revision.

For those who are curious, changes between versions are evaluated in org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper.map() (at least on evers 4.3.5.Final) which returns true if there are any changes between oldState and newState. It uses a specific mapper depending on the property compared.


EDIT: I'll put here how I solved the problem, but Dagmar's solution can also be used. Mine might be a little bit trickier and dirtier however.

I used Envers's EnversPostUpdateEventListenerImpl as describerd in The official documentation and various SO answers: I created mine and forced Envers to use it.

@Override
public void onPostUpdate(PostUpdateEvent event) {
    //Maybe you should try catch that !
    if ( event.getOldState() != null ) {
        final EntityPersister entityPersister = event.getPersister();
        final String[] propertiesNames = entityPersister.getPropertyNames();

        for ( int i = 0; i < propertiesNames.length; ++i ) {
            String propertyName = propertiesNames[i];
            if(checkProperty(propertyName){
                event.getOldState()[i] = event.getState()[i];
        }
    }
    // Normal Envers processing
    super.onPostUpdate(event);
}

My checkProperty(String propertyName) just checked if it was an update date property (propertyName.endsWith("lastUpdateDate") because that's how they are in our app). The trick is, I set the old state to the new state so if that's the only modified field in my entity, it won't audit it (persist it with envers). But if there are other fields which where modified, Envers will audit the entity with those modified fields and with the right lastUpdateDate.

I also had a problem where oldState was time with hh:mm:ss not set (only zero's) and the new state was the same day with hours set. So I used a similar trick:

Date oldDtEffet = (Date) event.getOldState()[i];
Date newDtEffet = (Date) event.getState()[i];
if(oldDtEffet != null && newDtEffet != null &&
        DateUtils.isDateEqualsWithoutTime(oldDtEffet,newDtEffet)){
    event.getOldState()[i] = event.getState()[i];
}

(Note: you must reimplement ALL event listeners, even though they will juste inherit Envers classes, there's no turnaround. Be sure that the org.hibernate.integrator.spi.Integrator is in your application)




回答2:


The good news is that Hibernate Envers works as expected - versions (entries into the AUD tables) are not created unless an auditable property is modified.

However, in our application we had implemented a MergeEventListener which was updating tracking fields (lastUpdated, lastUpdatedBy) on every entity save. This caused Envers to make a new version even when there were no changes to the entity.

The solution was quite simple in the end (for us) - using an example of how to use Interceptors and Events from Hibernate: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/events.html

We replaced our class implementing PersistEventListener and MergeEventListener with a class that extends EmptyInterceptor and overrides the onFlushDirty and onSave methods.

public class EntitySaveInterceptor extends EmptyInterceptor {

  @Override
  public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
    setModificationTrackerProperties(entity);
    return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
  }

  @Override
  public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
    setModificationTrackerProperties(entity);
    return super.onSave(entity, id, state, propertyNames, types);
  }

  private void setModificationTrackerProperties(Object object) {
    if (SecurityContextHolder.getContext() != null && SecurityContextHolder.getContext().getAuthentication() != null) {
      Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
      if (principal != null && principal instanceof MyApplicationUserDetails) {
        User user = ((MyApplicationUserDetails) principal).getUser();
        if (object instanceof ModificationTracker && user != null) {
          ModificationTracker entity = (ModificationTracker) object;
          Date currentDateTime = new Date();
          if (entity.getCreatedDate() == null) {
            entity.setCreatedDate(currentDateTime);
          }
          if (entity.getCreatedBy() == null) {
            entity.setCreatedBy(user);
          }
          entity.setLastUpdated(currentDateTime);
          entity.setLastUpdatedBy(user);
        }
      }
    }
  }
}

Hooking up the EntitySaveInterceptor to the Hibernate JPA persistence unit

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

    <persistence-unit name="myapplication" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <property name="hibernate.ejb.interceptor" value="org.myapplication.interceptor.EntitySaveInterceptor" />           
            <property name="hibernate.hbm2ddl.auto" value="none"/>
            <property name="hibernate.show_sql" value="false"/>
        </properties>
    </persistence-unit>

</persistence>

And for completeness, here is the ModificationTracker interface:

public interface ModificationTracker {

  public Date getLastUpdated();

  public Date getCreatedDate();

  public User getCreatedBy();

  public User getLastUpdatedBy();

  public void setLastUpdated(Date lastUpdated);

  public void setCreatedDate(Date createdDate);

  public void setCreatedBy(User createdBy);

  public void setLastUpdatedBy(User lastUpdatedBy);
}

It should also be possible to solve this problem by using an implementation of PreUpdateEventListener to set the ModificationTracker values because that listener is also only fired when the object is dirty.




回答3:


I had similar situation.

I found out that the reason for duplicate rows in audit tables was usage of LocalDateTime field in the audited entity.

LocalDateTime field is persisted to DATETIME field in MySQL database. The problem was that DATETIME field has precision of 1 second, while LocalDateTime has much higher precision, so when Envers compares the data from the database to the object it sees the difference, even the LocalDateTime field hasn't been changed.

I solved this by truncating LocalDateTime field to seconds.



来源:https://stackoverflow.com/questions/36134758/hibernate-envers-creating-a-record-when-no-changes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!