没有审计记录时Envers在ValidityAuditStrategy中抛出RuntimeException(表分区)

Posted

技术标签:

【中文标题】没有审计记录时Envers在ValidityAuditStrategy中抛出RuntimeException(表分区)【英文标题】:Envers throws RuntimeException in ValidityAuditStrategy when no audit records (table partitioning) 【发布时间】:2015-10-07 05:12:32 【问题描述】:

在 envers (persistence.xml) 中,我启用了表分区策略,根据开发指南:http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch15.html#envers-partitioning

类:ValidityAuditStrategy,当没有审计记录时抛出RuntimeException。当 Envers 尝试使用最终修订日期 (revend_tstmp) 更新审核记录但此审核记录不存在时,会发生此异常。

我的应用程序的数据库从外部应用程序接收数据负载,并且无法更改这些外部应用程序以包含其审计记录。

我没有机会处理这个异常(我不知道如何处理)。

在方法ValidityAuditStrategy#updateLastRevision:

if (l.size() == 1) 
  //... doStuff - OK
 else 
  throw new RuntimeException("Cannot find previous revision for entity " + auditedEntityName + " and id " + id);

在方法ValidityAuditStrategy#perform:

if ( rowCount != 1 ) 
  throw new RuntimeException("Cannot update previous revision for entity " + auditedEntityName + " and id " + id);

此链接出现类似问题:https://developer.jboss.org/thread/160195?tstart=0,但没有解决方案。

是否可以应用解决方法?

我使用 hibernate-envers-4.1.3-Final 版本。

日志:

2015-07-17 10:23:28,653 DEBUG [-] [org.hibernate.SQL] (http-/0.0.0.0:8080-5) update MY_ENTITY_AUD set ID_REV_FINAL=?, DATE_HOUR_REV_FINAL=? where ID_ENTITY=? and ID_REV <> ? and ID_REV_FINAL is null
2015-07-17 10:23:28,677 TRACE [-] [org.hibernate.type.descriptor.sql.BasicBinder] (http-/0.0.0.0:8080-5) binding parameter [1] as [INTEGER] - 422
2015-07-17 10:23:28,677 TRACE [-] [org.hibernate.type.descriptor.sql.BasicBinder] (http-/0.0.0.0:8080-5) binding parameter [2] as [TIMESTAMP] - Thu Jul 17 10:23:28 BRT 2015
2015-07-17 10:23:28,677 TRACE [-] [org.hibernate.type.descriptor.sql.BasicBinder] (http-/0.0.0.0:8080-5) binding parameter [3] as [INTEGER] - 12345
2015-07-17 10:23:28,678 TRACE [-] [org.hibernate.type.descriptor.sql.BasicBinder] (http-/0.0.0.0:8080-5) binding parameter [4] as [INTEGER] - 422
2015-07-17 10:23:28,803 ERROR [-] [org.hibernate.AssertionFailure] (http-/0.0.0.0:8080-5) HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): java.lang.RuntimeException: Cannot update previous revision for entity my.package.MyEntity_AUD and id 12345
2015-07-17 10:23:28,841 WARN  [-] [com.arjuna.ats.arjuna] (http-/0.0.0.0:8080-5) ARJUNA012125: TwoPhaseCoordinator.beforeCompletion - failed for SynchronizationImple< 0:ffffac1c045d:-3a5600e4:55a7c120:131, org.hibernate.engine.transaction.synchronization.internal.RegisteredSynchronization@5619c5a3 >: org.hibernate.AssertionFailure: Unable to perform beforeTransactionCompletion callback
    at org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:754) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.engine.spi.ActionQueue.beforeTransactionCompletion(ActionQueue.java:338) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:490) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.engine.transaction.synchronization.internal.SynchronizationCallbackCoordinatorNonTrackingImpl.beforeCompletion(SynchronizationCallbackCoordinatorNonTrackingImpl.java:114) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.engine.transaction.synchronization.internal.RegisteredSynchronization.beforeCompletion(RegisteredSynchronization.java:53) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple.beforeCompletion(SynchronizationImple.java:76)
    at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.beforeCompletion(TwoPhaseCoordinator.java:273)
    at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:93)
    at com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:162)
    at com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1189)
    at com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:126)
    at com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75)
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.endTransaction(CMTTxInterceptor.java:92) [jboss-as-ejb3-7.4.0.Final-redhat-19.jar:7.4.0.Final-redhat-19]
    ...
Caused by: java.lang.RuntimeException: Cannot update previous revision for entity entity my.package.MyEntity_AUD and id 12345
    at org.hibernate.envers.strategy.ValidityAuditStrategy.perform(ValidityAuditStrategy.java:210) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.envers.synchronization.work.AbstractAuditWorkUnit.perform(AbstractAuditWorkUnit.java:76) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.envers.synchronization.AuditProcess.executeInSession(AuditProcess.java:116) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.envers.synchronization.AuditProcess.doBeforeTransactionCompletion(AuditProcess.java:155) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.envers.synchronization.AuditProcessManager$1.doBeforeTransactionCompletion(AuditProcessManager.java:62) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    at org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:748) [hibernate-core-4.1.3-Final.jar:4.1.3-Final]
    ... 90 more

【问题讨论】:

【参考方案1】:

persistence.xml现有的一个属性中指定一个自定义AuditStrategeyorg.hibernate.envers.audit_strategy。 改变, 来自:

