Apply read-only enforcement after R2DBC transaction begin
Includes prepareTransactionalConnection variant aligned with JDBC DataSourceTransactionManager. Closes gh-28610
This commit is contained in:
parent
7055ddb489
commit
56fc64dd14
|
@ -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.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>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<Void> prepareTransactionalConnection(
|
||||
Connection con, TransactionDefinition definition, Object transaction) {
|
||||
|
||||
private Mono<Void> switchAutoCommitIfNecessary(Connection con, Object transaction) {
|
||||
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction;
|
||||
|
||||
Mono<Void> 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.
|
||||
* <p>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.
|
||||
* <p>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<Void> prepareTransactionalConnection(Connection con, TransactionDefinition definition) {
|
||||
Mono<Void> 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> T getAttribute(Option<T> option) {
|
||||
return (T) doGetValue(option);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue