【问题标题】:Listener on transaction start事务开始时的侦听器
【发布时间】:2016-09-28 11:29:40
【问题描述】:

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

一些东西:

@Component
class TransactionListener implements ?? {

    @Autowired
    private Something x;

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

}

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

我查看了来源,没有发现可以实现此类侦听器的任何痕迹。我发现的唯一解决方案是继承 HibernateTransactionManager 及其 doBegin() 方法,我觉得这不是特别好。

【问题讨论】:

  • 最简单的方法可能是为PlatformTransactionManager 创建一个包装器,并在 3 个方法调用上触发这些事件。但是为什么你需要注册呢?你想做什么?你提到解决问题,但这似乎有点奇怪。
  • 我面临的问题无关紧要,它是由我们的大型多模块架构引起的。如果我可以选择对架构做出决定,我会采取不同的方式。但这在这个遗留架构中是不可能的,没有沉浸式重写,这就是我需要这个的原因。

标签: java spring hibernate spring-transactions hibernate-session


【解决方案1】:

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 运气好

【讨论】:

  • 这将通知所有事件,除了事务开始。
【解决方案2】:

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

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

首先要做的是确定您正在使用哪种实现。在我们的例子中,它是在 @Configuration 类中声明的一个简单的 JpaTransactionManager,因此相当简单。

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

如果这还不够,可以很容易地对其进行子类化并替换前一个。然后只需覆盖您要拦截的方法,例如 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,我认为通知提交/回滚事件仍然更清晰。在doBegin() 之后立即调用此方法。

【讨论】:

    【解决方案3】:

    到目前为止,这对我有用。

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

    【讨论】:

    • 方面解决方案看起来不错,但不幸的是,它仅适用于 JDK 代理,而不适用于 CGLIB 类代理代理,因为 AbstractPlatformTransactionManager 中有许多 final 方法,一旦您尝试就会导致 NPE开始交易。据我了解,第二种方法不能全局拦截事务,它只适用于调用addMoreStuff()的代码。
    猜你喜欢
    • 2018-09-04
    • 2018-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-01
    相关资源
    最近更新 更多