Affects: 5.3.10

Please provide a method to register a listener to be notified when a transaction is created/committed/rolledback/finished.

Comment From: quaff

You can wrap TransacationManager.

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class WrappedTransactionManager implements PlatformTransactionManager {

    private final PlatformTransactionManager underlying;

    @Override
    public TransactionStatus getTransaction(TransactionDefinition transactionDefinition) throws TransactionException {
        // TODO
        return this.underlying.getTransaction(transactionDefinition);
    }

    @Override
    public void commit(TransactionStatus transactionStatus) throws TransactionException {
        // TODO
        this.underlying.commit(transactionStatus);
    }

    @Override
    public void rollback(TransactionStatus transactionStatus) throws TransactionException {
        // TODO
        this.underlying.rollback(transactionStatus);
    }

}

Comment From: mdeinum

You can register callbacks using the TransactionSynchronizationManager using the registerSynchronization method. This takes a TransactionSynchronization for which you can then implement the desired methods.

Another option is using the @TransactionalEventListener and make that run on the desired state.

Comment From: quaff

@mdeinum TransactionSynchronization and @TransactionalEventListener does not know when transaction created.

Comment From: cdalexndr

PlatformTransactionManager.getTransaction can retrieve existing transaction, but returned value can be checked TransactionStatus.isNewTransaction() to handle new transactions.

Comment From: cdalexndr

It seems that commit/rollback is also called for stacked virtual transactions so I have to also check them:

public class WrappedTransactionManager implements PlatformTransactionManager {

    private final PlatformTransactionManager underlying;

    @Override
    public TransactionStatus getTransaction(TransactionDefinition transactionDefinition) throws TransactionException {
        TransactionStatus status = this.underlying.getTransaction(transactionDefinition);
                if(status.isNewTransaction()){
                   //TODO
                }
                return status;
    }

    @Override
    public void commit(TransactionStatus transactionStatus) throws TransactionException {
        this.underlying.commit(transactionStatus);
                if(transactionStatus.isNewTransaction()){
                   //TODO
                }
    }

    @Override
    public void rollback(TransactionStatus transactionStatus) throws TransactionException {
        this.underlying.rollback(transactionStatus);
                if(transactionStatus.isNewTransaction()){
                   //TODO
                }
    }
}

Comment From: cdalexndr

WARNING registering a simple wrapper is not enough:

    //copy from org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration
    @Bean
    public PlatformTransactionManager transactionManager(
            ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers ) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManagerCustomizers.ifAvailable( ( customizers ) -> customizers.customize( transactionManager ) );
        return new WrappedTransactionManager ( transactionManager );  //<- WRAPPER HERE
    }

The original JpaTransactionManager implements some interfaces (ResourceTransactionManager, BeanFactoryAware, InitializingBean) used after bean creation, so if the wrapper doesn't forward those calls, erroneous behavior will arise. For example: in the same transaction EntityManager using one connection and JOOQ using another connection, because afterPropertiesSet() method was not forwarded.

Now, the wrapper can be made compatible, but what if, in a new spring version, another spring managed interface is added to JpaTransactionManager? I have to check at every version upgrade to make the wrapper compatible. Same for the non-trivial bean creation method that is a copy of the spring managed one. This becomes a maintenance hell.

This wrapper is a WORKAROUND only, and a spring managed solution must be provided for this feature.

Comment From: quaff

You can try BeanPostProcessor

    @Bean
    static BeanPostProcessor transactionManagerPostProcessor() {
        return new BeanPostProcessor() {

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof PlatformTransactionManager) {
                    ProxyFactory pf = new ProxyFactory(bean);
                    pf.addAdvice(new MethodInterceptor() {
                        @Override
                        public Object invoke(MethodInvocation invocation) throws Throwable {
                            Method m = invocation.getMethod();
                            if (m.getDeclaringClass() == PlatformTransactionManager.class) {
                                //TODO
                            }
                            return invocation.proceed();
                        }
                    });
                    bean = pf.getProxy();
                }
                return bean;
            }
        };
    }

Comment From: snicoll

@cdalexndr rather than asking for a technical solution like this, we'd very much prefer that you explain the use cases that you're trying to implement.

Comment From: cdalexndr

My use case: I want to track database transactions (start-end) to dump them when the app health is DOWN, for debugging purposes. I need to store postgres backend id together with the executing thread, so I can dump thread stacktrace with additional info like query and held/waiting database locks (query pg_stat_activity & pg_locks, filter by backend id).

Comment From: Cavva79

This issue maybe related to this one #18297.

Comment From: Nidhi-Tanwar14

Can you try using following phases:- BEFORE_COMMIT – The event will be handled before the transaction commit. AFTER_COMPLETION – The event will be handled when the transaction has completed regardless of success. AFTER_ROLLBACK – The event will be handled when the transaction has rolled back. AFTER_COMMIT – The event will be handled when the transaction gets committed successfully.

Comment From: jhoeller

I'm introducing a dedicated TransactionExecutionListener contract, with such listeners to be registered on the transaction manager itself. There are before/afterBegin, before/afterCommit and before/afterRollback callbacks which are being triggered by the transaction manager whenever it operates on an actual transaction, receiving the current transaction as a TransactionExecution (the base interface for TransactionStatus as well as ReactiveTransaction) which comes with a few new introspection methods now which are intended for listener implementations.

This is effectively a callback interface for stateless listening to transaction creation/completion steps in a transaction manager. It is primarily meant for observation and statistics, not for resource management purposes where stateful transaction synchronizations are still the way to go. In contrast to transaction synchronizations, the transaction execution listener contract is commonly supported for thread-bound transactions as well as reactive transactions, with a common registration facility in the new ConfigurableTransactionManager interface.

Comment From: jhoeller

I've settled on the term "transaction execution listener" for it, also naming the contract TransactionExecutionListener which goes nicely with the provided TransactionExecution argument in the callback methods. A more general "transaction listener" term can be confused with TransactionalApplicationListener and @TransactionalEventListener.

Comment From: jhoeller

@snicoll @wilkinsona I suppose Boot could automatically inject any TransactionExecutionListener beans in the context into a PlatformTransactionManager or ReactiveTransactionManager that it configures, along the following lines:

@Bean
public JdbcTransactionManager transactionManager(DataSource dataSource, Collection<TransactionExecutionListener> transactionExecutionListeners) {
    JdbcTransactionManager tm = new JdbcTransactionManager();
    tm.setDataSource(dataSource);
    tm.setTransactionExecutionListeners(transactionExecutionListeners);
    return tm;
}

Comment From: wilkinsona

Thanks for the suggestion, @jhoeller. I've opened https://github.com/spring-projects/spring-boot/issues/36770. Sorry for the noise of #31001 which I opened in the wrong tracker.