【问题标题】:Populate envers revision tables with existing data from Hibernate Entities使用来自 Hibernate 实体的现有数据填充 envers 修订表
【发布时间】:2010-10-28 06:21:46
【问题描述】:

我正在向现有的休眠实体添加环境。就审计而言,一切工作顺利,但是查询是一个不同的问题,因为修订表没有填充现有数据。有没有其他人已经解决了这个问题?也许您已经找到了一些方法来使用现有表填充修订表?只是想我会问,我相信其他人会发现它有用。

【问题讨论】:

  • 你是如何让审计工作的?我什至不能走那么远:(
  • 真的很简单,只要阅读相当简短的手册:jboss.org/files/envers/docs/index.html
  • 我想知道这一点,但我需要有关环境的审计信息。哪个用户以及他们“如何”进行了更改——他们正在执行的哪个高级用户操作触发了更改。这对于能够查看显式更改与“副作用”更改非常重要。你知道 envers 是否能满足这种需求吗?

标签: java hibernate hibernate-envers


【解决方案1】:

看看http://www.jboss.org/files/envers/docs/index.html#revisionlog

基本上,您可以使用@RevisionEntity 注释定义自己的“修订类型”, 然后实现一个 RevisionListener 接口来插入您的附加审计数据, 像当前用户和高级操作。通常这些是从 ThreadLocal 上下文中提取的。

【讨论】:

    【解决方案2】:

    你不需要。
    AuditQuery 允许您通过以下方式获取 RevisionEntity 和数据修订:

    AuditQuery query = getAuditReader().createQuery()
                    .forRevisionsOfEntity(YourAuditedEntity.class, false, false);
    

    这将构造一个返回对象 [3] 列表的查询。第一个元素是您的数据,第二个是修订实体,第三个是修订类型。

    【讨论】:

    • 您能进一步扩展吗?您是说如果修订实体为空,那么您应该只使用 AuditQuery 返回的数组的第一个元素?
    【解决方案3】:

    我们通过运行一系列原始 SQL 查询来填充初始数据,以模拟“插入”所有现有实体,就好像它们刚刚同时创建一样。例如:

    insert into REVINFO(REV,REVTSTMP) values (1,1322687394907); 
    -- this is the initial revision, with an arbitrary timestamp
    
    insert into item_AUD(REV,REVTYPE,id,col1,col1) select 1,0,id,col1,col2 from item; 
    -- this copies the relevant row data from the entity table to the audit table
    

    请注意,REVTYPE 值为 0,表示插入(而不是修改)。

    【讨论】:

    • 这种方法对我来说可以解决javax.persistence.EntityNotFoundException: Unable to find <class> with id x 异常。特别是当正确加载历史审计SO:5261139 的子数据时,对“静态”数据的引用(在 Hibernate 之外加载)会导致此异常。 (目前是休眠 4.2)。
    【解决方案4】:

    我们已经解决了使用现有数据填充审计日志的问题,如下所示:

    SessionFactory defaultSessionFactory;
    
    // special configured sessionfactory with envers audit listener + an interceptor 
    // which flags all properties as dirty, even if they are not.
    SessionFactory replicationSessionFactory;
    
    // Entities must be retrieved with a different session factory, otherwise the 
    // auditing tables are not updated. ( this might be because I did something 
    // wrong, I don't know, but I know it works if you do it as described above. Feel
    // free to improve )
    
    FooDao fooDao = new FooDao();
    fooDao.setSessionFactory( defaultSessionFactory );
    List<Foo> all = fooDao.findAll();
    
    // cleanup and close connection for fooDao here.
    ..
    
    // Obtain a session from the replicationSessionFactory here eg.
    Session session = replicationSessionFactory.getCurrentSession();
    
    // replicate all data, overwrite data if en entry for that id already exists
    // the trick is to let both session factories point to the SAME database.
    // By updating the data in the existing db, the audit listener gets triggered,
    // and inserts your "initial" data in the audit tables.
    for( Foo foo: all ) {
        session.replicate( foo, ReplicationMode.OVERWRITE ); 
    }     
    

    我的数据源的配置(通过 Spring):

    <bean id="replicationDataSource" 
          class="org.apache.commons.dbcp.BasicDataSource" 
          destroy-method="close">
      <property name="driverClassName" value="org.postgresql.Driver"/>
      <property name="url" value=".."/>
      <property name="username" value=".."/>
      <property name="password" value=".."/>
      <aop:scoped-proxy proxy-target-class="true"/>
    </bean>
    
    <bean id="auditEventListener" 
          class="org.hibernate.envers.event.AuditEventListener"/>
    
    <bean id="replicationSessionFactory"
          class="o.s.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    
      <property name="entityInterceptor">
        <bean class="com.foo.DirtyCheckByPassInterceptor"/>
      </property>
    
      <property name="dataSource" ref="replicationDataSource"/>
      <property name="packagesToScan">
        <list>
          <value>com.foo.**</value>
        </list>
      </property>
    
      <property name="hibernateProperties">
        <props>
          ..
          <prop key="org.hibernate.envers.audit_table_prefix">AUDIT_</prop>
          <prop key="org.hibernate.envers.audit_table_suffix"></prop>
        </props>
      </property>
      <property name="eventListeners">
        <map>
          <entry key="post-insert" value-ref="auditEventListener"/>
          <entry key="post-update" value-ref="auditEventListener"/>
          <entry key="post-delete" value-ref="auditEventListener"/>
          <entry key="pre-collection-update" value-ref="auditEventListener"/>
          <entry key="pre-collection-remove" value-ref="auditEventListener"/>
          <entry key="post-collection-recreate" value-ref="auditEventListener"/>
        </map>
      </property>
    </bean>
    

    拦截器:

    import org.hibernate.EmptyInterceptor;
    import org.hibernate.type.Type;
    ..
    
    public class DirtyCheckByPassInterceptor extends EmptyInterceptor {
    
      public DirtyCheckByPassInterceptor() {
        super();
      }
    
    
      /**
       * Flags ALL properties as dirty, even if nothing has changed. 
       */
      @Override
      public int[] findDirty( Object entity,
                          Serializable id,
                          Object[] currentState,
                          Object[] previousState,
                          String[] propertyNames,
                          Type[] types ) {
        int[] result = new int[ propertyNames.length ];
        for ( int i = 0; i < propertyNames.length; i++ ) {
          result[ i ] = i;
        }
        return result;
      }
    }
    

    ps:请记住,这是一个简化的示例。它不会开箱即用,但会引导您找到可行的解决方案。

    【讨论】:

      【解决方案5】:

      如果您使用 Envers ValidityAuditStrategy 并且创建的数据不是在启用 Envers 的情况下创建的,那么您将在此类别中遇到问题。

      在我们的例子中(Hibernate 4.2.8.Final),基本对象更新抛出“无法更新实体的先前修订版和”(记录为 [org.hibernate.AssertionFailure] HHH000099)。

      我花了一段时间才找到这个讨论/解释,所以交叉发布:

      ValidityAuditStrategy with no audit record

      【讨论】:

        【解决方案6】:

        您可以使用 find 方法的后备选项扩展 AuditReaderImpl,例如:

            public class AuditReaderWithFallback extends AuditReaderImpl {
        
            public AuditReaderWithFallback(
                    EnversService enversService,
                    Session session,
                    SessionImplementor sessionImplementor) {
                super(enversService, session, sessionImplementor);
            }
        
            @Override
            @SuppressWarnings({"unchecked"})
            public <T> T find(
                    Class<T> cls,
                    String entityName,
                    Object primaryKey,
                    Number revision,
                    boolean includeDeletions) throws IllegalArgumentException, NotAuditedException, IllegalStateException {
                T result = super.find(cls, entityName, primaryKey, revision, includeDeletions);
                if (result == null)
                    result = (T) super.getSession().get(entityName, (Serializable) primaryKey);
                return result;
            }
        }
        

        在某些情况下,您可以添加更多检查以返回 null。 您可能还想使用自己的工厂:

            public class AuditReaderFactoryWithFallback {
        
        
            /**
             * Create an audit reader associated with an open session.
             *
             * @param session An open session.
             * @return An audit reader associated with the given sesison. It shouldn't be used
             * after the session is closed.
             * @throws AuditException When the given required listeners aren't installed.
             */
            public static AuditReader get(Session session) throws AuditException {
                SessionImplementor sessionImpl;
                if (!(session instanceof SessionImplementor)) {
                    sessionImpl = (SessionImplementor) session.getSessionFactory().getCurrentSession();
                } else {
                    sessionImpl = (SessionImplementor) session;
                }
        
                final ServiceRegistry serviceRegistry = sessionImpl.getFactory().getServiceRegistry();
                final EnversService enversService = serviceRegistry.getService(EnversService.class);
        
                return new AuditReaderWithFallback(enversService, session, sessionImpl);
            }
        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-04-06
          • 2017-11-28
          • 2019-08-20
          • 2012-01-04
          • 2020-03-09
          • 1970-01-01
          • 2018-04-16
          • 1970-01-01
          相关资源
          最近更新 更多