Skip to content

Commit 8562972

Browse files
jhoellerpull[bot]
authored andcommitted
Consider target transaction manager for reactive transaction decision
Closes spring-projectsgh-23832
1 parent 23e04d6 commit 8562972

File tree

2 files changed

+74
-125
lines changed

2 files changed

+74
-125
lines changed

spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java

Lines changed: 60 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,11 @@ public static TransactionStatus currentTransactionStatus() throws NoTransactionE
173173
@Nullable
174174
private BeanFactory beanFactory;
175175

176-
private final ConcurrentMap<Object, Object> transactionManagerCache = new ConcurrentReferenceHashMap<>(4);
176+
private final ConcurrentMap<Object, TransactionManager> transactionManagerCache =
177+
new ConcurrentReferenceHashMap<>(4);
178+
179+
private final ConcurrentMap<Method, ReactiveTransactionSupport> transactionSupportCache =
180+
new ConcurrentReferenceHashMap<>(1024);
177181

178182

179183
protected TransactionAspectSupport() {
@@ -301,7 +305,7 @@ public void afterPropertiesSet() {
301305
if (getTransactionManager() == null && this.beanFactory == null) {
302306
throw new IllegalStateException(
303307
"Set the 'transactionManager' property or make sure to run within a BeanFactory " +
304-
"containing a PlatformTransactionManager bean!");
308+
"containing a TransactionManager bean!");
305309
}
306310
if (getTransactionAttributeSource() == null) {
307311
throw new IllegalStateException(
@@ -325,26 +329,35 @@ public void afterPropertiesSet() {
325329
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
326330
final InvocationCallback invocation) throws Throwable {
327331

328-
if (this.reactiveAdapterRegistry != null) {
329-
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
330-
throw new TransactionUsageException("Unsupported annotated transaction on suspending function detected: "
331-
+ method + ". Use TransactionalOperator.transactional extensions instead.");
332-
}
333-
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
334-
if (adapter != null) {
335-
return new ReactiveTransactionSupport(adapter).invokeWithinTransaction(method, targetClass, invocation);
336-
}
337-
}
338-
339332
// If the transaction attribute is null, the method is non-transactional.
340333
TransactionAttributeSource tas = getTransactionAttributeSource();
341334
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
342-
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
335+
final TransactionManager tm = determineTransactionManager(txAttr);
336+
337+
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
338+
ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
339+
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
340+
throw new TransactionUsageException(
341+
"Unsupported annotated transaction on suspending function detected: " + method +
342+
". Use TransactionalOperator.transactional extensions instead.");
343+
}
344+
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
345+
if (adapter == null) {
346+
throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
347+
method.getReturnType());
348+
}
349+
return new ReactiveTransactionSupport(adapter);
350+
});
351+
return txSupport.invokeWithinTransaction(
352+
method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
353+
}
354+
355+
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
343356
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
344357

345-
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
358+
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
346359
// Standard transaction demarcation with getTransaction and commit/rollback calls.
347-
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
360+
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
348361

