没有审计记录时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
现有的一个属性中指定一个自定义AuditStrategey
:org.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 的 java 实体
在 Spring Hibernate java 项目中使用“Envers”审计表