Skip to content

Commit 56fc64d

Browse files
committed
Apply read-only enforcement after R2DBC transaction begin
Includes prepareTransactionalConnection variant aligned with JDBC DataSourceTransactionManager. Closes gh-28610
1 parent 7055ddb commit 56fc64d

File tree

1 file changed

+28
-26
lines changed

1 file changed

+28
-26
lines changed

spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
* transaction definitions for vendor-specific attributes.
8181
*
8282
* @author Mark Paluch
83+
* @author Juergen Hoeller
8384
* @since 5.3
8485
* @see ConnectionFactoryUtils#getConnection(ConnectionFactory)
8586
* @see ConnectionFactoryUtils#releaseConnection
@@ -149,7 +150,7 @@ protected ConnectionFactory obtainConnectionFactory() {
149150
* transactional connection: "SET TRANSACTION READ ONLY" as understood by Oracle,
150151
* MySQL and Postgres.
151152
* <p>The exact treatment, including any SQL statement executed on the connection,
152-
* can be customized through through {@link #prepareTransactionalConnection}.
153+
* can be customized through {@link #prepareTransactionalConnection}.
153154
* @see #prepareTransactionalConnection
154155
*/
155156
public void setEnforceReadOnly(boolean enforceReadOnly) {
@@ -209,8 +210,9 @@ protected Mono<Void> doBegin(TransactionSynchronizationManager synchronizationMa
209210
connectionMono = Mono.just(txObject.getConnectionHolder().getConnection());
210211
}
211212

212-
return connectionMono.flatMap(con -> prepareTransactionalConnection(con, definition, transaction)
213+
return connectionMono.flatMap(con -> switchAutoCommitIfNecessary(con, transaction)
213214
.then(Mono.from(doBegin(definition, con)))
215+
.then(prepareTransactionalConnection(con, definition))
214216
.doOnSuccess(v -> {
215217
txObject.getConnectionHolder().setTransactionActive(true);
216218
Duration timeout = determineTimeout(definition);
@@ -375,6 +377,24 @@ protected Mono<Void> doCleanupAfterCompletion(TransactionSynchronizationManager
375377
});
376378
}
377379

380+
private Mono<Void> switchAutoCommitIfNecessary(Connection con, Object transaction) {
381+
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction;
382+
Mono<Void> prepare = Mono.empty();
383+
384+
// Switch to manual commit if necessary. This is very expensive in some R2DBC drivers,
385+
// so we don't want to do it unnecessarily (for example if we've explicitly
386+
// configured the connection pool to set it already).
387+
if (con.isAutoCommit()) {
388+
txObject.setMustRestoreAutoCommit(true);
389+
if (logger.isDebugEnabled()) {
390+
logger.debug("Switching R2DBC Connection [" + con + "] to manual commit");
391+
}
392+
prepare = prepare.then(Mono.from(con.setAutoCommit(false)));
393+
}
394+
395+
return prepare;
396+
}
397+
378398
/**
379399
* Prepare the transactional {@link Connection} right after transaction begin.
380400
* <p>The default implementation executes a "SET TRANSACTION READ ONLY" statement if the
@@ -385,33 +405,16 @@ protected Mono<Void> doCleanupAfterCompletion(TransactionSynchronizationManager
385405
* override this method accordingly.
386406
* @param con the transactional R2DBC Connection
387407
* @param definition the current transaction definition
388-
* @param transaction the transaction object
408+
* @since 5.3.22
389409
* @see #setEnforceReadOnly
390410
*/
391-
protected Mono<Void> prepareTransactionalConnection(
392-
Connection con, TransactionDefinition definition, Object transaction) {
393-
394-
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction;
395-
411+
protected Mono<Void> prepareTransactionalConnection(Connection con, TransactionDefinition definition) {
396412
Mono<Void> prepare = Mono.empty();
397-
398413
if (isEnforceReadOnly() && definition.isReadOnly()) {
399414
prepare = Mono.from(con.createStatement("SET TRANSACTION READ ONLY").execute())
400415
.flatMapMany(Result::getRowsUpdated)
401416
.then();
402417
}
403-
404-
// Switch to manual commit if necessary. This is very expensive in some R2DBC drivers,
405-
// so we don't want to do it unnecessarily (for example if we've explicitly
406-
// configured the connection pool to set it already).
407-
if (con.isAutoCommit()) {
408-
txObject.setMustRestoreAutoCommit(true);
409-
if (logger.isDebugEnabled()) {
410-
logger.debug("Switching R2DBC Connection [" + con + "] to manual commit");
411-
}
412-
prepare = prepare.then(Mono.from(con.setAutoCommit(false)));
413-
}
414-
415418
return prepare;
416419
}
417420

@@ -452,21 +455,20 @@ protected RuntimeException translateException(String task, R2dbcException ex) {
452455
* to R2DBC drivers when starting a transaction.
453456
*/
454457
private record ExtendedTransactionDefinition(@Nullable String transactionName,
455-
boolean readOnly,
456-
@Nullable IsolationLevel isolationLevel,
457-
Duration lockWaitTimeout) implements io.r2dbc.spi.TransactionDefinition {
458+
boolean readOnly, @Nullable IsolationLevel isolationLevel, Duration lockWaitTimeout)
459+
implements io.r2dbc.spi.TransactionDefinition {
458460

459461
private ExtendedTransactionDefinition(@Nullable String transactionName, boolean readOnly,
460462
@Nullable IsolationLevel isolationLevel, Duration lockWaitTimeout) {
463+
461464
this.transactionName = transactionName;
462465
this.readOnly = readOnly;
463466
this.isolationLevel = isolationLevel;
464467
this.lockWaitTimeout = lockWaitTimeout;
465468
}
466469

467-
468-
@Override
469470
@SuppressWarnings("unchecked")
471+
@Override
470472
public <T> T getAttribute(Option<T> option) {
471473
return (T) doGetValue(option);
472474
}

0 commit comments

Comments
 (0)