Hibernate envers 在没有更改时创建记录
Hibernate envers creating a record when no changes
我一直在寻找一种方法,让 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)。
问题不是来自应用程序,而是来自代码本身。我们的实体有一个字段 "lastUpdateDate",它在每个 merge() 的当前日期设置。合并后由 envers 进行比较,因此此字段自上次修订以来已发生变化。
对于那些好奇的人,版本之间的变化在 org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper.map()
中评估(至少在 evers 4.3.5.Final 中)returns 如果 [=] 之间有任何变化则为真13=] 和 newState
。它使用特定的映射器,具体取决于 属性 比较。
编辑:我将在此处说明我是如何解决问题的,但也可以使用 Dagmar 的解决方案。然而,我的可能有点棘手和肮脏。
我按照 The official documentation and various SO answers 中的描述使用了 Envers 的 EnversPostUpdateEventListenerImpl
:我创建了我的并强迫 Envers 使用它。
@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)
刚刚检查了它是否是更新日期 属性(propertyName.endsWith("lastUpdateDate")
因为它们在我们的应用程序中就是这样)。诀窍是,我将旧状态设置为新状态,所以如果这是我实体中唯一修改的字段,它不会审核它(用 envers 保存它)。但是,如果有其他字段被修改,Envers 将使用这些修改的字段和正确的 lastUpdateDate 审计实体。
我也遇到了一个问题,oldState 是未设置 hh:mm:ss 的时间(只有零),而新状态是同一天设置的时间。所以我使用了类似的技巧:
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];
}
(注意:您必须重新实现所有事件侦听器,即使它们只是继承 Envers 类,没有转机。请确保 org.hibernate.integrator.spi.Integrator 在您的应用程序中)
好消息是 Hibernate Envers 按预期工作 - 除非修改可审核的 属性,否则不会创建版本(AUD 表中的条目)。
但是,在我们的应用程序中,我们实现了一个 MergeEventListener
,它在每个实体保存时更新跟踪字段(lastUpdated、lastUpdatedBy)。这导致 Envers 制作了一个新版本,即使实体没有任何变化。
最终解决方案非常简单(对我们而言)- 使用一个示例说明如何使用 Hibernate 中的拦截器和事件:http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/events.html
我们将 class 实现 PersistEventListener
和 MergeEventListener
替换为扩展 EmptyInterceptor
并覆盖 onFlushDirty 和 onSave 方法的 class。
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
值来解决这个问题,因为该侦听器也仅在对象变脏时才会触发。
我也遇到过类似的情况。
我发现审计表中出现重复行的原因是在审计实体中使用了 LocalDateTime 字段。
LocalDateTime 字段保存到 MySQL 数据库中的 DATETIME 字段。问题在于 DATETIME 字段的精度为 1 秒,而 LocalDateTime 的精度要高得多,因此当 Envers 将数据库中的数据与对象进行比较时,它会看到差异,甚至 LocalDateTime 字段也没有更改。
我通过将 LocalDateTime 字段截断为秒来解决这个问题。
我一直在寻找一种方法,让 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)。
问题不是来自应用程序,而是来自代码本身。我们的实体有一个字段 "lastUpdateDate",它在每个 merge() 的当前日期设置。合并后由 envers 进行比较,因此此字段自上次修订以来已发生变化。
对于那些好奇的人,版本之间的变化在 org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper.map()
中评估(至少在 evers 4.3.5.Final 中)returns 如果 [=] 之间有任何变化则为真13=] 和 newState
。它使用特定的映射器,具体取决于 属性 比较。
编辑:我将在此处说明我是如何解决问题的,但也可以使用 Dagmar 的解决方案。然而,我的可能有点棘手和肮脏。
我按照 The official documentation and various SO answers 中的描述使用了 Envers 的 EnversPostUpdateEventListenerImpl
:我创建了我的并强迫 Envers 使用它。
@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)
刚刚检查了它是否是更新日期 属性(propertyName.endsWith("lastUpdateDate")
因为它们在我们的应用程序中就是这样)。诀窍是,我将旧状态设置为新状态,所以如果这是我实体中唯一修改的字段,它不会审核它(用 envers 保存它)。但是,如果有其他字段被修改,Envers 将使用这些修改的字段和正确的 lastUpdateDate 审计实体。
我也遇到了一个问题,oldState 是未设置 hh:mm:ss 的时间(只有零),而新状态是同一天设置的时间。所以我使用了类似的技巧:
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];
}
(注意:您必须重新实现所有事件侦听器,即使它们只是继承 Envers 类,没有转机。请确保 org.hibernate.integrator.spi.Integrator 在您的应用程序中)
好消息是 Hibernate Envers 按预期工作 - 除非修改可审核的 属性,否则不会创建版本(AUD 表中的条目)。
但是,在我们的应用程序中,我们实现了一个 MergeEventListener
,它在每个实体保存时更新跟踪字段(lastUpdated、lastUpdatedBy)。这导致 Envers 制作了一个新版本,即使实体没有任何变化。
最终解决方案非常简单(对我们而言)- 使用一个示例说明如何使用 Hibernate 中的拦截器和事件:http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/events.html
我们将 class 实现 PersistEventListener
和 MergeEventListener
替换为扩展 EmptyInterceptor
并覆盖 onFlushDirty 和 onSave 方法的 class。
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
值来解决这个问题,因为该侦听器也仅在对象变脏时才会触发。
我也遇到过类似的情况。
我发现审计表中出现重复行的原因是在审计实体中使用了 LocalDateTime 字段。
LocalDateTime 字段保存到 MySQL 数据库中的 DATETIME 字段。问题在于 DATETIME 字段的精度为 1 秒,而 LocalDateTime 的精度要高得多,因此当 Envers 将数据库中的数据与对象进行比较时,它会看到差异,甚至 LocalDateTime 字段也没有更改。
我通过将 LocalDateTime 字段截断为秒来解决这个问题。