21
21
import io .r2dbc .spi .Connection ;
22
22
import io .r2dbc .spi .ConnectionFactory ;
23
23
import io .r2dbc .spi .IsolationLevel ;
24
+ import io .r2dbc .spi .Option ;
24
25
import io .r2dbc .spi .R2dbcException ;
25
26
import io .r2dbc .spi .Result ;
27
+ import org .reactivestreams .Publisher ;
26
28
import reactor .core .publisher .Mono ;
27
29
28
30
import org .springframework .beans .factory .InitializingBean ;
47
49
* <p><b>Note: The {@code ConnectionFactory} that this transaction manager
48
50
* operates on needs to return independent {@code Connection}s.</b>
49
51
* The {@code Connection}s may come from a pool (the typical case), but the
50
- * {@code ConnectionFactory} must not return scoped scoped {@code Connection}s
52
+ * {@code ConnectionFactory} must not return scoped {@code Connection}s
51
53
* or the like. This transaction manager will associate {@code Connection}
52
54
* with context-bound transactions itself, according to the specified propagation
53
55
* behavior. It assumes that a separate, independent {@code Connection} can
72
74
* synchronizations (if synchronization is generally active), assuming resources
73
75
* operating on the underlying R2DBC {@code Connection}.
74
76
*
77
+ * <p>Spring's {@code TransactionDefinition} attributes are carried forward to R2DBC drivers
78
+ * using extensible R2DBC {@link io.r2dbc.spi.TransactionDefinition}. Subclasses may
79
+ * override {@link #createTransactionDefinition(TransactionDefinition)} to customize
80
+ * transaction definitions for vendor-specific attributes.
81
+ *
75
82
* @author Mark Paluch
76
83
* @since 5.3
77
84
* @see ConnectionFactoryUtils#getConnection(ConnectionFactory)
@@ -203,7 +210,8 @@ protected Mono<Void> doBegin(TransactionSynchronizationManager synchronizationMa
203
210
}
204
211
205
212
return connectionMono .flatMap (con -> {
206
- return prepareTransactionalConnection (con , definition , transaction ).then (Mono .from (con .beginTransaction ()))
213
+ return prepareTransactionalConnection (con , definition , transaction )
214
+ .then (Mono .from (doBegin (definition , con )))
207
215
.doOnSuccess (v -> {
208
216
txObject .getConnectionHolder ().setTransactionActive (true );
209
217
Duration timeout = determineTimeout (definition );
@@ -230,6 +238,31 @@ protected Mono<Void> doBegin(TransactionSynchronizationManager synchronizationMa
230
238
}).then ();
231
239
}
232
240
241
+ private Publisher <Void > doBegin (TransactionDefinition definition , Connection con ) {
242
+ io .r2dbc .spi .TransactionDefinition transactionDefinition = createTransactionDefinition (definition );
243
+ if (logger .isDebugEnabled ()) {
244
+ logger .debug ("Starting R2DBC transaction on Connection [" + con + "] using [" + transactionDefinition + "]" );
245
+ }
246
+ return con .beginTransaction (transactionDefinition );
247
+ }
248
+
249
+ /**
250
+ * Determine the transaction definition from our {@code TransactionDefinition}.
251
+ * Can be overridden to wrap the R2DBC {@code TransactionDefinition} to adjust or
252
+ * enhance transaction attributes.
253
+ * @param definition the transaction definition
254
+ * @return the actual transaction definition to use
255
+ * @since 6.0
256
+ * @see io.r2dbc.spi.TransactionDefinition
257
+ */
258
+ protected io .r2dbc .spi .TransactionDefinition createTransactionDefinition (TransactionDefinition definition ) {
259
+ // Apply specific isolation level, if any.
260
+ IsolationLevel isolationLevelToUse = resolveIsolationLevel (definition .getIsolationLevel ());
261
+ return new ExtendedTransactionDefinition (definition .getName (), definition .isReadOnly (),
262
+ definition .getIsolationLevel () != TransactionDefinition .ISOLATION_DEFAULT ? isolationLevelToUse : null ,
263
+ determineTimeout (definition ));
264
+ }
265
+
233
266
/**
234
267
* Determine the actual timeout to use for the given definition.
235
268
* Will fall back to this manager's default timeout if the
@@ -375,21 +408,6 @@ protected Mono<Void> prepareTransactionalConnection(
375
408
.then ();
376
409
}
377
410
378
- // Apply specific isolation level, if any.
379
- IsolationLevel isolationLevelToUse = resolveIsolationLevel (definition .getIsolationLevel ());
380
- if (isolationLevelToUse != null && definition .getIsolationLevel () != TransactionDefinition .ISOLATION_DEFAULT ) {
381
-
382
- if (logger .isDebugEnabled ()) {
383
- logger .debug ("Changing isolation level of R2DBC Connection [" + con + "] to " + isolationLevelToUse .asSql ());
384
- }
385
- IsolationLevel currentIsolation = con .getTransactionIsolationLevel ();
386
- if (!currentIsolation .asSql ().equalsIgnoreCase (isolationLevelToUse .asSql ())) {
387
-
388
- txObject .setPreviousIsolationLevel (currentIsolation );
389
- prepare = prepare .then (Mono .from (con .setTransactionIsolationLevel (isolationLevelToUse )));
390
- }
391
- }
392
-
393
411
// Switch to manual commit if necessary. This is very expensive in some R2DBC drivers,
394
412
// so we don't want to do it unnecessarily (for example if we've explicitly
395
413
// configured the connection pool to set it already).
@@ -436,6 +454,62 @@ protected RuntimeException translateException(String task, R2dbcException ex) {
436
454
}
437
455
438
456
457
+ /**
458
+ * Extended R2DBC transaction definition object providing transaction attributes
459
+ * to R2DBC drivers when starting a transaction.
460
+ */
461
+ private record ExtendedTransactionDefinition (@ Nullable String transactionName ,
462
+ boolean readOnly ,
463
+ @ Nullable IsolationLevel isolationLevel ,
464
+ Duration lockWaitTimeout ) implements io .r2dbc .spi .TransactionDefinition {
465
+
466
+ private ExtendedTransactionDefinition (@ Nullable String transactionName , boolean readOnly ,
467
+ @ Nullable IsolationLevel isolationLevel , Duration lockWaitTimeout ) {
468
+ this .transactionName = transactionName ;
469
+ this .readOnly = readOnly ;
470
+ this .isolationLevel = isolationLevel ;
471
+ this .lockWaitTimeout = lockWaitTimeout ;
472
+ }
473
+
474
+
475
+ @ Override
476
+ @ SuppressWarnings ("unchecked" )
477
+ public <T > T getAttribute (Option <T > option ) {
478
+ return (T ) doGetValue (option );
479
+ }
480
+
481
+ @ Nullable
482
+ private Object doGetValue (Option <?> option ) {
483
+ if (io .r2dbc .spi .TransactionDefinition .ISOLATION_LEVEL .equals (option )) {
484
+ return this .isolationLevel ;
485
+ }
486
+ if (io .r2dbc .spi .TransactionDefinition .NAME .equals (option )) {
487
+ return this .transactionName ;
488
+ }
489
+ if (io .r2dbc .spi .TransactionDefinition .READ_ONLY .equals (option )) {
490
+ return this .readOnly ;
491
+ }
492
+ if (io .r2dbc .spi .TransactionDefinition .LOCK_WAIT_TIMEOUT .equals (option )
493
+ && !this .lockWaitTimeout .isZero ()) {
494
+ return this .lockWaitTimeout ;
495
+ }
496
+ return null ;
497
+ }
498
+
499
+ @ Override
500
+ public String toString () {
501
+ StringBuilder sb = new StringBuilder ();
502
+ sb .append (getClass ().getSimpleName ());
503
+ sb .append (" [transactionName='" ).append (this .transactionName ).append ('\'' );
504
+ sb .append (", readOnly=" ).append (this .readOnly );
505
+ sb .append (", isolationLevel=" ).append (this .isolationLevel );
506
+ sb .append (", lockWaitTimeout=" ).append (this .lockWaitTimeout );
507
+ sb .append (']' );
508
+ return sb .toString ();
509
+ }
510
+ }
511
+
512
+
439
513
/**
440
514
* ConnectionFactory transaction object, representing a ConnectionHolder.
441
515
* Used as transaction object by R2dbcTransactionManager.
0 commit comments