Skip to content

Commit a68d700

Browse files
fmbenhassinemminella
authored andcommitted
Allow the transaction manager to be overridden in DefaultBatchConfigurer
Before this commit, it was not possible to override the transaction manager by subclassing DefaultBatchConfigurer and overriding the getTransactionManager method. This commit uses the getTransactionManager method in the initialize method in order to take into account the transaction manager provided by the user. Resolves BATCH-2294
1 parent e83ed1d commit a68d700

File tree

5 files changed

+401
-5
lines changed

5 files changed

+401
-5
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 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.
@@ -59,7 +59,8 @@ public void setDataSource(DataSource dataSource) {
5959
this.dataSource = dataSource;
6060
}
6161

62-
if(this.transactionManager == null) {
62+
if(getTransactionManager() == null) {
63+
logger.warn("No transaction manager was provided, using a DataSourceTransactionManager");
6364
this.transactionManager = new DataSourceTransactionManager(this.dataSource);
6465
}
6566
}
@@ -96,11 +97,12 @@ public void initialize() {
9697
if(dataSource == null) {
9798
logger.warn("No datasource was provided...using a Map based JobRepository");
9899

99-
if(this.transactionManager == null) {
100+
if(getTransactionManager() == null) {
101+
logger.warn("No transaction manager was provided, using a ResourcelessTransactionManager");
100102
this.transactionManager = new ResourcelessTransactionManager();
101103
}
102104

103-
MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(this.transactionManager);
105+
MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(getTransactionManager());
104106
jobRepositoryFactory.afterPropertiesSet();
105107
this.jobRepository = jobRepositoryFactory.getObject();
106108

@@ -128,7 +130,7 @@ protected JobLauncher createJobLauncher() throws Exception {
128130
protected JobRepository createJobRepository() throws Exception {
129131
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
130132
factory.setDataSource(dataSource);
131-
factory.setTransactionManager(transactionManager);
133+
factory.setTransactionManager(getTransactionManager());
132134
factory.afterPropertiesSet();
133135
return factory.getObject();
134136
}

spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java

+31
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,37 @@
111111
* job repository and transaction manager into every step</li>
112112
* </ul>
113113
*
114+
* The transaction manager provided by this annotation will be of type:
115+
*
116+
* <ul>
117+
* <li>{@link org.springframework.batch.support.transaction.ResourcelessTransactionManager}
118+
* if no {@link javax.sql.DataSource} is provided within the context</li>
119+
* <li>{@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
120+
* if a {@link javax.sql.DataSource} is provided within the context</li>
121+
* </ul>
122+
*
123+
* In order to use a custom transaction manager, a custom {@link BatchConfigurer} should be provided. For example:
124+
*
125+
* <pre class="code">
126+
* &#064;Configuration
127+
* &#064;EnableBatchProcessing
128+
* public class AppConfig extends DefaultBatchConfigurer {
129+
*
130+
* &#064;Bean
131+
* public Job job() {
132+
* ...
133+
* }
134+
*
135+
* &#064;Override
136+
* public PlatformTransactionManager getTransactionManager() {
137+
* return new MyTransactionManager();
138+
* }
139+
*
140+
* ...
141+
*
142+
* }
143+
* </pre>
144+
*
114145
* If the configuration is specified as <code>modular=true</code> then the context will also contain an
115146
* {@link AutomaticJobRegistrar}. The job registrar is useful for modularizing your configuration if there are multiple
116147
* jobs. It works by creating separate child application contexts containing job configurations and registering those
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2018 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+
* http://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.batch.core.configuration.annotation;
18+
19+
import javax.sql.DataSource;
20+
21+
import org.junit.runner.RunWith;
22+
import org.mockito.Mock;
23+
import org.mockito.junit.MockitoJUnitRunner;
24+
25+
import org.springframework.aop.Advisor;
26+
import org.springframework.aop.TargetSource;
27+
import org.springframework.aop.framework.Advised;
28+
import org.springframework.batch.core.repository.JobRepository;
29+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
30+
import org.springframework.transaction.PlatformTransactionManager;
31+
import org.springframework.transaction.interceptor.TransactionInterceptor;
32+
33+
/**
34+
* @author Mahmoud Ben Hassine
35+
*/
36+
@RunWith(MockitoJUnitRunner.class)
37+
public abstract class TransactionManagerConfigurationTests {
38+
39+
@Mock
40+
protected static PlatformTransactionManager transactionManager;
41+
42+
@Mock
43+
protected static PlatformTransactionManager transactionManager2;
44+
45+
/*
46+
* The transaction manager set on JobRepositoryFactoryBean in DefaultBatchConfigurer.createJobRepository
47+
* ends up in the TransactionInterceptor advise applied to the (proxied) JobRepository.
48+
* This method extracts the advise from the proxy and returns the transaction manager.
49+
*/
50+
PlatformTransactionManager getTransactionManagerSetOnJobRepository(JobRepository jobRepository) throws Exception {
51+
TargetSource targetSource = ((Advised) jobRepository).getTargetSource(); // proxy created in SimpleBatchConfiguration.createLazyProxy
52+
Advised target = (Advised) targetSource.getTarget(); // initial proxy created in AbstractJobRepositoryFactoryBean.initializeProxy
53+
Advisor[] advisors = target.getAdvisors();
54+
for (Advisor advisor : advisors) {
55+
if (advisor.getAdvice() instanceof TransactionInterceptor) {
56+
TransactionInterceptor transactionInterceptor = (TransactionInterceptor) advisor.getAdvice();
57+
return transactionInterceptor.getTransactionManager();
58+
}
59+
}
60+
return null;
61+
}
62+
63+
static DataSource createDataSource() {
64+
return new EmbeddedDatabaseBuilder()
65+
.addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql")
66+
.addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql")
67+
.build();
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright 2018 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+
* http://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.batch.core.configuration.annotation;
18+
19+
import javax.sql.DataSource;
20+
21+
import org.junit.Assert;
22+
import org.junit.Test;
23+
24+
import org.springframework.batch.core.repository.JobRepository;
25+
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
26+
import org.springframework.context.ApplicationContext;
27+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
30+
import org.springframework.test.util.AopTestUtils;
31+
import org.springframework.transaction.PlatformTransactionManager;
32+
33+
/**
34+
* @author Mahmoud Ben Hassine
35+
*/
36+
public class TransactionManagerConfigurationWithBatchConfigurerTests extends TransactionManagerConfigurationTests {
37+
38+
@Test
39+
public void testConfigurationWithNoDataSourceAndNoTransactionManager() throws Exception {
40+
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndNoTransactionManager.class);
41+
BatchConfigurer batchConfigurer = applicationContext.getBean(BatchConfigurer.class);
42+
43+
PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager();
44+
Assert.assertTrue(platformTransactionManager instanceof ResourcelessTransactionManager);
45+
Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), platformTransactionManager);
46+
}
47+
48+
@Test
49+
public void testConfigurationWithNoDataSourceAndTransactionManager() throws Exception {
50+
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndTransactionManager.class);
51+
BatchConfigurer batchConfigurer = applicationContext.getBean(BatchConfigurer.class);
52+
53+
PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager();
54+
Assert.assertSame(transactionManager, platformTransactionManager);
55+
Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), transactionManager);
56+
}
57+
58+
@Test
59+
public void testConfigurationWithDataSourceAndNoTransactionManager() throws Exception {
60+
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithDataSourceAndNoTransactionManager.class);
61+
BatchConfigurer batchConfigurer = applicationContext.getBean(BatchConfigurer.class);
62+
63+
PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager();
64+
Assert.assertTrue(platformTransactionManager instanceof DataSourceTransactionManager);
65+
DataSourceTransactionManager dataSourceTransactionManager = AopTestUtils.getTargetObject(platformTransactionManager);
66+
Assert.assertEquals(applicationContext.getBean(DataSource.class), dataSourceTransactionManager.getDataSource());
67+
Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), platformTransactionManager);
68+
}
69+
70+
@Test
71+
public void testConfigurationWithDataSourceAndTransactionManager() throws Exception {
72+
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithDataSourceAndTransactionManager.class);
73+
BatchConfigurer batchConfigurer = applicationContext.getBean(BatchConfigurer.class);
74+
75+
PlatformTransactionManager platformTransactionManager = batchConfigurer.getTransactionManager();
76+
Assert.assertSame(transactionManager, platformTransactionManager);
77+
Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), transactionManager);
78+
}
79+
80+
@EnableBatchProcessing
81+
public static class BatchConfigurationWithNoDataSourceAndNoTransactionManager {
82+
@Bean
83+
public BatchConfigurer batchConfigurer() {
84+
return new DefaultBatchConfigurer();
85+
}
86+
}
87+
88+
@EnableBatchProcessing
89+
public static class BatchConfigurationWithNoDataSourceAndTransactionManager {
90+
91+
@Bean
92+
public PlatformTransactionManager transactionManager() {
93+
return transactionManager;
94+
}
95+
96+
@Bean
97+
public BatchConfigurer batchConfigurer() {
98+
return new DefaultBatchConfigurer() {
99+
@Override
100+
public PlatformTransactionManager getTransactionManager() {
101+
return transactionManager();
102+
}
103+
};
104+
}
105+
106+
}
107+
108+
@EnableBatchProcessing
109+
public static class BatchConfigurationWithDataSourceAndNoTransactionManager {
110+
@Bean
111+
public DataSource dataSource() {
112+
return createDataSource();
113+
}
114+
115+
@Bean
116+
public BatchConfigurer batchConfigurer(DataSource dataSource) {
117+
return new DefaultBatchConfigurer(dataSource);
118+
}
119+
}
120+
121+
@EnableBatchProcessing
122+
public static class BatchConfigurationWithDataSourceAndTransactionManager {
123+
124+
@Bean
125+
public DataSource dataSource() {
126+
return createDataSource();
127+
}
128+
129+
@Bean
130+
public PlatformTransactionManager transactionManager() {
131+
return transactionManager;
132+
}
133+
134+
@Bean
135+
public BatchConfigurer batchConfigurer(DataSource dataSource) {
136+
return new DefaultBatchConfigurer(dataSource) {
137+
@Override
138+
public PlatformTransactionManager getTransactionManager() {
139+
return transactionManager();
140+
}
141+
};
142+
}
143+
144+
}
145+
146+
}

0 commit comments

Comments
 (0)