diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java index 7aea0c270a..fb6d57a4ef 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java @@ -80,6 +80,7 @@ import org.springframework.util.Assert; * transaction definitions for vendor-specific attributes. * * @author Mark Paluch + * @author Juergen Hoeller * @since 5.3 * @see ConnectionFactoryUtils#getConnection(ConnectionFactory) * @see ConnectionFactoryUtils#releaseConnection @@ -149,7 +150,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager * transactional connection: "SET TRANSACTION READ ONLY" as understood by Oracle, * MySQL and Postgres. *

The exact treatment, including any SQL statement executed on the connection, - * can be customized through through {@link #prepareTransactionalConnection}. + * can be customized through {@link #prepareTransactionalConnection}. * @see #prepareTransactionalConnection */ public void setEnforceReadOnly(boolean enforceReadOnly) { @@ -209,8 +210,9 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager connectionMono = Mono.just(txObject.getConnectionHolder().getConnection()); } - return connectionMono.flatMap(con -> prepareTransactionalConnection(con, definition, transaction) + return connectionMono.flatMap(con -> switchAutoCommitIfNecessary(con, transaction) .then(Mono.from(doBegin(definition, con))) + .then(prepareTransactionalConnection(con, definition)) .doOnSuccess(v -> { txObject.getConnectionHolder().setTransactionActive(true); Duration timeout = determineTimeout(definition); @@ -375,32 +377,10 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager }); } - /** - * Prepare the transactional {@link Connection} right after transaction begin. - *

The default implementation executes a "SET TRANSACTION READ ONLY" statement if the - * {@link #setEnforceReadOnly "enforceReadOnly"} flag is set to {@code true} and the - * transaction definition indicates a read-only transaction. - *

The "SET TRANSACTION READ ONLY" is understood by Oracle, MySQL and Postgres - * and may work with other databases as well. If you'd like to adapt this treatment, - * override this method accordingly. - * @param con the transactional R2DBC Connection - * @param definition the current transaction definition - * @param transaction the transaction object - * @see #setEnforceReadOnly - */ - protected Mono prepareTransactionalConnection( - Connection con, TransactionDefinition definition, Object transaction) { - + private Mono switchAutoCommitIfNecessary(Connection con, Object transaction) { ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; - Mono prepare = Mono.empty(); - if (isEnforceReadOnly() && definition.isReadOnly()) { - prepare = Mono.from(con.createStatement("SET TRANSACTION READ ONLY").execute()) - .flatMapMany(Result::getRowsUpdated) - .then(); - } - // Switch to manual commit if necessary. This is very expensive in some R2DBC drivers, // so we don't want to do it unnecessarily (for example if we've explicitly // configured the connection pool to set it already). @@ -415,6 +395,29 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager return prepare; } + /** + * Prepare the transactional {@link Connection} right after transaction begin. + *

The default implementation executes a "SET TRANSACTION READ ONLY" statement if the + * {@link #setEnforceReadOnly "enforceReadOnly"} flag is set to {@code true} and the + * transaction definition indicates a read-only transaction. + *

The "SET TRANSACTION READ ONLY" is understood by Oracle, MySQL and Postgres + * and may work with other databases as well. If you'd like to adapt this treatment, + * override this method accordingly. + * @param con the transactional R2DBC Connection + * @param definition the current transaction definition + * @since 5.3.22 + * @see #setEnforceReadOnly + */ + protected Mono prepareTransactionalConnection(Connection con, TransactionDefinition definition) { + Mono prepare = Mono.empty(); + if (isEnforceReadOnly() && definition.isReadOnly()) { + prepare = Mono.from(con.createStatement("SET TRANSACTION READ ONLY").execute()) + .flatMapMany(Result::getRowsUpdated) + .then(); + } + return prepare; + } + /** * Resolve the {@linkplain TransactionDefinition#getIsolationLevel() isolation level constant} to a R2DBC * {@link IsolationLevel}. If you'd like to extend isolation level translation for vendor-specific @@ -452,21 +455,20 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager * to R2DBC drivers when starting a transaction. */ private record ExtendedTransactionDefinition(@Nullable String transactionName, - boolean readOnly, - @Nullable IsolationLevel isolationLevel, - Duration lockWaitTimeout) implements io.r2dbc.spi.TransactionDefinition { + boolean readOnly, @Nullable IsolationLevel isolationLevel, Duration lockWaitTimeout) + implements io.r2dbc.spi.TransactionDefinition { private ExtendedTransactionDefinition(@Nullable String transactionName, boolean readOnly, @Nullable IsolationLevel isolationLevel, Duration lockWaitTimeout) { + this.transactionName = transactionName; this.readOnly = readOnly; this.isolationLevel = isolationLevel; this.lockWaitTimeout = lockWaitTimeout; } - - @Override @SuppressWarnings("unchecked") + @Override public T getAttribute(Option option) { return (T) doGetValue(option); }