使用 Hibernate 实体中的现有数据填充 envers 修订表

发布于 2024-07-22 06:12:58 字数 137 浏览 9 评论 0原文

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

I'm adding envers to an existing hibernate entities. Everything is working smoothly so far as far as auditing, however querying is a different issue because the revision tables aren’t populated with the existing data. Has anyone else already solved this issue? Maybe you’ve found some way to populate the revision tables with the existing table? Just thought I’d ask, I'm sure others would find it useful.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

多像笑话 2024-07-29 06:12:58

我们通过运行一系列原始 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 表示插入(而不是修改)。

We populated the initial data by running a series of raw SQL queries to simulate "inserting" all the existing entities as if they had just been created at the same time. For example:

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

Note that the REVTYPE value is 0 to indicate an insert (as opposed to a modification).

我偏爱纯白色 2024-07-29 06:12:58

如果您使用 Envers ValidityAuditStrategy 并拥有在启用 Envers 的情况下创建的数据。

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

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

没有审计记录的 ValidityAuditStrategy< /a>

You'll have a problem in this category if you are using Envers ValidityAuditStrategy and have data which has been created other than with Envers enabled.

In our case (Hibernate 4.2.8.Final) a basic object update throws "Cannot update previous revision for entity and " (logged as [org.hibernate.AssertionFailure] HHH000099).

Took me a while to find this discussion/explanation so cross-posting:

ValidityAuditStrategy with no audit record

浪漫人生路 2024-07-29 06:12:58

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

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

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

You don't need to.
AuditQuery allows you to get both RevisionEntity and data revision by :

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

This will construct a query which returns a list of Object [3]. Fisrt element is your data, the second is the revision entity and the third is the type of revision.

GRAY°灰色天空 2024-07-29 06:12:58

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

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:请记住,这是一个简化的示例。 它不会开箱即用,但会引导您找到可行的解决方案。

We have solved the issue of populating the audit logs with the existing data as follows:

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

The configuration of my data sources (via 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>

The interceptor:

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: keep in mind that this is a simplified example. It will not work out of the box but it will guide you towards a working solution.

夏天碎花小短裙 2024-07-29 06:12:58

您可以使用 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);
    }

}

You could extend the AuditReaderImpl with a fallback option for the find method, like:

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

You could add a few more checks in terms of returning null in some cases.
You might want to use your own factory as well:

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

}
靑春怀旧 2024-07-29 06:12:58

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

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

Take a look at http://www.jboss.org/files/envers/docs/index.html#revisionlog

Basically you can define your own 'revision type' using @RevisionEntity annotation,
and then implement a RevisionListener interface to insert your additional audit data,
like current user and high level operation. Usually those are pulled from ThreadLocal context.

清风疏影 2024-07-29 06:12:58

我检查了很多方法,但对我来说最好的方法是编写一个 PL/SQL 脚本,如下所示。

下面的脚本是为 PostgreSQL 编写的。 没有检查其他供应商,但他们必须具有相同的功能。

CREATE SEQUENCE hibernate_sequence START 1;

DO
$
    DECLARE
        u       RECORD;
        next_id BIGINT;

    BEGIN
        FOR u IN SELECT * FROM user
            LOOP
                SELECT NEXTVAL('hibernate_sequence')
                INTO next_id;
                INSERT INTO revision (rev, user_id, timestamp)
                VALUES (next_id,
                        '00000000-0000-0000-0000-000000000000',
                        (SELECT EXTRACT(EPOCH FROM NOW() AT TIME ZONE 'utc')) * 1000);
                INSERT INTO user_aud(rev,
                                     revend,
                                     revtype,
                                     id,
                                     created_at,
                                     created_by,
                                     last_modified_at,
                                     last_modified_by,
                                     name)
                VALUES (next_id,
                        NULL,
                        0,
                        f.id,
                        f.created_at,
                        f.created_by,
                        f.last_modified_at,
                        f.last_modified_by,
                        f.name);
            END LOOP;
    END;
$;

I've checked many ways, but the best way for me is to write a PL/SQL script as below.

The below script is written for PostgreSQL. Didn't check other vendors, but they must have the same feature.

CREATE SEQUENCE hibernate_sequence START 1;

DO
$
    DECLARE
        u       RECORD;
        next_id BIGINT;

    BEGIN
        FOR u IN SELECT * FROM user
            LOOP
                SELECT NEXTVAL('hibernate_sequence')
                INTO next_id;
                INSERT INTO revision (rev, user_id, timestamp)
                VALUES (next_id,
                        '00000000-0000-0000-0000-000000000000',
                        (SELECT EXTRACT(EPOCH FROM NOW() AT TIME ZONE 'utc')) * 1000);
                INSERT INTO user_aud(rev,
                                     revend,
                                     revtype,
                                     id,
                                     created_at,
                                     created_by,
                                     last_modified_at,
                                     last_modified_by,
                                     name)
                VALUES (next_id,
                        NULL,
                        0,
                        f.id,
                        f.created_at,
                        f.created_by,
                        f.last_modified_at,
                        f.last_modified_by,
                        f.name);
            END LOOP;
    END;
$;

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文