Create spring-boot-tx module

This commit is contained in:
Andy Wilkinson
2025-03-18 13:22:09 +00:00
committed by Phillip Webb
parent 524db779e5
commit 5360ef8321
74 changed files with 136 additions and 76 deletions

View File

@@ -0,0 +1,45 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import java.util.List;
import org.springframework.transaction.ConfigurableTransactionManager;
import org.springframework.transaction.TransactionExecutionListener;
/**
* {@link TransactionManagerCustomizer} that adds {@link TransactionExecutionListener
* execution listeners} to any transaction manager that is
* {@link ConfigurableTransactionManager configurable}.
*
* @author Andy Wilkinson
*/
class ExecutionListenersTransactionManagerCustomizer
implements TransactionManagerCustomizer<ConfigurableTransactionManager> {
private final List<TransactionExecutionListener> listeners;
ExecutionListenersTransactionManagerCustomizer(List<TransactionExecutionListener> listeners) {
this.listeners = listeners;
}
@Override
public void customize(ConfigurableTransactionManager transactionManager) {
this.listeners.forEach(transactionManager::addListener);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.AbstractTransactionManagementConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.aspectj.AbstractTransactionAspect;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for Spring transaction.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass(PlatformTransactionManager.class)
public class TransactionAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(ReactiveTransactionManager.class)
public TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) {
return TransactionalOperator.create(transactionManager);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(PlatformTransactionManager.class)
public static class TransactionTemplateConfiguration {
@Bean
@ConditionalOnMissingBean(TransactionOperations.class)
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(TransactionManager.class)
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public static class EnableTransactionManagementConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = false)
@ConditionalOnBooleanProperty(name = "spring.aop.proxy-target-class", havingValue = false)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = true)
@ConditionalOnBooleanProperty(name = "spring.aop.proxy-target-class", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(AbstractTransactionAspect.class)
static class AspectJTransactionManagementConfiguration {
@Bean
static LazyInitializationExcludeFilter eagerTransactionAspect() {
return LazyInitializationExcludeFilter.forBeanTypes(AbstractTransactionAspect.class);
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionExecutionListener;
import org.springframework.transaction.TransactionManager;
/**
* Auto-configuration for the customization of a {@link TransactionManager}.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfiguration(before = TransactionAutoConfiguration.class)
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionManagerCustomizationAutoConfiguration {
@Bean
@ConditionalOnMissingBean
TransactionManagerCustomizers platformTransactionManagerCustomizers(
ObjectProvider<TransactionManagerCustomizer<?>> customizers) {
return TransactionManagerCustomizers.of(customizers.orderedStream().toList());
}
@Bean
ExecutionListenersTransactionManagerCustomizer transactionExecutionListeners(
ObjectProvider<TransactionExecutionListener> listeners) {
return new ExecutionListenersTransactionManagerCustomizer(listeners.orderedStream().toList());
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import org.springframework.transaction.TransactionManager;
/**
* Callback interface that can be implemented by beans wishing to customize
* {@link TransactionManager TransactionManagers} while retaining default
* auto-configuration.
*
* @param <T> the transaction manager type
* @author Andy Wilkinson
* @since 4.0.0
*/
public interface TransactionManagerCustomizer<T extends TransactionManager> {
/**
* Customize the given transaction manager.
* @param transactionManager the transaction manager to customize
*/
void customize(T transactionManager);
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2012-2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.transaction.TransactionManager;
/**
* A collection of {@link TransactionManagerCustomizer TransactionManagerCustomizers}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 4.0.0
*/
public final class TransactionManagerCustomizers {
private final List<? extends TransactionManagerCustomizer<?>> customizers;
private TransactionManagerCustomizers(List<? extends TransactionManagerCustomizer<?>> customizers) {
this.customizers = customizers;
}
/**
* Customize the given {@code transactionManager}.
* @param transactionManager the transaction manager to customize
* @since 3.2.0
*/
@SuppressWarnings("unchecked")
public void customize(TransactionManager transactionManager) {
LambdaSafe.callbacks(TransactionManagerCustomizer.class, this.customizers, transactionManager)
.withLogger(TransactionManagerCustomizers.class)
.invoke((customizer) -> customizer.customize(transactionManager));
}
/**
* Returns a new {@code TransactionManagerCustomizers} instance containing the given
* {@code customizers}.
* @param customizers the customizers
* @return the new instance
* @since 3.2.0
*/
public static TransactionManagerCustomizers of(Collection<? extends TransactionManagerCustomizer<?>> customizers) {
return new TransactionManagerCustomizers(
(customizers != null) ? new ArrayList<>(customizers) : Collections.emptyList());
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
/**
* Configuration properties that can be applied to an
* {@link AbstractPlatformTransactionManager}.
*
* @author Kazuki Shimizu
* @author Phillip Webb
* @since 4.0.0
*/
@ConfigurationProperties("spring.transaction")
public class TransactionProperties implements TransactionManagerCustomizer<AbstractPlatformTransactionManager> {
/**
* Default transaction timeout. If a duration suffix is not specified, seconds will be
* used.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration defaultTimeout;
/**
* Whether to roll back on commit failures.
*/
private Boolean rollbackOnCommitFailure;
public Duration getDefaultTimeout() {
return this.defaultTimeout;
}
public void setDefaultTimeout(Duration defaultTimeout) {
this.defaultTimeout = defaultTimeout;
}
public Boolean getRollbackOnCommitFailure() {
return this.rollbackOnCommitFailure;
}
public void setRollbackOnCommitFailure(Boolean rollbackOnCommitFailure) {
this.rollbackOnCommitFailure = rollbackOnCommitFailure;
}
@Override
public void customize(AbstractPlatformTransactionManager transactionManager) {
if (this.defaultTimeout != null) {
transactionManager.setDefaultTimeout((int) this.defaultTimeout.getSeconds());
}
if (this.rollbackOnCommitFailure != null) {
transactionManager.setRollbackOnCommitFailure(this.rollbackOnCommitFailure);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for transaction support.
*/
package org.springframework.boot.transaction.autoconfigure;

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2012-2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.jta.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.jta.JtaTransactionManager;
/**
* JTA Configuration for a JNDI-managed {@link JtaTransactionManager}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Kazuki Shimizu
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(JtaTransactionManager.class)
@ConditionalOnJndi({ JtaTransactionManager.DEFAULT_USER_TRANSACTION_NAME, "java:comp/TransactionManager",
"java:appserver/TransactionManager", "java:pm/TransactionManager", "java:/TransactionManager" })
@ConditionalOnMissingBean(org.springframework.transaction.TransactionManager.class)
class JndiJtaConfiguration {
@Bean
JtaTransactionManager transactionManager(
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(jtaTransactionManager));
return jtaTransactionManager;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.jta.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration;
import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for JTA.
*
* @author Josh Long
* @author Phillip Webb
* @author Nishant Raut
* @since 4.0.0
*/
@AutoConfiguration(before = { TransactionAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class })
@ConditionalOnClass(jakarta.transaction.Transaction.class)
@ConditionalOnBooleanProperty(name = "spring.jta.enabled", matchIfMissing = true)
@Import(JndiJtaConfiguration.class)
public class JtaAutoConfiguration {
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for JTA.
*/
package org.springframework.boot.transaction.jta.autoconfigure;

View File

@@ -0,0 +1,3 @@
org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration
org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration
org.springframework.boot.transaction.jta.autoconfigure.JtaAutoConfiguration

View File

@@ -0,0 +1,46 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.ConfigurableTransactionManager;
import org.springframework.transaction.TransactionExecutionListener;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ExecutionListenersTransactionManagerCustomizer}.
*
* @author Andy Wilkinson
*/
class ExecutionListenersTransactionManagerCustomizerTests {
@Test
void whenTransactionManagerIsCustomizedThenExecutionListenersAreAddedToIt() {
TransactionExecutionListener listener1 = mock(TransactionExecutionListener.class);
TransactionExecutionListener listener2 = mock(TransactionExecutionListener.class);
ConfigurableTransactionManager transactionManager = mock(ConfigurableTransactionManager.class);
new ExecutionListenersTransactionManagerCustomizer(List.of(listener1, listener2)).customize(transactionManager);
then(transactionManager).should().addListener(listener1);
then(transactionManager).should().addListener(listener2);
}
}

View File

@@ -0,0 +1,329 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import java.util.UUID;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.aspectj.AbstractTransactionAspect;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link TransactionAutoConfiguration}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
class TransactionAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(TransactionAutoConfiguration.class));
@Test
void whenThereIsNoPlatformTransactionManagerNoTransactionTemplateIsAutoConfigured() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(TransactionTemplate.class));
}
@Test
void whenThereIsASinglePlatformTransactionManagerATransactionTemplateIsAutoConfigured() {
this.contextRunner.withUserConfiguration(SinglePlatformTransactionManagerConfiguration.class).run((context) -> {
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
TransactionTemplate transactionTemplate = context.getBean(TransactionTemplate.class);
assertThat(transactionTemplate.getTransactionManager()).isSameAs(transactionManager);
});
}
@Test
void whenThereIsASingleReactiveTransactionManagerATransactionalOperatorIsAutoConfigured() {
this.contextRunner.withUserConfiguration(SingleReactiveTransactionManagerConfiguration.class).run((context) -> {
ReactiveTransactionManager transactionManager = context.getBean(ReactiveTransactionManager.class);
TransactionalOperator transactionalOperator = context.getBean(TransactionalOperator.class);
assertThat(transactionalOperator).extracting("transactionManager").isSameAs(transactionManager);
});
}
@Test
void whenThereAreBothReactiveAndPlatformTransactionManagersATemplateAndAnOperatorAreAutoConfigured() {
this.contextRunner
.withUserConfiguration(SinglePlatformTransactionManagerConfiguration.class,
SingleReactiveTransactionManagerConfiguration.class)
.withPropertyValues("spring.datasource.url:jdbc:h2:mem:" + UUID.randomUUID())
.run((context) -> {
PlatformTransactionManager platformTransactionManager = context
.getBean(PlatformTransactionManager.class);
TransactionTemplate transactionTemplate = context.getBean(TransactionTemplate.class);
assertThat(transactionTemplate.getTransactionManager()).isSameAs(platformTransactionManager);
ReactiveTransactionManager reactiveTransactionManager = context
.getBean(ReactiveTransactionManager.class);
TransactionalOperator transactionalOperator = context.getBean(TransactionalOperator.class);
assertThat(transactionalOperator).extracting("transactionManager").isSameAs(reactiveTransactionManager);
});
}
@Test
void whenThereAreSeveralPlatformTransactionManagersNoTransactionTemplateIsAutoConfigured() {
this.contextRunner.withUserConfiguration(SeveralPlatformTransactionManagersConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(TransactionTemplate.class));
}
@Test
void whenThereAreSeveralReactiveTransactionManagersNoTransactionOperatorIsAutoConfigured() {
this.contextRunner.withUserConfiguration(SeveralReactiveTransactionManagersConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(TransactionalOperator.class));
}
@Test
void whenAUserProvidesATransactionTemplateTheAutoConfiguredTemplateBacksOff() {
this.contextRunner.withUserConfiguration(CustomPlatformTransactionManagerConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(TransactionTemplate.class);
assertThat(context.getBean("transactionTemplateFoo")).isInstanceOf(TransactionTemplate.class);
});
}
@Test
void whenAUserProvidesATransactionalOperatorTheAutoConfiguredOperatorBacksOff() {
this.contextRunner
.withUserConfiguration(SingleReactiveTransactionManagerConfiguration.class,
CustomTransactionalOperatorConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(TransactionalOperator.class);
assertThat(context.getBean("customTransactionalOperator")).isInstanceOf(TransactionalOperator.class);
});
}
@Test
void transactionNotManagedWithNoTransactionManager() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
.run((context) -> assertThat(context.getBean(TransactionalService.class).isTransactionActive()).isFalse());
}
@Test
void transactionManagerUsesCglibByDefault() {
this.contextRunner.withUserConfiguration(PlatformTransactionManagersConfiguration.class).run((context) -> {
assertThat(context.getBean(AnotherServiceImpl.class).isTransactionActive()).isTrue();
assertThat(context.getBeansOfType(TransactionalServiceImpl.class)).hasSize(1);
});
}
@Test
void transactionManagerCanBeConfiguredToJdkProxy() {
this.contextRunner.withUserConfiguration(PlatformTransactionManagersConfiguration.class)
.withPropertyValues("spring.aop.proxy-target-class=false")
.run((context) -> {
assertThat(context.getBean(AnotherService.class).isTransactionActive()).isTrue();
assertThat(context).doesNotHaveBean(AnotherServiceImpl.class);
assertThat(context).doesNotHaveBean(TransactionalServiceImpl.class);
});
}
@Test
void customEnableTransactionManagementTakesPrecedence() {
this.contextRunner
.withUserConfiguration(CustomTransactionManagementConfiguration.class,
PlatformTransactionManagersConfiguration.class)
.withPropertyValues("spring.aop.proxy-target-class=true")
.run((context) -> {
assertThat(context.getBean(AnotherService.class).isTransactionActive()).isTrue();
assertThat(context).doesNotHaveBean(AnotherServiceImpl.class);
assertThat(context).doesNotHaveBean(TransactionalServiceImpl.class);
});
}
@Test
void excludesAbstractTransactionAspectFromLazyInit() {
this.contextRunner.withUserConfiguration(AspectJTransactionManagementConfiguration.class).run((context) -> {
LazyInitializationExcludeFilter filter = context.getBean(LazyInitializationExcludeFilter.class);
assertThat(filter.isExcluded(null, null, AbstractTransactionAspect.class)).isTrue();
});
}
@Configuration
static class SinglePlatformTransactionManagerConfiguration {
@Bean
PlatformTransactionManager transactionManager() {
return mock(PlatformTransactionManager.class);
}
}
@Configuration
static class SingleReactiveTransactionManagerConfiguration {
@Bean
ReactiveTransactionManager reactiveTransactionManager() {
return mock(ReactiveTransactionManager.class);
}
}
@Configuration(proxyBeanMethods = false)
static class SeveralPlatformTransactionManagersConfiguration {
@Bean
PlatformTransactionManager transactionManagerOne() {
return mock(PlatformTransactionManager.class);
}
@Bean
PlatformTransactionManager transactionManagerTwo() {
return mock(PlatformTransactionManager.class);
}
}
@Configuration(proxyBeanMethods = false)
static class SeveralReactiveTransactionManagersConfiguration {
@Bean
ReactiveTransactionManager reactiveTransactionManager1() {
return mock(ReactiveTransactionManager.class);
}
@Bean
ReactiveTransactionManager reactiveTransactionManager2() {
return mock(ReactiveTransactionManager.class);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomPlatformTransactionManagerConfiguration {
@Bean
TransactionTemplate transactionTemplateFoo(PlatformTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
@Bean
PlatformTransactionManager transactionManagerFoo() {
return mock(PlatformTransactionManager.class);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomTransactionalOperatorConfiguration {
@Bean
TransactionalOperator customTransactionalOperator() {
return mock(TransactionalOperator.class);
}
}
@Configuration(proxyBeanMethods = false)
static class BaseConfiguration {
@Bean
TransactionalService transactionalService() {
return new TransactionalServiceImpl();
}
@Bean
AnotherServiceImpl anotherService() {
return new AnotherServiceImpl();
}
}
@Configuration(proxyBeanMethods = false)
@Import(BaseConfiguration.class)
static class PlatformTransactionManagersConfiguration {
@Bean
DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
DataSource dataSource() {
return DataSourceBuilder.create()
.driverClassName("org.hsqldb.jdbc.JDBCDriver")
.url("jdbc:hsqldb:mem:tx")
.username("sa")
.build();
}
}
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = false)
static class CustomTransactionManagementConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
static class AspectJTransactionManagementConfiguration {
}
interface TransactionalService {
@Transactional
boolean isTransactionActive();
}
static class TransactionalServiceImpl implements TransactionalService {
@Override
public boolean isTransactionActive() {
return TransactionSynchronizationManager.isActualTransactionActive();
}
}
interface AnotherService {
boolean isTransactionActive();
}
static class AnotherServiceImpl implements AnotherService {
@Override
@Transactional
public boolean isTransactionActive() {
return TransactionSynchronizationManager.isActualTransactionActive();
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2012-2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import java.util.Collections;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link TransactionManagerCustomizationAutoConfiguration}.
*
* @author Andy Wilkinson
*/
class TransactionManagerCustomizationAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(TransactionManagerCustomizationAutoConfiguration.class));
@Test
void autoConfiguresTransactionManagerCustomizers() {
this.contextRunner.run((context) -> {
TransactionManagerCustomizers customizers = context.getBean(TransactionManagerCustomizers.class);
assertThat(customizers).extracting("customizers")
.asInstanceOf(InstanceOfAssertFactories.LIST)
.hasSize(2)
.hasAtLeastOneElementOfType(TransactionProperties.class)
.hasAtLeastOneElementOfType(ExecutionListenersTransactionManagerCustomizer.class);
});
}
@Test
void autoConfiguredTransactionManagerCustomizersBacksOff() {
this.contextRunner.withUserConfiguration(CustomTransactionManagerCustomizersConfiguration.class)
.run((context) -> {
TransactionManagerCustomizers customizers = context.getBean(TransactionManagerCustomizers.class);
assertThat(customizers).extracting("customizers")
.asInstanceOf(InstanceOfAssertFactories.LIST)
.isEmpty();
});
}
@Configuration(proxyBeanMethods = false)
static class CustomTransactionManagerCustomizersConfiguration {
@Bean
TransactionManagerCustomizers customTransactionManagerCustomizers() {
return TransactionManagerCustomizers.of(Collections.<TransactionManagerCustomizer<?>>emptyList());
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2012-2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.autoconfigure;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link TransactionManagerCustomizers}.
*
* @author Phillip Webb
*/
class TransactionManagerCustomizersTests {
@Test
void customizeWithNullCustomizersShouldDoNothing() {
TransactionManagerCustomizers.of(null).customize(mock(TransactionManager.class));
}
@Test
void customizeShouldCheckGeneric() {
List<TestCustomizer<?>> list = new ArrayList<>();
list.add(new TestCustomizer<>());
list.add(new TestJtaCustomizer());
TransactionManagerCustomizers customizers = TransactionManagerCustomizers.of(list);
customizers.customize(mock(PlatformTransactionManager.class));
customizers.customize(mock(JtaTransactionManager.class));
assertThat(list.get(0).getCount()).isEqualTo(2);
assertThat(list.get(1).getCount()).isOne();
}
static class TestCustomizer<T extends PlatformTransactionManager> implements TransactionManagerCustomizer<T> {
private int count;
@Override
public void customize(T transactionManager) {
this.count++;
}
int getCount() {
return this.count;
}
}
static class TestJtaCustomizer extends TestCustomizer<JtaTransactionManager> {
}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright 2012-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.transaction.jta.autoconfigure;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import jakarta.transaction.TransactionManager;
import jakarta.transaction.UserTransaction;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.osjava.sj.loader.JndiLoader;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.transaction.jta.UserTransactionAdapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JtaAutoConfiguration}.
*
* @author Josh Long
* @author Phillip Webb
* @author Andy Wilkinson
* @author Kazuki Shimizu
* @author Nishant Raut
*/
@ClassPathExclusions("jetty-jndi-*.jar")
class JtaAutoConfigurationTests {
private AnnotationConfigApplicationContext context;
@AfterEach
void closeContext() {
if (this.context != null) {
this.context.close();
}
}
@ParameterizedTest
@ExtendWith(JndiExtension.class)
@MethodSource("transactionManagerJndiEntries")
void transactionManagerFromJndi(JndiEntry jndiEntry, InitialContext initialContext) throws NamingException {
jndiEntry.register(initialContext);
this.context = new AnnotationConfigApplicationContext(JtaAutoConfiguration.class);
JtaTransactionManager transactionManager = this.context.getBean(JtaTransactionManager.class);
if (jndiEntry.value instanceof UserTransaction) {
assertThat(transactionManager.getUserTransaction()).isEqualTo(jndiEntry.value);
assertThat(transactionManager.getTransactionManager()).isNull();
}
else {
assertThat(transactionManager.getUserTransaction()).isInstanceOf(UserTransactionAdapter.class);
assertThat(transactionManager.getTransactionManager()).isEqualTo(jndiEntry.value);
}
}
static List<Arguments> transactionManagerJndiEntries() {
return Arrays.asList(Arguments.of(new JndiEntry("java:comp/UserTransaction", UserTransaction.class)),
Arguments.of(new JndiEntry("java:appserver/TransactionManager", TransactionManager.class)),
Arguments.of(new JndiEntry("java:pm/TransactionManager", TransactionManager.class)),
Arguments.of(new JndiEntry("java:/TransactionManager", TransactionManager.class)));
}
@Test
void customTransactionManager() {
this.context = new AnnotationConfigApplicationContext(CustomTransactionManagerConfig.class,
JtaAutoConfiguration.class);
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.context.getBean(JtaTransactionManager.class));
}
@Test
@ExtendWith(JndiExtension.class)
void disableJtaSupport(InitialContext initialContext) throws NamingException {
new JndiEntry("java:comp/UserTransaction", UserTransaction.class).register(initialContext);
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.jta.enabled:false").applyTo(this.context);
this.context.register(JtaAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBeansOfType(JtaTransactionManager.class)).isEmpty();
}
@Configuration(proxyBeanMethods = false)
static class CustomTransactionManagerConfig {
@Bean
org.springframework.transaction.TransactionManager testTransactionManager() {
return mock(org.springframework.transaction.TransactionManager.class);
}
}
private static final class JndiEntry {
private final String name;
private final Class<?> type;
private final Object value;
private JndiEntry(String name, Class<?> type) {
this.name = name;
this.type = type;
this.value = mock(type);
}
private void register(InitialContext initialContext) throws NamingException {
String[] components = this.name.split("/");
String subcontextName = components[0];
String entryName = components[1];
Context javaComp = initialContext.createSubcontext(subcontextName);
JndiLoader loader = new JndiLoader(initialContext.getEnvironment());
Properties properties = new Properties();
properties.setProperty(entryName + "/type", this.type.getName());
properties.put(entryName + "/valueToConvert", this.value);
loader.load(properties, javaComp);
}
@Override
public String toString() {
return this.name;
}
}
private static final class JndiExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
Namespace namespace = Namespace.create(getClass(), context.getUniqueId());
context.getStore(namespace)
.getOrComputeIfAbsent(InitialContext.class, (k) -> createInitialContext(), InitialContext.class);
}
private InitialContext createInitialContext() {
try {
return new InitialContext();
}
catch (Exception ex) {
throw new RuntimeException();
}
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
Namespace namespace = Namespace.create(getClass(), context.getUniqueId());
InitialContext initialContext = context.getStore(namespace)
.remove(InitialContext.class, InitialContext.class);
initialContext.removeFromEnvironment("org.osjava.sj.jndi.ignoreClose");
initialContext.close();
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return InitialContext.class.isAssignableFrom(parameterContext.getParameter().getType());
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
Namespace namespace = Namespace.create(getClass(), extensionContext.getUniqueId());
return extensionContext.getStore(namespace).get(InitialContext.class);
}
}
}

View File

@@ -0,0 +1,5 @@
java.naming.factory.initial = org.osjava.sj.SimpleJndiContextFactory
org.osjava.sj.delimiter = /
org.osjava.sj.jndi.shared = true
org.osjava.sj.root = src/test/resources/simple-jndi
org.osjava.sj.jndi.ignoreClose = true