Hibernate 5和拦截器中的事务回滚检测

5
我们曾经使用 Hibernate 拦截器拦截了 afterTransactionCommit 并检查事务是否已提交 (wasCommited()),但我们最近升级到了 Hibernate 5.0.7.Final,而 hibernate 5 不再支持此调用。当我们调用 getStatus() 函数时,无论事务状态如何,我们似乎只会得到 ACTIVENOT_ACTIVE

我查看了 afterTransactionBegin,事务被标记为 ACTIVE,这是预期的,在 beforeTransactionCompletion 中它仍然被标记为 ACTIVE,这也是预期的,但是在 afterTransactionCommit 中它被标记为 NOT_ACTIVE,这对我来说没有意义。我本应该期望其中之一是 COMMITTED、ROLLED_BACK、FAILED_COMMIT。无论事务状态如何,即使我抛出导致回滚的异常,我仍然只能看到 NOT_ACTIVE

我们寻找这些信息的原因是为了确定是否需要将一些消息发布到队列中。基本上,如果事务未提交,则不要发布。目前在 Hibernate 5 中,我似乎找不到以编程方式确定事务是否成功的方法。

4个回答

3
Hibernate 5取消了拦截器中检测回滚的功能。现在,我们可以捕获事务是否已回滚并推断出如果没有提交则为回滚。例如:
public class MyInterceptor extends EmptyInterceptor {
.
.
.

private static ThreadLocal<Boolean> wasCommited = new ThreadLocal();

@Override
public void beforeTransactionCompletion(Transaction tx) {
    // not called for rollback
    wasCommited.set(Boolean.TRUE);
}


@Override
public void afterTransactionCompletion(Transaction tx) {

    if ( !Boolean.TRUE.equals(wasCommited.get()) ) {
        try {
           // handle transaction rolled back
        }
        finally {
            wasCommited.set(null);
        }
    }
}
}

1
你有什么问题?请明确阐述您的问题,并提供一些例子(如果可能)以说明您期望发生的情况。 - cyroxis
@Tim,你知道如果提交失败会发生什么吗? - vnov
我检查了这个解决方案,如果提交本身失败,则无法工作。在这种情况下,afterTransactionCompletion会看到未发生的提交。 - vnov
@vnov,你是正确的,如果提交失败就不会被调用。我看到了你下面的解决方案。对于使用JdbcResourceLocalTransactionCoordinatorImpl的提交失败情况,afterCompletion也不会被调用。:( - Tim Dugan
也就是说,在提交失败时,Synchronization.afterCompletion 也不会被调用,因此两种解决方案都无法奏效。 - Tim Dugan

2
如果您使用Spring事务,可以使用TransactionSynchronization来处理这种情况。例如:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
           void afterCommit() {
                ..
           }
});

请参见TransactionSynchronizationManager

1
经过一番努力,我最终采用了vnov的回答作为Hibernate 5解决方案(以替代现在缺失的org.hibernate.Transaction.wsComitted()方法)。该回答更深入地阐述了问题的解决方案。我使用Spring AOP通过org.hibernate.EmptyInterceptor实现。
@Before("bean(emptyInterceptor)
    && execution(public * *.afterTransactionBegin(..))
    && args(tx)")
protected void afterTransactionBegin(org.hibernate.Transaction tx) {
  tx.registerSynchronization(new javax.transaction.Synchronization() {
    @Override
    public void beforeCompletion() {
    }
    @Override
    public void afterCompletion(int status) {
      /*
        status is e.g. javax.transaction.Status.STATUS_COMMITTED
        do my stuff here,
        i.e. move here the body of the
        afterTransactionCompletion(Transaction) method
      */
    }
  });
}

JdbcResourceLocalTransactionCoordinatorImpl.TransactionDriverControlImpl类源代码中,我看到:

  • 如果提交失败,将捕获RuntimeException并回滚事务。因此,在成功回滚的情况下,Synchronization.afterCompletion(int)方法将以status == Status.STATUS_UNKNOWN调用。
  • 如果回滚失败并出现异常,则根本不会调用Synchronization.afterCompletion(int)方法。

编辑(2021年11月24日)

由于我们需要考虑回滚失败的可能性,因此我们想出了这个解决方案(在回滚失败后也会调用afterTransactionCompletion方法)。

  private ThreadLocal<Boolean> txCommitted = ThreadLocal.withInitial(() -> false);
@Before("bean(emptyInterceptor) && execution(public * *.afterTransactionBegin(..)) && args(tx)") protected void afterTransactionBegin(org.hibernate.Transaction tx) { tx.registerSynchronization(new Synchronization() { @Override public void beforeCompletion() { } @Override public void afterCompletion(int status) { txCommitted.set(status == Status.STATUS_COMMITTED); } }); }
@Before("bean(emptyInterceptor) && execution(public * *.afterTransactionCompletion(..)) && args(tx)") protected void afterTransactionCompletion(org.hibernate.Transaction tx) { if (txCommitted.get()) { /* 在此执行我的提交操作 */ } else { /* 在此执行其他操作 */ } }

嗨,我写这个评论是想问一下您是否仍然认为这是解决方案,自从您写这篇文章以来是否有任何更改。我正在解决一个类似的问题,而这个stackoverflow项目似乎是唯一涉及到它的东西。 - KoenDG
是的,自从我写下这个答案以来,我仍在使用这种方法。对于我们的用例似乎运行良好。 - Jiri Patera
谢谢回复。尽管Tim Dugan的答案中使用ThreadLocal看起来可以工作,但我无法使其适用于我的设置。虽然我还没有对此进行广泛测试。 - KoenDG
稍后回来说:我再试了一下,现在它可以工作了。我可能没有检查正确的状态。我现在看到你提到了Status.STATUS_UNKNOWN,我进入源代码也找到了这个。如果有回滚,它只是设置为那个...不是很详细,但我想还可以... - KoenDG

1

我成功地使用了javax.transaction.Synchronization,它可以与事务一起注册并接收正确的状态。可以使用Hibernate Interceptor进行注册:

  @Override
  public void afterTransactionBegin(Transaction tx) {
    tx.registerSynchronization(new Synchronization() {
      @Override
      public void afterCompletion(int status) {
        // Here the status is correct
      }
    });
  }

编辑:

不幸的是,这个解决方案只在提交或回滚成功时才有效,否则回调不会被调用。我最初的测试可能是错误的,你可以在JdbcResourceLocalTransactionCoordinatorImpl.TransactionDriverControlImpl的代码中看到。


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