交易开始时的监听器

Listener on transaction start

我正在寻找一个干净的解决方案来为事务启动提供一个侦听器。这意味着我希望侦听器是 spring 上下文中的一个 bean(组件),它会在新事务开始时从 TransactionPlatformManager 或 Hibernate Session 或类似的东西接收事务开始的事件。

一些事情:

@Component
class TransactionListener implements ?? {

    @Autowired
    private Something x;

    public void onTransactionBegin(...) {
        x.doSomething()
    }

}

具体来说,我正在缓解一个系统范围的问题,我需要在事务开始时设置一个本地线程,这样我就可以在处理休眠实体时进一步访问该本地线程以检索信息。

我查看了资源,没有找到可以实现此类侦听器的痕迹。我找到的唯一解决方案是子类化 HibernateTransactionManager 及其 doBegin() 方法,我觉得这不是特别好。

Spring 在其 TransactionSynchronization 中有一些交易回调,但是正如您正确地注意到的那样,交易开始时没有回调,我的错误。

据我所知,Spring 不会让您知道事务何时开始,尽管这可能因不同的实现而异 PlatformTransactionManager。如果你想挂入Spring事务,相信你还剩下

  1. 子类化事务管理器并调用一些回调
  2. 使用 spring-aop 为 @Transactional 创建建议(显然,这只有在使用注释时才有效)

如果您使用的是 Hibernate,您可能会在 https://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Interceptor.html#afterTransactionBegin(org.hibernate.Transaction)

中使用 afterTransactionBegin

到目前为止这对我有用。

@Aspect
@Component
public class StartTransactionInterceptor {

    @Pointcut("target(org.springframework.transaction.PlatformTransactionManager)")
    public void isPlatformTransactionManager() {
    }

    @Pointcut("execution(org.springframework.transaction.TransactionStatus getTransaction("
            + "org.springframework.transaction.TransactionDefinition)))")
    public void getsTransaction() {
    }

    @Around("isPlatformTransactionManager() && getsTransaction()")
    public Object registerSynchronization(ProceedingJoinPoint joinPoint) throws Throwable {
        TransactionStatus value = (TransactionStatus)joinPoint.proceed();
        if (value.isNewTransaction()) {
            // send some application event to others who are interested
        }
        return value;
    }
}

或者,您可以使用 Spring 的 SimpleTransactionScope 并为事务范围设置一个 bean。当其他人在事务中调用下面的 DealWithStuffPerTx.addMoreStuff(Object) 时,bean 将被延迟实例化。

@Configuration
public class TransactionScopeConfig implements BeanFactoryPostProcessor {

    public static final String NAME = "tx";
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope(NAME, new SimpleTransactionScope());
    }
}

@Component
@Scope(value = TransactionScopeConfig.NAME, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DealWithStuffPerTx extends TransactionSynchronizationAdapter {

    public void addMoreStuff(Object stuff) {
    }

    @Override
    public void afterCommit() {
        // deal with stuff
    }

    @PostConstruct
    public void init() {
        TransactionSynchronizationManager.registerSynchronization(this);
    }
}

我遇到了类似的问题,我想在事务开始后立即记录 Oracle 会话 ID,以便调查我们遇到的一些问题。

最后我想,由于Spring使用PlatformTransactionManager,您可以通过自定义访问所有信息。

首先要做的是确定您使用的是哪种实现。在我们的例子中,它是在 @Configuration class 中声明的简单 JpaTransactionManager,所以它相当容易。

完成此操作后,请注意您可以在此 class 上启用调试或跟踪日志记录,如果您的目标是调试问题,它已经提供了大量事务状态信息。

如果这还不够,很容易将其子class 并替换上一个。然后只需覆盖您想要拦截的方法,如 doBegin()prepareSynchronization().

例如我的实现:

@Slf4j
public class LoggingJpaTransactionManager extends JpaTransactionManager {
    @Autowired
    private EntityManager entityManager;

    LoggingJpaTransactionManager(EntityManagerFactory emf) {
        super(emf);
    }

    @Override
    protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
        super.prepareSynchronization(status, definition);
        if (status.isNewTransaction() && log.isInfoEnabled()) {
            Query query = entityManager.createNativeQuery("select sys_context('USERENV','SID') from dual");
            Object sessionId = query.getSingleResult();
            log.info("Started a new transaction on session id {}", sessionId);
            TransactionSynchronizationManager.registerSynchronization(…);
        }
    }
}

注意:我选择覆盖 prepareSynchronization() 而不是 doBegin(),因为它允许使用 TransactionSynchronizationManager,我认为它仍然更清晰,可以收到 commit/rollback 事件通知.这个方法在 doBegin() 之后立即被调用。