diff --git a/spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java b/spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java index ce39e136c1..fee049cacc 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java +++ b/spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ import org.springframework.context.event.ApplicationListenerMethodAdapter; import org.springframework.context.event.EventListener; import org.springframework.context.event.GenericApplicationListener; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.scheduling.annotation.Async; +import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; @@ -68,6 +70,11 @@ public class TransactionalApplicationListenerMethodAdapter extends ApplicationLi if (ann == null) { throw new IllegalStateException("No TransactionalEventListener annotation found on method: " + method); } + if (AnnotatedElementUtils.hasAnnotation(method, Transactional.class) && + !AnnotatedElementUtils.hasAnnotation(method, Async.class)) { + throw new IllegalStateException("@TransactionalEventListener method must not be annotated " + + "with @Transactional, unless when declared as @Async: " + method); + } this.annotation = ann; this.transactionPhase = ann.phase(); } diff --git a/spring-tx/src/test/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapterTests.java b/spring-tx/src/test/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapterTests.java index d59e5af504..89ca5af442 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapterTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,12 +24,15 @@ import org.springframework.context.PayloadApplicationEvent; import org.springframework.context.event.ApplicationListenerMethodAdapter; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.scheduling.annotation.Async; +import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatRuntimeException; /** @@ -123,6 +126,19 @@ public class TransactionalApplicationListenerMethodAdapterTests { assertThat(adapter.getListenerId()).endsWith("identifier"); } + @Test + public void withTransactionalAnnotation() { + Method m = ReflectionUtils.findMethod(SampleEvents.class, "withTransactionalAnnotation", String.class); + assertThatIllegalStateException().isThrownBy(() -> createTestInstance(m)); + } + + @Test + public void withAsyncTransactionalAnnotation() { + Method m = ReflectionUtils.findMethod(SampleEvents.class, "withAsyncTransactionalAnnotation", String.class); + supportsEventType(true, m, createGenericEventType(String.class)); + supportsEventType(false, m, createGenericEventType(Double.class)); + } + private static void assertPhase(Method method, TransactionPhase expected) { assertThat(method).as("Method must not be null").isNotNull(); @@ -194,6 +210,16 @@ public class TransactionalApplicationListenerMethodAdapterTests { @TransactionalEventListener(id = "identifier") public void identified(String data) { } + + @TransactionalEventListener + @Transactional + public void withTransactionalAnnotation(String data) { + } + + @TransactionalEventListener + @Async @Transactional + public void withAsyncTransactionalAnnotation(String data) { + } } }