<property name="org.hibernate.envers.audit_strategy" value="org.hibernate.envers.strategy.ValidityAuditStrategy"/>  

收件人:

<property name="org.hibernate.envers.audit_strategy" value="com.app.your.pack.YourCustomValidityAuditStrategy"/>

因此,现在您可以扩展 ValidityAuditStrategy 并覆盖 perform() 以在没有先前版本的实体时不抛出 RuntimeException,如下所示:

public class YourCustomValidityAuditStrategy extends ValidityAuditStrategy 

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    public void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data, Object revision) 
        try 
            super.perform(session, entityName, auditCfg, id, data, revision);           
         catch (RuntimeException re) 
            if (logger.isDebugEnabled()) 
                logger.debug("IGNORE RuntimeException: Cannot update previous revision for entity.", re);
            
        
    

【讨论】:

这个不行,回调中抛出异常,你得复制粘贴整个类,把抛出异常改成log什么的。【参考方案2】:

仅重写 perform 方法并捕获 RuntimeException 对您没有帮助,因为引发 RuntimeException 的代码包含在 BeforeTransactionCompletionProcess 类型的匿名类中并稍后执行。

ValidityAuditStrategy 不是很灵活,所以我看到的唯一解决方案是丑陋但应该可以工作:您必须将整个 ValidityAuditStrategy 代码复制到自定义类中,并在 BeforeTransactionCompletionProcess 匿名类中捕获 RuntimeException。然后在 persistence.xml 中指定您的自定义类:

<property name="org.hibernate.envers.audit_strategy "value="com.app.xxx.CustomValidityAuditStrategy"/>

perform() 方法应如下所示:

@Override
public void perform(
        final Session session,
        final String entityName,
        final EnversService enversService,
        final Serializable id,
        final Object data,
        final Object revision) 
    final AuditEntitiesConfiguration audEntitiesCfg = enversService.getAuditEntitiesConfiguration();
    final String auditedEntityName = audEntitiesCfg.getAuditEntityName( entityName );
    final String revisionInfoEntityName = enversService.getAuditEntitiesConfiguration().getRevisionInfoEntityName();

    // Save the audit data
    session.save( auditedEntityName, data );

    // Update the end date of the previous row.
    //
    // When application reuses identifiers of previously removed entities:
    // The UPDATE statement will no-op if an entity with a given identifier has been
    // inserted for the first time. But in case a deleted primary key value was
    // reused, this guarantees correct strategy behavior: exactly one row with
    // null end date exists for each identifier.
    final boolean reuseEntityIdentifier = enversService.getGlobalConfiguration().isAllowIdentifierReuse();
    if ( reuseEntityIdentifier || getRevisionType( enversService, data ) != RevisionType.ADD ) 
        // Register transaction completion process to guarantee execution of UPDATE statement after INSERT.
        ( (EventSource) session ).getActionQueue().registerProcess( new BeforeTransactionCompletionProcess() 
            @Override
            public void doBeforeTransactionCompletion(final SessionImplementor sessionImplementor) 
                final Queryable productionEntityQueryable = getQueryable( entityName, sessionImplementor );
                final Queryable rootProductionEntityQueryable = getQueryable(
                        productionEntityQueryable.getRootEntityName(), sessionImplementor
                );
                final Queryable auditedEntityQueryable = getQueryable( auditedEntityName, sessionImplementor );
                final Queryable rootAuditedEntityQueryable = getQueryable(
                        auditedEntityQueryable.getRootEntityName(), sessionImplementor
                );

                final String updateTableName;

                /*commented code*/
                ...

                /*comment the following piece of code*/
                /*if ( rowCount != 1 && ( !reuseEntityIdentifier || ( getRevisionType( enversService, data ) != RevisionType.ADD ) ) ) 
                    throw new RuntimeException(
                            "Cannot update previous revision for entity " + auditedEntityName + " and id " + id
                    );
                */
            
        );
    
    sessionCacheCleaner.scheduleAuditDataRemoval( session, data );

正如我所说,它很丑......

【讨论】:

我同意你关于不灵活的代码。但是在 Envers 的 4.2.14.Final 版本(我的情况)中,perform() 方法是不同的,并且不会在匿名类中引发异常(方法中唯一的匿名类是ReturningWork 而不是BeforeTransactionCompletionProcess) .在该版本中,您可以捕获异常。【参考方案3】:

设置 org.hibernate.envers.allow_identifier_reuse: true 在我的场景中有所帮助。

【讨论】:

欢迎来到 SO Michael!您能否添加更多关于设置此项的详细信息,如果可能的话,为什么这可以解决问题?谢谢! 在 spring.jpa.properties 中的 application.yml 中设置此项。我的场景是在默认情况下不允许在 AUD 表中重用 ID。我认为错误是一样的。

以上是关于没有审计记录时Envers在ValidityAuditStrategy中抛出RuntimeException(表分区)的主要内容,如果未能解决你的问题,请参考以下文章

使用 Hibernate Envers 进行审计

审计没有 Hibernate Envers 的 java 实体

Hibernate-Envers:审计动态组件

在 Spring Hibernate java 项目中使用“Envers”审计表

Hibernate Envers:如何从我的审计表中删除条目?

审计:对子修改的父实体修订(Javers/Envers/... + Hibernate)