Skip to content

Commit 4920499

Browse files
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
1 parent e3be498 commit 4920499

File tree

3 files changed

+167
-5
lines changed

3 files changed

+167
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2014-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.jdbc.config.annotation;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.beans.factory.annotation.Qualifier;
26+
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
27+
28+
/**
29+
* Qualifier annotation for a
30+
* {@link org.springframework.transaction.PlatformTransactionManager} to be injected in
31+
* {@link JdbcIndexedSessionRepository}.
32+
*
33+
* @author Marcus da Coregio
34+
* @since 3.1.5
35+
*/
36+
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
37+
@Retention(RetentionPolicy.RUNTIME)
38+
@Documented
39+
@Qualifier
40+
public @interface SpringSessionTransactionManager {
41+
42+
}

spring-session-jdbc/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2022 the original author or authors.
2+
* Copyright 2014-2024 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.
@@ -24,10 +24,14 @@
2424

2525
import javax.sql.DataSource;
2626

27+
import org.springframework.beans.BeansException;
2728
import org.springframework.beans.factory.BeanClassLoaderAware;
29+
import org.springframework.beans.factory.InitializingBean;
2830
import org.springframework.beans.factory.ObjectProvider;
2931
import org.springframework.beans.factory.annotation.Autowired;
3032
import org.springframework.beans.factory.annotation.Qualifier;
33+
import org.springframework.context.ApplicationContext;
34+
import org.springframework.context.ApplicationContextAware;
3135
import org.springframework.context.EmbeddedValueResolverAware;
3236
import org.springframework.context.annotation.Bean;
3337
import org.springframework.context.annotation.Configuration;
@@ -54,6 +58,7 @@
5458
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
5559
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
5660
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
61+
import org.springframework.session.jdbc.config.annotation.SpringSessionTransactionManager;
5762
import org.springframework.session.web.http.SessionRepositoryFilter;
5863
import org.springframework.transaction.PlatformTransactionManager;
5964
import org.springframework.transaction.TransactionDefinition;
@@ -77,7 +82,8 @@
7782
*/
7883
@Configuration(proxyBeanMethods = false)
7984
@Import(SpringHttpSessionConfiguration.class)
80-
public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
85+
public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
86+
ApplicationContextAware, InitializingBean {
8187

8288
private Duration maxInactiveInterval = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL;
8389

@@ -109,6 +115,21 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
109115

110116
private StringValueResolver embeddedValueResolver;
111117

118+
private ApplicationContext applicationContext;
119+
120+
@Override
121+
public void afterPropertiesSet() throws Exception {
122+
if (this.transactionOperations == null && this.transactionManager == null) {
123+
this.transactionManager = getUniqueTransactionManager();
124+
if (this.transactionManager == null) {
125+
throw new IllegalStateException(
126+
"""
127+
Could not resolve an unique PlatformTransactionManager bean from the application context.
128+
Please provide either a TransactionOperations bean named springSessionTransactionOperations or a PlatformTransactionManager bean qualified with @SpringSessionTransactionManager""");
129+
}
130+
}
131+
}
132+
112133
@Bean
113134
public JdbcIndexedSessionRepository sessionRepository() {
114135
JdbcTemplate jdbcTemplate = createJdbcTemplate(this.dataSource);
@@ -195,7 +216,8 @@ public void setDataSource(@SpringSessionDataSource ObjectProvider<DataSource> sp
195216
this.dataSource = dataSourceToUse;
196217
}
197218

198-
@Autowired
219+
@Autowired(required = false)
220+
@SpringSessionTransactionManager
199221
public void setTransactionManager(PlatformTransactionManager transactionManager) {
200222
this.transactionManager = transactionManager;
201223
}
@@ -266,14 +288,23 @@ public void setImportMetadata(AnnotationMetadata importMetadata) {
266288
this.saveMode = attributes.getEnum("saveMode");
267289
}
268290

291+
@Override
292+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
293+
this.applicationContext = applicationContext;
294+
}
295+
296+
private PlatformTransactionManager getUniqueTransactionManager() {
297+
return this.applicationContext.getBeanProvider(PlatformTransactionManager.class).getIfUnique();
298+
}
299+
269300
private static JdbcTemplate createJdbcTemplate(DataSource dataSource) {
270301
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
271302
jdbcTemplate.setExceptionTranslator(new SQLErrorCodeSQLExceptionTranslator(dataSource));
272303
jdbcTemplate.afterPropertiesSet();
273304
return jdbcTemplate;
274305
}
275306

276-
private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
307+
private TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
277308
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
278309
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
279310
transactionTemplate.afterPropertiesSet();

spring-session-jdbc/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2022 the original author or authors.
2+
* Copyright 2014-2024 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.
@@ -46,13 +46,19 @@
4646
import org.springframework.session.config.SessionRepositoryCustomizer;
4747
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
4848
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
49+
import org.springframework.session.jdbc.config.annotation.SpringSessionTransactionManager;
4950
import org.springframework.test.util.ReflectionTestUtils;
5051
import org.springframework.transaction.PlatformTransactionManager;
5152
import org.springframework.transaction.TransactionDefinition;
53+
import org.springframework.transaction.TransactionException;
54+
import org.springframework.transaction.TransactionManager;
55+
import org.springframework.transaction.TransactionStatus;
5256
import org.springframework.transaction.support.TransactionOperations;
5357

5458
import static org.assertj.core.api.Assertions.assertThat;
59+
import static org.assertj.core.api.Assertions.assertThatException;
5560
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
61+
import static org.assertj.core.api.Assertions.assertThatNoException;
5662
import static org.mockito.Mockito.mock;
5763

5864
/**
@@ -319,6 +325,34 @@ void importConfigAndCustomize() {
319325
assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
320326
}
321327

328+
// gh-2801
329+
@Test
330+
void configureWhenMultipleTransactionManagersAndQualifiedTransactionOperationsThenApplicationShouldStart() {
331+
assertThatNoException().isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class,
332+
CustomTransactionOperationsConfiguration.class));
333+
}
334+
335+
// gh-2801
336+
@Test
337+
void configureWhenMultipleTransactionManagersAndQualifiedTransactionManagerThenApplicationShouldStartAndUseQualified() {
338+
assertThatNoException().isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class,
339+
QualifiedTransactionManagerConfig.class, DefaultConfiguration.class));
340+
JdbcHttpSessionConfiguration configuration = this.context.getBean(JdbcHttpSessionConfiguration.class);
341+
Object transactionManager = ReflectionTestUtils.getField(configuration, "transactionManager");
342+
assertThat(transactionManager).isInstanceOf(MyTransactionManager.class);
343+
}
344+
345+
// gh-2801
346+
@Test
347+
void configureWhenMultipleTransactionManagersAndNoQualifiedBeanThenApplicationShouldFailToStart() {
348+
assertThatException()
349+
.isThrownBy(() -> registerAndRefresh(MultipleTransactionManagerConfig.class, DefaultConfiguration.class))
350+
.havingRootCause()
351+
.isInstanceOf(IllegalStateException.class)
352+
.withMessage("Could not resolve an unique PlatformTransactionManager bean from the application context.\n"
353+
+ "Please provide either a TransactionOperations bean named springSessionTransactionOperations or a PlatformTransactionManager bean qualified with @SpringSessionTransactionManager");
354+
}
355+
322356
private void registerAndRefresh(Class<?>... annotatedClasses) {
323357
this.context.register(annotatedClasses);
324358
this.context.refresh();
@@ -576,4 +610,59 @@ SessionRepositoryCustomizer<JdbcIndexedSessionRepository> sessionRepositoryCusto
576610

577611
}
578612

613+
@Configuration(proxyBeanMethods = false)
614+
static class MultipleTransactionManagerConfig {
615+
616+
@Bean
617+
DataSource dataSource() {
618+
return mock(DataSource.class);
619+
}
620+
621+
@Bean
622+
TransactionManager transactionManager1() {
623+
return new MyTransactionManager();
624+
}
625+
626+
@Bean
627+
TransactionManager transactionManager2() {
628+
return mock(PlatformTransactionManager.class);
629+
}
630+
631+
}
632+
633+
@Configuration(proxyBeanMethods = false)
634+
static class QualifiedTransactionManagerConfig {
635+
636+
@Bean
637+
DataSource dataSource() {
638+
return mock(DataSource.class);
639+
}
640+
641+
@Bean
642+
@SpringSessionTransactionManager
643+
TransactionManager myTransactionManager() {
644+
return new MyTransactionManager();
645+
}
646+
647+
}
648+
649+
static class MyTransactionManager implements PlatformTransactionManager {
650+
651+
@Override
652+
public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
653+
return null;
654+
}
655+
656+
@Override
657+
public void commit(TransactionStatus status) throws TransactionException {
658+
659+
}
660+
661+
@Override
662+
public void rollback(TransactionStatus status) throws TransactionException {
663+
664+
}
665+
666+
}
667+
579668
}

0 commit comments

Comments
 (0)