Current Behavior
DataSourceTransactionManager does not restore the original readOnly state of database connections after transaction completion. While the transaction isolation level is properly backed up and restored, the readOnly flag is unconditionally reset to false.
Code Analysis
In doBegin() (line ~325):
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel); // ✓ Backed up
txObject.setReadOnly(definition.isReadOnly()); // ✗ Current transaction's flag, not the original connection state
In doCleanupAfterCompletion() (line ~425):
DataSourceUtils.resetConnectionAfterTransaction(
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
// ^^^ Passes current transaction's readOnly flag
In DataSourceUtils.resetConnectionAfterTransaction() (line ~287):
if (resetReadOnly) {
con.setReadOnly(false); // ✗ Unconditionally resets to false
}
Expected Behavior
The connection's original readOnly state should be preserved and restored after transaction completion, similar to how the isolation level is handled.
Impact
-
Driver optimization loss: Some JDBC drivers (e.g., PostgreSQL) apply optimizations when readOnly=true. Unconditionally resetting to false loses these optimizations for subsequent transactions.
-
Unnecessary database queries: Drivers like MySQL execute SET SESSION TRANSACTION READ ONLY/READ WRITE commands. Incorrect state restoration causes unnecessary round-trips.
-
Connection pool state inconsistency: If a connection pool configures connections with specific default states, this behavior can leave the pool in an inconsistent state.
Steps to Reproduce
// Assume a connection pool that sets defaultReadOnly=true
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDefaultReadOnly(true); // All connections start as readOnly=true
DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource);
@Transactional(readOnly = true)
public void readOnlyOperation() {
// Connection comes in as readOnly=true
// Transaction executes
// After transaction: connection is reset to readOnly=false ✗
// Connection returned to pool with wrong state
}
Environment
- Spring Framework: 6.x (main branch)
- Affected classes:
org.springframework.jdbc.datasource.DataSourceTransactionManager
org.springframework.jdbc.datasource.DataSourceUtils
Proposed Solution
Add previousReadOnly field to DataSourceTransactionObject and restore the original state after transaction completion, following the same pattern as previousIsolationLevel.
Current Behavior
DataSourceTransactionManagerdoes not restore the originalreadOnlystate of database connections after transaction completion. While the transaction isolation level is properly backed up and restored, thereadOnlyflag is unconditionally reset tofalse.Code Analysis
In
doBegin()(line ~325):In
doCleanupAfterCompletion()(line ~425):In
DataSourceUtils.resetConnectionAfterTransaction()(line ~287):Expected Behavior
The connection's original
readOnlystate should be preserved and restored after transaction completion, similar to how the isolation level is handled.Impact
Driver optimization loss: Some JDBC drivers (e.g., PostgreSQL) apply optimizations when
readOnly=true. Unconditionally resetting tofalseloses these optimizations for subsequent transactions.Unnecessary database queries: Drivers like MySQL execute
SET SESSION TRANSACTION READ ONLY/READ WRITEcommands. Incorrect state restoration causes unnecessary round-trips.Connection pool state inconsistency: If a connection pool configures connections with specific default states, this behavior can leave the pool in an inconsistent state.
Steps to Reproduce
Environment
org.springframework.jdbc.datasource.DataSourceTransactionManagerorg.springframework.jdbc.datasource.DataSourceUtilsProposed Solution
Add
previousReadOnlyfield toDataSourceTransactionObjectand restore the original state after transaction completion, following the same pattern aspreviousIsolationLevel.