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

Hibernate 5 and Transaction rollback detection with interceptor

我们有一个 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.

以外的任何状态

我们查找此信息的原因是确定我们是否需要 post 将一些消息放入队列。基本上,如果事务未提交,则不要 post。截至目前使用 Hibernate 5,我似乎无法找到如何以编程方式确定事务是否成功。

如果您使用 Spring 交易,您可以使用 TransactionSynchronization 处理此类事务。例如:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
           void afterCommit() {
                ..
           }
});

TransactionSynchronizationManager

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);
        }
    }
}
}

我成功使用了 javax.transaction.Synchronization,它可以注册交易并收到正确的状态。它可以使用 Hibernate 拦截器注册:

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

编辑:

不幸的是,此解决方案仅在提交或回滚成功时有效,否则不会调用回调。我一定是一开始测试错了。您可以在 JdbcResourceLocalTransactionCoordinatorImpl.TransactionDriverControlImpl.

的代码中看到

经过一番努力,我最终得到了 的 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 class 来源 我看到了:

  • 如果提交失败,将捕获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()) {
      /* do my on-commit stuff here */
    } else {
      /* do my otherwise stuff here */
    }
  }