Fix issue where application fails to start with multiple TransactionManager beans
Prior to this commit, if an application had multiple PlatformTransactionManager beans it would fail to start even when configuring a proper TransactionOperations qualified bean. This commit introduces the @SpringSessionTransactionManager bean qualifier to specify which bean should be injected into Spring Session JDBC configuration, if no bean specified, it tries to resolve a unique PlatformTransactionManager bean from the application context to maintain backward compatibility. Closes gh-2801
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2014-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.session.jdbc.config.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
|
||||
|
||||
/**
|
||||
* Qualifier annotation for a
|
||||
* {@link org.springframework.transaction.PlatformTransactionManager} to be injected in
|
||||
* {@link JdbcIndexedSessionRepository}.
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 3.1.5
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Qualifier
|
||||
public @interface SpringSessionTransactionManager {
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -24,10 +24,14 @@ import java.util.stream.Collectors;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -54,6 +58,7 @@ import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
|
||||
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
|
||||
import org.springframework.session.jdbc.config.annotation.SpringSessionTransactionManager;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
@@ -77,7 +82,8 @@ import org.springframework.util.StringValueResolver;
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(SpringHttpSessionConfiguration.class)
|
||||
public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
|
||||
public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
|
||||
ApplicationContextAware, InitializingBean {
|
||||
|
||||
private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
|
||||
|
||||
@@ -109,6 +115,21 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
|
||||
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (this.transactionOperations == null && this.transactionManager == null) {
|
||||
this.transactionManager = getUniqueTransactionManager();
|
||||
if (this.transactionManager == null) {
|
||||
throw new IllegalStateException(
|
||||
"""
|
||||
Could not resolve an unique PlatformTransactionManager bean from the application context.
|
||||
Please provide either a TransactionOperations bean named springSessionTransactionOperations or a PlatformTransactionManager bean qualified with @SpringSessionTransactionManager""");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JdbcIndexedSessionRepository sessionRepository() {
|
||||
JdbcTemplate jdbcTemplate = createJdbcTemplate(this.dataSource);
|
||||
@@ -195,7 +216,8 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
|
||||
this.dataSource = dataSourceToUse;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
@SpringSessionTransactionManager
|
||||
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
@@ -266,6 +288,15 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
private PlatformTransactionManager getUniqueTransactionManager() {
|
||||
return this.applicationContext.getBeanProvider(PlatformTransactionManager.class).getIfUnique();
|
||||
}
|
||||
|
||||
private static JdbcTemplate createJdbcTemplate(DataSource dataSource) {
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.setExceptionTranslator(new SQLErrorCodeSQLExceptionTranslator(dataSource));
|
||||
@@ -273,7 +304,7 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
|
||||
return jdbcTemplate;
|
||||
}
|
||||
|
||||
private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
|
||||
private TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
transactionTemplate.afterPropertiesSet();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -46,13 +46,19 @@ import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
|
||||
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
|
||||
import org.springframework.session.jdbc.config.annotation.SpringSessionTransactionManager;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionOperations;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
@@ -319,6 +325,34 @@ class JdbcHttpSessionConfigurationTests {
|
||||
assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
|
||||
}
|
||||
|
||||
// gh-2801
|
||||
@Test
|
||||
void configureWhenMultipleTransactionManagersAndQualifiedTransactionOperationsThenApplicationShouldStart() {
|
||||
assertThatNoException().isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class,
|
||||
CustomTransactionOperationsConfiguration.class));
|
||||
}
|
||||
|
||||
// gh-2801
|
||||
@Test
|
||||
void configureWhenMultipleTransactionManagersAndQualifiedTransactionManagerThenApplicationShouldStartAndUseQualified() {
|
||||
assertThatNoException().isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class,
|
||||
QualifiedTransactionManagerConfig.class, DefaultConfiguration.class));
|
||||
JdbcHttpSessionConfiguration configuration = this.context.getBean(JdbcHttpSessionConfiguration.class);
|
||||
Object transactionManager = ReflectionTestUtils.getField(configuration, "transactionManager");
|
||||
assertThat(transactionManager).isInstanceOf(MyTransactionManager.class);
|
||||
}
|
||||
|
||||
// gh-2801
|
||||
@Test
|
||||
void configureWhenMultipleTransactionManagersAndNoQualifiedBeanThenApplicationShouldFailToStart() {
|
||||
assertThatException()
|
||||
.isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class, DefaultConfiguration.class))
|
||||
.havingRootCause()
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.withMessage("Could not resolve an unique PlatformTransactionManager bean from the application context.\n"
|
||||
+ "Please provide either a TransactionOperations bean named springSessionTransactionOperations or a PlatformTransactionManager bean qualified with @SpringSessionTransactionManager");
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
this.context.register(annotatedClasses);
|
||||
this.context.refresh();
|
||||
@@ -576,4 +610,59 @@ class JdbcHttpSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class MultipleTransactionManagerConfig {
|
||||
|
||||
@Bean
|
||||
DataSource dataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TransactionManager transactionManager1() {
|
||||
return new MyTransactionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
TransactionManager transactionManager2() {
|
||||
return mock(PlatformTransactionManager.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class QualifiedTransactionManagerConfig {
|
||||
|
||||
@Bean
|
||||
DataSource dataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SpringSessionTransactionManager
|
||||
TransactionManager myTransactionManager() {
|
||||
return new MyTransactionManager();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyTransactionManager implements PlatformTransactionManager {
|
||||
|
||||
@Override
|
||||
public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(TransactionStatus status) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(TransactionStatus status) throws TransactionException {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user