Listener on transaction start

一世执手 提交于 2020-02-01 05:19:53

问题


I am looking for a clean solution to have a listener for transaction start. That means I would like the listener to be a bean (component) in the spring context that would receive an event on transaction start from TransactionPlatformManager or Hibernate Session or something similar, at the point where a new transaction is started.

Something along:

@Component
class TransactionListener implements ?? {

    @Autowired
    private Something x;

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

}

To be concrete, I am mitigating a system wide problem and I need to set a thread local when transaction starts, so I can access that thread local further in the processing of hibernate entities to retrieve info.

I looked into sources and found no traces for such listener to be achievable. The only solution I found was to subclass HibernateTransactionManager and its doBegin() method, which I don't find particularly nice.


回答1:


Spring have some transaction callbacks in its TransactionSynchronization, however as you correctly have noticed, there is no callback for transaction start, my mistake.

As far as I know, Spring will not let you know when transactions start, although this may vary from different implementations PlatformTransactionManager. If you want to hook into Spring transaction, I believe you are left with

  1. Subclass the transaction manager and invoke some callback
  2. Create an advice for @Transactional with spring-aop (this will only work if you use annotations, obviously)

If you're using Hibernate, you might have some luck with afterTransactionBegin in https://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Interceptor.html#afterTransactionBegin(org.hibernate.Transaction)




回答2:


I had a similar problem where I wanted to log the Oracle session id as soon as a transaction starts, in order to investigate some issue we have.

In the end I figured that, since Spring uses PlatformTransactionManager, you can access all information by customizing it.

The first thing to do is to identify which implementation you are using. In our case, it was a simple JpaTransactionManager declared in a @Configuration class, so it was rather easy.

Once you have done this, notice that you can enable debug or trace logging on this class, which already provides a lot of transaction status information if your goal is to debug an issue.

If this is not enough, it's easy to subclass it and replace the previous one. Then just override the methods you want to intercept like doBegin() or prepareSynchronization().

See for instance my implementation:

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

Note: I chose to override prepareSynchronization() instead of doBegin() because it allows to use the TransactionSynchronizationManager, which I think remains cleaner to be notified of commit/rollback events. This method is invoked immediately after doBegin() anyway.




回答3:


This works for me so far.

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

Alternatively, you can use Spring's SimpleTransactionScope and scope a bean for transaction scope. The bean would get lazily instantiated as others call into DealWithStuffPerTx.addMoreStuff(Object) below within a transaction.

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


来源:https://stackoverflow.com/questions/37528975/listener-on-transaction-start

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!