Skip to content

Commit 33338a4

Browse files
committed
Remove datasource autowiring in AbstractBatchConfiguration
Before this commit, the datasource was autowired in AbstractBatchConfiguration. This was causing context startup failures when no datasource or more than one datasource is present in the context. This commit fixes these failures by looking for the datasource in the application context. This also prevents cyclic configuration dependencies when the datasource bean is defined in the same class where other batch artifacts are autowired. Resolves #3991
1 parent 5bce152 commit 33338a4

File tree

6 files changed

+141
-26
lines changed

6 files changed

+141
-26
lines changed

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
import org.springframework.batch.core.launch.JobLauncher;
2626
import org.springframework.batch.core.repository.JobRepository;
2727
import org.springframework.beans.factory.InitializingBean;
28+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
29+
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
2830
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.context.ApplicationContext;
2932
import org.springframework.context.annotation.Bean;
3033
import org.springframework.context.annotation.Configuration;
3134
import org.springframework.context.annotation.Import;
@@ -50,7 +53,7 @@
5053
public abstract class AbstractBatchConfiguration implements ImportAware, InitializingBean {
5154

5255
@Autowired
53-
private DataSource dataSource;
56+
private ApplicationContext context;
5457

5558
private BatchConfigurer configurer;
5659

@@ -153,7 +156,20 @@ protected BatchConfigurer getConfigurer(Collection<BatchConfigurer> configurers)
153156
return this.configurer;
154157
}
155158
if (configurers == null || configurers.isEmpty()) {
156-
DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(this.dataSource);
159+
DataSource dataSource;
160+
try {
161+
dataSource = this.context.getBean(DataSource.class);
162+
} catch (NoUniqueBeanDefinitionException exception) {
163+
throw new IllegalStateException(
164+
"Multiple data sources are defined in the application context and no primary candidate was found. " +
165+
"To use the default BatchConfigurer, one of the data sources should be annotated with '@Primary'.",
166+
exception);
167+
} catch (NoSuchBeanDefinitionException exception) {
168+
throw new IllegalStateException(
169+
"To use the default BatchConfigurer, the application context must contain at least one data source.",
170+
exception);
171+
}
172+
DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource);
157173
configurer.initialize();
158174
this.configurer = configurer;
159175
return configurer;

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
* }
6363
* </pre>
6464
*
65-
* The user should to provide a {@link DataSource} as a bean in the context, or else implement {@link BatchConfigurer} in
65+
* The user should provide a {@link DataSource} as a bean in the context, or else implement {@link BatchConfigurer} in
6666
* the configuration class itself, e.g.
6767
*
6868
* <pre class="code">
@@ -85,11 +85,8 @@
8585
* }
8686
* </pre>
8787
*
88-
* If multiple {@link javax.sql.DataSource}s are defined in the context, the one annotated with
89-
* {@link org.springframework.context.annotation.Primary} will be used (Note that if none
90-
* of them is annotated with {@link org.springframework.context.annotation.Primary}, the one
91-
* named <code>dataSource</code> will be used if any, otherwise a {@link UnsatisfiedDependencyException}
92-
* will be thrown).
88+
* If multiple {@link javax.sql.DataSource}s are defined in the context, the primary autowire candidate
89+
* will be used, otherwise an exception will be thrown.
9390
*
9491
* Note that only one of your configuration classes needs to have the <code>&#064;EnableBatchProcessing</code>
9592
* annotation. Once you have an <code>&#064;EnableBatchProcessing</code> class in your configuration you will have an

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2022 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+
package org.springframework.batch.core.configuration.annotation;
17+
18+
import javax.sql.DataSource;
19+
20+
import org.junit.Assert;
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
24+
import org.springframework.batch.core.ExitStatus;
25+
import org.springframework.batch.core.Job;
26+
import org.springframework.batch.core.JobExecution;
27+
import org.springframework.batch.core.JobParameters;
28+
import org.springframework.batch.core.JobParametersInvalidException;
29+
import org.springframework.batch.core.launch.JobLauncher;
30+
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
31+
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
32+
import org.springframework.batch.core.repository.JobRestartException;
33+
import org.springframework.batch.repeat.RepeatStatus;
34+
import org.springframework.beans.factory.annotation.Autowired;
35+
import org.springframework.context.ApplicationContext;
36+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
37+
import org.springframework.context.annotation.Bean;
38+
import org.springframework.context.annotation.Configuration;
39+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
40+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
41+
import org.springframework.test.context.ContextConfiguration;
42+
import org.springframework.test.context.junit4.SpringRunner;
43+
44+
@RunWith(SpringRunner.class)
45+
@ContextConfiguration
46+
public class InlineDataSourceDefinitionTests {
47+
48+
@Test
49+
public void testInlineDataSourceDefinition() throws Exception {
50+
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyJobConfiguration.class);
51+
Job job = applicationContext.getBean(Job.class);
52+
JobLauncher jobLauncher = applicationContext.getBean(JobLauncher.class);
53+
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
54+
Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
55+
}
56+
57+
@Configuration
58+
@EnableBatchProcessing
59+
static class MyJobConfiguration {
60+
61+
private JobBuilderFactory jobs;
62+
private StepBuilderFactory steps;
63+
64+
public MyJobConfiguration(JobBuilderFactory jobs, StepBuilderFactory steps) {
65+
this.jobs = jobs;
66+
this.steps = steps;
67+
}
68+
69+
@Bean
70+
public Job job() {
71+
return jobs.get("job")
72+
.start(steps.get("step")
73+
.tasklet((contribution, chunkContext) -> {
74+
System.out.println("hello world");
75+
return RepeatStatus.FINISHED;
76+
})
77+
.build())
78+
.build();
79+
}
80+
81+
@Bean
82+
public DataSource dataSource() {
83+
return new EmbeddedDatabaseBuilder()
84+
.setType(EmbeddedDatabaseType.H2)
85+
.addScript("/org/springframework/batch/core/schema-drop-h2.sql")
86+
.addScript("/org/springframework/batch/core/schema-h2.sql")
87+
.generateUniqueName(true)
88+
.build();
89+
}
90+
}
91+
}

spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithBatchConfigurerTests.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2021 the original author or authors.
2+
* Copyright 2018-2022 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.
@@ -21,11 +21,14 @@
2121
import org.junit.Assert;
2222
import org.junit.Test;
2323

24+
import org.springframework.batch.core.Job;
2425
import org.springframework.batch.core.repository.JobRepository;
26+
import org.springframework.batch.repeat.RepeatStatus;
2527
import org.springframework.beans.factory.UnsatisfiedDependencyException;
2628
import org.springframework.context.ApplicationContext;
2729
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2830
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
2932
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
3033
import org.springframework.test.util.AopTestUtils;
3134
import org.springframework.transaction.PlatformTransactionManager;
@@ -35,11 +38,6 @@
3538
*/
3639
public class TransactionManagerConfigurationWithBatchConfigurerTests extends TransactionManagerConfigurationTests {
3740

38-
@Test(expected = UnsatisfiedDependencyException.class)
39-
public void testConfigurationWithNoDataSourceAndNoTransactionManager() {
40-
new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndNoTransactionManager.class);
41-
}
42-
4341
@Test
4442
public void testConfigurationWithDataSourceAndNoTransactionManager() throws Exception {
4543
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BatchConfigurationWithDataSourceAndNoTransactionManager.class);
@@ -62,11 +60,7 @@ public void testConfigurationWithDataSourceAndTransactionManager() throws Except
6260
Assert.assertSame(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)), transactionManager);
6361
}
6462

65-
@EnableBatchProcessing
66-
public static class BatchConfigurationWithNoDataSourceAndNoTransactionManager {
67-
68-
}
69-
63+
@Configuration
7064
@EnableBatchProcessing
7165
public static class BatchConfigurationWithDataSourceAndNoTransactionManager {
7266
@Bean
@@ -80,6 +74,7 @@ public BatchConfigurer batchConfigurer(DataSource dataSource) {
8074
}
8175
}
8276

