-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
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 stateIn doCleanupAfterCompletion() (line ~425):
DataSourceUtils.resetConnectionAfterTransaction(
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
// ^^^ Passes current transaction's readOnly flagIn 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 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
// 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.DataSourceTransactionManagerorg.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.