diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java index 265e00167c6..ebbf7e3c566 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-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. @@ -21,6 +21,7 @@ import java.time.Duration; import jakarta.jms.ConnectionFactory; import jakarta.jms.ExceptionListener; +import org.springframework.boot.autoconfigure.jms.JmsProperties.Listener.Session; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.destination.DestinationResolver; @@ -32,6 +33,7 @@ import org.springframework.util.Assert; * * @author Stephane Nicoll * @author Eddú Meléndez + * @author Vedran Pavic * @since 1.3.3 */ public final class DefaultJmsListenerContainerFactoryConfigurer { @@ -101,12 +103,18 @@ public final class DefaultJmsListenerContainerFactoryConfigurer { Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); factory.setConnectionFactory(connectionFactory); factory.setPubSubDomain(this.jmsProperties.isPubSubDomain()); + JmsProperties.Listener listener = this.jmsProperties.getListener(); + Session session = listener.getSession(); + Boolean sessionTransacted = session.getTransacted(); if (this.transactionManager != null) { factory.setTransactionManager(this.transactionManager); } - else { + else if (sessionTransacted == null) { factory.setSessionTransacted(true); } + if (sessionTransacted != null) { + factory.setSessionTransacted(sessionTransacted); + } if (this.destinationResolver != null) { factory.setDestinationResolver(this.destinationResolver); } @@ -116,9 +124,8 @@ public final class DefaultJmsListenerContainerFactoryConfigurer { if (this.exceptionListener != null) { factory.setExceptionListener(this.exceptionListener); } - JmsProperties.Listener listener = this.jmsProperties.getListener(); factory.setAutoStartup(listener.isAutoStartup()); - factory.setSessionAcknowledgeMode(listener.getSession().getAcknowledgeMode().getMode()); + factory.setSessionAcknowledgeMode(session.getAcknowledgeMode().getMode()); String concurrency = listener.formatConcurrency(); if (concurrency != null) { factory.setConcurrency(concurrency); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java index 91880a571ee..cc6d3140690 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java @@ -234,6 +234,12 @@ public class JmsProperties { */ private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO; + /** + * Whether the listener container should use transacted JMS sessions. Defaults + * to false in the presence of a JtaTransactionManager and true otherwise. + */ + private Boolean transacted; + public AcknowledgeMode getAcknowledgeMode() { return this.acknowledgeMode; } @@ -242,6 +248,14 @@ public class JmsProperties { this.acknowledgeMode = acknowledgeMode; } + public Boolean getTransacted() { + return this.transacted; + } + + public void setTransacted(Boolean transacted) { + this.transacted = transacted; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java index d7cca9e7cef..49f5ddb3b27 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java @@ -57,6 +57,7 @@ import static org.mockito.Mockito.mock; * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Eddú Meléndez + * @author Vedran Pavic */ class JmsAutoConfigurationTests { @@ -143,7 +144,8 @@ class JmsAutoConfigurationTests { void testJmsListenerContainerFactoryWithCustomSettings() { this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) .withPropertyValues("spring.jms.listener.autoStartup=false", - "spring.jms.listener.session.acknowledgeMode=client", "spring.jms.listener.minConcurrency=2", + "spring.jms.listener.session.acknowledgeMode=client", + "spring.jms.listener.session.transacted=false", "spring.jms.listener.minConcurrency=2", "spring.jms.listener.receiveTimeout=2s", "spring.jms.listener.maxConcurrency=10") .run(this::testJmsListenerContainerFactoryWithCustomSettings); } @@ -152,6 +154,7 @@ class JmsAutoConfigurationTests { DefaultMessageListenerContainer container = getContainer(loaded, "jmsListenerContainerFactory"); assertThat(container.isAutoStartup()).isFalse(); assertThat(container.getSessionAcknowledgeMode()).isEqualTo(Session.CLIENT_ACKNOWLEDGE); + assertThat(container.isSessionTransacted()).isFalse(); assertThat(container.getConcurrentConsumers()).isEqualTo(2); assertThat(container.getMaxConcurrentConsumers()).isEqualTo(10); assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 2000L); @@ -179,6 +182,18 @@ class JmsAutoConfigurationTests { }); } + @Test + void testDefaultContainerFactoryWithJtaTransactionManagerAndSessionTransactedEnabled() { + this.contextRunner.withUserConfiguration(TestConfiguration7.class, EnableJmsConfiguration.class) + .withPropertyValues("spring.jms.listener.session.transacted=true") + .run((context) -> { + DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); + assertThat(container.isSessionTransacted()).isTrue(); + assertThat(container).hasFieldOrPropertyWithValue("transactionManager", + context.getBean(JtaTransactionManager.class)); + }); + } + @Test void testDefaultContainerFactoryNonJtaTransactionManager() { this.contextRunner.withUserConfiguration(TestConfiguration8.class, EnableJmsConfiguration.class) @@ -198,6 +213,17 @@ class JmsAutoConfigurationTests { }); } + @Test + void testDefaultContainerFactoryNoTransactionManagerAndSessionTransactedDisabled() { + this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) + .withPropertyValues("spring.jms.listener.session.transacted=false") + .run((context) -> { + DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); + assertThat(container.isSessionTransacted()).isFalse(); + assertThat(container).hasFieldOrPropertyWithValue("transactionManager", null); + }); + } + @Test void testDefaultContainerFactoryWithMessageConverters() { this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class, EnableJmsConfiguration.class)