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.