|
|
|
@ -24,7 +24,6 @@ import io.r2dbc.spi.IsolationLevel; |
|
|
|
import io.r2dbc.spi.Option; |
|
|
|
import io.r2dbc.spi.Option; |
|
|
|
import io.r2dbc.spi.R2dbcException; |
|
|
|
import io.r2dbc.spi.R2dbcException; |
|
|
|
import io.r2dbc.spi.Result; |
|
|
|
import io.r2dbc.spi.Result; |
|
|
|
import org.reactivestreams.Publisher; |
|
|
|
|
|
|
|
import reactor.core.publisher.Mono; |
|
|
|
import reactor.core.publisher.Mono; |
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.beans.factory.InitializingBean; |
|
|
|
import org.springframework.beans.factory.InitializingBean; |
|
|
|
@ -38,46 +37,42 @@ import org.springframework.transaction.reactive.TransactionSynchronizationManage |
|
|
|
import org.springframework.util.Assert; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* {@link org.springframework.transaction.ReactiveTransactionManager} |
|
|
|
* {@link org.springframework.transaction.ReactiveTransactionManager} implementation |
|
|
|
* implementation for a single R2DBC {@link ConnectionFactory}. This class is |
|
|
|
* for a single R2DBC {@link ConnectionFactory}. This class is capable of working |
|
|
|
* capable of working in any environment with any R2DBC driver, as long as the |
|
|
|
* in any environment with any R2DBC driver, as long as the setup uses a |
|
|
|
* setup uses a {@code ConnectionFactory} as its {@link Connection} factory |
|
|
|
* {@code ConnectionFactory} as its {@link Connection} factory mechanism. |
|
|
|
* mechanism. Binds a R2DBC {@code Connection} from the specified |
|
|
|
* Binds a R2DBC {@code Connection} from the specified {@code ConnectionFactory} |
|
|
|
* {@code ConnectionFactory} to the current subscriber context, potentially |
|
|
|
* to the current subscriber context, potentially allowing for one context-bound |
|
|
|
* allowing for one context-bound {@code Connection} per {@code ConnectionFactory}. |
|
|
|
* {@code Connection} per {@code ConnectionFactory}. |
|
|
|
* |
|
|
|
* |
|
|
|
* <p><b>Note: The {@code ConnectionFactory} that this transaction manager |
|
|
|
* <p><b>Note: The {@code ConnectionFactory} that this transaction manager operates |
|
|
|
* operates on needs to return independent {@code Connection}s.</b> |
|
|
|
* on needs to return independent {@code Connection}s.</b> The {@code Connection}s |
|
|
|
* The {@code Connection}s may come from a pool (the typical case), but the |
|
|
|
* typically come from a connection pool but the {@code ConnectionFactory} must not |
|
|
|
* {@code ConnectionFactory} must not return scoped {@code Connection}s |
|
|
|
* return specifically scoped or constrained {@code Connection}s. This transaction |
|
|
|
* or the like. This transaction manager will associate {@code Connection} |
|
|
|
* manager will associate {@code Connection} with context-bound transactions, |
|
|
|
* with context-bound transactions itself, according to the specified propagation |
|
|
|
* according to the specified propagation behavior. It assumes that a separate, |
|
|
|
* behavior. It assumes that a separate, independent {@code Connection} can |
|
|
|
* independent {@code Connection} can be obtained even during an ongoing transaction. |
|
|
|
* be obtained even during an ongoing transaction. |
|
|
|
|
|
|
|
* |
|
|
|
* |
|
|
|
* <p>Application code is required to retrieve the R2DBC Connection via |
|
|
|
* <p>Application code is required to retrieve the R2DBC Connection via |
|
|
|
* {@link ConnectionFactoryUtils#getConnection(ConnectionFactory)} |
|
|
|
* {@link ConnectionFactoryUtils#getConnection(ConnectionFactory)} |
|
|
|
* instead of a standard R2DBC-style {@link ConnectionFactory#create()} call. |
|
|
|
* instead of a standard R2DBC-style {@link ConnectionFactory#create()} call. |
|
|
|
* Spring classes such as {@code DatabaseClient} use this strategy implicitly. |
|
|
|
* Spring classes such as {@code DatabaseClient} use this strategy implicitly. |
|
|
|
* If not used in combination with this transaction manager, the |
|
|
|
* If not used in combination with this transaction manager, the |
|
|
|
* {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the |
|
|
|
* {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the native |
|
|
|
* native {@code ConnectionFactory} lookup; it can thus be used in a portable fashion. |
|
|
|
* {@code ConnectionFactory} lookup; it can thus be used in a portable fashion. |
|
|
|
* |
|
|
|
* |
|
|
|
* <p>Alternatively, you can allow application code to work with the standard |
|
|
|
* <p>Alternatively, you can allow application code to work with the lookup pattern |
|
|
|
* R2DBC lookup pattern {@link ConnectionFactory#create()}, for example for code |
|
|
|
* {@link ConnectionFactory#create()}, for example for code not aware of Spring. |
|
|
|
* that is not aware of Spring at all. In that case, define a |
|
|
|
* In that case, define a {@link TransactionAwareConnectionFactoryProxy} for your |
|
|
|
* {@link TransactionAwareConnectionFactoryProxy} for your target {@code ConnectionFactory}, |
|
|
|
* target {@code ConnectionFactory}, and pass that proxy {@code ConnectionFactory} |
|
|
|
* and pass that proxy {@code ConnectionFactory} to your DAOs, which will automatically |
|
|
|
* to your DAOs which will automatically participate in Spring-managed transactions |
|
|
|
* participate in Spring-managed transactions when accessing it. |
|
|
|
* when accessing it. |
|
|
|
* |
|
|
|
* |
|
|
|
* <p>This transaction manager triggers flush callbacks on registered transaction |
|
|
|
* <p>Spring's {@code TransactionDefinition} attributes are carried forward to |
|
|
|
* synchronizations (if synchronization is generally active), assuming resources |
|
|
|
* R2DBC drivers using extensible R2DBC {@link io.r2dbc.spi.TransactionDefinition}. |
|
|
|
* operating on the underlying R2DBC {@code Connection}. |
|
|
|
* Subclasses may override {@link #createTransactionDefinition(TransactionDefinition)} |
|
|
|
* |
|
|
|
* to customize transaction definitions for vendor-specific attributes. As of 6.0.10, |
|
|
|
* <p>Spring's {@code TransactionDefinition} attributes are carried forward to R2DBC drivers |
|
|
|
* this transaction manager supports nested transactions via R2DBC savepoints as well. |
|
|
|
* using extensible R2DBC {@link io.r2dbc.spi.TransactionDefinition}. Subclasses may |
|
|
|
|
|
|
|
* override {@link #createTransactionDefinition(TransactionDefinition)} to customize |
|
|
|
|
|
|
|
* transaction definitions for vendor-specific attributes. |
|
|
|
|
|
|
|
* |
|
|
|
* |
|
|
|
* @author Mark Paluch |
|
|
|
* @author Mark Paluch |
|
|
|
* @author Juergen Hoeller |
|
|
|
* @author Juergen Hoeller |
|
|
|
@ -97,7 +92,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Create a new {@code R2dbcTransactionManager} instance. |
|
|
|
* Create a new {@code R2dbcTransactionManager} instance. |
|
|
|
* A ConnectionFactory has to be set to be able to use it. |
|
|
|
* A {@code ConnectionFactory} has to be set to be able to use it. |
|
|
|
* @see #setConnectionFactory |
|
|
|
* @see #setConnectionFactory |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public R2dbcTransactionManager() {} |
|
|
|
public R2dbcTransactionManager() {} |
|
|
|
@ -114,12 +109,13 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Set the R2DBC {@link ConnectionFactory} that this instance should manage transactions for. |
|
|
|
* Set the R2DBC {@link ConnectionFactory} that this instance should manage transactions |
|
|
|
* <p>This will typically be a locally defined {@code ConnectionFactory}, for example a connection pool. |
|
|
|
* for. This will typically be a locally defined {@code ConnectionFactory}, for example |
|
|
|
* <p><b>The {@code ConnectionFactory} passed in here needs to return independent {@link Connection}s.</b> |
|
|
|
* an R2DBC connection pool. |
|
|
|
* The {@code Connection}s may come from a pool (the typical case), but the {@code ConnectionFactory} |
|
|
|
* <p><b>The {@code ConnectionFactory} passed in here needs to return independent |
|
|
|
* must not return scoped {@code Connection}s or the like. |
|
|
|
* {@link Connection}s.</b> The {@code Connection}s typically come from a connection |
|
|
|
* @see TransactionAwareConnectionFactoryProxy |
|
|
|
* pool but the {@code ConnectionFactory} must not return specifically scoped or |
|
|
|
|
|
|
|
* constrained {@code Connection}s. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setConnectionFactory(@Nullable ConnectionFactory connectionFactory) { |
|
|
|
public void setConnectionFactory(@Nullable ConnectionFactory connectionFactory) { |
|
|
|
this.connectionFactory = connectionFactory; |
|
|
|
this.connectionFactory = connectionFactory; |
|
|
|
@ -183,8 +179,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected boolean isExistingTransaction(Object transaction) { |
|
|
|
protected boolean isExistingTransaction(Object transaction) { |
|
|
|
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; |
|
|
|
return ((ConnectionFactoryTransactionObject) transaction).isTransactionActive(); |
|
|
|
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@ -193,6 +188,11 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
|
|
|
|
|
|
|
|
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; |
|
|
|
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED && |
|
|
|
|
|
|
|
txObject.isTransactionActive()) { |
|
|
|
|
|
|
|
return txObject.createSavepoint(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Mono.defer(() -> { |
|
|
|
return Mono.defer(() -> { |
|
|
|
Mono<Connection> connectionMono; |
|
|
|
Mono<Connection> connectionMono; |
|
|
|
|
|
|
|
|
|
|
|
@ -210,7 +210,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
connectionMono = Mono.just(txObject.getConnectionHolder().getConnection()); |
|
|
|
connectionMono = Mono.just(txObject.getConnectionHolder().getConnection()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return connectionMono.flatMap(con -> Mono.from(doBegin(definition, con)) |
|
|
|
return connectionMono.flatMap(con -> doBegin(definition, con) |
|
|
|
.then(prepareTransactionalConnection(con, definition)) |
|
|
|
.then(prepareTransactionalConnection(con, definition)) |
|
|
|
.doOnSuccess(v -> { |
|
|
|
.doOnSuccess(v -> { |
|
|
|
txObject.getConnectionHolder().setTransactionActive(true); |
|
|
|
txObject.getConnectionHolder().setTransactionActive(true); |
|
|
|
@ -234,12 +234,12 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
}).then(); |
|
|
|
}).then(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Publisher<Void> doBegin(TransactionDefinition definition, Connection con) { |
|
|
|
private Mono<Void> doBegin(TransactionDefinition definition, Connection con) { |
|
|
|
io.r2dbc.spi.TransactionDefinition transactionDefinition = createTransactionDefinition(definition); |
|
|
|
io.r2dbc.spi.TransactionDefinition transactionDefinition = createTransactionDefinition(definition); |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("Starting R2DBC transaction on Connection [" + con + "] using [" + transactionDefinition + "]"); |
|
|
|
logger.debug("Starting R2DBC transaction on Connection [" + con + "] using [" + transactionDefinition + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
return con.beginTransaction(transactionDefinition); |
|
|
|
return Mono.from(con.beginTransaction(transactionDefinition)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -300,12 +300,11 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
GenericReactiveTransaction status) throws TransactionException { |
|
|
|
GenericReactiveTransaction status) throws TransactionException { |
|
|
|
|
|
|
|
|
|
|
|
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); |
|
|
|
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); |
|
|
|
Connection connection = txObject.getConnectionHolder().getConnection(); |
|
|
|
|
|
|
|
if (status.isDebug()) { |
|
|
|
if (status.isDebug()) { |
|
|
|
logger.debug("Committing R2DBC transaction on Connection [" + connection + "]"); |
|
|
|
logger.debug("Committing R2DBC transaction on Connection [" + |
|
|
|
|
|
|
|
txObject.getConnectionHolder().getConnection() + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
return Mono.from(connection.commitTransaction()) |
|
|
|
return txObject.commit().onErrorMap(R2dbcException.class, ex -> translateException("R2DBC commit", ex)); |
|
|
|
.onErrorMap(R2dbcException.class, ex -> translateException("R2DBC commit", ex)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@ -313,12 +312,11 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
GenericReactiveTransaction status) throws TransactionException { |
|
|
|
GenericReactiveTransaction status) throws TransactionException { |
|
|
|
|
|
|
|
|
|
|
|
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); |
|
|
|
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) status.getTransaction(); |
|
|
|
Connection connection = txObject.getConnectionHolder().getConnection(); |
|
|
|
|
|
|
|
if (status.isDebug()) { |
|
|
|
if (status.isDebug()) { |
|
|
|
logger.debug("Rolling back R2DBC transaction on Connection [" + connection + "]"); |
|
|
|
logger.debug("Rolling back R2DBC transaction on Connection [" + |
|
|
|
|
|
|
|
txObject.getConnectionHolder().getConnection() + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
return Mono.from(connection.rollbackTransaction()) |
|
|
|
return txObject.rollback().onErrorMap(R2dbcException.class, ex -> translateException("R2DBC rollback", ex)); |
|
|
|
.onErrorMap(R2dbcException.class, ex -> translateException("R2DBC rollback", ex)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@ -496,6 +494,9 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
|
|
|
|
|
|
|
|
private boolean newConnectionHolder; |
|
|
|
private boolean newConnectionHolder; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
|
|
|
private String savepointName; |
|
|
|
|
|
|
|
|
|
|
|
void setConnectionHolder(@Nullable ConnectionHolder connectionHolder, boolean newConnectionHolder) { |
|
|
|
void setConnectionHolder(@Nullable ConnectionHolder connectionHolder, boolean newConnectionHolder) { |
|
|
|
setConnectionHolder(connectionHolder); |
|
|
|
setConnectionHolder(connectionHolder); |
|
|
|
this.newConnectionHolder = newConnectionHolder; |
|
|
|
this.newConnectionHolder = newConnectionHolder; |
|
|
|
@ -505,10 +506,6 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
return this.newConnectionHolder; |
|
|
|
return this.newConnectionHolder; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void setRollbackOnly() { |
|
|
|
|
|
|
|
getConnectionHolder().setRollbackOnly(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) { |
|
|
|
public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) { |
|
|
|
this.connectionHolder = connectionHolder; |
|
|
|
this.connectionHolder = connectionHolder; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -521,6 +518,34 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager |
|
|
|
public boolean hasConnectionHolder() { |
|
|
|
public boolean hasConnectionHolder() { |
|
|
|
return (this.connectionHolder != null); |
|
|
|
return (this.connectionHolder != null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public boolean isTransactionActive() { |
|
|
|
|
|
|
|
return (this.connectionHolder != null && this.connectionHolder.isTransactionActive()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Mono<Void> createSavepoint() { |
|
|
|
|
|
|
|
ConnectionHolder holder = getConnectionHolder(); |
|
|
|
|
|
|
|
this.savepointName = holder.nextSavepoint(); |
|
|
|
|
|
|
|
return Mono.from(holder.getConnection().createSavepoint(this.savepointName)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Mono<Void> commit() { |
|
|
|
|
|
|
|
Connection connection = getConnectionHolder().getConnection(); |
|
|
|
|
|
|
|
return (this.savepointName != null ? |
|
|
|
|
|
|
|
Mono.from(connection.releaseSavepoint(this.savepointName)) : |
|
|
|
|
|
|
|
Mono.from(connection.commitTransaction())); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Mono<Void> rollback() { |
|
|
|
|
|
|
|
Connection connection = getConnectionHolder().getConnection(); |
|
|
|
|
|
|
|
return (this.savepointName != null ? |
|
|
|
|
|
|
|
Mono.from(connection.rollbackTransactionToSavepoint(this.savepointName)) : |
|
|
|
|
|
|
|
Mono.from(connection.rollbackTransaction())); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void setRollbackOnly() { |
|
|
|
|
|
|
|
getConnectionHolder().setRollbackOnly(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|