It seems like there might be a Connection Leak issue when using the NOT_SUPPORTED propagation attribute or creating a transactionless state in Spring Batch and then calling a non-transactional Repository.

The commonality is that, in both NOT_SUPPORTED and BATCH codes, TransactionSynchronizationManager has activeTransaction set to false and the ResourceMap is empty, but TransactionSynchronizationManager.isSynchronizationActive() returns true.

Looking at the example below might make it easier.

@Service
@RequiredArgsConstructor
public class TestService {

    private final TestService2  testService2;
    @Transactional
    public void test() {
        testService2.test();
    }
}

@Service
@RequiredArgsConstructor
public class TestService2 {

    private final TestRepository TestRepository;

    @Transactional(propagation = NOT_SUPPORTED)
    public void test() {
        var e = TestRepository.findByIdNoTx(1L);
    }
}

In the scenario described above, after executing the repository in TestService2::test, the connection remains in the TransactionSynchronizationManager's ResourceMap without being properly removed.

Typically, users might believe that since there is no transaction, the connection will be immediately released. However, it seems that in this case, that assumption does not hold true.

Below is an example of a Spring Batch scenario:

    @Bean
    public Step step1() {
        DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute();
        transactionAttribute.setPropagationBehavior(Propagation.NEVER.value());

        return stepBuilderFactory.get("step1").tasklet((contribution, chunkContext) -> {
                var e = testRepository.findByIdNoTx(1L);
                return RepeatStatus.FINISHED;
            })
            .transactionAttribute(transactionAttribute)
            .build();
    }

Even in cases like the one described, it seems that after the execution of testRepository.findByIdNoTx(), the transaction manager retains the resource in the resource map, leading to the connection not being released as expected.

The problem described above can cause an exception due to db wiat_timeout when the user creates logic that takes a long time between two repository requests (expecting no connection because there is no transaction).

The cause of the problem seems to be the entityManager creation availability branch in EntityManagerFactoryUtils.

        else if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            // Indicate that we can't obtain a transactional EntityManager.
            return null;
        }

Line 249 in EntityManagerFactoryUtils is currently using only isSynchronizationActive() for validation.

After creating a typical API application, if you execute the code like the one in the example above when there is no transaction, the TransactionSynchronizationManager.isSynchronizationActive() will be marked as false, returning null, and not maintaining the connection. I believe this approach is correct.

I'm curious about your thoughts on adding the following validation logic here:

else if (!TransactionSynchronizationManager.isActualTransactionActive() || !TransactionSynchronizationManager.isSynchronizationActive()) {
            // Indicate that we can't obtain a transactional EntityManager.
            return null;
        }

Thank you!

Comment From: jhoeller

There is a semantic distinction between "active transaction" and "active synchronization": For registering synchronizations that reuse a resource across a defined scope, all we need is active synchronization. This is the case for all managed resources and therefore also for EntityManagers where the registered TransactionScopedEntityManagerSynchronization will close its EntityManager on completion even outside of an active transaction.

Are you maybe seeing the effect of an EntityManager kept alive for the entire transaction boundary? So not leaking, just living for the entire scope of the transaction-annotated method? For better or for worse, this is the case for NOT_SUPPORTED as well. Fundamentally, that propagation behavior does not directly relate to resource reuse, just to suspension of an existing transaction. In that sense, NOT_SUPPORTED behaves just like SUPPORTS if there is no existing transaction to begin with.

Comment From: bperhaps

Thank you for your answer :) What I meant was, the matter I was referring to is more related to the temporary suspension of transactions, and it is not related to resource reuse, so it's more akin to a feature than a leak, right? I understood! Thank you!

Comment From: bperhaps

@jhoeller Would it be okay to ask a few more questions? From what I studied, the lifecycle of the transaction manager is within the transaction scope under the assumption that OSIV is false. Even though it is within the transactional annotation, I think it is better not to maintain the connection if there is no transaction. What do you think? I would appreciate your opinion. Thank you!

Comment From: jhoeller

The EntityManager is first and foremost an expensive object that is being retained in memory for reuse across multiple operations. The holding of underlying JDBC connections can be configured through the persistence provider, see e.g. https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/transactions-connection-release.html

Note that Spring's HibernateJpaVendorAdapter applies a transaction-optimized default of "DELAYED_ACQUISITION_AND_HOLD", you may want to change that to "DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT" - or simply turn off connection preparation through HibernateJpaVendorAdapter.setPrepareConnection(false).

Comment From: bperhaps

Thank you for your response. I am learning a lot thanks to you!