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

7
我正在使用Hibernate Envers来持久化对象历史记录。在某些时候,我们想要捕获对象图状态的快照 - 我们可以通过知道相应的Envers修订版来做到这一点,然后将其存储在审核记录中。
但是我们遇到了一个问题。在创建并存储其子审核记录(包括Envers修订版)的事务中,父对象被更新。我们可以获取最新的修订版:
Number revision = reader.getRevisionNumberForDate(new Date(Long.MAX_VALUE));

或者创建一个新的版本:
Number revision = reader.getCurrentRevision(DefaultRevisionEntity.class, true).getId();

您可以使用任一版本,但父级的提交总是在此之后发生。 这就是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
@adamw 是的,那会创建一个新版本。但是当我的封闭事务被提交后,在 *_AUD 表中相关更新行的实际版本 > n (当没有其他数据库更新与此 tx 重叠时为 n + 1)。 因此,从 getCurrentRevision() 返回的对象上的 Id 不是在提交当前 Tx 时应用的那个 Id。 我的解决方法是使用虚拟(-1)版本进行创建,并在第二个事务中读取并更新我的版本引用。 (环境:Hibernate Envers 4.2、Spring 3.2 的@Transactional 注释事务;DB2 9.1) - user598656
嗯,如果你在一个事务中调用getCurrentRevision(),你应该得到该事务的修订版本。你之间有没有执行过flush()操作呢?虽然这并不重要。 - adamw
@adamw 感谢您的回复。我意识到我需要提供完整的代码以进行进一步的重现。但是,我的两个Tx解决方法现在已经足够好了。顺便说一下,我也在 https://community.jboss.org/thread/171696 上对此问题发表了评论。 - user598656
看一下这篇博客文章:http://azagorneanu.blogspot.com/2013/06/transaction-synchronization-callbacks.html,通过注册事务提交后的回调函数,它可能会让你的2个TX解决方案更简单。 - Krešimir Nesek
1个回答

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.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接