Hibernate会在没有变化的情况下创建记录

时间:2016-03-21 15:10:18

标签: java hibernate-envers

我一直在寻找一种方法让envers不记录我自上次记录以来没有修改时合并的任何实体。 我发现这应该是Envers'正常行为(如果没有修改则不进行审核)。

实体只有@Audited注释,但即使自上次审核后没有任何更改,它们仍会继续审核。 这是我的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"/>

我找到了Hibernate Envers: Auditing an object, calling merge on it, gives an audit record EVERY time even with no change?,但没有答案。

我的一些equals()/hascode()方法仅测试ID(主键),但我没有找到任何与此相关的主题。

我还看到有一个新参数可以查看哪个字段已更改,但我也不认为这与我的问题有关。

我使用Postgresql,如果这很重要。

对此行为的任何想法?我目前唯一的解决方案是让实体通过entityManager并进行比较(如果涉及到这一点,我会使用一些基于反射的API)。

3 个答案:

答案 0 :(得分:4)

问题不是来自应用程序,而是来自代码本身。我们的entites有一个字段“lastUpdateDate”,它在每个merge()的当前日期设置。比较在合并之后由envers完成,因此该字段自上次修订以来已发生变化。

对于那些好奇的人,版本之间的更改将在org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper.map()中评估(至少在4.3.5.Final上),如果oldState和{{1}之间有任何更改,则会返回true }。它根据所比较的属性使用特定的映射器。

编辑:我会在这里解释我是如何解决问题的,但也可以使用Dagmar的解决方案。然而,我可能会有点棘手和肮脏。

我在The official documentationvarious SO answers中使用了Envers的newState作为描述:我创建了我的并强迫Envers使用它。

EnversPostUpdateEventListenerImpl

我的@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); } 刚刚检查过它是否是更新日期属性(checkProperty(String propertyName),因为它们在我们的应用中就是这样)。诀窍是,我将旧状态设置为新状态,因此,如果这是我实体中唯一的修改字段,则不会对其进行审核(使用envers持久化)。但是,如果还有其他字段需要修改,Envers将使用这些修改后的字段和右边的lastUpdateDate审核实体。

我也有一个问题,其中oldState是时间hh:mm:ss未设置(仅为零),新状态是设置小时数的同一天。所以我使用了类似的技巧:

propertyName.endsWith("lastUpdateDate")

(注意:你必须重新实现所有事件监听器,即使他们将继承Envers类,也没有转变。确保org.hibernate.integrator.spi.Integrator在你的应用程序中)

答案 1 :(得分:1)

好消息是Hibernate Envers按预期工作 - 除非修改了可审计属性,否则不会创建版本(AUD表中的条目)。

但是,在我们的应用程序中,我们已经实现了MergeEventListener,它正在更新每个实体保存的跟踪字段(lastUpdated,lastUpdatedBy)。即使没有对实体进行任何更改,这也会导致Envers创建新版本。

最终解决方案非常简单(对我们而言) - 使用如何使用来自Hibernate的拦截器和事件的示例:http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/events.html

我们将实现PersistEventListenerMergeEventListener的类替换为扩展EmptyInterceptor的类,并覆盖onFlushDirty和onSave方法。

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);
        }
      }
    }
  }
}

将EntitySaveInterceptor连接到Hibernate JPA持久性单元

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

为了完整起见,这里是ModificationTracker接口:

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);
}

还应该可以通过使用PreUpdateEventListener的实现来设置ModificationTracker值来解决此问题,因为只有在对象变脏时才会触发该侦听器。

答案 2 :(得分:0)

我也遇到过类似的情况。

我发现审计表中重复行的原因是在审计实体中使用了 LocalDateTime 字段。

LocalDateTime 字段被持久化到 MySQL 数据库中的 DATETIME 字段。问题是 DATETIME 字段的精度为 1 秒,而 LocalDateTime 的精度要高得多,因此当 Envers 将数据库中的数据与对象进行比较时,它会发现差异,甚至 LocalDateTime 字段也没有更改。

我通过将 LocalDateTime 字段截断为秒来解决这个问题。