Skip to content

Commit 057f974

Browse files
committed
Allow customization of Spring Batch TaskExecutor
1 parent 0f93c6c commit 057f974

File tree

8 files changed

+213
-10
lines changed

8 files changed

+213
-10
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java

Lines changed: 26 additions & 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.
@@ -28,6 +28,7 @@
2828
import org.springframework.beans.factory.InitializingBean;
2929
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
3030
import org.springframework.boot.context.properties.PropertyMapper;
31+
import org.springframework.core.task.TaskExecutor;
3132
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
3233
import org.springframework.transaction.PlatformTransactionManager;
3334

@@ -38,6 +39,7 @@
3839
* @author Andy Wilkinson
3940
* @author Kazuki Shimizu
4041
* @author Stephane Nicoll
42+
* @author Andreas Ahlenstorf
4143
* @since 1.0.0
4244
*/
4345
public class BasicBatchConfigurer implements BatchConfigurer, InitializingBean {
@@ -50,6 +52,8 @@ public class BasicBatchConfigurer implements BatchConfigurer, InitializingBean {
5052

5153
private final TransactionManagerCustomizers transactionManagerCustomizers;
5254

55+
private final TaskExecutor taskExecutor;
56+
5357
private JobRepository jobRepository;
5458

5559
private JobLauncher jobLauncher;
@@ -62,12 +66,30 @@ public class BasicBatchConfigurer implements BatchConfigurer, InitializingBean {
6266
* @param dataSource the underlying data source
6367
* @param transactionManagerCustomizers transaction manager customizers (or
6468
* {@code null})
69+
* @deprecated since 2.7.0 for removal in 3.0.0 in favor of
70+
* {@link #BasicBatchConfigurer(BatchProperties, DataSource, TransactionManagerCustomizers, TaskExecutor)}
6571
*/
72+
@Deprecated
6673
protected BasicBatchConfigurer(BatchProperties properties, DataSource dataSource,
6774
TransactionManagerCustomizers transactionManagerCustomizers) {
75+
this(properties, dataSource, transactionManagerCustomizers, null);
76+
}
77+
78+
/**
79+
* Create a new {@link BasicBatchConfigurer} instance.
80+
* @param properties the batch properties
81+
* @param dataSource the underlying data source
82+
* @param transactionManagerCustomizers transaction manager customizers (or
83+
* {@code null})
84+
* @param taskExecutor the executor to be used by {@link JobLauncher} (or
85+
* {@code null})
86+
*/
87+
protected BasicBatchConfigurer(BatchProperties properties, DataSource dataSource,
88+
TransactionManagerCustomizers transactionManagerCustomizers, TaskExecutor taskExecutor) {
6889
this.properties = properties;
6990
this.dataSource = dataSource;
7091
this.transactionManagerCustomizers = transactionManagerCustomizers;
92+
this.taskExecutor = taskExecutor;
7193
}
7294

7395
@Override
@@ -119,6 +141,9 @@ protected JobExplorer createJobExplorer() throws Exception {
119141
protected JobLauncher createJobLauncher() throws Exception {
120142
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
121143
jobLauncher.setJobRepository(getJobRepository());
144+
if (this.taskExecutor != null) {
145+
jobLauncher.setTaskExecutor(this.taskExecutor);
146+
}
122147
jobLauncher.afterPropertiesSet();
123148
return jobLauncher;
124149
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchConfigurerConfiguration.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 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.
@@ -27,12 +27,14 @@
2727
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
2828
import org.springframework.context.annotation.Bean;
2929
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.core.task.TaskExecutor;
3031
import org.springframework.transaction.PlatformTransactionManager;
3132

3233
/**
3334
* Provide a {@link BatchConfigurer} according to the current environment.
3435
*
3536
* @author Stephane Nicoll
37+
* @author Andreas Ahlenstorf
3638
*/
3739
@ConditionalOnClass(PlatformTransactionManager.class)
3840
@ConditionalOnBean(DataSource.class)
@@ -47,9 +49,10 @@ static class JdbcBatchConfiguration {
4749
@Bean
4850
BasicBatchConfigurer batchConfigurer(BatchProperties properties, DataSource dataSource,
4951
@BatchDataSource ObjectProvider<DataSource> batchDataSource,
50-
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
52+
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers,
53+
@BatchTaskExecutor ObjectProvider<TaskExecutor> batchTaskExecutor) {
5154
return new BasicBatchConfigurer(properties, batchDataSource.getIfAvailable(() -> dataSource),
52-
transactionManagerCustomizers.getIfAvailable());
55+
transactionManagerCustomizers.getIfAvailable(), batchTaskExecutor.getIfAvailable());
5356
}
5457

5558
}
@@ -63,9 +66,11 @@ static class JpaBatchConfiguration {
6366
JpaBatchConfigurer batchConfigurer(BatchProperties properties, DataSource dataSource,
6467
@BatchDataSource ObjectProvider<DataSource> batchDataSource,
6568
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers,
66-
EntityManagerFactory entityManagerFactory) {
69+
EntityManagerFactory entityManagerFactory,
70+
@BatchTaskExecutor ObjectProvider<TaskExecutor> batchTaskExecutor) {
6771
return new JpaBatchConfigurer(properties, batchDataSource.getIfAvailable(() -> dataSource),
68-
transactionManagerCustomizers.getIfAvailable(), entityManagerFactory);
72+
transactionManagerCustomizers.getIfAvailable(), entityManagerFactory,
73+
batchTaskExecutor.getIfAvailable());
6974
}
7075

7176
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-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+
17+
package org.springframework.boot.autoconfigure.batch;
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+
27+
/**
28+
* Qualifier annotation for a TaskExecutor to be injected into Batch auto-configuration.
29+
*
30+
* @author Andreas Ahlenstorf
31+
* @since 2.7.0
32+
*/
33+
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
34+
@Retention(RetentionPolicy.RUNTIME)
35+
@Documented
36+
@Qualifier
37+
public @interface BatchTaskExecutor {
38+
39+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JpaBatchConfigurer.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 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.
@@ -23,13 +23,15 @@
2323
import org.apache.commons.logging.LogFactory;
2424

2525
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
26+
import org.springframework.core.task.TaskExecutor;
2627
import org.springframework.orm.jpa.JpaTransactionManager;
2728
import org.springframework.transaction.PlatformTransactionManager;
2829

2930
/**
3031
* A {@link BasicBatchConfigurer} tailored for JPA.
3132
*
3233
* @author Stephane Nicoll
34+
* @author Andreas Ahlenstorf
3335
* @since 2.0.0
3436
*/
3537
public class JpaBatchConfigurer extends BasicBatchConfigurer {
@@ -45,10 +47,30 @@ public class JpaBatchConfigurer extends BasicBatchConfigurer {
4547
* @param transactionManagerCustomizers transaction manager customizers (or
4648
* {@code null})
4749
* @param entityManagerFactory the entity manager factory (or {@code null})
50+
* @deprecated since 2.7.0 for removal in 3.0.0 in favor of
51+
* {@link #JpaBatchConfigurer(BatchProperties, DataSource, TransactionManagerCustomizers, EntityManagerFactory, TaskExecutor)}
4852
*/
53+
@Deprecated
4954
protected JpaBatchConfigurer(BatchProperties properties, DataSource dataSource,
5055
TransactionManagerCustomizers transactionManagerCustomizers, EntityManagerFactory entityManagerFactory) {
51-
super(properties, dataSource, transactionManagerCustomizers);
56+
super(properties, dataSource, transactionManagerCustomizers, null);
57+
this.entityManagerFactory = entityManagerFactory;
58+
}
59+
60+
/**
61+
* Create a new {@link BasicBatchConfigurer} instance.
62+
* @param properties the batch properties
63+
* @param dataSource the underlying data source
64+
* @param transactionManagerCustomizers transaction manager customizers (or
65+
* {@code null})
66+
* @param entityManagerFactory the entity manager factory (or {@code null})
67+
* @param taskExecutor the executor to be used by
68+
* {@link org.springframework.batch.core.launch.JobLauncher} (or {@code null})
69+
*/
70+
protected JpaBatchConfigurer(BatchProperties properties, DataSource dataSource,
71+
TransactionManagerCustomizers transactionManagerCustomizers, EntityManagerFactory entityManagerFactory,
72+
TaskExecutor taskExecutor) {
73+
super(properties, dataSource, transactionManagerCustomizers, taskExecutor);
5274
this.entityManagerFactory = entityManagerFactory;
5375
}
5476

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
import org.springframework.context.annotation.Bean;
6161
import org.springframework.context.annotation.Configuration;
6262
import org.springframework.context.annotation.Primary;
63+
import org.springframework.core.task.SyncTaskExecutor;
64+
import org.springframework.core.task.TaskExecutor;
6365
import org.springframework.jdbc.BadSqlGrammarException;
6466
import org.springframework.jdbc.core.JdbcTemplate;
6567
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@@ -77,6 +79,7 @@
7779
* @author Stephane Nicoll
7880
* @author Vedran Pavic
7981
* @author Kazuki Shimizu
82+
* @author Andreas Ahlenstorf
8083
*/
8184
class BatchAutoConfigurationTests {
8285

@@ -339,6 +342,37 @@ void whenTheUserDefinesTheirOwnDatabaseInitializerThenTheAutoConfiguredBatchInit
339342
.hasBean("customInitializer"));
340343
}
341344

345+
@Test
346+
void jobLauncherUsesBatchTaskExecutorIfPresent() {
347+
this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
348+
BatchTaskExecutorConfiguration.class).run((context) -> {
349+
assertThat(context).hasBean("batchTaskExecutor");
350+
351+
TaskExecutor taskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class);
352+
BatchConfigurer batchConfigurer = context.getBean(BatchConfigurer.class);
353+
354+
assertThat(batchConfigurer.getJobLauncher()).hasFieldOrPropertyWithValue("taskExecutor",
355+
taskExecutor);
356+
});
357+
}
358+
359+
@Test
360+
void jobLauncherConfiguredByJpaBatchConfigurerUsesBatchTaskExecutorIfPresent() {
361+
this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
362+
HibernateJpaAutoConfiguration.class, BatchTaskExecutorConfiguration.class).run((context) -> {
363+
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
364+
assertThat(transactionManager.toString().contains("JpaTransactionManager")).isTrue();
365+
366+
assertThat(context).hasBean("batchTaskExecutor");
367+
368+
TaskExecutor taskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class);
369+
BatchConfigurer batchConfigurer = context.getBean(BatchConfigurer.class);
370+
371+
assertThat(batchConfigurer.getJobLauncher()).hasFieldOrPropertyWithValue("taskExecutor",
372+
taskExecutor);
373+
});
374+
}
375+
342376
@Configuration(proxyBeanMethods = false)
343377
protected static class BatchDataSourceConfiguration {
344378

@@ -503,4 +537,15 @@ DataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource) {
503537

504538
}
505539

540+
@Configuration(proxyBeanMethods = false)
541+
protected static class BatchTaskExecutorConfiguration {
542+
543+
@BatchTaskExecutor
544+
@Bean
545+
public TaskExecutor batchTaskExecutor() {
546+
return new SyncTaskExecutor();
547+
}
548+
549+
}
550+
506551
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/JobLauncherApplicationRunnerTests.java

Lines changed: 2 additions & 2 deletions
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.
@@ -223,7 +223,7 @@ static class BatchConfiguration extends BasicBatchConfigurer {
223223
private final DataSource dataSource;
224224

225225
protected BatchConfiguration(DataSource dataSource) {
226-
super(new BatchProperties(), dataSource, new TransactionManagerCustomizers(null));
226+
super(new BatchProperties(), dataSource, new TransactionManagerCustomizers(null), null);
227227
this.dataSource = dataSource;
228228
}
229229

spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
A number of questions often arise when people use Spring Batch from within a Spring Boot application.
44
This section addresses those questions.
55

6+
[[howto.batch.architectural-considerations]]
7+
=== Architectural Considerations
8+
9+
The default configuration of Spring Batch is geared towards single-job containerized Spring Boot apps orchestrated by a job scheduler like Kubernetes, optionally using https://dataflow.spring.io/[Spring Cloud Dataflow]:
10+
11+
* All `Jobs` in the application context are executed once on application startup; see <<#howto.batch.running-jobs-on-startup>>.
12+
* Jobs are run on the same thread they are launched on.
13+
14+
However, it is also a valid option to run batch jobs as part of other applications, notably web applications running in servlet containers.
15+
Example use cases include reporting, ad-hoc job running, and web application support.
16+
See {spring-batch-docs}job.html#runningJobsFromWebContainer[the Spring Batch documentation] for how to do this.
17+
You might need to <<#howto.batch.customizing-task-executor,customize the task executor for batch jobs>> if you opt for this approach.
18+
619

720

821
[[howto.batch.specifying-a-data-source]]
@@ -57,3 +70,20 @@ This provides only one argument to the batch job: `someParameter=someValue`.
5770
Spring Batch requires a data store for the `Job` repository.
5871
If you use Spring Boot, you must use an actual database.
5972
Note that it can be an in-memory database, see {spring-batch-docs}job.html#configuringJobRepository[Configuring a Job Repository].
73+
74+
[[howto.batch.customizing-task-executor]]
75+
=== Customizing the TaskExecutor for Batch Jobs
76+
77+
By default, jobs are run on the same thread they are launched on.
78+
See {spring-batch-api}/core/configuration/annotation/EnableBatchProcessing.html[the Javadoc of `@EnableBatchProcessing`] for details.
79+
Depending on your type of application, this might be undesirable because the starting thread is blocked while the batch job runs.
80+
Furthermore, additional jobs might have to wait for the currently running job to complete.
81+
In such a case, it is necessary to customize the `TaskExecutor` used by Spring Batch.
82+
The auto-configuration of Spring Batch automatically picks up any `TaskExecutor` annotated with `@BatchTaskExecutor`, for example:
83+
84+
[source,java,indent=0,subs="verbatim"]
85+
----
86+
include::{docs-java}/howto/batch/customizingtaskexecutor/BatchConfiguration.java[tag=bean]
87+
----
88+
89+
The example configures a `TaskExecutor` that dynamically adapts it size and uses up to 4 threads with a maximum queue size of 10. Adjust this configuration to match the needs of your application.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2012-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+
17+
package org.springframework.boot.docs.howto.batch.customizingtaskexecutor;
18+
19+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
20+
import org.springframework.boot.autoconfigure.batch.BatchTaskExecutor;
21+
import org.springframework.boot.task.TaskExecutorBuilder;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.core.task.TaskExecutor;
24+
25+
@EnableBatchProcessing
26+
public class BatchConfiguration {
27+
28+
// tag::bean[]
29+
@Bean
30+
@BatchTaskExecutor
31+
public TaskExecutor batchTaskExecutor(TaskExecutorBuilder builder) {
32+
return builder.threadNamePrefix("batch-").corePoolSize(0).maxPoolSize(4).queueCapacity(10)
33+
.allowCoreThreadTimeOut(true).awaitTermination(false).build();
34+
}
35+
// end::bean[]
36+
37+
}

0 commit comments

Comments
 (0)