Skip to content

Commit 4f8b347

Browse files
committed
Do not trigger transactional event listener if no transaction is active
This commit fixes the behaviour of not triggering a transactional event listener if no transaction is active. Previously, a transaction boundary was all that was necessary to trigger the listener regardless of the fact there was an active transaction. This commit now prevents `Propagation.NOT_SUPPORTED` and `Propagation.SUPPORTS` without an active transaction to trigger the listener. Closes gh-23276
1 parent b420782 commit 4f8b347

File tree

3 files changed

+84
-27
lines changed

3 files changed

+84
-27
lines changed

spring-tx/src/main/java/org/springframework/transaction/event/ApplicationListenerMethodTransactionalAdapter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -60,7 +60,8 @@ public ApplicationListenerMethodTransactionalAdapter(String beanName, Class<?> t
6060

6161
@Override
6262
public void onApplicationEvent(ApplicationEvent event) {
63-
if (TransactionSynchronizationManager.isSynchronizationActive()) {
63+
if (TransactionSynchronizationManager.isSynchronizationActive()
64+
&& TransactionSynchronizationManager.isActualTransactionActive()) {
6465
TransactionSynchronization transactionSynchronization = createTransactionSynchronization(event);
6566
TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
6667
}

spring-tx/src/main/java/org/springframework/transaction/event/TransactionalEventListener.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,9 +28,9 @@
2828
/**
2929
* An {@link EventListener} that is invoked according to a {@link TransactionPhase}.
3030
*
31-
* <p>If the event is not published within the boundaries of a managed transaction, the
32-
* event is discarded unless the {@link #fallbackExecution} flag is explicitly set. If a
33-
* transaction is running, the event is processed according to its {@code TransactionPhase}.
31+
* <p>If the event is not published within an active transaction, the event is discarded
32+
* unless the {@link #fallbackExecution} flag is explicitly set. If a transaction is
33+
* running, the event is processed according to its {@code TransactionPhase}.
3434
*
3535
* <p>Adding {@link org.springframework.core.annotation.Order @Order} to your annotated
3636
* method allows you to prioritize that listener amongst other listeners running before

spring-tx/src/test/java/org/springframework/transaction/event/TransactionalEventListenerTests.java

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.junit.Test;
3131

3232
import org.springframework.beans.factory.annotation.Autowired;
33+
import org.springframework.context.ApplicationEventPublisher;
3334
import org.springframework.context.ConfigurableApplicationContext;
3435
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3536
import org.springframework.context.annotation.Bean;
@@ -39,19 +40,16 @@
3940
import org.springframework.stereotype.Component;
4041
import org.springframework.tests.transaction.CallCountingTransactionManager;
4142
import org.springframework.transaction.annotation.EnableTransactionManagement;
43+
import org.springframework.transaction.annotation.Propagation;
4244
import org.springframework.transaction.annotation.Transactional;
4345
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
4446
import org.springframework.transaction.support.TransactionSynchronizationManager;
4547
import org.springframework.transaction.support.TransactionTemplate;
4648
import org.springframework.util.LinkedMultiValueMap;
4749
import org.springframework.util.MultiValueMap;
4850

49-
import static org.assertj.core.api.Assertions.assertThat;
50-
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
51-
import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT;
52-
import static org.springframework.transaction.event.TransactionPhase.AFTER_COMPLETION;
53-
import static org.springframework.transaction.event.TransactionPhase.AFTER_ROLLBACK;
54-
import static org.springframework.transaction.event.TransactionPhase.BEFORE_COMMIT;
51+
import static org.assertj.core.api.Assertions.*;
52+
import static org.springframework.transaction.event.TransactionPhase.*;
5553

5654
/**
5755
* Integration tests for {@link TransactionalEventListener} support
@@ -66,7 +64,7 @@ public class TransactionalEventListenerTests {
6664

6765
private EventCollector eventCollector;
6866

69-
private TransactionTemplate transactionTemplate = new TransactionTemplate(new CallCountingTransactionManager());
67+
private TransactionTemplate transactionTemplate;
7068

7169

7270
@After
@@ -148,7 +146,7 @@ public void afterCommit() {
148146

149147
@Test
150148
public void afterCommitWithTransactionalComponentListenerProxiedViaDynamicProxy() {
151-
load(TransactionalConfiguration.class, TransactionalComponentTestListener.class);
149+
load(TransactionalComponentTestListener.class);
152150
this.transactionTemplate.execute(status -> {
153151
getContext().publishEvent("SKIP");
154152
getEventCollector().assertNoEventReceived();
@@ -250,6 +248,38 @@ public void noTransaction() {
250248
getEventCollector().assertTotalEventsCount(0);
251249
}
252250

251+
@Test
252+
public void transactionDemarcationWithNotSupportedPropagation() {
253+
load(BeforeCommitTestListener.class, AfterCompletionTestListener.class);
254+
getContext().getBean(TestBean.class).notSupported();
255+
getEventCollector().assertTotalEventsCount(0);
256+
}
257+
258+
@Test
259+
public void transactionDemarcationWithSupportsPropagationAndNoTransaction() {
260+
load(BeforeCommitTestListener.class, AfterCompletionTestListener.class);
261+
getContext().getBean(TestBean.class).supports();
262+
getEventCollector().assertTotalEventsCount(0);
263+
}
264+
265+
@Test
266+
public void transactionDemarcationWithSupportsPropagationAndExistingTransaction() {
267+
load(BeforeCommitTestListener.class, AfterCompletionTestListener.class);
268+
this.transactionTemplate.execute(status -> {
269+
getContext().getBean(TestBean.class).supports();
270+
getEventCollector().assertNoEventReceived();
271+
return null;
272+
});
273+
getEventCollector().assertTotalEventsCount(2);
274+
}
275+
276+
@Test
277+
public void transactionDemarcationWithRequiredPropagation() {
278+
load(BeforeCommitTestListener.class, AfterCompletionTestListener.class);
279+
getContext().getBean(TestBean.class).required();
280+
getEventCollector().assertTotalEventsCount(2);
281+
}
282+
253283
@Test
254284
public void noTransactionWithFallbackExecution() {
255285
load(FallbackExecutionTestListener.class);
@@ -299,49 +329,50 @@ public void conditionFoundOnMetaAnnotation() {
299329

300330

301331
protected EventCollector getEventCollector() {
302-
return eventCollector;
332+
return this.eventCollector;
303333
}
304334

305335
protected ConfigurableApplicationContext getContext() {
306-
return context;
336+
return this.context;
307337
}
308338

309339
private void load(Class<?>... classes) {
310340
List<Class<?>> allClasses = new ArrayList<>();
311341
allClasses.add(BasicConfiguration.class);
312342
allClasses.addAll(Arrays.asList(classes));
313-
doLoad(allClasses.toArray(new Class<?>[allClasses.size()]));
343+
doLoad(allClasses.toArray(new Class<?>[0]));
314344
}
315345

316346
private void doLoad(Class<?>... classes) {
317347
this.context = new AnnotationConfigApplicationContext(classes);
318348
this.eventCollector = this.context.getBean(EventCollector.class);
349+
this.transactionTemplate = this.context.getBean(TransactionTemplate.class);
319350
}
320351

321352

322353
@Configuration
354+
@EnableTransactionManagement
323355
static class BasicConfiguration {
324356

325-
@Bean // set automatically with tx management
326-
public TransactionalEventListenerFactory transactionalEventListenerFactory() {
327-
return new TransactionalEventListenerFactory();
328-
}
329-
330357
@Bean
331358
public EventCollector eventCollector() {
332359
return new EventCollector();
333360
}
334-
}
335-
336361

337-
@EnableTransactionManagement
338-
@Configuration
339-
static class TransactionalConfiguration {
362+
@Bean
363+
public TestBean testBean(ApplicationEventPublisher eventPublisher) {
364+
return new TestBean(eventPublisher);
365+
}
340366

341367
@Bean
342368
public CallCountingTransactionManager transactionManager() {
343369
return new CallCountingTransactionManager();
344370
}
371+
372+
@Bean
373+
public TransactionTemplate transactionTemplate() {
374+
return new TransactionTemplate(transactionManager());
375+
}
345376
}
346377

347378

@@ -399,6 +430,31 @@ public void assertTotalEventsCount(int number) {
399430
}
400431

401432

433+
static class TestBean {
434+
435+
private final ApplicationEventPublisher eventPublisher;
436+
437+
TestBean(ApplicationEventPublisher eventPublisher) {
438+
this.eventPublisher = eventPublisher;
439+
}
440+
441+
@Transactional(propagation = Propagation.NOT_SUPPORTED)
442+
public void notSupported() {
443+
this.eventPublisher.publishEvent("test");
444+
}
445+
446+
@Transactional(propagation = Propagation.SUPPORTS)
447+
public void supports() {
448+
this.eventPublisher.publishEvent("test");
449+
}
450+
451+
@Transactional(propagation = Propagation.REQUIRED)
452+
public void required() {
453+
this.eventPublisher.publishEvent("test");
454+
}
455+
}
456+
457+
402458
static abstract class BaseTransactionalTestListener {
403459

404460
static final String FAIL_MSG = "FAIL";

0 commit comments

Comments
 (0)