Affects: spring-tx 4.3 and later


hello , maybe I found one problem. In my program, Logger is not the standard Logger, will throw IllegalStateException when invoke logger.trace().And in AbstractPlatformTransactionManager, some method invoke logger.trace() so when I invoke method with @Transactional annotation, will throw exception like below, but in catch code , do not end transaction, still have transaction connection. so the same thread do other things still in transaction and reuse transaction connection,


java.lang.IllegalStateException: Unknown Priority TRACE
    at org.apache.log4j.Category.priorityToLevelInt(Category.java:326)
    at org.apache.log4j.Category.log(Category.java:296)
    at com.doudou.common.logging.spi.log4j.Log4jLogger.trace(Log4jLogger.java:106)
    at org.springframework.transaction.support.TransactionSynchronizationManager.initSynchronization(TransactionSynchronizationManager.java:272)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.prepareSynchronization(AbstractPlatformTransactionManager.java:546)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:434)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:353)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:461)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:277)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
    at com.doudou.meta.service.table.impl.MetaTableServiceImpl$$EnhancerBySpringCGLIB$$b5c28abd.addTableIgnoreIfExists(<generated>)

and in this scenario, I read the spring-tx code

    public static void initSynchronization() throws IllegalStateException {
        if (isSynchronizationActive()) {
            throw new IllegalStateException("Cannot activate transaction synchronization - already active");
        }
                 // here throw exception
        logger.trace("Initializing transaction synchronization");
        synchronizations.set(new LinkedHashSet<TransactionSynchronization>());
    }

but in catch code, only do resume ,do not clear synchronizations & transaction connection

            try {
                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
                DefaultTransactionStatus status = newTransactionStatus(
                        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                doBegin(transaction, definition);
                prepareSynchronization(status, definition);
                return status;
            }
            catch (RuntimeException ex) {
                                 // here only do resume
                resume(null, suspendedResources);
                throw ex;
            }

maybe do like below is better:

            DefaultTransactionStatus status = null;
            try {
                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
                status = newTransactionStatus(
                        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                doBegin(transaction, definition);
                prepareSynchronization(status, definition);
                return status;
            }
            catch (RuntimeException ex) {
                if (status != null) {
                    cleanupAfterCompletion(status);
                }
                throw ex;
            }

I see cleanupAfterCompletion will do 3 things 1. clear synchronizations 2. reset connection unbind resource. this is importent

    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

        // Remove the connection holder from the thread, if exposed.
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.unbindResource(this.dataSource);
        }

        // Reset connection.
        Connection con = txObject.getConnectionHolder().getConnection();
        try {
            if (txObject.isMustRestoreAutoCommit()) {
                con.setAutoCommit(true);
            }
            DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
        }
        catch (Throwable ex) {
            logger.debug("Could not reset JDBC Connection after transaction", ex);
        }

        if (txObject.isNewConnectionHolder()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
            }
            DataSourceUtils.releaseConnection(con, this.dataSource);
        }

        txObject.getConnectionHolder().clear();
    }

Comment From: ZZZKROSS

😯

Comment From: rstoyanchev

The given logger.trace was removed in 22686fbfa1bbe36101c608c494d54a97ebb653d0, so it is not present in the currently supported branches 5.3.x and 6.0.x.

Comment From: doudouOUC

OK. I think when catch exception, should trigger doCleanupAfterCompletion to reset connection