Affects: 5.3.22
I have a DataSource
of type TransactionAwareDataSourceProxy
. My PlatformTransactionManager
is JpaTransactionManager
.
I have the following tests:
@SpringBootTest
class DataSourceTest {
@Inject private PlatformTransactionManager transactionManager;
@Inject private DataSource dataSource;
@Test
void test1() throws SQLException {
TransactionStatus transaction = transactionManager.getTransaction(null);
Connection conn = DataSourceUtils.getConnection(dataSource);
transactionManager.commit(transaction);
conn.createStatement().execute("select 1");
}
@Test
void test2() throws SQLException {
TransactionStatus transaction = transactionManager.getTransaction(null);
Connection conn = dataSource.getConnection();
transactionManager.commit(transaction);
conn.createStatement().execute("select 1");
}
}
As you can see, test1
takes the Connection
via DataSourceUtils.getConnection
while test2
takes it directly on the DataSource
.
test1
fails on createStatement()
because the connection is closed.
test2
does not fail.
Note that I didn't find any documentation telling to avoid using DataSourceUtils
on a TransactionAwareDataSourceProxy
.
Therefore: - IMO, this is a bug because tests should have the same output - I don't know what should be the correct output? Is that normal that a connection "leaks" after closing a transaction like in test2 ?
Comment From: reda-alaoui
Here is test3
:
@Test
void test3() throws SQLException {
TransactionStatus transaction = transactionManager.getTransaction(null);
DataSource unWrappedDataSource =
((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
Connection conn = DataSourceUtils.getConnection(unWrappedDataSource);
transactionManager.commit(transaction);
conn.createStatement().execute("select 1");
}
It is similar to test1
but unwrap the DataSource
from TransactionAwareDataSourceProxy
before using DataSourceUtils
. It behaves as test1
, it fails.
Comment From: reda-alaoui
If I rewrite test2
like this:
@Test
void test2() throws SQLException {
TransactionStatus transaction = transactionManager.getTransaction(null);
Connection conn = dataSource.getConnection();
conn.createStatement().execute("select 1"); <-- added line
transactionManager.commit(transaction);
conn.createStatement().execute("select 1");
}
test2
will then behave as test1
and test3
(i.e. the second statement creation will fail).
I think I found the reason of these differences:
- JpaTransactionManager
unwraps TransactionAwareDataSourceProxy
to manipulate only the underlying DataSource
- TransactionAwareDataSourceProxy
connection's proxy delays underlying connection fetch as much as possible
What happens:
- When JpaTransactionManager
commits, it closes the underlying Connection
. TransactionAwareDataSourceProxy
is unaware of that.
- Creating the statement on the TransactionAwareDataSourceProxy
connection's proxy after the commits leads to the acquisition of a new connection, not bound to any transaction
IMO, the easiest fix would be to make TransactionAwareDataSourceProxy
fetch its underlying connection eagerly if TransactionAwareDataSourceProxy#reobtainTransactionalConnections
is false (default value).
An alternative that looks more risky would be to make JpaTransactionManager
works with TransactionAwareDataSourceProxy
(without unwrapping it).
Comment From: reda-alaoui
Ping ?
Comment From: jhoeller
Superseded by PR #29423.
Comment From: jhoeller
For completeness, note that the general DataSource
usage pattern is incomplete in the examples above: For every DataSource.getConnection()
call, there needs to be a corresponding Connection.close()
call within the same scope. It is generally not proper usage to open a Connection
handle with the scope of a transaction and then continue using it afterwards.
The main reason for the current default behavior is an outdated usage pattern from Java EE land: opening a Connection
first, then starting a transaction, then committing the transaction, then closing the Connection
. Lazy target binding on first actual usage was our way to make that scenario work properly. As a positive side effect, this also avoids connection contention for transactions where no actual resource has been fetched, e.g. in JTA scenarios.
As of #29423, a configuration flag will allow to opt out of that lazy target binding behavior.