77+
@Configuration
8378
@EnableBatchProcessing
8479
public static class BatchConfigurationWithDataSourceAndTransactionManager {
8580

spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/TransactionManagerConfigurationWithoutBatchConfigurerTests.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2021 the original author or authors.
2+
* Copyright 2018-2022 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.
@@ -21,11 +21,15 @@
2121
import org.junit.Assert;
2222
import org.junit.Test;
2323

24+
import org.springframework.batch.core.JobParameters;
25+
import org.springframework.batch.core.launch.JobLauncher;
2426
import org.springframework.batch.core.repository.JobRepository;
27+
import org.springframework.beans.factory.BeanCreationException;
2528
import org.springframework.beans.factory.UnsatisfiedDependencyException;
2629
import org.springframework.context.ApplicationContext;
2730
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2831
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
2933
import org.springframework.context.annotation.Primary;
3034
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
3135
import org.springframework.test.util.AopTestUtils;
@@ -36,14 +40,22 @@
3640
*/
3741
public class TransactionManagerConfigurationWithoutBatchConfigurerTests extends TransactionManagerConfigurationTests {
3842

39-
@Test(expected = UnsatisfiedDependencyException.class)
43+
@Test(expected = IllegalStateException.class)
4044
public void testConfigurationWithNoDataSourceAndNoTransactionManager() {
41-
new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndNoTransactionManager.class);
45+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndNoTransactionManager.class);
46+
// beans created by `@EnableBatchProcessing` are lazy proxies, SimpleBatchConfiguration.initialize is only triggered
47+
// when a method is called on one of these proxies
48+
JobRepository jobRepository = context.getBean(JobRepository.class);
49+
Assert.assertFalse(jobRepository.isJobInstanceExists("myJob", new JobParameters()));
4250
}
4351

44-
@Test(expected = UnsatisfiedDependencyException.class)
52+
@Test(expected = IllegalStateException.class)
4553
public void testConfigurationWithNoDataSourceAndTransactionManager() {
46-
new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndTransactionManager.class);
54+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BatchConfigurationWithNoDataSourceAndTransactionManager.class);
55+
// beans created by `@EnableBatchProcessing` are lazy proxies, SimpleBatchConfiguration.initialize is only triggered
56+
// when a method is called on one of these proxies
57+
JobRepository jobRepository = context.getBean(JobRepository.class);
58+
Assert.assertFalse(jobRepository.isJobInstanceExists("myJob", new JobParameters()));
4759
}
4860

4961
@Test
@@ -73,14 +85,15 @@ public void testConfigurationWithDataSourceAndMultipleTransactionManagers() thro
7385
// In this case, the supplied primary transaction manager won't be used by batch and a DataSourceTransactionManager will be used instead.
7486
// The user has to provide a custom BatchConfigurer.
7587
Assert.assertTrue(getTransactionManagerSetOnJobRepository(applicationContext.getBean(JobRepository.class)) instanceof DataSourceTransactionManager);
76-
7788
}
7889

90+
@Configuration
7991
@EnableBatchProcessing
8092
public static class BatchConfigurationWithNoDataSourceAndNoTransactionManager {
8193

8294
}
8395

96+
@Configuration
8497
@EnableBatchProcessing
8598
public static class BatchConfigurationWithNoDataSourceAndTransactionManager {
8699

@@ -90,6 +103,7 @@ public PlatformTransactionManager transactionManager() {
90103
}
91104
}
92105

106+
@Configuration
93107
@EnableBatchProcessing
94108
public static class BatchConfigurationWithDataSourceAndNoTransactionManager {
95109

@@ -99,6 +113,7 @@ public DataSource dataSource() {
99113
}
100114
}
101115

116+
@Configuration
102117
@EnableBatchProcessing
103118
public static class BatchConfigurationWithDataSourceAndOneTransactionManager {
104119

@@ -113,6 +128,7 @@ public PlatformTransactionManager transactionManager() {
113128
}
114129
}
115130

131+
@Configuration
116132
@EnableBatchProcessing
117133
public static class BatchConfigurationWithDataSourceAndMultipleTransactionManagers {
118134

0 commit comments

Comments
 (0)