我一直在寻找一种方法让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"/>
我的一些equals()/hascode()
方法仅测试ID(主键),但我没有找到任何与此相关的主题。
我还看到有一个新参数可以查看哪个字段已更改,但我也不认为这与我的问题有关。
我使用Postgresql,如果这很重要。
对此行为的任何想法?我目前唯一的解决方案是让实体通过entityManager
并进行比较(如果涉及到这一点,我会使用一些基于反射的API)。
答案 0 :(得分:4)
问题不是来自应用程序,而是来自代码本身。我们的entites有一个字段“lastUpdateDate”,它在每个merge()的当前日期设置。比较在合并之后由envers完成,因此该字段自上次修订以来已发生变化。
对于那些好奇的人,版本之间的更改将在org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper.map()
中评估(至少在4.3.5.Final上),如果oldState
和{{1}之间有任何更改,则会返回true }。它根据所比较的属性使用特定的映射器。
我在The official documentation和various 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
我们将实现PersistEventListener
和MergeEventListener
的类替换为扩展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 字段截断为秒来解决这个问题。