Skip to content

Preserve Connection readOnly state for DataSource with defaultReadOnly configuration #35743

@NYgomets

Description

@NYgomets

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

  1. Driver optimization loss: Some JDBC drivers (e.g., PostgreSQL) apply optimizations when readOnly=true. Unconditionally resetting to false loses these optimizations for subsequent transactions.

  2. Unnecessary database queries: Drivers like MySQL execute SET SESSION TRANSACTION READ ONLY/READ WRITE commands. Incorrect state restoration causes unnecessary round-trips.

  3. 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.

Metadata

Metadata

Assignees

Labels

in: dataIssues in data modules (jdbc, orm, oxm, tx)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions