如何强制 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:如何从我的审计表中删除条目?