349362
Object retVal;
350363
try {
@@ -378,8 +391,8 @@ protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targe
378391

379392
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
380393
try {
381-
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
382-
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
394+
Object result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
395+
TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
383396
try {
384397
Object retVal = invocation.proceedWithInvocation();
385398
if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
@@ -446,10 +459,10 @@ protected void clearTransactionManagerCache() {
446459
* Determine the specific transaction manager to use for the given transaction.
447460
*/
448461
@Nullable
449-
protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
462+
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
450463
// Do not attempt to lookup tx manager if no tx attributes are set
451464
if (txAttr == null || this.beanFactory == null) {
452-
return asPlatformTransactionManager(getTransactionManager());
465+
return getTransactionManager();
453466
}
454467

455468
String qualifier = txAttr.getQualifier();
@@ -460,12 +473,11 @@ else if (StringUtils.hasText(this.transactionManagerBeanName)) {
460473
return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
461474
}
462475
else {
463-
PlatformTransactionManager defaultTransactionManager = asPlatformTransactionManager(getTransactionManager());
476+
TransactionManager defaultTransactionManager = getTransactionManager();
464477
if (defaultTransactionManager == null) {
465-
defaultTransactionManager = asPlatformTransactionManager(
466-
this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY));
478+
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
467479
if (defaultTransactionManager == null) {
468-
defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
480+
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
469481
this.transactionManagerCache.putIfAbsent(
470482
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
471483
}
@@ -474,11 +486,11 @@ else if (StringUtils.hasText(this.transactionManagerBeanName)) {
474486
}
475487
}
476488

477-
private PlatformTransactionManager determineQualifiedTransactionManager(BeanFactory beanFactory, String qualifier) {
478-
PlatformTransactionManager txManager = asPlatformTransactionManager(this.transactionManagerCache.get(qualifier));
489+
private TransactionManager determineQualifiedTransactionManager(BeanFactory beanFactory, String qualifier) {
490+
TransactionManager txManager = this.transactionManagerCache.get(qualifier);
479491
if (txManager == null) {
480492
txManager = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
481-
beanFactory, PlatformTransactionManager.class, qualifier);
493+
beanFactory, TransactionManager.class, qualifier);
482494
this.transactionManagerCache.putIfAbsent(qualifier, txManager);
483495
}
484496
return txManager;
@@ -841,33 +853,30 @@ public ReactiveTransactionSupport(ReactiveAdapter adapter) {
841853
this.adapter = adapter;
842854
}
843855

844-
public Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, InvocationCallback invocation) {
845-
// If the transaction attribute is null, the method is non-transactional.
846-
TransactionAttributeSource tas = getTransactionAttributeSource();
847-
TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
848-
ReactiveTransactionManager tm = determineTransactionManager(txAttr);
856+
public Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
857+
InvocationCallback invocation, @Nullable TransactionAttribute txAttr, ReactiveTransactionManager rtm) {
858+
849859
String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
850860

851861
// Optimize for Mono
852862
if (Mono.class.isAssignableFrom(method.getReturnType())) {
853863
return TransactionContextManager.currentContext().flatMap(context ->
854-
createTransactionIfNecessary(tm, txAttr, joinpointIdentification).flatMap(it -> {
864+
createTransactionIfNecessary(rtm, txAttr, joinpointIdentification).flatMap(it -> {
855865
try {
856866
// Need re-wrapping until we get hold of the exception through usingWhen.
857-
return Mono
858-
.<Object, ReactiveTransactionInfo>usingWhen(
859-
Mono.just(it),
860-
txInfo -> {
861-
try {
862-
return (Mono<?>) invocation.proceedWithInvocation();
863-
}
864-
catch (Throwable ex) {
865-
return Mono.error(ex);
866-
}
867-
},
868-
this::commitTransactionAfterReturning,
869-
(txInfo, err) -> Mono.empty(),
870-
this::commitTransactionAfterReturning)
867+
return Mono.<Object, ReactiveTransactionInfo>usingWhen(
868+
Mono.just(it),
869+
txInfo -> {
870+
try {
871+
return (Mono<?>) invocation.proceedWithInvocation();
872+
}
873+
catch (Throwable ex) {
874+
return Mono.error(ex);
875+
}
876+
},
877+
this::commitTransactionAfterReturning,
878+
(txInfo, err) -> Mono.empty(),
879+
this::commitTransactionAfterReturning)
871880
.onErrorResume(ex ->
872881
completeTransactionAfterThrowing(it, ex).then(Mono.error(ex)));
873882
}
@@ -881,7 +890,7 @@ public Object invokeWithinTransaction(Method method, @Nullable Class<?> targetCl
881890

882891
// Any other reactive type, typically a Flux
883892
return this.adapter.fromPublisher(TransactionContextManager.currentContext().flatMapMany(context ->
884-
createTransactionIfNecessary(tm, txAttr, joinpointIdentification).flatMapMany(it -> {
893+
createTransactionIfNecessary(rtm, txAttr, joinpointIdentification).flatMapMany(it -> {
885894
try {
886895
// Need re-wrapping until we get hold of the exception through usingWhen.
887896
return Flux
@@ -909,58 +918,8 @@ public Object invokeWithinTransaction(Method method, @Nullable Class<?> targetCl
909918
.subscriberContext(TransactionContextManager.getOrCreateContextHolder()));
910919
}
911920

912-
@Nullable
913-
private ReactiveTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
914-
// Do not attempt to lookup tx manager if no tx attributes are set
915-
if (txAttr == null || beanFactory == null) {
916-
return asReactiveTransactionManager(getTransactionManager());
917-
}
918-
919-
String qualifier = txAttr.getQualifier();
920-
if (StringUtils.hasText(qualifier)) {
921-
return determineQualifiedTransactionManager(beanFactory, qualifier);
922-
}
923-
else if (StringUtils.hasText(transactionManagerBeanName)) {
924-
return determineQualifiedTransactionManager(beanFactory, transactionManagerBeanName);
925-
}
926-
else {
927-
ReactiveTransactionManager defaultTransactionManager = asReactiveTransactionManager(getTransactionManager());
928-
if (defaultTransactionManager == null) {
929-
defaultTransactionManager = asReactiveTransactionManager(
930-
transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY));
931-
if (defaultTransactionManager == null) {
932-
defaultTransactionManager = beanFactory.getBean(ReactiveTransactionManager.class);
933-
transactionManagerCache.putIfAbsent(
934-
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
935-
}
936-
}
937-
return defaultTransactionManager;
938-
}
939-
}
940-
941-
private ReactiveTransactionManager determineQualifiedTransactionManager(BeanFactory beanFactory, String qualifier) {
942-
ReactiveTransactionManager txManager = asReactiveTransactionManager(transactionManagerCache.get(qualifier));
943-
if (txManager == null) {
944-
txManager = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
945-
beanFactory, ReactiveTransactionManager.class, qualifier);
946-
transactionManagerCache.putIfAbsent(qualifier, txManager);
947-
}
948-
return txManager;
949-
}
950-
951-
@Nullable
952-
private ReactiveTransactionManager asReactiveTransactionManager(@Nullable Object transactionManager) {
953-
if (transactionManager == null || transactionManager instanceof ReactiveTransactionManager) {
954-
return (ReactiveTransactionManager) transactionManager;
955-
}
956-
else {
957-
throw new IllegalStateException(
958-
"Specified transaction manager is not a ReactiveTransactionManager: " + transactionManager);
959-
}
960-
}
961-
962921
@SuppressWarnings("serial")
963-
private Mono<ReactiveTransactionInfo> createTransactionIfNecessary(@Nullable ReactiveTransactionManager tm,
922+
private Mono<ReactiveTransactionInfo> createTransactionIfNecessary(ReactiveTransactionManager tm,
964923
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
965924

966925
// If no name specified, apply method identification as transaction name.
@@ -972,21 +931,9 @@ public String getName() {
972931
}
973932
};
974933
}
975-
TransactionAttribute attrToUse = txAttr;
976-
977-
Mono<ReactiveTransaction> tx = Mono.empty();
978-
if (txAttr != null) {
979-
if (tm != null) {
980-
tx = tm.getReactiveTransaction(txAttr);
981-
}
982-
else {
983-
if (logger.isDebugEnabled()) {
984-
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
985-
"] because no transaction manager has been configured");
986-
}
987-
}
988-
}
989934

935+
final TransactionAttribute attrToUse = txAttr;
936+
Mono<ReactiveTransaction> tx = (attrToUse != null ? tm.getReactiveTransaction(attrToUse) : Mono.empty());
990937
return tx.map(it -> prepareTransactionInfo(tm, attrToUse, joinpointIdentification, it)).switchIfEmpty(
991938
Mono.defer(() -> Mono.just(prepareTransactionInfo(tm, attrToUse, joinpointIdentification, null))));
992939
}

spring-tx/src/test/java/org/springframework/transaction/interceptor/TransactionInterceptorTests.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.transaction.PlatformTransactionManager;
2929
import org.springframework.transaction.TransactionDefinition;
3030
import org.springframework.transaction.TransactionException;
31+
import org.springframework.transaction.TransactionManager;
3132
import org.springframework.transaction.TransactionStatus;
3233
import org.springframework.util.SerializationTestUtils;
3334

@@ -42,12 +43,13 @@
4243
* Mock object based tests for TransactionInterceptor.
4344
*
4445
* @author Rod Johnson
46+
* @author Juergen Hoeller
4547
* @since 16.03.2003
4648
*/
4749
public class TransactionInterceptorTests extends AbstractTransactionAspectTests {
4850

4951
@Override
50-
protected Object advised(Object target, PlatformTransactionManager ptm, TransactionAttributeSource[] tas) throws Exception {
52+
protected Object advised(Object target, PlatformTransactionManager ptm, TransactionAttributeSource[] tas) {
5153
TransactionInterceptor ti = new TransactionInterceptor();
5254
ti.setTransactionManager(ptm);
5355
ti.setTransactionAttributeSources(tas);
@@ -214,14 +216,14 @@ public void determineTransactionManagerWithQualifierSeveralTimes() {
214216

215217
DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
216218
attribute.setQualifier("fooTransactionManager");
217-
PlatformTransactionManager actual = ti.determineTransactionManager(attribute);
219+
TransactionManager actual = ti.determineTransactionManager(attribute);
218220
assertThat(actual).isSameAs(txManager);
219221

220222
// Call again, should be cached
221-
PlatformTransactionManager actual2 = ti.determineTransactionManager(attribute);
223+
TransactionManager actual2 = ti.determineTransactionManager(attribute);
222224
assertThat(actual2).isSameAs(txManager);
223225
verify(beanFactory, times(1)).containsBean("fooTransactionManager");
224-
verify(beanFactory, times(1)).getBean("fooTransactionManager", PlatformTransactionManager.class);
226+
verify(beanFactory, times(1)).getBean("fooTransactionManager", TransactionManager.class);
225227
}
226228

227229
@Test
@@ -233,13 +235,13 @@ public void determineTransactionManagerWithBeanNameSeveralTimes() {
233235
PlatformTransactionManager txManager = associateTransactionManager(beanFactory, "fooTransactionManager");
234236

235237
DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
236-
PlatformTransactionManager actual = ti.determineTransactionManager(attribute);
238+
TransactionManager actual = ti.determineTransactionManager(attribute);
237239
assertThat(actual).isSameAs(txManager);
238240

239241
// Call again, should be cached
240-
PlatformTransactionManager actual2 = ti.determineTransactionManager(attribute);
242+
TransactionManager actual2 = ti.determineTransactionManager(attribute);
241243
assertThat(actual2).isSameAs(txManager);
242-
verify(beanFactory, times(1)).getBean("fooTransactionManager", PlatformTransactionManager.class);
244+
verify(beanFactory, times(1)).getBean("fooTransactionManager", TransactionManager.class);
243245
}
244246

245247
@Test
@@ -248,16 +250,16 @@ public void determineTransactionManagerDefaultSeveralTimes() {
248250
TransactionInterceptor ti = simpleTransactionInterceptor(beanFactory);
249251

250252
PlatformTransactionManager txManager = mock(PlatformTransactionManager.class);
251-
given(beanFactory.getBean(PlatformTransactionManager.class)).willReturn(txManager);
253+
given(beanFactory.getBean(TransactionManager.class)).willReturn(txManager);
252254

253255
DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
254-
PlatformTransactionManager actual = ti.determineTransactionManager(attribute);
256+
TransactionManager actual = ti.determineTransactionManager(attribute);
255257
assertThat(actual).isSameAs(txManager);
256258

257259
// Call again, should be cached
258-
PlatformTransactionManager actual2 = ti.determineTransactionManager(attribute);
260+
TransactionManager actual2 = ti.determineTransactionManager(attribute);
259261
assertThat(actual2).isSameAs(txManager);
260-
verify(beanFactory, times(1)).getBean(PlatformTransactionManager.class);
262+
verify(beanFactory, times(1)).getBean(TransactionManager.class);
261263
}
262264

263265

@@ -299,7 +301,7 @@ private TransactionInterceptor simpleTransactionInterceptor(BeanFactory beanFact
299301
private PlatformTransactionManager associateTransactionManager(BeanFactory beanFactory, String name) {
300302
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
301303
given(beanFactory.containsBean(name)).willReturn(true);
302-
given(beanFactory.getBean(name, PlatformTransactionManager.class)).willReturn(transactionManager);
304+
given(beanFactory.getBean(name, TransactionManager.class)).willReturn(transactionManager);
303305
return transactionManager;
304306
}
305307

0 commit comments

Comments
 (0)