2

这是我的用例

我有两个实体:Personn 和 Email(@OneToMany 关系)。两者都经过审核。

首先我创建一个新的 Personn,带有一个电子邮件(=> 他们都有一个修订版 1),然后我修改了电子邮件(=> 电子邮件有一个修订版 2,但 Personn 只有一个修订版 1)

在 Web 应用程序中,最终用户只有一个视图来显示人员的属性以及他的电子邮件属性。在此视图中,我想显示此 Personn 的所有现有修订。但是当我查询审计系统时,它没有显示修订版 2,因为 Personn 没有被修改。

我理解技术问题,但从最终用户的角度来看,他希望看到修订版 2,因为他修改了人员的电子邮件!他不知道(也不必知道)我们决定将这些信息分成 2 个 Java 对象。当然,这个问题不仅适用于 Personn-Email 关系(我在 Personn 和其他显示在同一视图中的对象之间有很多关系 - Adress、Job、Site、Card 等等)

我想到了2个解决方案:

1-查询所有关系以了解是否存在修订(但我想它会生成一个大请求或多个请求 - 我有很多关系)。

2- 将“hibernate.listeners.envers.autoRegister”设置为 false,编写我自己的 EnversIntegrator 和事件实现。在事件实现中(覆盖默认的 Envers 实现),当电子邮件的属性被修改时,我将为 Personn 创建一个 ModWorkUnit(当然它不会被硬编码:在 personn 字段上添加一个自定义注释,如 @AuditedPropagation)。该解决方案的缺陷是即使没有修改,也要为 Personn 创建很多行。

您如何看待这些解决方案?您知道解决这种用例的更好方法吗?

感谢您的建议。

4

2 回答 2

0

我尝试实施第二个解决方案:

  1. 首先我的集成器添加了一个新的更新后监听器(RevisionOnCollectionPostUpdateEventListenerImpl)

    public class RevisionOnCollectionUpdateIntegrator implements Integrator {
    private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, RevisionOnCollectionUpdateIntegrator.class.getName());
    
    public static final String REGISTER_ON_UPDATE = "org.hibernate.envers.revision_on_collection_update";
    
    @Override
    public void integrate(Configuration configuration, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    
        final boolean autoRegister = ConfigurationHelper.getBoolean(REGISTER_ON_UPDATE, configuration.getProperties(), true);
        if (!autoRegister) {
            LOG.debug("Skipping 'revision_on_collection_update' listener auto registration");
            return;
        }
    
        EventListenerRegistry listenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
        listenerRegistry.addDuplicationStrategy(EnversListenerDuplicationStrategy.INSTANCE);
    
        final AuditConfiguration enversConfiguration = AuditConfiguration.getFor(configuration, serviceRegistry.getService(ClassLoaderService.class));
        if (enversConfiguration.getEntCfg().hasAuditedEntities()) {
            listenerRegistry.appendListeners(EventType.POST_UPDATE, new RevisionOnCollectionPostUpdateEventListenerImpl(enversConfiguration));
        }
    }
    
  2. 然后是更新后的监听器(扩展):

    public class RevisionOnCollectionPostUpdateEventListenerImpl extends EnversPostUpdateEventListenerImpl {
    protected final void generateBidirectionalWorkUnits(AuditProcess auditProcess, EntityPersister entityPersister, String entityName, Object[] newState,
            Object[] oldState, SessionImplementor session) {
        // Checking if this is enabled in configuration ...
        if (!getAuditConfiguration().getGlobalCfg().isGenerateRevisionsForCollections()) {
            return;
        }
    
        // Checks every property of the entity, if it is an "owned" to-one relation to another entity.
        // If the value of that property changed, and the relation is bi-directional, a new revision
        // for the related entity is generated.
        String[] propertyNames = entityPersister.getPropertyNames();
    
        for (int i = 0; i < propertyNames.length; i++) {
            String propertyName = propertyNames[i];
            RelationDescription relDesc = getAuditConfiguration().getEntCfg().getRelationDescription(entityName, propertyName);
            if (relDesc != null && relDesc.isBidirectional() && relDesc.getRelationType() == RelationType.TO_ONE && relDesc.isInsertable()) {
                // Checking for changes
                Object oldValue = oldState == null ? null : oldState[i];
                Object newValue = newState == null ? null : newState[i];
    
                        // Here is the magic part !!!!!!!!!
                        // The super class verify if old and new value (of the owner value) are equals or not
                        // If different (add or delete) then an audit entry is also added for the owned entity
                        // When commented, an audit row for the owned entity is added when a related entity is updated
            //  if (!Tools.entitiesEqual(session, relDesc.getToEntityName(), oldValue, newValue)) {
                    // We have to generate changes both in the old collection (size decreses) and new collection
                    // (size increases).
                    if (newValue != null) {
                        addCollectionChangeWorkUnit(auditProcess, session, entityName, relDesc, newValue);
                    }
    
                    if (oldValue != null) {
                        addCollectionChangeWorkUnit(auditProcess, session, entityName, relDesc, oldValue);
                    }
            //  }
            }
        }
    }
    

它似乎有效,但我必须进行更多测试。

于 2012-05-29T15:52:25.197 回答
0

我无法使自定义更新后侦听器解决方案工作。addCollectionChangeWorkUnit 似乎直到休眠 4.1 才存在,它被标记为私有。EnversPostUpdateEventListenerImpl 似乎出现在休眠 4.0 的某个时刻

我通过在相当于您的 A 实体上添加隐藏的 lastUpdated 日期字段解决了我的问题。

@Entity
public class A {
    private Date lastModified;
    @OneToMany(mappedBy = "a", cascade = CascadeType.ALL )
    private List<B> blist;
    public void touch(){
        lastModified=new Date();
    }
}

在相关实体(如您的 B 字段)中,我添加了以下内容:

public class B {
    @ManyToOne
    private A a; 

    @PreUpdate
    public void ensureParentUpdated(){
        if(a!=null){
            a.touch();
        }
    }
}

这确保了无论何时将修订添加到 B,都会将修订添加到 A,即使它需要许多实体中的自定义代码。

于 2012-06-28T12:01:06.677 回答