DataSourceUtils resolution getTargetConnection gets stuck in an endless loop under certain conditions in spring-jdbc 6.2.2/6.2.3.

I am creating a DataSource, wrapping it into the TransactionAwareDataSourceProxy and then creating an EntityManagerFactory. Then the datasource and the emf both get posted to a JpaTransactionManager. Upon trying to commit a transaction, the application goes into an endless loop in DataSourceUtils.getTargetConnection().

As far as I could understand it by debugging, the following happens. TransactionAwareDataSourceProxy gets asked for a connection and creates a lazy initialization proxy. As this proxy is lazy, it does not initialize an inner. The proxy handler is the TransactionAwareInvocationHandler.

Next step is, that this proxy gets bound to the current transaction-context in the TransactionSynchronizationManager. This happens during JpaTransactionManager.doBegin(Object, TransactionDefinition).

Now, during release of the connection while committing, the connection proxy gets into DataSourceUtils.getTargetConnection() and there it is determined, it is an instanceof ConnectionProxy and therefore gets resolved within TransactionAwareDataSourceProxy, which delegates to DataSourceUtils.doGetConnection, which delegates to TransactionSynchronizationManager.getResource which returns the uninitialized proxy.

I am not quite sure, why most invocations do not trigger this issue and some do. My current assumption is, that there is no issue, as long as the databaseconnection is actually used within the transaction. We have, however, cases, where a spring bean caches data, requests a transaction but does not actually trigger database queries upon cache-hits.

I am not quite sure, how to solve this correctly. Probably a non-initialized proxy for a connection should not be registered at all within the TransactionAwareDataSourceProxy?

Comment From: sbrannen

DataSourceUtils resolution getTargetConnection gets stuck in an endless loop under certain conditions in spring-jdbc 6.2.2/6.2.3.

Are you claiming that this is a regression that did not occur before Spring Framework 6.2.2?

In any case, can you please provide a small sample application that reproduces the problem (preferably something that we can download and run, such as a public Git repository or a ZIP file attached to this issue)?

Thanks

Comment From: drachenpalme

Sorry, I didn't make that clear. It occurs both in 6.2.2 and 6.2.3, I have not tested other versions. I will try to create a sample app.

Comment From: drachenpalme

Hi Sam,

I managed to distill the issue into a mini-project here: https://github.com/drachenpalme/spring_ticket_34484

Upon executing Main.main you will first enter the TransactionalBean#doWithDb(), which actually does trigger unproxying the javax.sql.Connection prior to the doCommit() and therefore behaves as expected. It will then enter TransactionalBean#doWithoutDb(), which will immediately return, not unproxying the Connection and therefor triggering the issue.

Please let me know, if you need further help or info. As I said, as I do not understand the concepts within the spring code, I am not sure, what the best way to solve this is. My assumption would be, DataSouerceUtils#doReleaseConnection should be made aware, if a connection is completely unused. Maybe ConnectionHolder.hasConnection should be more like ConnectionHolder.hasActiveConnection and that should check, whether the connectionHandle is a non-initialized proxy?

Comment From: jhoeller

I was able to reproduce this locally but only with the very specific configuration in your repro project. Changing emf.setJpaDialect(new HibernateJpaDialect()) to emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter()) (the recommended variant which applies HibernateJpaDialect plus a few extra settings) makes the test pass for me. Digging deeper to find out why this is so nuanced. In any case, we need to be able to defensively handle uninitialized proxies in all scenarios.

Comment From: jhoeller

It's caused by the Hibernate connection release mode which our HibernateJpaVendorAdapter switches to DELAYED_ACQUISITION_AND_HOLD while Hibernate has a more aggressive release-and-reacquire policy by default. And only with the latter, we end up going in a loop where getTargetConnection seems to end up with the same uninitialized proxy that we called the method on.

Comment From: jhoeller

Aside from the Hibernate connection release mode, it is also unusual to provide a TransactionAwareDataSourceProxy to the JPA setup. The persistence provider should rather see the actual target DataSource, and only the JDBC-accessing applications beans need to see the TransactionAwareDataSourceProxy if they perform direct DataSource interactions. This is part of the problem here, without that part the test passes fine as well.

In any case, I've revised this for more defensiveness in getTargetConnection handling within TransactionAwareDataSourceProxy. This revision is available in the latest 6.2.4 snapshot already, please give it an early try if you have the chance...

Comment From: drachenpalme

Thanks a lot for the quick fix. I actually successfully tested your suggestion to set the vendoradapter instead of the jpadialect, which works well enough. As for your remark in relation to the TransactionAwareDataSourceProxy - thanks a lot. I will keep this in mind. All of this is part of the solution of some really ugly bug with some heavy trial and error.