Skip to content

Commit 38739db

Browse files
committed
Allow customization of Spring Batch TaskExecutor
1 parent c379088 commit 38739db

File tree

8 files changed

+211
-8
lines changed

8 files changed

+211
-8
lines changed

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

+25
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.boot.autoconfigure.batch.BatchProperties.Isolation;
3030
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
3131
import org.springframework.boot.context.properties.PropertyMapper;
32+
import org.springframework.core.task.TaskExecutor;
3233
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
3334
import org.springframework.transaction.PlatformTransactionManager;
3435

@@ -39,6 +40,7 @@
3940
* @author Andy Wilkinson
4041
* @author Kazuki Shimizu
4142
* @author Stephane Nicoll
43+
* @author Andreas Ahlenstorf
4244
* @since 1.0.0
4345
*/
4446
public class BasicBatchConfigurer implements BatchConfigurer, InitializingBean {
@@ -51,6 +53,8 @@ public class BasicBatchConfigurer implements BatchConfigurer, InitializingBean {
5153

5254
private final TransactionManagerCustomizers transactionManagerCustomizers;
5355

56+
private final TaskExecutor taskExecutor;
57+
5458
private JobRepository jobRepository;
5559

5660
private JobLauncher jobLauncher;
@@ -63,12 +67,30 @@ public class BasicBatchConfigurer implements BatchConfigurer, InitializingBean {
6367
* @param dataSource the underlying data source
6468
* @param transactionManagerCustomizers transaction manager customizers (or
6569
* {@code null})
70+
* @deprecated since 2.7.0 for removal in 3.0.0 in favor of
71+
* {@link #BasicBatchConfigurer(BatchProperties, DataSource, TransactionManagerCustomizers, TaskExecutor)}
6672
*/
73+
@Deprecated
6774
protected BasicBatchConfigurer(BatchProperties properties, DataSource dataSource,
6875
TransactionManagerCustomizers transactionManagerCustomizers) {
76+
this(properties, dataSource, transactionManagerCustomizers, null);
77+
}
78+
79+
/**
80+
* Create a new {@link BasicBatchConfigurer} instance.
81+
* @param properties the batch properties
82+
* @param dataSource the underlying data source
83+
* @param transactionManagerCustomizers transaction manager customizers (or
84+
* {@code null})
85+
* @param taskExecutor the executor to be used by {@link JobLauncher} (or
86+
* {@code null})
87+
*/
88+
protected BasicBatchConfigurer(BatchProperties properties, DataSource dataSource,
89+
TransactionManagerCustomizers transactionManagerCustomizers, TaskExecutor taskExecutor) {
6990
this.properties = properties;
7091
this.dataSource = dataSource;
7192
this.transactionManagerCustomizers = transactionManagerCustomizers;
93+
this.taskExecutor = taskExecutor;
7294
}
7395

7496
@Override
@@ -120,6 +142,9 @@ protected JobExplorer createJobExplorer() throws Exception {
120142
protected JobLauncher createJobLauncher() throws Exception {
121143
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
122144
jobLauncher.setJobRepository(getJobRepository());
145+
if (this.taskExecutor != null) {
146+
jobLauncher.setTaskExecutor(this.taskExecutor);
147+
}
123148
jobLauncher.afterPropertiesSet();
124149
return jobLauncher;
125150
}

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

+10-5
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

+23-1
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@
2424

2525
import org.springframework.boot.autoconfigure.batch.BatchProperties.Isolation;
2626
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
27+
import org.springframework.core.task.TaskExecutor;
2728
import org.springframework.orm.jpa.JpaTransactionManager;
2829
import org.springframework.transaction.PlatformTransactionManager;
2930

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

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

+45
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
import org.springframework.context.annotation.Bean;
6464
import org.springframework.context.annotation.Configuration;
6565
import org.springframework.context.annotation.Primary;
66+
import org.springframework.core.task.SyncTaskExecutor;
67+
import org.springframework.core.task.TaskExecutor;
6668
import org.springframework.jdbc.BadSqlGrammarException;
6769
import org.springframework.jdbc.core.JdbcTemplate;
6870
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@@ -80,6 +82,7 @@
8082
* @author Stephane Nicoll
8183
* @author Vedran Pavic
8284
* @author Kazuki Shimizu
85+
* @author Andreas Ahlenstorf
8386
*/
8487
@ExtendWith(OutputCaptureExtension.class)
8588
class BatchAutoConfigurationTests {
@@ -367,6 +370,37 @@ void whenTheUserDefinesTheirOwnDatabaseInitializerThenTheAutoConfiguredBatchInit
367370
.hasBean("customInitializer"));
368371
}
369372

373+
@Test
374+
void jobLauncherUsesBatchTaskExecutorIfPresent() {
375+
this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
376+
BatchTaskExecutorConfiguration.class).run((context) -> {
377+
assertThat(context).hasBean("batchTaskExecutor");
378+
379+
TaskExecutor taskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class);
380+
BatchConfigurer batchConfigurer = context.getBean(BatchConfigurer.class);
381+
382+
assertThat(batchConfigurer.getJobLauncher()).hasFieldOrPropertyWithValue("taskExecutor",
383+
taskExecutor);
384+
});
385+
}
386+
387+
@Test
388+
void jobLauncherConfiguredByJpaBatchConfigurerUsesBatchTaskExecutorIfPresent() {
389+
this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
390+
HibernateJpaAutoConfiguration.class, BatchTaskExecutorConfiguration.class).run((context) -> {
391+
PlatformTransactionManager transactionManager = context.getBean(PlatformTransactionManager.class);
392+
assertThat(transactionManager.toString().contains("JpaTransactionManager")).isTrue();
393+
394+
assertThat(context).hasBean("batchTaskExecutor");
395+
396+
TaskExecutor taskExecutor = context.getBean("batchTaskExecutor", TaskExecutor.class);
397+
BatchConfigurer batchConfigurer = context.getBean(BatchConfigurer.class);
398+
399+
assertThat(batchConfigurer.getJobLauncher()).hasFieldOrPropertyWithValue("taskExecutor",
400+
taskExecutor);
401+
});
402+
}
403+
370404
@Configuration(proxyBeanMethods = false)
371405
protected static class BatchDataSourceConfiguration {
372406

@@ -531,4 +565,15 @@ DataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource) {
531565

532566
}
533567

568+
@Configuration(proxyBeanMethods = false)
569+
protected static class BatchTaskExecutorConfiguration {
570+
571+
@BatchTaskExecutor
572+
@Bean
573+
public TaskExecutor batchTaskExecutor() {
574+
return new SyncTaskExecutor();
575+
}
576+
577+
}
578+
534579
}

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

+2-2
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

+30
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)