如何强制 Hibernate Envers 在 Spring @Transactional 方法中提交修订

Posted

技术标签:

【中文标题】如何强制 Hibernate Envers 在 Spring @Transactional 方法中提交修订【英文标题】:How to force Hibernate Envers to commit a revision within a Spring @Transactional method 【发布时间】:2014-05-17 03:35:56 【问题描述】:

我正在使用 Hibernate Envers 来保存对象历史记录。在某些时候,我们想要捕获对象图状态的快照——我们可以通过知道相应的 Envers 修订来做到这一点,然后我们将其存储在审计记录中。

但是我们有一个问题。父对象在我们创建和存储其子审计记录的同一事务中更新 - 完成 Envers 修订。我们可以得到最新的版本:

Number revision = reader.getRevisionNumberForDate(new Date(Long.MAX_VALUE));

或创建一个新版本:

Number revision = reader.getCurrentRevision(DefaultRevisionEntity.class, true).getId();

并使用其中之一,但父级的 commit 总是在那之后发生。这就是 Envers 增加修订的时候。因此,我们实际需要在审计记录中引用的修订总是高于存储的值。在最简单的情况下,我们获取并存储修订版本 N,但我们需要的父版本存储为 N + 1。

可以通过以下方式获得 AuditReader 阅读器参考:

JpaTransactionManager transactionManager;  // injected
EntityManagerFactory emf = transactionManager.getEntityManagerFactory();
EntityManager entityManager = emf.createEntityManager();
AuditReader reader = AuditReaderFactory.get(entityManager);

我们正在使用 Spring 3 @Transactional 注释和 Hibernate 4.2。

最小类图:

Parent.class
    int version           // for hibernate optimistic locking
    String revisionName
    List<AuditChild> audits

AuditChild.class
    int enversRevision    // use to retrieve previous graphs of parent

我尝试了许多方法来强制先提交父项,其中包括:

使用 @Transactional(propagation = Propagation.REQUIRES_NEW) 在多个方法中拆分代码 entityManager.flush() 的显式调用;

我尝试过的一切要么没有效果,要么导致了其他问题。我很高兴听到对其他人有用的解决方案。谢谢。

【问题讨论】:

修订号对于一个事务应该是唯一的,你确定reader.getCurrentRevision(DefaultRevisionEntity.class, true).getId() 不起作用吗?您应该需要引用当前事务的“当前”版本。 @adamw 是的,它创建了一个新修订版。但是,当我的封闭事务已提交时,*_AUD 表中相关更新行的实际修订> n(当数据库没有其他更新与此 tx 重叠时,n + 1)。因此,getCurrentRevision() 返回的对象上的 ID 不是当前 Tx 提交时应用的 ID。我的解决方法是使用虚拟 (-1) 修订版创建并在第二个事务中回读并更新我的修订版参考。 (环境:Hibernate Envers 4.2、Spring 3.2 @Transactional 注释事务;DB2 9.1)。 嗯,如果你在 TX 中调用 getCurrentRevision(),你应该得到该交易的修订。中间有flush()es吗?虽然这不应该真的很重要。 @adamw 感谢您的回复。我意识到我需要为完整的复制案例提供代码才能进一步研究。但是我的两个 Tx 解决方法现在已经足够好了。仅供参考,我也在 community.jboss.org/thread/171696 上评论过这个问题 看看这篇博文:azagorneanu.blogspot.com/2013/06/…,它可以通过注册提交后回调使你的 2 TX 解决方法更简单。 【参考方案1】:

更简单的选择是使用@Version

首先,Envers 需要配置为实际审核乐观锁定字段的值,默认情况下它不会这样做。您可以通过设置以下配置来做到这一点:

org.hibernate.envers.do_not_audit_optimistic_locking_field=false

此时,Envers 将在审计历史表中包含该字段,并将分配的 ORM 版本值复制到审计历史表中。此时,我们有一种独特的方法可以使用@Formula 将 ORM 实体行与审计历史记录行连接起来。公式 SQL 将是:

SELECT e.REV
  FROM YourEntity_AUD e
 WHERE e.originalId.id = id  
   AND e.version = version   

现在就像向实体添加字段一样简单:

@Formula( /* the SQL from above */)
@NotAudited
private Integer revisionNumber;

HTH。

【讨论】:

以上是关于如何强制 Hibernate Envers 在 Spring @Transactional 方法中提交修订的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate Envers:初始化 Envers 代理

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

Hibernate envers如何向审计表添加字段

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

Hibernate Envers 如何通过 EmbeddedId 的属性获取修订

我可以更改以及如何更改 Hibernate Envers 中的 REVTYPE 值吗?