From 3eceae7009fea7dbd6eecbe5769659c13d8d874f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 20 Aug 2025 15:30:02 +0200 Subject: [PATCH 01/49] Next development version --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9bcd904abf..54e1de7696 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 6.0.0-M2 + 6.0.0-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 51b9491a83..a833c69e25 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-M2 + 6.0.0-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 8a61dd00f0..ae505b3dad 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-M2 + 6.0.0-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index a11dee44d1..8a9524165b 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-M2 + 6.0.0-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index a4de25b3fc..ca268e592d 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-M2 + 6.0.0-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 3a51a2acac..fa06f58bce 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-M2 + 6.0.0-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index a528ecc12d..7196aa21b9 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-M2 + 6.0.0-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index b1c309c129..30ca92fe41 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-M2 + 6.0.0-SNAPSHOT spring-batch-test Spring Batch Test From 6dad5031cc15a0f313fe9bd27c5178928b774a31 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 20 Aug 2025 17:45:17 +0200 Subject: [PATCH 02/49] Update Spring dependencies to latest snapshots --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 54e1de7696..420c120f6b 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 7.0.0-M8 - 2.0.12 - 7.0.0-M2 - 1.16.0-M2 + 7.0.0-SNAPSHOT + 2.0.13-SNAPSHOT + 7.0.0-SNAPSHOT + 1.16.0-SNAPSHOT - 4.0.0-M5 - 4.0.0-M5 - 4.0.0-M5 - 5.0.0-M5 - 4.0.0-M4 - 4.0.0-M4 - 4.0.0-M2 + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 5.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT + 4.0.0-SNAPSHOT 2.19.2 1.12.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.6.0-M2 + 1.6.0-SNAPSHOT 1.4.21 4.13.2 From ae5767adfd6a1c9eb8292a078feac6938af6f9ea Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 20 Aug 2025 17:45:44 +0200 Subject: [PATCH 03/49] Update latest news with the 6.0.0-M2 release --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8c87850fc..a7b15015e6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Latest news +* August 20, 2025: [Spring Batch 6.0.0 M2 available now](https://spring.io/blog/2025/08/20/spring-batch-6) * July 23, 2025: [Spring Batch 6.0.0 M1 is out!](https://spring.io/blog/2025/07/23/spring-batch-6) * March 19, 2025: [Spring Batch 5.2.2 available now](https://spring.io/blog/2025/03/19/spring-batch-5-2-2-available-now) * December 18, 2024: [Spring Batch 5.1.3 and 5.2.1 available now](https://spring.io/blog/2024/12/18/spring-batch-5-1-3-and-5-2-1-available-now) * November 24, 2024: [Bootiful Spring Boot 3.4: Spring Batch](https://spring.io/blog/2024/11/24/bootiful-34-batch) -* November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) From c65635b9b2decab00b7bd28ffcea1478899aff27 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 27 Aug 2025 09:38:57 +0200 Subject: [PATCH 04/49] Remove unused distribution management section --- pom.xml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pom.xml b/pom.xml index 420c120f6b..025cdd5ae9 100644 --- a/pom.xml +++ b/pom.xml @@ -39,15 +39,6 @@ Github Actions https://github.com/spring-projects/spring-batch/actions - - - spring-snapshots - https://repo.spring.io/libs-snapshot-local - - false - - - Apache 2.0 From 79468bf8c6a5611caae339b33ececb9b9eca08a7 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 2 Sep 2025 10:39:42 +0200 Subject: [PATCH 05/49] Add step type in execution context of ChunkOrientedStep --- .../springframework/batch/core/step/item/ChunkOrientedStep.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index a5943c96ee..abacea8ea6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -309,6 +309,7 @@ protected void close(ExecutionContext executionContext) throws Exception { @Override protected void doExecute(StepExecution stepExecution) throws Exception { + stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); while (this.chunkTracker.moreItems()) { // check interruption policy before processing next chunk try { From d18dc0132bce008bd1a5e214e09b8a59ee8aa7f7 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 2 Sep 2025 19:15:45 +0200 Subject: [PATCH 06/49] Add ability to stop any kind of step Before this commit, it was only possible to stop tasklet steps. This commit introduces a new interface to be implemented by any step that is able to handle external stop signals. Resolves #4965 --- .../launch/support/SimpleJobOperator.java | 6 +++ .../batch/core/step/AbstractStep.java | 2 +- .../batch/core/step/StoppableStep.java | 37 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java index 047c5dd6d5..e51cad77be 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java @@ -36,6 +36,7 @@ import org.springframework.batch.core.job.parameters.JobParametersIncrementer; import org.springframework.batch.core.job.parameters.JobParametersInvalidException; import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.StoppableStep; import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.job.UnexpectedJobExecutionException; import org.springframework.batch.core.configuration.JobRegistry; @@ -351,6 +352,11 @@ public boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningExce StepSynchronizationManager.release(); } } + if (step instanceof StoppableStep stoppableStep) { + StepSynchronizationManager.register(stepExecution); + stoppableStep.stop(stepExecution); + StepSynchronizationManager.release(); + } } catch (NoSuchStepException e) { logger.warn("Step not found", e); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java index ba83296433..b545aac50e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java @@ -63,7 +63,7 @@ * @author Mahmoud Ben Hassine * @author Jinwoo Bae */ -public abstract class AbstractStep implements Step, InitializingBean, BeanNameAware { +public abstract class AbstractStep implements StoppableStep, InitializingBean, BeanNameAware { private static final Log logger = LogFactory.getLog(AbstractStep.class); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java new file mode 100644 index 0000000000..e455861f96 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StoppableStep.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.step; + +/** + * Extension of the {@link Step} interface to be implemented by steps that support being + * stopped. + * + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +public interface StoppableStep extends Step { + + /** + * Callback to signal the step to stop. The default implementation sets the + * {@link StepExecution} to terminate only. Concrete implementations can override this + * method to add custom stop logic. + * @param stepExecution the current step execution + */ + default void stop(StepExecution stepExecution) { + stepExecution.setTerminateOnly(); + } + +} From b01a19d85a561a3d894e7b8b402f007fd7e72e32 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 2 Sep 2025 19:18:32 +0200 Subject: [PATCH 07/49] Add support to handle external stop signals in ChunkOrientedStep --- .../core/step/item/ChunkOrientedStep.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index abacea8ea6..7a5ea20740 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -19,8 +19,6 @@ import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; -import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.job.JobInterruptedException; import org.springframework.batch.core.listener.ChunkListener; import org.springframework.batch.core.listener.CompositeChunkListener; @@ -309,18 +307,8 @@ protected void close(ExecutionContext executionContext) throws Exception { @Override protected void doExecute(StepExecution stepExecution) throws Exception { - stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); - while (this.chunkTracker.moreItems()) { - // check interruption policy before processing next chunk - try { - this.interruptionPolicy.checkInterrupted(stepExecution); - } - catch (JobInterruptedException exception) { - stepExecution.setTerminateOnly(); - stepExecution.setStatus(BatchStatus.STOPPED); - stepExecution.setExitStatus(ExitStatus.STOPPED); - return; - } + stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); + while (this.chunkTracker.moreItems() && !interrupted(stepExecution)) { // process next chunk in its own transaction this.transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -355,6 +343,25 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } } + /* + * Check if the step has been interrupted either internally via user defined policy or + * externally via job operator. This will be checked at chunk boundaries. + */ + private boolean interrupted(StepExecution stepExecution) { + // check internal interruption via user defined policy + try { + this.interruptionPolicy.checkInterrupted(stepExecution); + } + catch (JobInterruptedException exception) { + return true; + } + // check external interruption via job operator + if (stepExecution.isTerminateOnly()) { + return true; + } + return false; + } + private Chunk read(StepContribution contribution) throws Exception { Chunk chunk = new Chunk<>(); for (int i = 0; i < chunkSize; i++) { From bdb2bfc8fe2124b5c53121ef4e2d4f7597a65c74 Mon Sep 17 00:00:00 2001 From: HyunSangHan Date: Sun, 24 Nov 2024 13:18:02 +0900 Subject: [PATCH 08/49] Add StepExecution parameter to StoppableTasklet.stop Resolves #4703 --- .../core/launch/support/SimpleJobOperator.java | 5 +++-- .../core/step/tasklet/StoppableTasklet.java | 12 ++++++++++++ .../step/tasklet/SystemCommandTasklet.java | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java index e51cad77be..48d2e41ad1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java @@ -82,6 +82,7 @@ * @author Mahmoud Ben Hassine * @author Andrey Litvitski * @author Yejeong Ham + * @author Hyunsang Han * @since 2.0 * @deprecated since 6.0 in favor of {@link TaskExecutorJobOperator}. Scheduled for * removal in 6.2 or later. @@ -348,7 +349,7 @@ public boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningExce Tasklet tasklet = taskletStep.getTasklet(); if (tasklet instanceof StoppableTasklet stoppableTasklet) { StepSynchronizationManager.register(stepExecution); - stoppableTasklet.stop(); + stoppableTasklet.stop(stepExecution); StepSynchronizationManager.release(); } } @@ -366,7 +367,7 @@ public boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningExce } } catch (NoSuchJobException e) { - logger.warn("Cannot find Job object in the job registry. StoppableTasklet#stop() will not be called", e); + logger.warn("Cannot find Job object in the job registry. StoppableTasklet#stop(StepExecution stepExecution) will not be called", e); } return true; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java index 5795b6cfa5..697cf7292a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.core.step.tasklet; +import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.step.StepContribution; @@ -29,6 +30,7 @@ * so the appropriate thread safety and visibility controls should be put in place. * * @author Will Schipp + * @author Hyunsang Han * @since 3.0 */ public interface StoppableTasklet extends Tasklet { @@ -39,4 +41,14 @@ public interface StoppableTasklet extends Tasklet { */ void stop(); + /** + * Used to signal that the job should stop, providing access to the current + * {@link StepExecution} context. + * + * @param stepExecution the current {@link StepExecution} context in which the job + * is being executed + */ + default void stop(StepExecution stepExecution) { + stop(); + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java index 041589b035..2f6bc9e1ba 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java @@ -61,6 +61,7 @@ * @author Will Schipp * @author Mahmoud Ben Hassine * @author Injae Kim + * @author Hyunsang Han */ public class SystemCommandTasklet implements StepExecutionListener, StoppableTasklet, InitializingBean { @@ -275,4 +276,21 @@ public void stop() { stopped = true; } + /** + * Interrupts the execution of the system command if the given {@link StepExecution} + * matches the current execution context. This method allows for granular control over + * stopping specific step executions, ensuring that only the intended command is halted. + * + * @param stepExecution the current {@link StepExecution} context; the execution is + * interrupted if it matches the ongoing one. + * @since 6.0 + * @see StoppableTasklet#stop(StepExecution) + */ + @Override + public void stop(StepExecution stepExecution) { + if (stepExecution.equals(execution)) { + stopped = true; + } + } + } From 0056722fffa3bbf9cf9cd114b2ebb42293fb7db3 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 3 Sep 2025 11:25:52 +0200 Subject: [PATCH 09/49] Refine contribution #4715 - Deprecate stop method - Fix imports - Update Javadocs --- .../core/launch/support/SimpleJobOperator.java | 4 +++- .../batch/core/step/tasklet/StoppableTasklet.java | 13 ++++++++----- .../core/step/tasklet/SystemCommandTasklet.java | 12 ++++++++---- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java index 48d2e41ad1..db0c1a351a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java @@ -367,7 +367,9 @@ public boolean stop(JobExecution jobExecution) throws JobExecutionNotRunningExce } } catch (NoSuchJobException e) { - logger.warn("Cannot find Job object in the job registry. StoppableTasklet#stop(StepExecution stepExecution) will not be called", e); + logger.warn( + "Cannot find Job object in the job registry. StoppableTasklet#stop(StepExecution stepExecution) will not be called", + e); } return true; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java index 697cf7292a..23e022d787 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/StoppableTasklet.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package org.springframework.batch.core.step.tasklet; -import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.step.StepContribution; @@ -38,17 +38,20 @@ public interface StoppableTasklet extends Tasklet { /** * Used to signal that the job this {@link Tasklet} is executing within has been * requested to stop. + * @deprecated Since 6.0, use {@link #stop(StepExecution)} instead. */ + @Deprecated(since = "6.0", forRemoval = true) void stop(); /** * Used to signal that the job should stop, providing access to the current * {@link StepExecution} context. - * - * @param stepExecution the current {@link StepExecution} context in which the job - * is being executed + * @param stepExecution the current {@link StepExecution} context in which the job is + * being executed + * @since 6.0 */ default void stop(StepExecution stepExecution) { stop(); } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java index 2f6bc9e1ba..467eb473c9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java @@ -279,8 +279,12 @@ public void stop() { /** * Interrupts the execution of the system command if the given {@link StepExecution} * matches the current execution context. This method allows for granular control over - * stopping specific step executions, ensuring that only the intended command is halted. - * + * stopping specific step executions, ensuring that only the intended command is + * halted. + *

+ * This method will interrupt the thread executing the system command only if + * {@link #setInterruptOnCancel(boolean)} has been set to true. Otherwise, the + * underlying command will be allowed to finish before the tasklet ends. * @param stepExecution the current {@link StepExecution} context; the execution is * interrupted if it matches the ongoing one. * @since 6.0 @@ -288,8 +292,8 @@ public void stop() { */ @Override public void stop(StepExecution stepExecution) { - if (stepExecution.equals(execution)) { - stopped = true; + if (stepExecution.equals(this.execution)) { + this.stopped = true; } } From c7aabeb0d752ed49836fa611a92fbe92054e4156 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 3 Sep 2025 12:09:03 +0200 Subject: [PATCH 10/49] Add default implementation to Job#getName Resolves #4966 --- .../org/springframework/batch/core/job/Job.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java index 80fdd5583b..22c91a5651 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/Job.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,9 +28,18 @@ * @author Dave Syer * @author Mahmoud Ben Hassine */ +@FunctionalInterface public interface Job { - String getName(); + /** + * The name of the job. This is used to distinguish between different jobs and must be + * unique within the job repository. If not explicitly set, the name will default to + * the fully qualified class name. + * @return the name of the job (never {@code null}) + */ + default String getName() { + return this.getClass().getName(); + } /** * Flag to indicate if this job can be restarted, at least in principle. From b980c7ab0c889b5c7766d866f4aa5de0edc500e8 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 3 Sep 2025 16:22:09 +0200 Subject: [PATCH 11/49] Add method to increment filter count by 1 in StepContribution --- .../batch/core/step/StepContribution.java | 10 +++++++++- .../batch/core/step/item/ChunkOrientedStep.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java index 340505c964..ae703f9277 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/StepContribution.java @@ -73,7 +73,15 @@ public ExitStatus getExitStatus() { } /** - * Increment the counter for the number of items processed. + * Increment the counter for the number of filtered items. + * @since 6.0.0 + */ + public void incrementFilterCount() { + this.incrementFilterCount(1); + } + + /** + * Increment the counter for the number of filtered items. * @param count The {@code long} amount to increment by. */ public void incrementFilterCount(long count) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index 7a5ea20740..71b0f551bb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -428,7 +428,7 @@ private Chunk process(Chunk chunk, StepContribution contribution) throws E O processedItem = doProcess(item); this.compositeItemProcessListener.afterProcess(item, processedItem); if (processedItem == null) { - contribution.incrementFilterCount(1); + contribution.incrementFilterCount(); } else { processedChunk.add(processedItem); From 0501a87ca4ea8ec32289a2a98ee63b1ba5147773 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 3 Sep 2025 17:48:07 +0200 Subject: [PATCH 12/49] Fix ItemProcessListener#afterProcess call order in ChunkOrientedStep The javadoc of ItemProcessListener#afterProcess states that this method is still called with a null result, allowing for notification of "filtered" items. --- .../springframework/batch/core/step/item/ChunkOrientedStep.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index 71b0f551bb..683cfff2c4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -426,13 +426,13 @@ private Chunk process(Chunk chunk, StepContribution contribution) throws E try { this.compositeItemProcessListener.beforeProcess(item); O processedItem = doProcess(item); - this.compositeItemProcessListener.afterProcess(item, processedItem); if (processedItem == null) { contribution.incrementFilterCount(); } else { processedChunk.add(processedItem); } + this.compositeItemProcessListener.afterProcess(item, processedItem); } catch (Exception exception) { this.compositeItemProcessListener.onProcessError(item, exception); From 95e1896e7d05e06345aa8bfabd730204a3c2fc28 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 4 Sep 2025 12:32:05 +0200 Subject: [PATCH 13/49] Add concurrency support in ChunkOrientedStep Resolves #4955 --- .../builder/ChunkOrientedStepBuilder.java | 18 ++ .../core/step/item/ChunkOrientedStep.java | 247 +++++++++++++----- .../ChunkOrientedStepIntegrationTests.java | 175 +++++++++++++ 3 files changed, 371 insertions(+), 69 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java index 21ffbcb799..c196d63ac2 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java @@ -52,6 +52,7 @@ import org.springframework.batch.support.ReflectionUtils; import org.springframework.core.retry.RetryListener; import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttribute; @@ -93,6 +94,8 @@ public class ChunkOrientedStepBuilder extends StepBuilderHelper> skipListeners = new LinkedHashSet<>(); + private AsyncTaskExecutor asyncTaskExecutor; + /** * Create a new {@link ChunkOrientedStepBuilder} with the given job repository and * transaction manager. The step name will be assigned to the bean name. @@ -296,6 +299,18 @@ public ChunkOrientedStepBuilder skipListener(SkipListener skipListen return self(); } + /** + * Set the asynchronous task executor to be used for processing items concurrently. + * This allows for concurrent processing of items, improving performance and + * throughput. If not set, the step will process items sequentially. + * @param asyncTaskExecutor the asynchronous task executor to use + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder taskExecutor(AsyncTaskExecutor asyncTaskExecutor) { + this.asyncTaskExecutor = asyncTaskExecutor; + return self(); + } + @SuppressWarnings("unchecked") public ChunkOrientedStep build() { ChunkOrientedStep chunkOrientedStep = new ChunkOrientedStep<>(this.getName(), this.chunkSize, this.reader, @@ -306,6 +321,9 @@ public ChunkOrientedStep build() { chunkOrientedStep.setRetryPolicy(this.retryPolicy); chunkOrientedStep.setSkipPolicy(this.skipPolicy); chunkOrientedStep.setFaultTolerant(this.faultTolerant); + if (this.asyncTaskExecutor != null) { + chunkOrientedStep.setTaskExecutor(this.asyncTaskExecutor); + } streams.forEach(chunkOrientedStep::registerItemStream); stepListeners.forEach(stepListener -> { if (stepListener instanceof ItemReadListener) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index 683cfff2c4..7555910d42 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -15,6 +15,10 @@ */ package org.springframework.batch.core.step.item; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Future; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; @@ -30,6 +34,8 @@ import org.springframework.batch.core.listener.ItemReadListener; import org.springframework.batch.core.listener.ItemWriteListener; import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.scope.context.StepContext; +import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; @@ -52,6 +58,7 @@ import org.springframework.core.retry.RetryTemplate; import org.springframework.core.retry.Retryable; import org.springframework.core.retry.support.CompositeRetryListener; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; @@ -61,11 +68,13 @@ import org.springframework.util.Assert; /** - * Step implementation for the chunk-oriented processing model. + * Step implementation for the chunk-oriented processing model. This class also supports + * faut-tolerance features (retry and skip) as well as concurrent item processing when a + * {@link AsyncTaskExecutor} is provided. * - * @author Mahmoud Ben Hassine * @param type of input items * @param type of output items + * @author Mahmoud Ben Hassine * @since 6.0 */ public class ChunkOrientedStep extends AbstractStep { @@ -128,6 +137,11 @@ public class ChunkOrientedStep extends AbstractStep { private final CompositeSkipListener compositeSkipListener = new CompositeSkipListener<>(); + /* + * Concurrency parameters + */ + private AsyncTaskExecutor taskExecutor; + /** * Create a new {@link ChunkOrientedStep}. * @param name the name of the step @@ -232,6 +246,15 @@ public void setFaultTolerant(boolean faultTolerant) { this.faultTolerant = faultTolerant; } + /** + * Set the {@link AsyncTaskExecutor} to use for processing items asynchronously. + * @param asyncTaskExecutor the asynchronous task executor to set + */ + public void setTaskExecutor(AsyncTaskExecutor asyncTaskExecutor) { + Assert.notNull(asyncTaskExecutor, "Task executor must not be null"); + this.taskExecutor = asyncTaskExecutor; + } + /** * Set the {@link RetryPolicy} for this step. * @param retryPolicy the retry policy to set @@ -314,35 +337,93 @@ protected void doExecute(StepExecution stepExecution) throws Exception { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { StepContribution contribution = stepExecution.createStepContribution(); - Chunk inputChunk = new Chunk<>(); - Chunk processedChunk = new Chunk<>(); - try { - inputChunk = read(contribution); - if (inputChunk.isEmpty()) { - return; - } - compositeChunkListener.beforeChunk(inputChunk); - processedChunk = process(inputChunk, contribution); - write(processedChunk, contribution); - compositeChunkListener.afterChunk(processedChunk); - stepExecution.apply(contribution); - stepExecution.incrementCommitCount(); - compositeItemStream.update(stepExecution.getExecutionContext()); - getJobRepository().update(stepExecution); - getJobRepository().updateExecutionContext(stepExecution); + if (isConcurrent()) { + processChunkConcurrently(status, contribution, stepExecution); } - catch (Exception e) { - logger.error("Rolling back chunk transaction", e); - status.setRollbackOnly(); - stepExecution.incrementRollbackCount(); - compositeChunkListener.onChunkError(e, processedChunk); - throw new FatalStepExecutionException("Unable to process chunk", e); + else { + processChunkSequentially(status, contribution, stepExecution); } } }); } } + private void processChunkConcurrently(TransactionStatus status, StepContribution contribution, + StepExecution stepExecution) { + List> itemProcessingTasks = new LinkedList<>(); + try { + // read items and submit concurrent item processing tasks + for (int i = 0; i < this.chunkSize; i++) { + I item = readItem(contribution); + if (item != null) { + Future itemProcessingFuture = this.taskExecutor.submit(() -> processItem(item, contribution)); + itemProcessingTasks.add(itemProcessingFuture); + } + } + // exclude empty chunks (when the total items is a multiple of the chunk size) + if (itemProcessingTasks.isEmpty()) { + return; + } + + // collect processed items + Chunk processedChunk = new Chunk<>(); + for (Future future : itemProcessingTasks) { + O processedItem = future.get(); + if (processedItem != null) { + processedChunk.add(processedItem); + } + } + + // write processed items + writeChunk(processedChunk, contribution); + + // apply contribution and update job repository + stepExecution.apply(contribution); + stepExecution.incrementCommitCount(); + this.compositeItemStream.update(stepExecution.getExecutionContext()); + getJobRepository().update(stepExecution); + getJobRepository().updateExecutionContext(stepExecution); + + } + catch (Exception e) { + logger.error("Rolling back chunk transaction", e); + status.setRollbackOnly(); + stepExecution.incrementRollbackCount(); + throw new FatalStepExecutionException("Unable to process chunk", e); + } + + } + + private void processChunkSequentially(TransactionStatus status, StepContribution contribution, + StepExecution stepExecution) { + Chunk inputChunk = new Chunk<>(); + Chunk processedChunk = new Chunk<>(); + try { + inputChunk = readChunk(contribution); + if (inputChunk.isEmpty()) { + return; + } + compositeChunkListener.beforeChunk(inputChunk); + processedChunk = processChunk(inputChunk, contribution); + writeChunk(processedChunk, contribution); + compositeChunkListener.afterChunk(processedChunk); + + // apply contribution and update job repository + stepExecution.apply(contribution); + stepExecution.incrementCommitCount(); + compositeItemStream.update(stepExecution.getExecutionContext()); + getJobRepository().update(stepExecution); + getJobRepository().updateExecutionContext(stepExecution); + } + catch (Exception e) { + logger.error("Rolling back chunk transaction", e); + status.setRollbackOnly(); + stepExecution.incrementRollbackCount(); + compositeChunkListener.onChunkError(e, processedChunk); + throw new FatalStepExecutionException("Unable to process chunk", e); + } + } + /* * Check if the step has been interrupted either internally via user defined policy or * externally via job operator. This will be checked at chunk boundaries. @@ -362,36 +443,42 @@ private boolean interrupted(StepExecution stepExecution) { return false; } - private Chunk read(StepContribution contribution) throws Exception { + private Chunk readChunk(StepContribution contribution) throws Exception { Chunk chunk = new Chunk<>(); for (int i = 0; i < chunkSize; i++) { - this.compositeItemReadListener.beforeRead(); - try { - I item = doRead(); - if (item == null) { - chunkTracker.noMoreItems(); - break; - } - else { - chunk.add(item); - contribution.incrementReadCount(); - this.compositeItemReadListener.afterRead(item); - } - } - catch (Exception exception) { - this.compositeItemReadListener.onReadError(exception); - if (this.faultTolerant && exception instanceof RetryException retryException) { - doSkipInRead(retryException, contribution); - } - else { - throw exception; - } + I item = readItem(contribution); + if (item != null) { + chunk.add(item); } - } return chunk; } + @Nullable private I readItem(StepContribution contribution) throws Exception { + this.compositeItemReadListener.beforeRead(); + I item = null; + try { + item = doRead(); + if (item == null) { + this.chunkTracker.noMoreItems(); + } + else { + contribution.incrementReadCount(); + this.compositeItemReadListener.afterRead(item); + } + } + catch (Exception exception) { + this.compositeItemReadListener.onReadError(exception); + if (this.faultTolerant && exception instanceof RetryException retryException) { + doSkipInRead(retryException, contribution); + } + else { + throw exception; + } + } + return item; + } + @Nullable private I doRead() throws Exception { if (this.faultTolerant) { Retryable retryableRead = new Retryable<>() { @@ -420,39 +507,57 @@ private void doSkipInRead(RetryException retryException, StepContribution contri } } - private Chunk process(Chunk chunk, StepContribution contribution) throws Exception { + private Chunk processChunk(Chunk chunk, StepContribution contribution) throws Exception { Chunk processedChunk = new Chunk<>(); for (I item : chunk) { - try { - this.compositeItemProcessListener.beforeProcess(item); - O processedItem = doProcess(item); - if (processedItem == null) { - contribution.incrementFilterCount(); - } - else { - processedChunk.add(processedItem); - } - this.compositeItemProcessListener.afterProcess(item, processedItem); - } - catch (Exception exception) { - this.compositeItemProcessListener.onProcessError(item, exception); - if (this.faultTolerant && exception instanceof RetryException retryException) { - doSkipInProcess(item, retryException, contribution); - } - else { - throw exception; - } + O processedItem = processItem(item, contribution); + if (processedItem != null) { + processedChunk.add(processedItem); } } return processedChunk; } + private O processItem(I item, StepContribution contribution) throws Exception { + O processedItem = null; + try { + this.compositeItemProcessListener.beforeProcess(item); + processedItem = doProcess(item); + if (processedItem == null) { + contribution.incrementFilterCount(); + } + this.compositeItemProcessListener.afterProcess(item, processedItem); + } + catch (Exception exception) { + this.compositeItemProcessListener.onProcessError(item, exception); + if (this.faultTolerant && exception instanceof RetryException retryException) { + doSkipInProcess(item, retryException, contribution); + } + else { + throw exception; + } + } + return processedItem; + } + @Nullable private O doProcess(I item) throws Exception { if (this.faultTolerant) { Retryable retryableProcess = new Retryable<>() { @Override public @Nullable O execute() throws Throwable { - return itemProcessor.process(item); + StepContext context = StepSynchronizationManager.getContext(); + final StepExecution stepExecution = context == null ? null : context.getStepExecution(); + if (isConcurrent() && stepExecution != null) { + StepSynchronizationManager.register(stepExecution); + } + try { + return itemProcessor.process(item); + } + finally { + if (isConcurrent() && stepExecution != null) { + StepSynchronizationManager.close(); + } + } } @Override @@ -475,7 +580,7 @@ private void doSkipInProcess(I item, RetryException retryException, StepContribu } } - private void write(Chunk chunk, StepContribution contribution) throws Exception { + private void writeChunk(Chunk chunk, StepContribution contribution) throws Exception { try { this.compositeItemWriteListener.beforeWrite(chunk); doWrite(chunk); @@ -538,6 +643,10 @@ private void scan(Chunk chunk, StepContribution contribution) { } } + private boolean isConcurrent() { + return this.taskExecutor != null; + } + private static class ChunkTracker { private boolean moreItems = true; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java index 0f105f1315..4ec0794f1b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java @@ -55,6 +55,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; @@ -69,6 +70,9 @@ */ public class ChunkOrientedStepIntegrationTests { + // TODO use parameterized tests for serial and concurrent steps + // The outcome should be the same for both + @Test void testChunkOrientedStep() throws Exception { // given @@ -94,6 +98,31 @@ void testChunkOrientedStep() throws Exception { Assertions.assertEquals(5, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); } + @Test + void testConcurrentChunkOrientedStep() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ConcurrentChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + Assertions.assertEquals(ExitStatus.COMPLETED, stepExecution.getExitStatus()); + Assertions.assertEquals(5, stepExecution.getReadCount()); + Assertions.assertEquals(5, stepExecution.getWriteCount()); + Assertions.assertEquals(3, stepExecution.getCommitCount()); + Assertions.assertEquals(0, stepExecution.getRollbackCount()); + Assertions.assertEquals(5, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + } + @Test void testChunkOrientedStepFailure() throws Exception { // given @@ -124,6 +153,36 @@ void testChunkOrientedStepFailure() throws Exception { System.clearProperty("fail"); } + @Test + void testConcurrentChunkOrientedStepFailure() throws Exception { + // given + System.setProperty("fail", "true"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ConcurrentChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Assertions.assertTrue(stepExecutionExitStatus.getExitDescription() + .contains("Unable to process item Person[id=1, name=foo1]")); + Assertions.assertEquals(0, stepExecution.getReadCount()); + Assertions.assertEquals(0, stepExecution.getWriteCount()); + Assertions.assertEquals(0, stepExecution.getCommitCount()); + Assertions.assertEquals(1, stepExecution.getRollbackCount()); + Assertions.assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("fail"); + } + @Test void testFaultTolerantChunkOrientedStep() throws Exception { // given @@ -155,6 +214,37 @@ void testFaultTolerantChunkOrientedStep() throws Exception { System.clearProperty("skipLimit"); } + @Test + void testConcurrentFaultTolerantChunkOrientedStep() throws Exception { + // given + System.setProperty("skipLimit", "3"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ConcurrentFaultTolerantChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons-bad-data.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.COMPLETED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Assertions.assertEquals(4, stepExecution.getReadCount()); + Assertions.assertEquals(3, stepExecution.getWriteCount()); + Assertions.assertEquals(3, stepExecution.getCommitCount()); + Assertions.assertEquals(0, stepExecution.getRollbackCount()); + Assertions.assertEquals(2, stepExecution.getReadSkipCount()); + Assertions.assertEquals(1, stepExecution.getWriteSkipCount()); + Assertions.assertEquals(3, stepExecution.getSkipCount()); + Assertions.assertEquals(3, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("skipLimit"); + } + @Test void testFaultTolerantChunkOrientedStepFailure() throws Exception { // given @@ -190,6 +280,41 @@ void testFaultTolerantChunkOrientedStepFailure() throws Exception { System.clearProperty("skipLimit"); } + @Test + void testConcurrentFaultTolerantChunkOrientedStepFailure() throws Exception { + // given + System.setProperty("skipLimit", "1"); + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class, + ConcurrentFaultTolerantChunkOrientedStepConfiguration.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); + + // when + JobParameters jobParameters = new JobParametersBuilder().addString("file", "data/persons-bad-data.csv") + .toJobParameters(); + JobExecution jobExecution = jobOperator.start(job, jobParameters); + + // then + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); + ExitStatus stepExecutionExitStatus = stepExecution.getExitStatus(); + Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); + Throwable failureException = stepExecution.getFailureExceptions().iterator().next(); + Assertions.assertInstanceOf(FatalStepExecutionException.class, failureException); + Assertions.assertInstanceOf(SkipLimitExceededException.class, failureException.getCause()); + Assertions.assertEquals(2, stepExecution.getReadCount()); + Assertions.assertEquals(2, stepExecution.getWriteCount()); + Assertions.assertEquals(1, stepExecution.getCommitCount()); + Assertions.assertEquals(1, stepExecution.getRollbackCount()); + // contribution not applied on second chunk rollback + Assertions.assertEquals(0, stepExecution.getReadSkipCount()); + Assertions.assertEquals(0, stepExecution.getWriteSkipCount()); + Assertions.assertEquals(0, stepExecution.getSkipCount()); + Assertions.assertEquals(2, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); + System.clearProperty("skipLimit"); + } + record Person(int id, String name) { } @@ -267,6 +392,22 @@ public Step chunkOrientedStep(JobRepository jobRepository, JdbcTransactionManage } + @Configuration + static class ConcurrentChunkOrientedStepConfiguration { + + @Bean + public Step concurrentChunkOrientedStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, + ItemReader itemReader, ItemProcessor itemProcessor, + ItemWriter itemWriter) { + return new ChunkOrientedStepBuilder(jobRepository, transactionManager, 2).reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .taskExecutor(new SimpleAsyncTaskExecutor()) + .build(); + } + + } + @Configuration static class FaultTolerantChunkOrientedStepConfiguration { @@ -300,4 +441,38 @@ public Step faulTolerantChunkOrientedStep(JobRepository jobRepository, } + @Configuration + static class ConcurrentFaultTolerantChunkOrientedStepConfiguration { + + @Bean + public Step concurrentFaulTolerantChunkOrientedStep(JobRepository jobRepository, + JdbcTransactionManager transactionManager, ItemReader itemReader, + ItemProcessor itemProcessor, ItemWriter itemWriter) { + // retry policy configuration + int retryLimit = 3; + Set> nonRetrybaleExceptions = Set.of(FlatFileParseException.class, + DataIntegrityViolationException.class); + RetryPolicy retryPolicy = RetryPolicy.builder() + .maxAttempts(retryLimit) + .excludes(nonRetrybaleExceptions) + .build(); + + // skip policy configuration + int skipLimit = Integer.parseInt(System.getProperty("skipLimit")); + Map, Boolean> skippableExceptions = Map.of(FlatFileParseException.class, true, + DataIntegrityViolationException.class, true); + LimitCheckingItemSkipPolicy skipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, skippableExceptions); + + return new ChunkOrientedStepBuilder(jobRepository, transactionManager, 2).reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .taskExecutor(new SimpleAsyncTaskExecutor()) + .faultTolerant() + .retryPolicy(retryPolicy) + .skipPolicy(skipPolicy) + .build(); + } + + } + } \ No newline at end of file From 503c49e558df265d93f2fdfa0a3d255764422af7 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Thu, 21 Aug 2025 11:36:28 +0200 Subject: [PATCH 14/49] Remove ErrorProne exclusions Signed-off-by: Stefano Cordio --- pom.xml | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/pom.xml b/pom.xml index 025cdd5ae9..e0c2827a38 100644 --- a/pom.xml +++ b/pom.xml @@ -180,45 +180,6 @@ --should-stop=ifError=FLOW -Xplugin:ErrorProne - -Xep:AlmostJavadoc:OFF - -Xep:ByteBufferBackingArray:OFF - -Xep:ClassCanBeStatic:OFF - -Xep:CollectionUndefinedEquality:OFF - -Xep:DefaultCharset:OFF - -Xep:DirectInvocationOnMock:OFF - -Xep:DoNotCallSuggester:OFF - -Xep:EmptyCatch:OFF - -Xep:EqualsGetClass:OFF - -Xep:Finally:OFF - -Xep:FutureReturnValueIgnored:OFF - -Xep:HidingField:OFF - -Xep:ImmutableEnumChecker:OFF - -Xep:InlineMeSuggester:OFF - -Xep:InputStreamSlowMultibyteRead:OFF - -Xep:JavaTimeDefaultTimeZone:OFF - -Xep:JavaUtilDate:OFF - -Xep:JdkObsolete:OFF - -Xep:MissingSummary:OFF - -Xep:MixedMutabilityReturnType:OFF - -Xep:MutablePublicArray:OFF - -Xep:NonAtomicVolatileUpdate:OFF - -Xep:RedundantControlFlow:OFF - -Xep:ReferenceEquality:OFF - -Xep:StaticAssignmentInConstructor:OFF - -Xep:StaticAssignmentOfThrowable:OFF - -Xep:StaticMockMember:OFF - -Xep:StreamResourceLeak:OFF - -Xep:StringCaseLocaleUsage:OFF - -Xep:StringSplitter:OFF - -Xep:SynchronizeOnNonFinalField:OFF - -Xep:ThreadLocalUsage:OFF - -Xep:ThreadPriorityCheck:OFF - -Xep:TypeParameterUnusedInFormals:OFF - -Xep:UndefinedEquals:OFF - -Xep:UnnecessaryStringBuilder:OFF - -Xep:UnusedMethod:OFF - -Xep:UnusedVariable:OFF - -Xep:WaitNotInLoop:OFF From 43a323f91b2f6a95ca8c295adc4b38c1df49790b Mon Sep 17 00:00:00 2001 From: isanghaessi Date: Tue, 15 Jul 2025 00:12:04 +0900 Subject: [PATCH 15/49] Fix RecordFieldSetMapper for empty record - add initialization for constructorParameterNames, constructorParameterTypes for empty record case. - add TC for empty record case. Signed-off-by: Seungyong Hong --- .../file/mapping/RecordFieldSetMapper.java | 7 ++++++- .../mapping/RecordFieldSetMapperTests.java | 20 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java index a86079cc0f..0eb449dab4 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ * * @param type of mapped items * @author Mahmoud Ben Hassine + * @author Seungyong Hong * @since 4.3 */ public class RecordFieldSetMapper implements FieldSetMapper { @@ -63,6 +64,10 @@ public RecordFieldSetMapper(Class targetType, ConversionService conversionSer this.constructorParameterNames = BeanUtils.getParameterNames(this.mappedConstructor); this.constructorParameterTypes = this.mappedConstructor.getParameterTypes(); } + else { + this.constructorParameterNames = new String[0]; + this.constructorParameterTypes = new Class[0]; + } } @Override diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java index a3fc68ff44..3a1dde5eef 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.batch.item.file.mapping; import org.junit.jupiter.api.Test; - import org.springframework.batch.item.file.transform.DefaultFieldSet; import org.springframework.batch.item.file.transform.FieldSet; @@ -26,6 +25,7 @@ /** * @author Mahmoud Ben Hassine + * @author Seungyong Hong */ class RecordFieldSetMapperTests { @@ -68,7 +68,23 @@ void testMapFieldSetWhenFieldNamesAreNotSpecified() { assertEquals("Field names must be specified", exception.getMessage()); } + @Test + void testMapFieldSetWhenEmptyRecord() { + // given + RecordFieldSetMapper mapper = new RecordFieldSetMapper<>(EmptyRecord.class); + FieldSet fieldSet = new DefaultFieldSet(new String[0], new String[0]); + + // when + EmptyRecord empty = mapper.mapFieldSet(fieldSet); + + // then + assertNotNull(empty); + } + record Person(int id, String name) { } + record EmptyRecord() { + } + } From 73fbca84af908b09e3dca44a863a99041d70c2d1 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 4 Sep 2025 13:11:40 +0200 Subject: [PATCH 16/49] Remove IDE related configurations in favor of spring-javaformat plugin --- spring-eclipse-code-conventions.xml | 251 ---------------------------- 1 file changed, 251 deletions(-) delete mode 100644 spring-eclipse-code-conventions.xml diff --git a/spring-eclipse-code-conventions.xml b/spring-eclipse-code-conventions.xml deleted file mode 100644 index 0ed42acd4b..0000000000 --- a/spring-eclipse-code-conventions.xml +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 952f75656dbb8d69580c21dc7de6affa0d5c8dc3 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 4 Sep 2025 17:13:48 +0200 Subject: [PATCH 17/49] Make spring-data-commons optional in core module --- spring-batch-core/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index ae505b3dad..048a1e24f1 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -128,6 +128,7 @@ org.springframework.data spring-data-commons ${spring-data-commons.version} + true org.mongodb From 56c6870e3867a408f7a10a09926ed17a4fd475aa Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 8 Sep 2025 15:16:05 +0200 Subject: [PATCH 18/49] Make transaction manager optional in JobOperatorFactoryBean Resolves #4970 --- .../annotation/BatchRegistrar.java | 8 +++-- .../support/JobOperatorFactoryBean.java | 6 +++- .../support/JobOperatorFactoryBeanTests.java | 32 ++++++++++++++----- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index fdea238b95..8cd8806aa7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -226,13 +226,15 @@ private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchPro BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(JobOperatorFactoryBean.class); // set mandatory properties - String transactionManagerRef = batchAnnotation.transactionManagerRef(); - beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); - beanDefinitionBuilder.addPropertyReference(JOB_REPOSITORY, JOB_REPOSITORY); beanDefinitionBuilder.addPropertyReference(JOB_REGISTRY, JOB_REGISTRY); // set optional properties + String transactionManagerRef = batchAnnotation.transactionManagerRef(); + if (registry.containsBeanDefinition(transactionManagerRef)) { + beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); + } + String taskExecutorRef = batchAnnotation.taskExecutorRef(); if (registry.containsBeanDefinition(taskExecutorRef)) { beanDefinitionBuilder.addPropertyReference("taskExecutor", taskExecutorRef); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java index 648e0ac85b..a8b2258d7b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java @@ -28,6 +28,7 @@ import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.task.SyncTaskExecutor; @@ -73,7 +74,10 @@ public class JobOperatorFactoryBean implements FactoryBean, Initial public void afterPropertiesSet() throws Exception { Assert.notNull(this.jobRepository, "JobRepository must not be null"); Assert.notNull(this.jobRegistry, "JobRegistry must not be null"); - Assert.notNull(this.transactionManager, "TransactionManager must not be null"); + if (this.transactionManager == null) { + this.transactionManager = new ResourcelessTransactionManager(); + logger.info("No transaction manager has been set, defaulting to ResourcelessTransactionManager."); + } if (this.taskExecutor == null) { logger.info("No TaskExecutor has been set, defaulting to synchronous executor."); this.taskExecutor = new SyncTaskExecutor(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java index 2ef929624c..00d76bf39d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.core.launch.support; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -25,6 +26,7 @@ import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.test.util.AopTestUtils; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.interceptor.TransactionAttributeSource; @@ -65,6 +67,26 @@ public void testJobOperatorCreation() throws Exception { Assertions.assertEquals(this.transactionManager, getTransactionManagerSetOnJobOperator(jobOperator)); } + @Test + public void testDefaultTransactionManagerConfiguration() throws Exception { + // given + JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); + jobOperatorFactoryBean.setJobRegistry(this.jobRegistry); + jobOperatorFactoryBean.setJobRepository(this.jobRepository); + jobOperatorFactoryBean.setJobParametersConverter(this.jobParametersConverter); + jobOperatorFactoryBean.afterPropertiesSet(); + + // when + JobOperator jobOperator = jobOperatorFactoryBean.getObject(); + + // then + Assertions.assertNotNull(jobOperator); + Object targetObject = AopTestUtils.getTargetObject(jobOperator); + Assertions.assertInstanceOf(TaskExecutorJobOperator.class, targetObject); + Assertions.assertInstanceOf(ResourcelessTransactionManager.class, + getTransactionManagerSetOnJobOperator(jobOperator)); + } + @Test public void testCustomTransactionAttributesSource() throws Exception { // given @@ -87,10 +109,7 @@ public void testCustomTransactionAttributesSource() throws Exception { } private PlatformTransactionManager getTransactionManagerSetOnJobOperator(JobOperator jobOperator) { - Advised target = (Advised) jobOperator; // proxy created by - // AbstractJobOperatorFactoryBean - Advisor[] advisors = target.getAdvisors(); - for (Advisor advisor : advisors) { + for (Advisor advisor : ((Advised) jobOperator).getAdvisors()) { if (advisor.getAdvice() instanceof TransactionInterceptor transactionInterceptor) { return (PlatformTransactionManager) transactionInterceptor.getTransactionManager(); } @@ -99,10 +118,7 @@ private PlatformTransactionManager getTransactionManagerSetOnJobOperator(JobOper } private TransactionAttributeSource getTransactionAttributesSourceSetOnJobOperator(JobOperator jobOperator) { - Advised target = (Advised) jobOperator; // proxy created by - // AbstractJobOperatorFactoryBean - Advisor[] advisors = target.getAdvisors(); - for (Advisor advisor : advisors) { + for (Advisor advisor : ((Advised) jobOperator).getAdvisors()) { if (advisor.getAdvice() instanceof TransactionInterceptor transactionInterceptor) { return transactionInterceptor.getTransactionAttributeSource(); } From 5b7f000c8c0186b9d85a7e0967e4dafdbed4df8a Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 8 Sep 2025 19:52:05 +0200 Subject: [PATCH 19/49] Make JobRegistry optional in the default batch configuration Resolves #4971 --- .../annotation/BatchRegistrar.java | 28 +++++---------- .../annotation/EnableBatchProcessing.java | 11 ++++-- .../support/DefaultBatchConfiguration.java | 26 +++++++++----- .../JdbcDefaultBatchConfiguration.java | 1 - .../MongoDefaultBatchConfiguration.java | 1 - .../support/JobOperatorFactoryBean.java | 34 +++++++++++++++++-- .../annotation/BatchRegistrarTests.java | 2 -- .../JobLoaderConfigurationTests.java | 2 ++ .../DefaultBatchConfigurationTests.java | 3 -- .../support/trivial-context-autoregister.xml | 2 -- ...tStepAllowStartIfCompleteTests-context.xml | 2 -- .../batch/core/partition/launch-context.xml | 5 +-- .../OptimisticLockingFailureTests-context.xml | 10 +----- .../dao/TablePrefixTests-context.xml | 5 +-- .../CommitIntervalJobParameter-context.xml | 5 +-- .../core/step/RestartLoopTests-context.xml | 5 +-- ...tTolerantExceptionClassesTests-context.xml | 5 +-- .../item/ScriptItemProcessorTests-context.xml | 10 +----- .../skip/ReprocessExceptionTests-context.xml | 5 +-- .../resources/simple-job-launcher-context.xml | 11 ++---- .../ROOT/pages/job/configuring-operator.adoc | 33 ++++++++---------- ...ltTolerantStepIntegrationTests-context.xml | 5 +-- ...olerantStepJmsIntegrationTests-context.xml | 5 +-- ...emoteChunkStepIntegrationTests-context.xml | 5 +-- ...JobLaunchingGatewayParserTests-context.xml | 5 +-- ...chingGatewayParserTestsRunning-context.xml | 5 +-- .../resources/simple-job-launcher-context.xml | 5 +-- .../batch/samples/jpa/job/jpa.xml | 4 +-- .../batch/samples/jpa/job/repository.xml | 5 +-- .../restart/stop/stopRestartSample.xml | 7 ++-- .../resources/simple-job-launcher-context.xml | 7 ++-- .../SpringBatchTestIntegrationTests.java | 16 ++------- .../resources/simple-job-launcher-context.xml | 5 +-- 33 files changed, 108 insertions(+), 172 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index 8cd8806aa7..ece0498bc1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -53,10 +53,6 @@ class BatchRegistrar implements ImportBeanDefinitionRegistrar { private static final String JOB_OPERATOR = "jobOperator"; - private static final String JOB_REGISTRY = "jobRegistry"; - - private static final String JOB_LOADER = "jobLoader"; - @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { StopWatch watch = new StopWatch(); @@ -66,7 +62,6 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B .get(EnableBatchProcessing.class) .synthesize(); registerJobRepository(registry, importingClassMetadata); - registerJobRegistry(registry); registerJobOperator(registry, batchAnnotation); registerAutomaticJobRegistrar(registry, batchAnnotation); watch.stop(); @@ -206,17 +201,6 @@ private void registerDefaultJobRepository(BeanDefinitionRegistry registry) { registry.registerBeanDefinition(JOB_REPOSITORY, beanDefinitionBuilder.getBeanDefinition()); } - private void registerJobRegistry(BeanDefinitionRegistry registry) { - if (registry.containsBeanDefinition(JOB_REGISTRY)) { - LOGGER.info("Bean jobRegistry already defined in the application context, skipping" - + " the registration of a jobRegistry"); - return; - } - BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapJobRegistry.class) - .getBeanDefinition(); - registry.registerBeanDefinition(JOB_REGISTRY, beanDefinition); - } - private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { if (registry.containsBeanDefinition(JOB_OPERATOR)) { LOGGER.info("Bean jobOperator already defined in the application context, skipping" @@ -227,9 +211,13 @@ private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchPro .genericBeanDefinition(JobOperatorFactoryBean.class); // set mandatory properties beanDefinitionBuilder.addPropertyReference(JOB_REPOSITORY, JOB_REPOSITORY); - beanDefinitionBuilder.addPropertyReference(JOB_REGISTRY, JOB_REGISTRY); // set optional properties + String jobRegistryRef = batchAnnotation.jobRegistryRef(); + if (registry.containsBeanDefinition(jobRegistryRef)) { + beanDefinitionBuilder.addPropertyReference("jobRegistry", jobRegistryRef); + } + String transactionManagerRef = batchAnnotation.transactionManagerRef(); if (registry.containsBeanDefinition(transactionManagerRef)) { beanDefinitionBuilder.addPropertyReference("transactionManager", transactionManagerRef); @@ -258,12 +246,12 @@ private void registerAutomaticJobRegistrar(BeanDefinitionRegistry registry, Enab return; } BeanDefinition jobLoaderBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DefaultJobLoader.class) - .addPropertyReference(JOB_REGISTRY, JOB_REGISTRY) + .addPropertyValue("jobRegistry", new MapJobRegistry()) .getBeanDefinition(); - registry.registerBeanDefinition(JOB_LOADER, jobLoaderBeanDefinition); + registry.registerBeanDefinition("jobLoader", jobLoaderBeanDefinition); BeanDefinition jobRegistrarBeanDefinition = BeanDefinitionBuilder .genericBeanDefinition(AutomaticJobRegistrar.class) - .addPropertyReference(JOB_LOADER, JOB_LOADER) + .addPropertyReference("jobLoader", "jobLoader") .getBeanDefinition(); registry.registerBeanDefinition("jobRegistrar", jobRegistrarBeanDefinition); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index a8341b553d..63647d77a0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -76,8 +76,6 @@ *

    *
  • a {@link JobRepository} (bean name "jobRepository" of type * {@link org.springframework.batch.core.repository.support.ResourcelessJobRepository})
  • - *
  • a {@link JobRegistry} (bean name "jobRegistry" of type - * {@link org.springframework.batch.core.configuration.support.MapJobRegistry})
  • *
  • a {@link org.springframework.batch.core.launch.JobOperator} (bean name * "jobOperator" of type * {@link org.springframework.batch.core.launch.support.TaskExecutorJobOperator})
  • @@ -134,7 +132,7 @@ * * * +"org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> * * * @@ -174,6 +172,13 @@ */ String taskExecutorRef() default "taskExecutor"; + /** + * Set the job registry to use in the job operator. + * @return the bean name of the job registry to use. Defaults to + * {@literal jobRegistry} + */ + String jobRegistryRef() default "jobRegistry"; + /** * Set the transaction manager to use in the job operator. * @return the bean name of the transaction manager to use. Defaults to diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index d1b6dc9cd6..9b3bd71828 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -15,7 +15,9 @@ */ package org.springframework.batch.core.configuration.support; +import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobInstance; import org.springframework.batch.core.job.JobKeyGenerator; import org.springframework.batch.core.configuration.BatchConfigurationException; @@ -48,7 +50,6 @@ * *
      *
    • a {@link ResourcelessJobRepository} named "jobRepository"
    • - *
    • a {@link MapJobRegistry} named "jobRegistry"
    • *
    • a {@link TaskExecutorJobOperator} named "jobOperator"
    • *
    • a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
    • *
    • a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
    • @@ -93,16 +94,10 @@ public JobRepository jobRepository() { } @Bean - public JobRegistry jobRegistry() { - return new MapJobRegistry(); - } - - @Bean - public JobOperator jobOperator(JobRepository jobRepository, JobRegistry jobRegistry) - throws BatchConfigurationException { + public JobOperator jobOperator(JobRepository jobRepository) throws BatchConfigurationException { JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); jobOperatorFactoryBean.setJobRepository(jobRepository); - jobOperatorFactoryBean.setJobRegistry(jobRegistry); + jobOperatorFactoryBean.setJobRegistry(getJobRegistry()); jobOperatorFactoryBean.setTransactionManager(getTransactionManager()); jobOperatorFactoryBean.setJobParametersConverter(getJobParametersConverter()); jobOperatorFactoryBean.setTaskExecutor(getTaskExecutor()); @@ -115,6 +110,19 @@ public JobOperator jobOperator(JobRepository jobRepository, JobRegistry jobRegis } } + protected JobRegistry getJobRegistry() { + MapJobRegistry jobRegistry = new MapJobRegistry(); + this.applicationContext.getBeansOfType(Job.class).values().forEach(job -> { + try { + jobRegistry.register(job); + } + catch (DuplicateJobException e) { + throw new BatchConfigurationException(e); + } + }); + return jobRegistry; + } + /** * Return the transaction manager to use for the job operator. Defaults to * {@link ResourcelessTransactionManager}. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java index 172cb98809..09976310fa 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java @@ -61,7 +61,6 @@ * *
        *
      • a {@link JobRepository} named "jobRepository"
      • - *
      • a {@link JobRegistry} named "jobRegistry"
      • *
      • a {@link JobOperator} named "jobOperator"
      • *
      • a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
      • *
      • a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
      • diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java index 7f5cbb25e2..905bead2b1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java @@ -34,7 +34,6 @@ * *
          *
        • a {@link JobRepository} named "jobRepository"
        • - *
        • a {@link JobRegistry} named "jobRegistry"
        • *
        • a {@link JobOperator} named "jobOperator"
        • *
        • a {@link org.springframework.batch.core.scope.StepScope} named "stepScope"
        • *
        • a {@link org.springframework.batch.core.scope.JobScope} named "jobScope"
        • diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java index a8b2258d7b..aa45dfc492 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java @@ -22,15 +22,22 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.batch.core.configuration.BatchConfigurationException; +import org.springframework.batch.core.configuration.DuplicateJobException; import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.configuration.support.MapJobRegistry; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; +import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.transaction.PlatformTransactionManager; @@ -50,10 +57,12 @@ * @author Mahmoud Ben Hassine * @since 5.0 */ -public class JobOperatorFactoryBean implements FactoryBean, InitializingBean { +public class JobOperatorFactoryBean implements FactoryBean, ApplicationContextAware, InitializingBean { protected static final Log logger = LogFactory.getLog(JobOperatorFactoryBean.class); + private ApplicationContext applicationContext; + private PlatformTransactionManager transactionManager; private TransactionAttributeSource transactionAttributeSource; @@ -70,10 +79,20 @@ public class JobOperatorFactoryBean implements FactoryBean, Initial private final ProxyFactory proxyFactory = new ProxyFactory(); + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + @Override public void afterPropertiesSet() throws Exception { Assert.notNull(this.jobRepository, "JobRepository must not be null"); - Assert.notNull(this.jobRegistry, "JobRegistry must not be null"); + if (this.jobRegistry == null) { + this.jobRegistry = new MapJobRegistry(); + populateJobRegistry(); + logger.info( + "No JobRegistry has been set, defaulting to a MapJobRegistry populated with jobs defined in the application context."); + } if (this.transactionManager == null) { this.transactionManager = new ResourcelessTransactionManager(); logger.info("No transaction manager has been set, defaulting to ResourcelessTransactionManager."); @@ -97,6 +116,17 @@ public void afterPropertiesSet() throws Exception { } } + private void populateJobRegistry() { + this.applicationContext.getBeansOfType(Job.class).values().forEach(job -> { + try { + jobRegistry.register(job); + } + catch (DuplicateJobException e) { + throw new BatchConfigurationException(e); + } + }); + } + /** * Setter for the job registry. * @param jobRegistry the job registry to set diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java index 46fd03e979..39b848ef10 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java @@ -141,12 +141,10 @@ void testDefaultInfrastructureBeansRegistration() { // when JobRepository jobRepository = context.getBean(JobRepository.class); - JobRegistry jobRegistry = context.getBean(JobRegistry.class); JobOperator jobOperator = context.getBean(JobOperator.class); // then Assertions.assertNotNull(jobRepository); - Assertions.assertNotNull(jobRegistry); Assertions.assertNotNull(jobOperator); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java index 45c46d9251..c5a056c694 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobLoaderConfigurationTests.java @@ -19,6 +19,7 @@ import jakarta.annotation.PostConstruct; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; @@ -52,6 +53,7 @@ * @author Mahmoud Ben Hassine * */ +@Disabled class JobLoaderConfigurationTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java index c216be342f..e2a12a3ff2 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java @@ -27,7 +27,6 @@ import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.step.Step; -import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.xml.DummyJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.JobOperator; @@ -79,12 +78,10 @@ void testDefaultInfrastructureBeansRegistration() { // when JobRepository jobRepository = context.getBean(JobRepository.class); - JobRegistry jobRegistry = context.getBean(JobRegistry.class); JobOperator jobOperator = context.getBean(JobOperator.class); // then Assertions.assertNotNull(jobRepository); - Assertions.assertNotNull(jobRegistry); Assertions.assertNotNull(jobOperator); } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/trivial-context-autoregister.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/trivial-context-autoregister.xml index 7ef2ee26ed..e73d1861f5 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/trivial-context-autoregister.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/trivial-context-autoregister.xml @@ -11,6 +11,4 @@ - - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests-context.xml index ffcebb51ef..fc7c37b53a 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/TaskletStepAllowStartIfCompleteTests-context.xml @@ -36,6 +36,4 @@ - - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/partition/launch-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/partition/launch-context.xml index 40a88d4e3b..16e6ad60da 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/partition/launch-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/partition/launch-context.xml @@ -62,12 +62,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - - - - - - - - - + - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/TablePrefixTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/TablePrefixTests-context.xml index 437c6c5921..e756500308 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/TablePrefixTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/repository/dao/TablePrefixTests-context.xml @@ -16,11 +16,8 @@ - - - + - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml index 7d7ecd5eca..3001f87717 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/scope/context/CommitIntervalJobParameter-context.xml @@ -9,12 +9,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml index 31f1db5bb7..88d4275ef5 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml @@ -142,12 +142,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml index 798a82c2af..dda0ba4d01 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/ScriptItemProcessorTests-context.xml @@ -19,16 +19,8 @@ - - - - - - - - + - diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/skip/ReprocessExceptionTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/skip/ReprocessExceptionTests-context.xml index bba1cff094..f85e093f5f 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/skip/ReprocessExceptionTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/skip/ReprocessExceptionTests-context.xml @@ -6,12 +6,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - diff --git a/spring-batch-core/src/test/resources/simple-job-launcher-context.xml b/spring-batch-core/src/test/resources/simple-job-launcher-context.xml index db6b23764c..de0eace8fd 100644 --- a/spring-batch-core/src/test/resources/simple-job-launcher-context.xml +++ b/spring-batch-core/src/test/resources/simple-job-launcher-context.xml @@ -11,15 +11,8 @@ p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager" /> - - - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean" + p:jobRepository-ref="jobRepository"/> diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring-operator.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring-operator.adoc index 0c0d4c15d9..fc0f61b9ba 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/configuring-operator.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring-operator.adoc @@ -2,40 +2,40 @@ = Configuring a JobOperator The most basic implementation of the `JobOperator` interface is the `TaskExecutorJobOperator`. -It only requires two dependencies: a `JobRepository` and a `JobRegistry`. - +It requires only one dependency: a `JobRepository`. All other dependencies like `JobRegistry`, +`MeterRegistry`, `TransactionManager`, etc are optional. Spring Batch provides a factory bean +to simplify the configuration of this operator: `JobOperatorFactoryBean`. This factory bean +creates a transactional proxy around the `TaskExecutorJobOperator` to ensure that all its public methods +are executed within a transaction. [tabs] ==== Java:: + -The following example shows a `TaskExecutorJobLauncher` in Java: +The following example shows how to configure a `TaskExecutorJobOperator` in Java: + .Java Configuration [source, java] ---- ... @Bean -public JobOperator jobOperator(JobRepository jobRepository, JobRegistry jobRegistry) throws Exception { - TaskExecutorJobJobOperator jobOperator = new TaskExecutorJobOperator(); +public JobOperatorFactoryBean jobOperator(JobRepository jobRepository) { + JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); jobOperator.setJobRepository(jobRepository); - jobOperator.setJobRegistry(jobRegistry); - jobOperator.afterPropertiesSet(); - return jobOperator; + return jobOperatorFactoryBean; } ... ---- XML:: + -The following example shows a `TaskExecutorJobOperator` in XML: +The following example shows how to configure a `TaskExecutorJobOperator` in XML: + .XML Configuration [source, xml] ---- - + - ---- @@ -72,13 +72,11 @@ The following Java example configures a `TaskExecutorJobOperator` to return imme [source, java] ---- @Bean -public JobOperator jobOperator(JobRepository jobRepository, JobRegistry jobRegistry) throws Exception { - TaskExecutorJobJobOperator jobOperator = new TaskExecutorJobOperator(); +public JobOperatorFactoryBean jobOperator(JobRepository jobRepository) { + JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); jobOperator.setJobRepository(jobRepository); - jobOperator.setJobRegistry(jobRegistry); jobOperator.setTaskExecutor(new SimpleAsyncTaskExecutor()); - jobOperator.afterPropertiesSet(); - return jobOperator; + return jobOperatorFactoryBean; } ---- @@ -89,9 +87,8 @@ The following XML example configures a `TaskExecutorJobOperator` to return immed .XML Configuration [source, xml] ---- - + - diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests-context.xml index 9f40ab4b64..90ac290c6d 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepIntegrationTests-context.xml @@ -75,12 +75,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests-context.xml index 61ce74d2ce..702dec1c86 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkFaultTolerantStepJmsIntegrationTests-context.xml @@ -109,12 +109,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml index e2dc82f365..0ce037dd99 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/RemoteChunkStepIntegrationTests-context.xml @@ -62,12 +62,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests-context.xml index e0821915e4..1d3772de55 100644 --- a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests-context.xml +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/config/xml/JobLaunchingGatewayParserTests-context.xml @@ -15,12 +15,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - - - - + - diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/jpa.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/jpa.xml index 0e87b7d115..bd1518d9bc 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/jpa.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/jpa.xml @@ -55,12 +55,10 @@ - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/repository.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/repository.xml index ee5720533b..13ac9a73f3 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/repository.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/jpa/job/repository.xml @@ -75,12 +75,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/restart/stop/stopRestartSample.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/restart/stop/stopRestartSample.xml index cfd657f588..5ec9d360bf 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/restart/stop/stopRestartSample.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/restart/stop/stopRestartSample.xml @@ -10,16 +10,13 @@ p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager"/> + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean" + p:jobRepository-ref="jobRepository"> - - diff --git a/spring-batch-samples/src/main/resources/simple-job-launcher-context.xml b/spring-batch-samples/src/main/resources/simple-job-launcher-context.xml index bfec5974bc..8872e36eca 100644 --- a/spring-batch-samples/src/main/resources/simple-job-launcher-context.xml +++ b/spring-batch-samples/src/main/resources/simple-job-launcher-context.xml @@ -11,11 +11,8 @@ p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager"/> - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean" + p:jobRepository-ref="jobRepository"/> diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java index d4de807f90..42db71c432 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java @@ -18,13 +18,10 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.support.MapJobRegistry; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.support.JobOperatorFactoryBean; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.ResourcelessJobRepository; -import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.batch.test.JobOperatorTestUtils; import org.springframework.batch.test.JobRepositoryTestUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +35,7 @@ /** * @author Stefano Cordio + * @author Mahmoud Ben Hassine */ @SpringJUnitConfig @SpringBatchTest @@ -86,18 +84,10 @@ public JobRepository jobRepository() { } @Bean - public JobRegistry jobRegistry() { - return new MapJobRegistry(); - } - - @Bean - public JobOperator jobOperator(JobRepository jobRepository, JobRegistry jobRegistry) throws Exception { + public JobOperatorFactoryBean jobOperator(JobRepository jobRepository) throws Exception { JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); jobOperatorFactoryBean.setJobRepository(jobRepository); - jobOperatorFactoryBean.setJobRegistry(jobRegistry); - jobOperatorFactoryBean.setTransactionManager(new ResourcelessTransactionManager()); - jobOperatorFactoryBean.afterPropertiesSet(); - return jobOperatorFactoryBean.getObject(); + return jobOperatorFactoryBean; } } diff --git a/spring-batch-test/src/test/resources/simple-job-launcher-context.xml b/spring-batch-test/src/test/resources/simple-job-launcher-context.xml index 80e3509026..499aa0501d 100755 --- a/spring-batch-test/src/test/resources/simple-job-launcher-context.xml +++ b/spring-batch-test/src/test/resources/simple-job-launcher-context.xml @@ -14,12 +14,9 @@ - - + class="org.springframework.batch.core.launch.support.JobOperatorFactoryBean"> - Date: Tue, 9 Sep 2025 07:14:10 +0200 Subject: [PATCH 20/49] Extract transaction attributes definition in separate classes --- .../support/JobOperatorFactoryBean.java | 32 ++++++++++++------- .../AbstractJobRepositoryFactoryBean.java | 25 +++++++++------ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java index aa45dfc492..82c50dd82d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBean.java @@ -102,17 +102,7 @@ public void afterPropertiesSet() throws Exception { this.taskExecutor = new SyncTaskExecutor(); } if (this.transactionAttributeSource == null) { - this.transactionAttributeSource = new MethodMapTransactionAttributeSource(); - DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); - Method stopMethod = TaskExecutorJobOperator.class.getMethod("stop", JobExecution.class); - Method abandonMethod = TaskExecutorJobOperator.class.getMethod("abandon", JobExecution.class); - Method recoverMethod = TaskExecutorJobOperator.class.getMethod("recover", JobExecution.class); - ((MethodMapTransactionAttributeSource) this.transactionAttributeSource).addTransactionalMethod(stopMethod, - transactionAttribute); - ((MethodMapTransactionAttributeSource) this.transactionAttributeSource) - .addTransactionalMethod(abandonMethod, transactionAttribute); - ((MethodMapTransactionAttributeSource) this.transactionAttributeSource) - .addTransactionalMethod(recoverMethod, transactionAttribute); + this.transactionAttributeSource = new DefaultJobOperatorTransactionAttributeSource(); } } @@ -223,4 +213,24 @@ private TaskExecutorJobOperator getTarget() throws Exception { return taskExecutorJobOperator; } + private static class DefaultJobOperatorTransactionAttributeSource extends MethodMapTransactionAttributeSource { + + public DefaultJobOperatorTransactionAttributeSource() { + DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); + try { + Method stopMethod = TaskExecutorJobOperator.class.getMethod("stop", JobExecution.class); + Method abandonMethod = TaskExecutorJobOperator.class.getMethod("abandon", JobExecution.class); + Method recoverMethod = TaskExecutorJobOperator.class.getMethod("recover", JobExecution.class); + addTransactionalMethod(stopMethod, transactionAttribute); + addTransactionalMethod(abandonMethod, transactionAttribute); + addTransactionalMethod(recoverMethod, transactionAttribute); + } + catch (NoSuchMethodException e) { + throw new IllegalStateException("Failed to initialize default transaction attributes for JobOperator", + e); + } + } + + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java index 1d304dba63..50a5ef4186 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/AbstractJobRepositoryFactoryBean.java @@ -196,15 +196,8 @@ public void afterPropertiesSet() throws Exception { jobKeyGenerator = new DefaultJobKeyGenerator(); } if (this.transactionAttributeSource == null) { - Properties transactionAttributes = new Properties(); - transactionAttributes.setProperty("create*", - TRANSACTION_PROPAGATION_PREFIX + Propagation.REQUIRES_NEW + "," + this.isolationLevelForCreate); - transactionAttributes.setProperty("getLastJobExecution*", - TRANSACTION_PROPAGATION_PREFIX + Propagation.REQUIRES_NEW + "," + this.isolationLevelForCreate); - transactionAttributes.setProperty("*", "PROPAGATION_REQUIRED"); - this.transactionAttributeSource = new NameMatchTransactionAttributeSource(); - ((NameMatchTransactionAttributeSource) this.transactionAttributeSource) - .setProperties(transactionAttributes); + this.transactionAttributeSource = new DefaultJobRepositoryTransactionAttributeSource( + this.isolationLevelForCreate); } } @@ -237,4 +230,18 @@ private Object getTarget() throws Exception { createExecutionContextDao()); } + private static class DefaultJobRepositoryTransactionAttributeSource extends NameMatchTransactionAttributeSource { + + public DefaultJobRepositoryTransactionAttributeSource(String isolationLevelForCreate) { + Properties transactionAttributes = new Properties(); + transactionAttributes.setProperty("create*", + TRANSACTION_PROPAGATION_PREFIX + Propagation.REQUIRES_NEW + "," + isolationLevelForCreate); + transactionAttributes.setProperty("getLastJobExecution*", + TRANSACTION_PROPAGATION_PREFIX + Propagation.REQUIRES_NEW + "," + isolationLevelForCreate); + transactionAttributes.setProperty("*", "PROPAGATION_REQUIRED"); + this.setProperties(transactionAttributes); + } + + } + } From 3c869468739c307b64806cc05e1af8f7707f9345 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 9 Sep 2025 18:22:03 +0200 Subject: [PATCH 21/49] Update step builder with a new method to configure the new ChunkOrientedStep This commit also deprecates APIs that are used by the legacy implementation. Related to #3950 --- .../builder/ChunkOrientedStepBuilder.java | 32 +++++++++++------ .../builder/FaultTolerantStepBuilder.java | 4 +++ .../core/step/builder/SimpleStepBuilder.java | 3 ++ .../batch/core/step/builder/StepBuilder.java | 21 ++++++++++-- .../factory/BatchListenerFactoryHelper.java | 5 +-- .../factory/FaultTolerantStepFactoryBean.java | 3 +- .../step/factory/SimpleStepFactoryBean.java | 3 +- .../core/step/item/BatchRetryTemplate.java | 3 +- .../batch/core/step/item/ChunkMonitor.java | 2 ++ .../core/step/item/ChunkOrientedStep.java | 34 +++++++++++++------ .../core/step/item/ChunkOrientedTasklet.java | 4 +++ .../batch/core/step/item/ChunkProcessor.java | 2 ++ .../batch/core/step/item/ChunkProvider.java | 2 ++ .../step/item/DefaultItemFailureHandler.java | 3 +- .../item/FaultTolerantChunkProcessor.java | 4 +++ .../step/item/FaultTolerantChunkProvider.java | 4 +++ .../batch/core/step/item/KeyGenerator.java | 4 ++- .../core/step/item/SimpleChunkProcessor.java | 4 +++ .../core/step/item/SimpleChunkProvider.java | 4 +++ .../item/SimpleRetryExceptionHandler.java | 5 ++- .../ChunkOrientedStepIntegrationTests.java | 12 ++++--- 21 files changed, 124 insertions(+), 34 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java index c196d63ac2..dff5037f53 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java @@ -50,6 +50,7 @@ import org.springframework.batch.item.ItemStream; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.support.ReflectionUtils; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.core.retry.RetryListener; import org.springframework.core.retry.RetryPolicy; import org.springframework.core.task.AsyncTaskExecutor; @@ -74,7 +75,7 @@ public class ChunkOrientedStepBuilder extends StepBuilderHelper writer; - private final PlatformTransactionManager transactionManager; + private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); @@ -96,17 +97,19 @@ public class ChunkOrientedStepBuilder extends StepBuilderHelper parent, int chunkSize) { + super(parent); + this.chunkSize = chunkSize; + } + /** * Create a new {@link ChunkOrientedStepBuilder} with the given job repository and * transaction manager. The step name will be assigned to the bean name. * @param jobRepository the job repository - * @param transactionManager the transaction manager * @param chunkSize the size of the chunk to be processed */ - public ChunkOrientedStepBuilder(JobRepository jobRepository, PlatformTransactionManager transactionManager, - int chunkSize) { + public ChunkOrientedStepBuilder(JobRepository jobRepository, int chunkSize) { super(jobRepository); - this.transactionManager = transactionManager; this.chunkSize = chunkSize; } @@ -115,13 +118,10 @@ public ChunkOrientedStepBuilder(JobRepository jobRepository, PlatformTransaction * repository and transaction manager. * @param name the step name * @param jobRepository the job repository - * @param transactionManager the transaction manager * @param chunkSize the size of the chunk to be processed */ - public ChunkOrientedStepBuilder(String name, JobRepository jobRepository, - PlatformTransactionManager transactionManager, int chunkSize) { + public ChunkOrientedStepBuilder(String name, JobRepository jobRepository, int chunkSize) { super(name, jobRepository); - this.transactionManager = transactionManager; this.chunkSize = chunkSize; } @@ -166,6 +166,17 @@ public ChunkOrientedStepBuilder writer(ItemWriter writer) { return self(); } + /** + * Sets the transaction manager to use for the chunk-oriented tasklet. Defaults to a + * {@link ResourcelessTransactionManager} if none is provided. + * @param transactionManager a transaction manager set + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder transactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + return self(); + } + /** * Sets the transaction attributes for the tasklet execution. Defaults to the default * values for the transaction manager, but can be manipulated to provide longer @@ -314,8 +325,9 @@ public ChunkOrientedStepBuilder taskExecutor(AsyncTaskExecutor asyncTaskEx @SuppressWarnings("unchecked") public ChunkOrientedStep build() { ChunkOrientedStep chunkOrientedStep = new ChunkOrientedStep<>(this.getName(), this.chunkSize, this.reader, - this.writer, this.getJobRepository(), this.transactionManager); + this.writer, this.getJobRepository()); chunkOrientedStep.setItemProcessor(this.processor); + chunkOrientedStep.setTransactionManager(this.transactionManager); chunkOrientedStep.setTransactionAttribute(this.transactionAttribute); chunkOrientedStep.setInterruptionPolicy(this.interruptionPolicy); chunkOrientedStep.setRetryPolicy(this.retryPolicy); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java index b40688b58c..f18c567eb3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java @@ -92,7 +92,11 @@ * @author Mahmoud Ben Hassine * @author Ian Choi * @since 2.2 + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder} instead. + * Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class FaultTolerantStepBuilder extends SimpleStepBuilder { private final ChunkMonitor chunkMonitor = new ChunkMonitor(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java index 0ce25a8184..bd711e81e1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java @@ -66,7 +66,10 @@ * @author Mahmoud Ben Hassine * @author Parikshit Dutta * @since 2.2 + * @deprecated Since 6.0 in favor of {@link ChunkOrientedStepBuilder}. Scheduled for + * removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleStepBuilder extends AbstractTaskletStepBuilder> { private static final int DEFAULT_COMMIT_INTERVAL = 1; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java index 90fcca83b0..2c744fa857 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java @@ -82,11 +82,28 @@ public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager tr * @param the type of item to be processed as input * @param the type of item to be output * @since 5.0 + * @deprecated since 6.0, use {@link #chunk(int)} instead. Scheduled for removal in + * 7.0. */ + @Deprecated(since = "6.0", forRemoval = true) public SimpleStepBuilder chunk(int chunkSize, PlatformTransactionManager transactionManager) { return new SimpleStepBuilder(this).transactionManager(transactionManager).chunk(chunkSize); } + /** + * Build a step that processes items in chunks with the size provided. To extend the + * step to being fault-tolerant, call the + * {@link ChunkOrientedStepBuilder#faultTolerant()} method on the builder. + * @param chunkSize the chunk size (commit interval) + * @return a {@link ChunkOrientedStepBuilder} for method chaining + * @param the type of item to be processed as input + * @param the type of item to be output + * @since 6.0 + */ + public ChunkOrientedStepBuilder chunk(int chunkSize) { + return new ChunkOrientedStepBuilder<>(this, chunkSize); + } + /** * Build a step that processes items in chunks with the completion policy provided. To * extend the step to being fault tolerant, call the @@ -105,8 +122,8 @@ public SimpleStepBuilder chunk(int chunkSize, PlatformTransactionMa * @param the type of item to be processed as input * @param the type of item to be output * @since 5.0 - * @deprecated since 6.0, use {@link #chunk(int, PlatformTransactionManager)} instead. - * Scheduled for removal in 6.2 or later. + * @deprecated since 6.0, use {@link #chunk(int)} instead. Scheduled for removal in + * 7.0. */ @Deprecated(since = "6.0", forRemoval = true) public SimpleStepBuilder chunk(CompletionPolicy completionPolicy, diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java index 5d6d22ce0c..0204b7dfea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/BatchListenerFactoryHelper.java @@ -22,10 +22,11 @@ /** * Package private helper for step factory beans. - * + * * @author Dave Syer - * + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) abstract class BatchListenerFactoryHelper { public static List getListeners(StepListener[] listeners, Class cls) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java index 61e867db80..e85c960622 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java @@ -48,8 +48,9 @@ * @author Robert Kasanicky * @author Morten Andersen-Gott * @author Ian Choi - * + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean { private Map, Boolean> skippableExceptionClasses = new HashMap<>(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/SimpleStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/SimpleStepFactoryBean.java index f12f4fda1a..646928d85a 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/SimpleStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/SimpleStepFactoryBean.java @@ -59,8 +59,9 @@ * @author Dave Syer * @author Robert Kasanicky * @author Mahmoud Ben Hassine - * + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleStepFactoryBean implements FactoryBean, BeanNameAware { private String name; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchRetryTemplate.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchRetryTemplate.java index 4d16fbf665..8004deb3f1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchRetryTemplate.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/BatchRetryTemplate.java @@ -53,8 +53,9 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class BatchRetryTemplate implements RetryOperations { private static class BatchRetryState extends DefaultRetryState { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java index edcb5b0a34..a1a4044b05 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java @@ -34,7 +34,9 @@ * @author Mahmoud Ben Hassine * @author Seungrae Kim * @since 2.0 + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class ChunkMonitor extends ItemStreamSupport { private final Log logger = LogFactory.getLog(getClass()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index 7555910d42..f35253aaa9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -52,6 +52,7 @@ import org.springframework.batch.item.ItemStream; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.item.support.CompositeItemStream; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.core.retry.RetryException; import org.springframework.core.retry.RetryListener; import org.springframework.core.retry.RetryPolicy; @@ -107,7 +108,7 @@ public class ChunkOrientedStep extends AbstractStep { /* * Transaction related parameters */ - private final PlatformTransactionManager transactionManager; + private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); private TransactionTemplate transactionTemplate; @@ -149,16 +150,14 @@ public class ChunkOrientedStep extends AbstractStep { * @param itemReader the item reader to read items * @param itemWriter the item writer to write items * @param jobRepository the job repository to use for this step - * @param transactionManager the transaction manager to use for this step */ public ChunkOrientedStep(String name, int chunkSize, ItemReader itemReader, ItemWriter itemWriter, - JobRepository jobRepository, PlatformTransactionManager transactionManager) { + JobRepository jobRepository) { super(name); this.chunkSize = chunkSize; this.itemReader = itemReader; this.itemWriter = itemWriter; setJobRepository(jobRepository); - this.transactionManager = transactionManager; } /** @@ -226,6 +225,16 @@ public void registerChunkListener(ChunkListener chunkListener) { this.compositeChunkListener.register(chunkListener); } + /** + * Set the {@link PlatformTransactionManager} to use for the chunk-oriented tasklet. + * Defaults to a {@link ResourcelessTransactionManager}. + * @param transactionManager a transaction manager set, must not be null. + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + Assert.notNull(transactionManager, "Transaction manager must not be null"); + this.transactionManager = transactionManager; + } + /** * Set the transaction attribute for this step. * @param transactionAttribute the transaction attribute to set @@ -337,17 +346,22 @@ protected void doExecute(StepExecution stepExecution) throws Exception { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { StepContribution contribution = stepExecution.createStepContribution(); - if (isConcurrent()) { - processChunkConcurrently(status, contribution, stepExecution); - } - else { - processChunkSequentially(status, contribution, stepExecution); - } + processNextChunk(status, contribution, stepExecution); } }); } } + private void processNextChunk(TransactionStatus status, StepContribution contribution, + StepExecution stepExecution) { + if (isConcurrent()) { + processChunkConcurrently(status, contribution, stepExecution); + } + else { + processChunkSequentially(status, contribution, stepExecution); + } + } + private void processChunkConcurrently(TransactionStatus status, StepContribution contribution, StepExecution stepExecution) { List> itemProcessingTasks = new LinkedList<>(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java index cd780c2977..d6a7bb47c8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java @@ -31,7 +31,11 @@ * @author Dave Syer * @author Mahmoud Ben Hassine * @param input item type + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class ChunkOrientedTasklet implements Tasklet { private static final String INPUTS_KEY = "INPUTS"; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java index ed29a3e005..757f8422b5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProcessor.java @@ -24,7 +24,9 @@ * * @author Kyeonghoon Lee (Add FunctionalInterface annotation) * @since 2.0 + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) @FunctionalInterface public interface ChunkProcessor { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProvider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProvider.java index 2541148a42..ceb2ef825b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProvider.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkProvider.java @@ -25,7 +25,9 @@ * * @since 2.0 * @see ChunkOrientedTasklet + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public interface ChunkProvider { Chunk provide(StepContribution contribution) throws Exception; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/DefaultItemFailureHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/DefaultItemFailureHandler.java index e51403aeb6..8d47d4e3f9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/DefaultItemFailureHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/DefaultItemFailureHandler.java @@ -29,8 +29,9 @@ * * @author Lucas Ward * @author Mahmoud Ben Hassine - * + * @deprecated Since 6.0 with no replacement. Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class DefaultItemFailureHandler extends ItemListenerSupport { protected static final Log logger = LogFactory.getLog(DefaultItemFailureHandler.class); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java index 04ef046685..27cb244a1b 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java @@ -50,7 +50,11 @@ * FaultTolerant implementation of the {@link ChunkProcessor} interface, that allows for * skipping or retry of items that cause exceptions during writing. * + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class FaultTolerantChunkProcessor extends SimpleChunkProcessor { private SkipPolicy itemProcessSkipPolicy = new LimitCheckingItemSkipPolicy(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProvider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProvider.java index e000650e44..1dfe0e5c4d 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProvider.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProvider.java @@ -33,7 +33,11 @@ * FaultTolerant implementation of the {@link ChunkProvider} interface, that allows for * skipping or retry of items that cause exceptions during reading or processing. * + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class FaultTolerantChunkProvider extends SimpleChunkProvider { /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/KeyGenerator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/KeyGenerator.java index ee705659f3..b1926cc90f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/KeyGenerator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/KeyGenerator.java @@ -21,8 +21,10 @@ * * @author Dave Syer * @author Taeik Lim - * + * @deprecated Since 6.0 in favor of equals/hashcode in a wrapper type if needed. + * Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) @FunctionalInterface public interface KeyGenerator { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java index 58ea60dcdb..246cdbd5d6 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java @@ -40,7 +40,11 @@ * writing and processing. Any exceptions encountered will be rethrown. * * @see ChunkOrientedTasklet + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleChunkProcessor implements ChunkProcessor, InitializingBean { private ItemProcessor itemProcessor; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProvider.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProvider.java index d5420b6123..2ba00208af 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProvider.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProvider.java @@ -43,7 +43,11 @@ * @author Michael Minella * @author Mahmoud Ben Hassine * @see ChunkOrientedTasklet + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleChunkProvider implements ChunkProvider { protected final Log logger = LogFactory.getLog(getClass()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleRetryExceptionHandler.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleRetryExceptionHandler.java index 85c1d8a128..6d802f11f1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleRetryExceptionHandler.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleRetryExceptionHandler.java @@ -34,8 +34,11 @@ * exception handling to another {@link ExceptionHandler}. * * @author Dave Syer - * + * @deprecated Since 6.0, use + * {@link org.springframework.batch.core.step.item.ChunkOrientedStep} instead. Scheduled + * for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class SimpleRetryExceptionHandler implements RetryListener, ExceptionHandler { /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java index 4ec0794f1b..bdf124707e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java @@ -384,9 +384,10 @@ static class ChunkOrientedStepConfiguration { public Step chunkOrientedStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemProcessor itemProcessor, ItemWriter itemWriter) { - return new ChunkOrientedStepBuilder(jobRepository, transactionManager, 2).reader(itemReader) + return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) .processor(itemProcessor) .writer(itemWriter) + .transactionManager(transactionManager) .build(); } @@ -399,9 +400,10 @@ static class ConcurrentChunkOrientedStepConfiguration { public Step concurrentChunkOrientedStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemProcessor itemProcessor, ItemWriter itemWriter) { - return new ChunkOrientedStepBuilder(jobRepository, transactionManager, 2).reader(itemReader) + return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) .processor(itemProcessor) .writer(itemWriter) + .transactionManager(transactionManager) .taskExecutor(new SimpleAsyncTaskExecutor()) .build(); } @@ -430,9 +432,10 @@ public Step faulTolerantChunkOrientedStep(JobRepository jobRepository, DataIntegrityViolationException.class, true); LimitCheckingItemSkipPolicy skipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, skippableExceptions); - return new ChunkOrientedStepBuilder(jobRepository, transactionManager, 2).reader(itemReader) + return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) .processor(itemProcessor) .writer(itemWriter) + .transactionManager(transactionManager) .faultTolerant() .retryPolicy(retryPolicy) .skipPolicy(skipPolicy) @@ -463,9 +466,10 @@ public Step concurrentFaulTolerantChunkOrientedStep(JobRepository jobRepository, DataIntegrityViolationException.class, true); LimitCheckingItemSkipPolicy skipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, skippableExceptions); - return new ChunkOrientedStepBuilder(jobRepository, transactionManager, 2).reader(itemReader) + return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) .processor(itemProcessor) .writer(itemWriter) + .transactionManager(transactionManager) .taskExecutor(new SimpleAsyncTaskExecutor()) .faultTolerant() .retryPolicy(retryPolicy) From 1459045c586e149dc9c894c131b85e7aac356b76 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 13:45:58 +0200 Subject: [PATCH 22/49] Handle errors thrown by skip listeners in ChunkOrientedStep --- .../core/step/item/ChunkOrientedStep.java | 28 +++++++++++++++---- .../skip/SkipListenerFailedException.java | 10 +++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index f35253aaa9..f6bac75629 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -44,6 +44,7 @@ import org.springframework.batch.core.step.StepInterruptionPolicy; import org.springframework.batch.core.step.ThreadStepInterruptionPolicy; import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy; +import org.springframework.batch.core.step.skip.SkipListenerFailedException; import org.springframework.batch.core.step.skip.SkipPolicy; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ExecutionContext; @@ -516,8 +517,13 @@ public String getName() { private void doSkipInRead(RetryException retryException, StepContribution contribution) { Throwable cause = retryException.getCause(); if (this.skipPolicy.shouldSkip(cause, contribution.getStepSkipCount())) { - this.compositeSkipListener.onSkipInRead(cause); - contribution.incrementReadSkipCount(); + try { + this.compositeSkipListener.onSkipInRead(cause); + contribution.incrementReadSkipCount(); + } + catch (Throwable throwable) { + throw new SkipListenerFailedException("Unable to apply onSkipInRead", throwable); + } } } @@ -589,8 +595,13 @@ public String getName() { private void doSkipInProcess(I item, RetryException retryException, StepContribution contribution) { Throwable cause = retryException.getCause(); if (this.skipPolicy.shouldSkip(cause, contribution.getStepSkipCount())) { - this.compositeSkipListener.onSkipInProcess(item, retryException.getCause()); - contribution.incrementProcessSkipCount(); + try { + this.compositeSkipListener.onSkipInProcess(item, retryException.getCause()); + contribution.incrementProcessSkipCount(); + } + catch (Throwable throwable) { + throw new SkipListenerFailedException("Unable to apply onSkipInProcess", throwable); + } } } @@ -646,8 +657,13 @@ private void scan(Chunk chunk, StepContribution contribution) { } catch (Exception exception) { if (this.skipPolicy.shouldSkip(exception, contribution.getStepSkipCount())) { - this.compositeSkipListener.onSkipInWrite(item, exception); - contribution.incrementWriteSkipCount(); + try { + this.compositeSkipListener.onSkipInWrite(item, exception); + contribution.incrementWriteSkipCount(); + } + catch (Throwable throwable) { + throw new SkipListenerFailedException("Unable to apply onSkipInWrite", throwable); + } } else { logger.error("Failed to write item: " + item, exception); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipListenerFailedException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipListenerFailedException.java index f22850b279..8af3a4904b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipListenerFailedException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/SkipListenerFailedException.java @@ -28,6 +28,16 @@ */ public class SkipListenerFailedException extends UnexpectedJobExecutionException { + /** + * Create a new {@link SkipListenerFailedException}. + * @param message the exception message + * @param throwable the error that was thrown by a {@link SkipListener} + * @since 6.0 + */ + public SkipListenerFailedException(String message, Throwable throwable) { + super(message, throwable); + } + /** * @param message describes the error to the user * @param ex the exception that was thrown by a {@link SkipListener} From db86cb2b736f49f2bf1042360b88fddefb7e4264 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 13:53:00 +0200 Subject: [PATCH 23/49] Fix chunk contribution in ChunkOrientedStep --- .../core/step/item/ChunkOrientedStep.java | 23 ++++++++----------- .../ChunkOrientedStepIntegrationTests.java | 18 +++++++-------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index f6bac75629..c4efbbbdc6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -391,14 +391,7 @@ private void processChunkConcurrently(TransactionStatus status, StepContribution // write processed items writeChunk(processedChunk, contribution); - - // apply contribution and update job repository - stepExecution.apply(contribution); stepExecution.incrementCommitCount(); - this.compositeItemStream.update(stepExecution.getExecutionContext()); - getJobRepository().update(stepExecution); - getJobRepository().updateExecutionContext(stepExecution); - } catch (Exception e) { logger.error("Rolling back chunk transaction", e); @@ -406,6 +399,11 @@ private void processChunkConcurrently(TransactionStatus status, StepContribution stepExecution.incrementRollbackCount(); throw new FatalStepExecutionException("Unable to process chunk", e); } + finally { + // apply contribution and update streams + stepExecution.apply(contribution); + this.compositeItemStream.update(stepExecution.getExecutionContext()); + } } @@ -422,13 +420,7 @@ private void processChunkSequentially(TransactionStatus status, StepContribution processedChunk = processChunk(inputChunk, contribution); writeChunk(processedChunk, contribution); compositeChunkListener.afterChunk(processedChunk); - - // apply contribution and update job repository - stepExecution.apply(contribution); stepExecution.incrementCommitCount(); - compositeItemStream.update(stepExecution.getExecutionContext()); - getJobRepository().update(stepExecution); - getJobRepository().updateExecutionContext(stepExecution); } catch (Exception e) { logger.error("Rolling back chunk transaction", e); @@ -437,6 +429,11 @@ private void processChunkSequentially(TransactionStatus status, StepContribution compositeChunkListener.onChunkError(e, processedChunk); throw new FatalStepExecutionException("Unable to process chunk", e); } + finally { + // apply contribution and update streams + stepExecution.apply(contribution); + compositeItemStream.update(stepExecution.getExecutionContext()); + } } /* diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java index bdf124707e..b767c0c659 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java @@ -145,7 +145,7 @@ void testChunkOrientedStepFailure() throws Exception { Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); Assertions.assertTrue(stepExecutionExitStatus.getExitDescription() .contains("Unable to process item Person[id=1, name=foo1]")); - Assertions.assertEquals(0, stepExecution.getReadCount()); + Assertions.assertEquals(2, stepExecution.getReadCount()); Assertions.assertEquals(0, stepExecution.getWriteCount()); Assertions.assertEquals(0, stepExecution.getCommitCount()); Assertions.assertEquals(1, stepExecution.getRollbackCount()); @@ -175,7 +175,7 @@ void testConcurrentChunkOrientedStepFailure() throws Exception { Assertions.assertEquals(ExitStatus.FAILED.getExitCode(), stepExecutionExitStatus.getExitCode()); Assertions.assertTrue(stepExecutionExitStatus.getExitDescription() .contains("Unable to process item Person[id=1, name=foo1]")); - Assertions.assertEquals(0, stepExecution.getReadCount()); + Assertions.assertEquals(2, stepExecution.getReadCount()); Assertions.assertEquals(0, stepExecution.getWriteCount()); Assertions.assertEquals(0, stepExecution.getCommitCount()); Assertions.assertEquals(1, stepExecution.getRollbackCount()); @@ -268,14 +268,13 @@ void testFaultTolerantChunkOrientedStepFailure() throws Exception { Throwable failureException = stepExecution.getFailureExceptions().iterator().next(); Assertions.assertInstanceOf(FatalStepExecutionException.class, failureException); Assertions.assertInstanceOf(SkipLimitExceededException.class, failureException.getCause()); - Assertions.assertEquals(2, stepExecution.getReadCount()); + Assertions.assertEquals(3, stepExecution.getReadCount()); Assertions.assertEquals(2, stepExecution.getWriteCount()); Assertions.assertEquals(1, stepExecution.getCommitCount()); Assertions.assertEquals(1, stepExecution.getRollbackCount()); - // contribution not applied on second chunk rollback - Assertions.assertEquals(0, stepExecution.getReadSkipCount()); + Assertions.assertEquals(1, stepExecution.getReadSkipCount()); Assertions.assertEquals(0, stepExecution.getWriteSkipCount()); - Assertions.assertEquals(0, stepExecution.getSkipCount()); + Assertions.assertEquals(1, stepExecution.getSkipCount()); Assertions.assertEquals(2, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); System.clearProperty("skipLimit"); } @@ -303,14 +302,13 @@ void testConcurrentFaultTolerantChunkOrientedStepFailure() throws Exception { Throwable failureException = stepExecution.getFailureExceptions().iterator().next(); Assertions.assertInstanceOf(FatalStepExecutionException.class, failureException); Assertions.assertInstanceOf(SkipLimitExceededException.class, failureException.getCause()); - Assertions.assertEquals(2, stepExecution.getReadCount()); + Assertions.assertEquals(3, stepExecution.getReadCount()); Assertions.assertEquals(2, stepExecution.getWriteCount()); Assertions.assertEquals(1, stepExecution.getCommitCount()); Assertions.assertEquals(1, stepExecution.getRollbackCount()); - // contribution not applied on second chunk rollback - Assertions.assertEquals(0, stepExecution.getReadSkipCount()); + Assertions.assertEquals(1, stepExecution.getReadSkipCount()); Assertions.assertEquals(0, stepExecution.getWriteSkipCount()); - Assertions.assertEquals(0, stepExecution.getSkipCount()); + Assertions.assertEquals(1, stepExecution.getSkipCount()); Assertions.assertEquals(2, JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target")); System.clearProperty("skipLimit"); } From 7950f91f6aa22bb00dbdafb42b8888bcb6e84397 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 13:54:13 +0200 Subject: [PATCH 24/49] Update Javadoc of ChunkListener about concurrent steps --- .../batch/core/listener/ChunkListener.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java index 01a4c721f3..45b91756db 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListener.java @@ -24,6 +24,9 @@ *

          * {@link ChunkListener} shouldn't throw exceptions and expect continued processing, they * must be handled in the implementation or the step will terminate. + *

          + * Note: This listener is not called in concurrent steps. + *

          * * @author Lucas Ward * @author Michael Minella @@ -78,14 +81,16 @@ default void afterChunkError(ChunkContext context) { } /** - * Callback before the chunk is processed, inside the transaction. + * Callback before the chunk is processed, inside the transaction. This method is not + * called in concurrent steps. * @since 6.0 */ default void beforeChunk(Chunk chunk) { } /** - * Callback after the chunk is written, inside the transaction. + * Callback after the chunk is written, inside the transaction. This method is not + * called in concurrent steps. * @since 6.0 */ default void afterChunk(Chunk chunk) { @@ -95,7 +100,7 @@ default void afterChunk(Chunk chunk) { * Callback if an exception occurs while processing or writing a chunk, inside the * transaction, which is about to be rolled back. As a result, you should use * {@code PROPAGATION_REQUIRES_NEW} for any transactional operation that is called - * here. + * here. This method is not called in concurrent steps. * @param exception the exception that caused the underlying rollback. * @param chunk the processed chunk * @since 6.0 From 82f930a11976a50b6cfb22c713273efdb6f773dc Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 14:47:34 +0200 Subject: [PATCH 25/49] Add new limit checking skip policy that is not based on Spring Retry APIs Related to #4868 --- ...tCheckingExceptionHierarchySkipPolicy.java | 74 +++++++++++++++++++ .../skip/LimitCheckingItemSkipPolicy.java | 3 + 2 files changed, 77 insertions(+) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingExceptionHierarchySkipPolicy.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingExceptionHierarchySkipPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingExceptionHierarchySkipPolicy.java new file mode 100644 index 0000000000..58492c91a0 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingExceptionHierarchySkipPolicy.java @@ -0,0 +1,74 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.step.skip; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.util.Assert; + +/** + * A composite {@link SkipPolicy} that checks if the exception is assignable from one of + * the given skippable exceptions, and counts the number of skips to not exceed a given + * limit. + * + * @author Mahmoud Ben Hassine + * @since 6.0 + */ +public class LimitCheckingExceptionHierarchySkipPolicy implements SkipPolicy { + + private Set> skippableExceptions = new HashSet<>(); + + private long skipLimit = -1; + + /** + * Create a new {@link LimitCheckingExceptionHierarchySkipPolicy} instance. + * @param skippableExceptions exception classes that can be skipped (non-critical) + * @param skipLimit the number of skippable exceptions that are allowed to be skipped + */ + public LimitCheckingExceptionHierarchySkipPolicy(Set> skippableExceptions, + long skipLimit) { + Assert.notEmpty(skippableExceptions, "The skippableExceptions must not be empty"); + Assert.isTrue(skipLimit > 0, "The skipLimit must be greater than zero"); + this.skippableExceptions = skippableExceptions; + this.skipLimit = skipLimit; + } + + @Override + public boolean shouldSkip(Throwable t, long skipCount) throws SkipLimitExceededException { + if (!isSkippable(t)) { + return false; + } + if (skipCount < this.skipLimit) { + return true; + } + else { + throw new SkipLimitExceededException(this.skipLimit, t); + } + } + + private boolean isSkippable(Throwable t) { + boolean isSkippable = false; + for (Class skippableException : this.skippableExceptions) { + if (skippableException.isAssignableFrom(t.getClass())) { + isSkippable = true; + break; + } + } + return isSkippable; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingItemSkipPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingItemSkipPolicy.java index 209fb969da..ceeedc374a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingItemSkipPolicy.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/LimitCheckingItemSkipPolicy.java @@ -49,7 +49,10 @@ * @author Dave Syer * @author Dan Garrette * @author Mahmoud Ben Hassine + * @deprecated Since 6.0, use {@link LimitCheckingExceptionHierarchySkipPolicy} instead. + * Scheduled for removal in 7.0. */ +@Deprecated(since = "6.0", forRemoval = true) public class LimitCheckingItemSkipPolicy implements SkipPolicy { private long skipLimit; From 1a58e6b0c220d85754b1e5e115fec8072a01f022 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 14:55:30 +0200 Subject: [PATCH 26/49] Add new methods to create predefined retry/skip policies Related to #3950 --- .../builder/ChunkOrientedStepBuilder.java | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java index dff5037f53..e0c98437cb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java @@ -16,6 +16,7 @@ package org.springframework.batch.core.step.builder; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; @@ -44,6 +45,8 @@ import org.springframework.batch.core.step.ThreadStepInterruptionPolicy; import org.springframework.batch.core.step.item.ChunkOrientedStep; import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy; +import org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy; +import org.springframework.batch.core.step.skip.SkipLimitExceededException; import org.springframework.batch.core.step.skip.SkipPolicy; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; @@ -57,6 +60,7 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.util.Assert; /** * A builder for {@link ChunkOrientedStep}. This class extends {@link StepBuilderHelper} @@ -87,14 +91,22 @@ public class ChunkOrientedStepBuilder extends StepBuilderHelper false; + private RetryPolicy retryPolicy; private final Set retryListeners = new LinkedHashSet<>(); - private SkipPolicy skipPolicy = new AlwaysSkipItemSkipPolicy(); + private final Set> retryableExceptions = new HashSet<>(); + + private long retryLimit = -1; + + private SkipPolicy skipPolicy; private final Set> skipListeners = new LinkedHashSet<>(); + private final Set> skippableExceptions = new HashSet<>(); + + private long skipLimit = -1; + private AsyncTaskExecutor asyncTaskExecutor; ChunkOrientedStepBuilder(StepBuilderHelper parent, int chunkSize) { @@ -270,6 +282,7 @@ public ChunkOrientedStepBuilder faultTolerant() { * @return this for fluent chaining */ public ChunkOrientedStepBuilder retryPolicy(RetryPolicy retryPolicy) { + Assert.notNull(retryPolicy, "retryPolicy must not be null"); this.retryPolicy = retryPolicy; return self(); } @@ -285,6 +298,18 @@ public ChunkOrientedStepBuilder retryListener(RetryListener retryListener) return self(); } + @SafeVarargs + public final ChunkOrientedStepBuilder retry(Class... retryableExceptions) { + this.retryableExceptions.addAll(Arrays.stream(retryableExceptions).toList()); + return self(); + } + + public ChunkOrientedStepBuilder retryLimit(long retryLimit) { + Assert.isTrue(retryLimit > 0, "retryLimit must be positive"); + this.retryLimit = retryLimit; + return self(); + } + /** * Set the skip policy for the step. This policy determines how the step handles * skipping items in case of failures. It can be used to define the conditions under @@ -294,6 +319,7 @@ public ChunkOrientedStepBuilder retryListener(RetryListener retryListener) * @return this for fluent chaining */ public ChunkOrientedStepBuilder skipPolicy(SkipPolicy skipPolicy) { + Assert.notNull(skipPolicy, "skipPolicy must not be null"); this.skipPolicy = skipPolicy; return self(); } @@ -310,6 +336,18 @@ public ChunkOrientedStepBuilder skipListener(SkipListener skipListen return self(); } + @SafeVarargs + public final ChunkOrientedStepBuilder skip(Class... skippableExceptions) { + this.skippableExceptions.addAll(Arrays.stream(skippableExceptions).toList()); + return self(); + } + + public ChunkOrientedStepBuilder skipLimit(long skipLimit) { + Assert.isTrue(skipLimit > 0, "skipLimit must be positive"); + this.skipLimit = skipLimit; + return self(); + } + /** * Set the asynchronous task executor to be used for processing items concurrently. * This allows for concurrent processing of items, improving performance and @@ -326,11 +364,33 @@ public ChunkOrientedStepBuilder taskExecutor(AsyncTaskExecutor asyncTaskEx public ChunkOrientedStep build() { ChunkOrientedStep chunkOrientedStep = new ChunkOrientedStep<>(this.getName(), this.chunkSize, this.reader, this.writer, this.getJobRepository()); - chunkOrientedStep.setItemProcessor(this.processor); + if (this.processor != null) { + chunkOrientedStep.setItemProcessor(this.processor); + } chunkOrientedStep.setTransactionManager(this.transactionManager); chunkOrientedStep.setTransactionAttribute(this.transactionAttribute); chunkOrientedStep.setInterruptionPolicy(this.interruptionPolicy); + if (this.retryPolicy == null) { + if (!this.retryableExceptions.isEmpty() || this.retryLimit > 0) { + this.retryPolicy = RetryPolicy.builder() + .maxAttempts(this.retryLimit) + .includes(this.retryableExceptions) + .build(); + } + else { + this.retryPolicy = throwable -> false; + } + } chunkOrientedStep.setRetryPolicy(this.retryPolicy); + if (this.skipPolicy == null) { + if (!this.skippableExceptions.isEmpty() || this.skipLimit > 0) { + this.skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy(this.skippableExceptions, + this.skipLimit); + } + else { + this.skipPolicy = new AlwaysSkipItemSkipPolicy(); + } + } chunkOrientedStep.setSkipPolicy(this.skipPolicy); chunkOrientedStep.setFaultTolerant(this.faultTolerant); if (this.asyncTaskExecutor != null) { From b04cd3f9a837430ab702317b2b73a4680754bcb2 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 14:59:02 +0200 Subject: [PATCH 27/49] Update docs and samples with the new chunk step implementation Related to #3950 --- .../modules/ROOT/pages/common-patterns.adoc | 2 +- .../modules/ROOT/pages/processor.adoc | 4 +-- .../delegate-pattern-registering.adoc | 2 +- .../modules/ROOT/pages/scalability.adoc | 4 +-- .../commit-interval.adoc | 2 +- .../configuring-skip.adoc | 4 +-- .../configuring.adoc | 2 +- .../controlling-rollback.adoc | 4 +-- .../intercepting-execution.adoc | 2 +- .../registering-item-streams.adoc | 2 +- .../chunk-oriented-processing/restart.adoc | 10 ++++---- .../retry-logic.adoc | 2 +- .../transaction-attributes.adoc | 2 +- .../samples/amqp/AmqpJobConfiguration.java | 5 ++-- .../delimited/DelimitedJobConfiguration.java | 18 ++++++++++++- .../fixed/FixedLengthJobConfiguration.java | 18 ++++++++++++- .../file/json/JsonJobConfiguration.java | 5 ++-- .../multiline/MultiLineJobConfiguration.java | 18 ++++++++++++- .../MultiRecordTypeJobConfiguration.java | 5 ++-- .../MultiResourceJobConfiguration.java | 5 ++-- .../samples/file/xml/XmlJobConfiguration.java | 18 ++++++++++++- .../football/FootballJobConfiguration.java | 25 ++++++++++++++++--- .../jdbc/JdbcReaderBatchWriterSampleJob.java | 5 ++-- ...onousItemProcessingWithVirtualThreads.java | 5 ++-- ...ningConcurrentStepsWithVirtualThreads.java | 5 ++-- .../samples/metrics/Job2Configuration.java | 5 ++-- .../mongodb/DeletionJobConfiguration.java | 5 ++-- .../mongodb/InsertionJobConfiguration.java | 5 ++-- .../OwnersExportJobConfiguration.java | 3 ++- .../retry/RetrySampleConfiguration.java | 5 ++-- ...SkippableExceptionDuringProcessSample.java | 4 +-- .../SkippableExceptionDuringReadSample.java | 4 +-- .../SkippableExceptionDuringWriteSample.java | 4 +-- .../ValidationSampleConfiguration.java | 5 ++-- ...positeItemReaderSampleFunctionalTests.java | 3 ++- 35 files changed, 156 insertions(+), 61 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc b/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc index 5e25cddbb7..d1b05fe799 100644 --- a/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc +++ b/spring-batch-docs/modules/ROOT/pages/common-patterns.adoc @@ -766,7 +766,7 @@ public Job job1(JobRepository jobRepository, Step step1, Step step2) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(reader()) .writer(savingWriter()) .listener(promotionListener()) diff --git a/spring-batch-docs/modules/ROOT/pages/processor.adoc b/spring-batch-docs/modules/ROOT/pages/processor.adoc index 02993846de..8e24984dcf 100644 --- a/spring-batch-docs/modules/ROOT/pages/processor.adoc +++ b/spring-batch-docs/modules/ROOT/pages/processor.adoc @@ -101,7 +101,7 @@ public Job ioSampleJob(JobRepository jobRepository, Step step1) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(fooReader()) .processor(fooProcessor()) .writer(barWriter()) @@ -205,7 +205,7 @@ public Job ioSampleJob(JobRepository jobRepository, Step step1) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(fooReader()) .processor(compositeProcessor()) .writer(foobarWriter()) diff --git a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/delegate-pattern-registering.adoc b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/delegate-pattern-registering.adoc index c89e4f2498..aaeb0f56b9 100644 --- a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/delegate-pattern-registering.adoc +++ b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/delegate-pattern-registering.adoc @@ -30,7 +30,7 @@ public Job ioSampleJob(JobRepository jobRepository, Step step1) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(fooReader()) .processor(fooProcessor()) .writer(compositeItemWriter()) diff --git a/spring-batch-docs/modules/ROOT/pages/scalability.adoc b/spring-batch-docs/modules/ROOT/pages/scalability.adoc index 48f711e6b2..697d9f2d77 100644 --- a/spring-batch-docs/modules/ROOT/pages/scalability.adoc +++ b/spring-batch-docs/modules/ROOT/pages/scalability.adoc @@ -52,7 +52,7 @@ public TaskExecutor taskExecutor() { @Bean public Step sampleStep(TaskExecutor taskExecutor, JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("sampleStep", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .taskExecutor(taskExecutor) @@ -103,7 +103,7 @@ follows: @Bean public Step sampleStep(TaskExecutor taskExecutor, JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("sampleStep", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .taskExecutor(taskExecutor) diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/commit-interval.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/commit-interval.adoc index 60528a2c28..dd56270eef 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/commit-interval.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/commit-interval.adoc @@ -29,7 +29,7 @@ public Job sampleJob(JobRepository jobRepository, Step step1) { @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .build(); diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc index 5c5136c825..7eef3ac47c 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc @@ -22,7 +22,7 @@ The following Java example shows an example of using a skip limit: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(flatFileItemReader()) .writer(itemWriter()) .faultTolerant() @@ -83,7 +83,7 @@ The following Java example shows an example excluding a particular exception: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(flatFileItemReader()) .writer(itemWriter()) .faultTolerant() diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring.adoc index 100782cd63..95b3af9a31 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring.adoc @@ -33,7 +33,7 @@ public Job sampleJob(JobRepository jobRepository, Step sampleStep) { public Step sampleStep(JobRepository jobRepository, // <2> PlatformTransactionManager transactionManager) { // <1> return new StepBuilder("sampleStep", jobRepository) - .chunk(10, transactionManager) // <3> + .chunk(10).transactionManager(transactionManager) // <3> .reader(itemReader()) .writer(itemWriter()) .build(); diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/controlling-rollback.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/controlling-rollback.adoc index 69b704c5ae..a4e21ee625 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/controlling-rollback.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/controlling-rollback.adoc @@ -21,7 +21,7 @@ In Java, you can control rollback as follows: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .faultTolerant() @@ -75,7 +75,7 @@ The following example shows how to create a reader that does not buffer items in @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .readerIsTransactionalQueue() diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc index d07884516a..3bb0748bc2 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc @@ -27,7 +27,7 @@ The following example shows a listener applied at the chunk level in Java: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(reader()) .writer(writer()) .listener(chunkListener()) diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/registering-item-streams.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/registering-item-streams.adoc index 643087d7d6..ae2c2118ac 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/registering-item-streams.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/registering-item-streams.adoc @@ -25,7 +25,7 @@ The following example shows how to register a `stream` on a `step` in Java: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(compositeItemWriter()) .stream(fileItemWriter1()) diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc index 20e80bd72d..b59e949372 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc @@ -28,7 +28,7 @@ The following code fragment shows an example of a start limit configuration in J @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .startLimit(1) @@ -80,7 +80,7 @@ The following code fragment shows how to define a restartable job in Java: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .allowStartIfComplete(true) @@ -132,7 +132,7 @@ public Job footballJob(JobRepository jobRepository, Step playerLoad, Step gameLo @Bean public Step playerLoad(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("playerLoad", jobRepository) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(playerFileItemReader()) .writer(playerWriter()) .build(); @@ -142,7 +142,7 @@ public Step playerLoad(JobRepository jobRepository, PlatformTransactionManager t public Step gameLoad(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("gameLoad", jobRepository) .allowStartIfComplete(true) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(gameFileItemReader()) .writer(gameWriter()) .build(); @@ -152,7 +152,7 @@ public Step gameLoad(JobRepository jobRepository, PlatformTransactionManager tra public Step playerSummarization(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("playerSummarization", jobRepository) .startLimit(2) - .chunk(10, transactionManager) + .chunk(10).transactionManager(transactionManager) .reader(playerSummarizationSource()) .writer(summaryWriter()) .build(); diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/retry-logic.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/retry-logic.adoc index c841e94a7c..e81fcc228d 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/retry-logic.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/retry-logic.adoc @@ -20,7 +20,7 @@ In Java, retry should be configured as follows: @Bean public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .faultTolerant() diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/transaction-attributes.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/transaction-attributes.adoc index fdd7fcc327..f48f0e3895 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/transaction-attributes.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/transaction-attributes.adoc @@ -25,7 +25,7 @@ public Step step1(JobRepository jobRepository, PlatformTransactionManager transa attribute.setTimeout(30); return new StepBuilder("step1", jobRepository) - .chunk(2, transactionManager) + .chunk(2).transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .transactionAttribute(attribute) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/amqp/AmqpJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/amqp/AmqpJobConfiguration.java index 4e2238969e..9fcb89077e 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/amqp/AmqpJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/amqp/AmqpJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,8 @@ public Job job(JobRepository jobRepository, Step step) { @Bean public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager, RabbitTemplate rabbitInputTemplate, RabbitTemplate rabbitOutputTemplate) { - return new StepBuilder("step", jobRepository).chunk(1, transactionManager) + return new StepBuilder("step", jobRepository).chunk(1) + .transactionManager(transactionManager) .reader(amqpItemReader(rabbitInputTemplate)) .processor(new MessageProcessor()) .writer(amqpItemWriter(rabbitOutputTemplate)) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java index d0c2f118d7..df4ef2203b 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.batch.samples.file.delimited; import org.springframework.batch.core.job.Job; @@ -54,7 +69,8 @@ public FlatFileItemWriter itemWriter( public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java index 19466c8e01..9b74da95da 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.batch.samples.file.fixed; import org.springframework.batch.core.job.Job; @@ -57,7 +72,8 @@ public FlatFileItemWriter itemWriter( public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java index 135a9ec821..47f791adfb 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2023 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,7 +69,8 @@ public JsonFileItemWriter itemWriter(@Value("#{jobParameters[outputFile]} @Bean public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager, JsonItemReader itemReader, JsonFileItemWriter itemWriter) { - return new StepBuilder("step", jobRepository).chunk(2, transactionManager) + return new StepBuilder("step", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .writer(itemWriter) .build(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java index c8283b1be1..4941b673cd 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.batch.samples.file.multiline; import org.springframework.batch.core.job.Job; @@ -57,7 +72,8 @@ public MultiLineTradeItemWriter itemWriter(@Value("#{jobParameters[outputFile]}" public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, MultiLineTradeItemReader itemReader, MultiLineTradeItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .writer(itemWriter) .build()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java index c85d4cf2ef..ad72390d55 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -129,7 +129,8 @@ public FormatterLineAggregator customerLineAggregator() { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, FlatFileItemReader itemReader, FlatFileItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .writer(itemWriter) .build()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java index 1888961de2..1d5fc97347 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,7 +92,8 @@ public FlatFileItemWriter delegateWriter() { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java index 520818c62d..c2c0af7941 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.batch.samples.file.xml; import java.math.BigDecimal; @@ -69,7 +84,8 @@ public StaxEventItemWriter itemWriter( public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java index fe81064cba..8eb09166bb 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.batch.samples.football; import javax.sql.DataSource; @@ -55,7 +70,8 @@ public PlayerItemWriter playerWriter(DataSource dataSource) { @Bean public Step playerLoad(JobRepository jobRepository, JdbcTransactionManager transactionManager, FlatFileItemReader playerFileItemReader, PlayerItemWriter playerWriter) { - return new StepBuilder("playerLoad", jobRepository).chunk(2, transactionManager) + return new StepBuilder("playerLoad", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(playerFileItemReader) .writer(playerWriter) .build(); @@ -84,7 +100,8 @@ public JdbcGameDao gameWriter(DataSource dataSource) { @Bean public Step gameLoad(JobRepository jobRepository, JdbcTransactionManager transactionManager, FlatFileItemReader gameFileItemReader, JdbcGameDao gameWriter) { - return new StepBuilder("gameLoad", jobRepository).chunk(2, transactionManager) + return new StepBuilder("gameLoad", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(gameFileItemReader) .writer(gameWriter) .build(); @@ -120,8 +137,8 @@ public JdbcPlayerSummaryDao summaryWriter(DataSource dataSource) { @Bean public Step summarizationStep(JobRepository jobRepository, JdbcTransactionManager transactionManager, JdbcCursorItemReader playerSummarizationSource, JdbcPlayerSummaryDao summaryWriter) { - return new StepBuilder("summarizationStep", jobRepository) - .chunk(2, transactionManager) + return new StepBuilder("summarizationStep", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(playerSummarizationSource) .writer(summaryWriter) .build(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java index 6e18f63573..c375c62dd8 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,7 +56,8 @@ public JdbcBatchItemWriter itemWriter(DataSource dataSource) { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, JdbcBatchItemWriter itemWriter) { return new JobBuilder("ioSampleJob", jobRepository) - .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager) + .start(new StepBuilder("step1", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(new CustomerCreditIncreaseProcessor()) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java index d5862c7aa4..bc14e70d80 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,7 +77,8 @@ public AsyncItemWriter itemWriter() { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, AsyncItemProcessor itemProcessor, AsyncItemWriter itemWriter) { - Step step = new StepBuilder("step", jobRepository).>chunk(2, transactionManager) + Step step = new StepBuilder("step", jobRepository).>chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .processor(itemProcessor) .writer(itemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java index 129cfc7d69..616fb77b1a 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,7 +78,8 @@ public ItemWriter itemWriter() { @Bean public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, ItemReader itemReader, ItemWriter itemWriter) { - Step step = new StepBuilder("step", jobRepository).chunk(2, transactionManager) + Step step = new StepBuilder("step", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(itemReader) .writer(itemWriter) .taskExecutor(new VirtualThreadTaskExecutor("spring-batch-")) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job2Configuration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job2Configuration.java index c6c4cbd587..e0f59048ff 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job2Configuration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/Job2Configuration.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,8 @@ public Job job2(JobRepository jobRepository, PlatformTransactionManager transact @Bean public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - return new StepBuilder("step1", jobRepository).chunk(3, transactionManager) + return new StepBuilder("step1", jobRepository).chunk(3) + .transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .build(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java index 81cae7737d..483b500181 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,8 @@ public MongoItemWriter mongoPersonRemover(MongoTemplate mongoTemplate) { @Bean public Step deletionStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, MongoPagingItemReader mongoPersonReader, MongoItemWriter mongoPersonRemover) { - return new StepBuilder("step", jobRepository).chunk(2, transactionManager) + return new StepBuilder("step", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(mongoPersonReader) .writer(mongoPersonRemover) .build(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java index 38b9319f8b..b245b02084 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,8 @@ public MongoItemWriter mongoItemWriter(MongoTemplate mongoTemplate) { @Bean public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, MongoPagingItemReader mongoItemReader, MongoItemWriter mongoItemWriter) { - return new StepBuilder("step", jobRepository).chunk(2, transactionManager) + return new StepBuilder("step", jobRepository).chunk(2) + .transactionManager(transactionManager) .reader(mongoItemReader) .writer(mongoItemWriter) .build(); diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java index 40a096ff3b..492d47d9ec 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java @@ -61,7 +61,8 @@ public FlatFileItemWriter ownersWriter() { public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, JdbcCursorItemReader ownersReader, FlatFileItemWriter ownersWriter) { return new JobBuilder("ownersExportJob", jobRepository) - .start(new StepBuilder("ownersExportStep", jobRepository).chunk(5, transactionManager) + .start(new StepBuilder("ownersExportStep", jobRepository).chunk(5) + .transactionManager(transactionManager) .reader(ownersReader) .writer(ownersWriter) .build()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java index 593bd39a8b..3635a32007 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,8 @@ public Job retrySample(JobRepository jobRepository, Step step) { @Bean protected Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager) { - return new StepBuilder("step", jobRepository).chunk(1, transactionManager) + return new StepBuilder("step", jobRepository).chunk(1) + .transactionManager(transactionManager) .reader(reader()) .writer(writer()) .faultTolerant() diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java index 3808ccf02e..f4f60674cf 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,7 +84,7 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3, this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3).transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java index 14a9e5c266..53917539ea 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,7 +84,7 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3, this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3).transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java index 39b217c459..ca45e81316 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,7 +84,7 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3, this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3).transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java index 39c5aaff24..1e9674c257 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2022 the original author or authors. + * Copyright 2018-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,7 +65,8 @@ public BeanValidatingItemProcessor itemValidator() throws Exception { @Bean public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager) throws Exception { - return new StepBuilder("step", jobRepository).chunk(1, transactionManager) + return new StepBuilder("step", jobRepository).chunk(1) + .transactionManager(transactionManager) .reader(itemReader()) .processor(itemValidator()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java index 0119ab58c9..70002751e1 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java @@ -120,7 +120,8 @@ public JdbcBatchItemWriter itemWriter() { @Bean public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager) { return new JobBuilder("job", jobRepository) - .start(new StepBuilder("step", jobRepository).chunk(5, transactionManager) + .start(new StepBuilder("step", jobRepository).chunk(5) + .transactionManager(transactionManager) .reader(itemReader()) .writer(itemWriter()) .build()) From 7e44f4e3651d27da43d4d052aab0dc1d9986361b Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 16:42:03 +0200 Subject: [PATCH 28/49] Fix code formatting --- .../samples/skip/SkippableExceptionDuringProcessSample.java | 3 ++- .../batch/samples/skip/SkippableExceptionDuringReadSample.java | 3 ++- .../samples/skip/SkippableExceptionDuringWriteSample.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java index f4f60674cf..a408f08e34 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java @@ -84,7 +84,8 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3).transactionManager(this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3) + .transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java index 53917539ea..8f9ed81e84 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java @@ -84,7 +84,8 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3).transactionManager(this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3) + .transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java index ca45e81316..8a3a1776da 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java @@ -84,7 +84,8 @@ public ItemWriter itemWriter() { @Bean public Step step(JobRepository jobRepository) { - return new StepBuilder("step", jobRepository).chunk(3).transactionManager(this.transactionManager) + return new StepBuilder("step", jobRepository).chunk(3) + .transactionManager(this.transactionManager) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) From b92c7a05206c124259c464b40ec64c9c56477b32 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 16:42:14 +0200 Subject: [PATCH 29/49] Remove unused imports --- .../batch/core/annotation/BeforeChunk.java | 1 - .../annotation/EnableBatchProcessing.java | 1 - .../support/JdbcDefaultBatchConfiguration.java | 1 - .../support/MongoDefaultBatchConfiguration.java | 1 - .../core/step/builder/ChunkOrientedStepBuilder.java | 1 - .../launch/support/JobOperatorFactoryBeanTests.java | 1 - .../launch/support/TaskExecutorJobOperatorTests.java | 11 +++++++++-- .../test/context/SpringBatchTestIntegrationTests.java | 1 - 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java index 0a575eede7..d65910710f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/BeforeChunk.java @@ -21,7 +21,6 @@ import java.lang.annotation.Target; import org.springframework.batch.core.listener.ChunkListener; -import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.item.Chunk; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index 63647d77a0..983780f50a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -15,7 +15,6 @@ */ package org.springframework.batch.core.configuration.annotation; -import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.ApplicationContextFactory; import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; import org.springframework.batch.core.configuration.support.GroupAwareJob; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java index 09976310fa..3b57ec1496 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.batch.core.configuration.support; import org.springframework.batch.core.configuration.BatchConfigurationException; -import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.converter.DateToStringConverter; import org.springframework.batch.core.converter.LocalDateTimeToStringConverter; import org.springframework.batch.core.converter.LocalDateToStringConverter; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java index 905bead2b1..2c4db64f6c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.batch.core.configuration.support; import org.springframework.batch.core.configuration.BatchConfigurationException; -import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java index e0c98437cb..683c7a9d56 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java @@ -46,7 +46,6 @@ import org.springframework.batch.core.step.item.ChunkOrientedStep; import org.springframework.batch.core.step.skip.AlwaysSkipItemSkipPolicy; import org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy; -import org.springframework.batch.core.step.skip.SkipLimitExceededException; import org.springframework.batch.core.step.skip.SkipPolicy; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java index 00d76bf39d..6a9315326c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobOperatorFactoryBeanTests.java @@ -15,7 +15,6 @@ */ package org.springframework.batch.core.launch.support; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperatorTests.java index a841f430a9..a35e5ea63e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperatorTests.java @@ -55,9 +55,16 @@ import org.springframework.batch.support.PropertiesConverter; import org.springframework.lang.Nullable; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; /** * @author Dave Syer diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java index 42db71c432..adbcbeee8d 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/context/SpringBatchTestIntegrationTests.java @@ -18,7 +18,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.support.JobOperatorFactoryBean; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.ResourcelessJobRepository; From e183272dea4d321488aade823aa3ea708b12eb0f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 17:53:00 +0200 Subject: [PATCH 30/49] Mark functional tests as integration tests --- pom.xml | 2 ++ ...rtTests.java => VirtualThreadsSupportFunctionalTests.java} | 2 +- ...eLauncherTests.java => RemoteLauncherFunctionalTests.java} | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) rename spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/{VirtualThreadsSupportTests.java => VirtualThreadsSupportFunctionalTests.java} (99%) rename spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/{RemoteLauncherTests.java => RemoteLauncherFunctionalTests.java} (98%) diff --git a/pom.xml b/pom.xml index e0c2827a38..7858b92b28 100644 --- a/pom.xml +++ b/pom.xml @@ -199,6 +199,7 @@ ${surefireArgLine} **/*IntegrationTests.java + **/*FunctionalTests.java @@ -209,6 +210,7 @@ **/*IntegrationTests.java + **/*FunctionalTests.java diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportFunctionalTests.java similarity index 99% rename from spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportTests.java rename to spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportFunctionalTests.java index 21022b16ff..ae7e247a47 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/loom/VirtualThreadsSupportFunctionalTests.java @@ -65,7 +65,7 @@ * @author Mahmoud Ben Hassine */ @EnabledForJreRange(min = JRE.JAVA_21) -public class VirtualThreadsSupportTests { +public class VirtualThreadsSupportFunctionalTests { @Test public void testJobLaunchingWithVirtualThreads() throws Exception { diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherFunctionalTests.java similarity index 98% rename from spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java rename to spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherFunctionalTests.java index f573060466..0e656fc01f 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherFunctionalTests.java @@ -46,9 +46,9 @@ * */ @SuppressWarnings("removal") -class RemoteLauncherTests { +class RemoteLauncherFunctionalTests { - private static final Log logger = LogFactory.getLog(RemoteLauncherTests.class); + private static final Log logger = LogFactory.getLog(RemoteLauncherFunctionalTests.class); private static final List errors = new ArrayList<>(); From 9641f1718f82440baf96df61ecd11a2344917367 Mon Sep 17 00:00:00 2001 From: Hyunsang Han Date: Sat, 6 Sep 2025 16:24:13 +0900 Subject: [PATCH 31/49] Fix @BeforeChunk/@AfterChunk to support Chunk parameter as documented Signed-off-by: Hyunsang Han --- .../batch/core/listener/StepListenerMetaData.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java index 7ceff8a96f..4fb5aad1b1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java @@ -46,6 +46,7 @@ * methods, their interfaces, annotation, and expected arguments. * * @author Lucas Ward + * @author Hyunsang Han * @since 2.0 * @see StepListenerFactoryBean */ @@ -53,8 +54,8 @@ public enum StepListenerMetaData implements ListenerMetaData { BEFORE_STEP("beforeStep", "before-step-method", BeforeStep.class, StepExecutionListener.class, StepExecution.class), AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, StepExecution.class), - BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, ChunkContext.class), - AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, ChunkContext.class), + BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, Chunk.class), + AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, Chunk.class), AFTER_CHUNK_ERROR("afterChunkError", "after-chunk-error-method", AfterChunkError.class, ChunkListener.class, ChunkContext.class), BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class), From fc9cc6f8c2385b09f5a3b6d897458ef2cba2c9aa Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 10 Sep 2025 19:10:10 +0200 Subject: [PATCH 32/49] Fix resource handling in integration tests --- .../samples/file/multiline/MultiLineJobConfiguration.java | 4 ++-- .../multirecordtype/MultiRecordTypeJobConfiguration.java | 4 ++-- .../batch/samples/file/multiline/job/multiLine.xml | 6 +++++- .../samples/file/multirecordtype/job/multiRecordType.xml | 8 ++++++-- .../batch/samples/headerfooter/job/headerFooterSample.xml | 4 ++-- .../samples/file/multiline/MultiLineFunctionalTests.java | 7 +++---- .../multirecordtype/MultiRecordTypeFunctionalTests.java | 7 +++---- .../PatternMatchingJobFunctionalTests.java | 5 ++--- 8 files changed, 25 insertions(+), 20 deletions(-) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java index 4941b673cd..3351cb4553 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java @@ -34,7 +34,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.core.io.Resource; +import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.WritableResource; import org.springframework.jdbc.support.JdbcTransactionManager; @@ -45,7 +45,7 @@ public class MultiLineJobConfiguration { @Bean @StepScope - public MultiLineTradeItemReader itemReader(@Value("#{jobParameters[inputFile]}") Resource resource) { + public MultiLineTradeItemReader itemReader(@Value("#{jobParameters[inputFile]}") FileSystemResource resource) { FlatFileItemReader
          delegate = new FlatFileItemReaderBuilder
          ().name("delegateItemReader") .resource(resource) .lineTokenizer(new DelimitedLineTokenizer()) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java index ad72390d55..cac11c77e6 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java @@ -41,7 +41,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.core.io.Resource; +import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.WritableResource; import org.springframework.jdbc.support.JdbcTransactionManager; @@ -56,7 +56,7 @@ public class MultiRecordTypeJobConfiguration { @Bean @StepScope public FlatFileItemReader itemReader(PatternMatchingCompositeLineMapper lineMapper, - @Value("#{jobParameters[inputFile]}") Resource resource) { + @Value("#{jobParameters[inputFile]}") FileSystemResource resource) { return new FlatFileItemReaderBuilder().name("itemReader").resource(resource).lineMapper(lineMapper).build(); } diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multiline/job/multiLine.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multiline/job/multiLine.xml index a21502bd22..879db2bd43 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multiline/job/multiLine.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multiline/job/multiLine.xml @@ -14,10 +14,14 @@ + + + + - + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multirecordtype/job/multiRecordType.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multirecordtype/job/multiRecordType.xml index d28c4382b4..dd1ca5b5fd 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multirecordtype/job/multiRecordType.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/file/multirecordtype/job/multiRecordType.xml @@ -14,8 +14,12 @@ - - + + + + + + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/headerfooter/job/headerFooterSample.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/headerfooter/job/headerFooterSample.xml index 05658f6e05..b734b6a773 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/headerfooter/job/headerFooterSample.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/headerfooter/job/headerFooterSample.xml @@ -55,9 +55,9 @@ - + + value="src/main/resources/org/springframework/batch/samples/headerfooter/data/input.txt" /> Date: Thu, 11 Sep 2025 14:54:51 +0200 Subject: [PATCH 33/49] Refactor ChunkOrientedStep to use TransactionTemplate#executeWithoutResult --- .../batch/core/step/item/ChunkOrientedStep.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index c4efbbbdc6..efdca2fc23 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -343,12 +343,9 @@ protected void doExecute(StepExecution stepExecution) throws Exception { stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); while (this.chunkTracker.moreItems() && !interrupted(stepExecution)) { // process next chunk in its own transaction - this.transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - StepContribution contribution = stepExecution.createStepContribution(); - processNextChunk(status, contribution, stepExecution); - } + this.transactionTemplate.executeWithoutResult(transactionStatus -> { + StepContribution contribution = stepExecution.createStepContribution(); + processNextChunk(transactionStatus, contribution, stepExecution); }); } } From b1714e60f8d531313fd10d3c881d4c58060a3ee5 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 11 Sep 2025 22:43:34 +0200 Subject: [PATCH 34/49] Add initial support for observability with JFR This commit introduces new JFR events to monitor and trace job and step executions. The most important design decision is to have a single event for each action, instead of two separate events (like ChunkTransactionEvent instead of ChunkTransactionStartEvent and ChunkTransactionStopEvent). This design has two main advantages: - Ability to time events and have a non-zero duration - Avoid the need to correlate events on the consumer side Further improvements ideas: - Create an observability template to time actions - Use AOP for observability aspects Resolves #4972 --- .../batch/core/job/AbstractJob.java | 7 +++ .../support/TaskExecutorJobOperator.java | 2 + .../core/observability/BatchMetrics.java | 6 ++- .../jfr/events/job/JobExecutionEvent.java | 46 +++++++++++++++++ .../jfr/events/job/JobLaunchEvent.java | 39 +++++++++++++++ .../jfr/events/step/StepExecutionEvent.java | 50 +++++++++++++++++++ .../jfr/events/step/chunk/ChunkScanEvent.java | 42 ++++++++++++++++ .../step/chunk/ChunkTransactionEvent.java | 42 ++++++++++++++++ .../events/step/chunk/ChunkWriteEvent.java | 46 +++++++++++++++++ .../events/step/chunk/ItemProcessEvent.java | 42 ++++++++++++++++ .../jfr/events/step/chunk/ItemReadEvent.java | 42 ++++++++++++++++ .../partition/PartitionAggregateEvent.java | 39 +++++++++++++++ .../step/partition/PartitionSplitEvent.java | 42 ++++++++++++++++ .../step/tasklet/TaskletExecutionEvent.java | 46 +++++++++++++++++ .../batch/core/partition/PartitionStep.java | 15 +++++- .../batch/core/step/AbstractStep.java | 7 +++ .../core/step/item/ChunkOrientedStep.java | 39 ++++++++++++++- .../batch/core/step/tasklet/TaskletStep.java | 10 +++- 18 files changed, 557 insertions(+), 5 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 7c60e2da7a..1868399af5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java @@ -37,6 +37,7 @@ import org.springframework.batch.core.job.parameters.JobParametersValidator; import org.springframework.batch.core.listener.JobExecutionListener; import org.springframework.batch.core.SpringBatchVersion; +import org.springframework.batch.core.observability.jfr.events.job.JobExecutionEvent; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.launch.NoSuchJobException; @@ -277,6 +278,9 @@ public final void execute(JobExecution execution) { } JobSynchronizationManager.register(execution); + JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(execution.getJobInstance().getJobName(), + execution.getJobInstance().getId(), execution.getId()); + jobExecutionEvent.begin(); String activeJobMeterName = "job.active"; LongTaskTimer longTaskTimer = BatchMetrics.createLongTaskTimer(this.meterRegistry, activeJobMeterName, "Active jobs", Tag.of(BatchMetrics.METRICS_PREFIX + activeJobMeterName + ".name", @@ -349,6 +353,8 @@ public final void execute(JobExecution execution) { execution.setExitStatus(exitStatus.and(newExitStatus)); } stopObservation(execution, observation); + jobExecutionEvent.exitStatus = execution.getExitStatus().getExitCode(); + jobExecutionEvent.commit(); longTaskTimerSample.stop(); execution.setEndTime(LocalDateTime.now()); @@ -364,6 +370,7 @@ public final void execute(JobExecution execution) { finally { JobSynchronizationManager.release(); } + System.out.println("execution = " + execution); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java index 6434a22cfd..48bb126b5b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobOperator.java @@ -27,6 +27,7 @@ import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.launch.NoSuchJobExecutionException; +import org.springframework.batch.core.observability.jfr.events.job.JobLaunchEvent; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; @@ -92,6 +93,7 @@ public JobExecution start(Job job, JobParameters jobParameters) JobRestartException, JobParametersInvalidException { Assert.notNull(job, "Job must not be null"); Assert.notNull(jobParameters, "JobParameters must not be null"); + new JobLaunchEvent(job.getName(), jobParameters.toString()).commit(); return super.start(job, jobParameters); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java index 7e5a9e7595..6884dac28a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/BatchMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 the original author or authors. + * Copyright 2019-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,10 @@ public final class BatchMetrics { public static final String STATUS_FAILURE = "FAILURE"; + public static final String STATUS_COMMITTED = "COMMITTED"; + + public static final String STATUS_ROLLED_BACK = "ROLLED_BACK"; + private BatchMetrics() { } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java new file mode 100644 index 0000000000..416c524444 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobExecutionEvent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.job; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Job Execution") +@Description("Job Execution Event") +@Category({ "Spring Batch", "Job" }) +public class JobExecutionEvent extends Event { + + @Label("Job Name") + public String jobName; + + @Label("Job Instance Id") + public long jobInstanceId; + + @Label("Job Execution Id") + public long jobExecutionId; + + @Label("Job Exit Status") + public String exitStatus; + + public JobExecutionEvent(String jobName, long jobInstanceId, long jobExecutionId) { + this.jobName = jobName; + this.jobInstanceId = jobInstanceId; + this.jobExecutionId = jobExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java new file mode 100644 index 0000000000..269d099196 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/job/JobLaunchEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.job; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Job Launch Request") +@Description("Job Launch Request Event") +@Category({ "Spring Batch", "Job" }) +public class JobLaunchEvent extends Event { + + @Label("Job Name") + public String jobName; + + @Label("Job Parameters") + public String jobParameters; + + public JobLaunchEvent(String jobName, String jobParameters) { + this.jobParameters = jobParameters; + this.jobName = jobName; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java new file mode 100644 index 0000000000..6e7784fc79 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/StepExecutionEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Step Execution") +@Description("Step Execution Event") +@Category({ "Spring Batch", "Step" }) +public class StepExecutionEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Job Name") + public String jobName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Job Execution Id") + public long jobExecutionId; + + @Label("Step Exit Status") + public String exitStatus; + + public StepExecutionEvent(String stepName, String jobName, long stepExecutionId, long jobExecutionId) { + this.stepName = stepName; + this.jobName = jobName; + this.stepExecutionId = stepExecutionId; + this.jobExecutionId = jobExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java new file mode 100644 index 0000000000..d5d11e7d1b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkScanEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Chunk Scan") +@Description("Chunk Scan Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ChunkScanEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Skip Count") + public long skipCount; + + public ChunkScanEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java new file mode 100644 index 0000000000..695f3afcfa --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkTransactionEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Chunk Transaction") +@Description("Chunk Transaction Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ChunkTransactionEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Transaction Status") + public String transactionStatus; + + public ChunkTransactionEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java new file mode 100644 index 0000000000..6139abb60b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ChunkWriteEvent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Chunk Write") +@Description("Chunk Write Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ChunkWriteEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Chunk Write Status") + public String chunkWriteStatus; + + @Label("Item Count") + public long itemCount; + + public ChunkWriteEvent(String stepName, long stepExecutionId, long itemCount) { + this.itemCount = itemCount; + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java new file mode 100644 index 0000000000..358794dcff --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemProcessEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Item Process") +@Description("Item Process Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ItemProcessEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Item Process Status") + public String itemProcessStatus; + + public ItemProcessEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java new file mode 100644 index 0000000000..5e55c0de3d --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/chunk/ItemReadEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step.chunk; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Item Read") +@Description("Item Read Event") +@Category({ "Spring Batch", "Step", "Chunk" }) +public class ItemReadEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Item Read Status") + public String itemReadStatus; + + public ItemReadEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java new file mode 100644 index 0000000000..f516b08de1 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionAggregateEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step.partition; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Partition Aggregate") +@Description("Partition Aggregate Event") +@Category({ "Spring Batch", "Step", "Partition" }) +public class PartitionAggregateEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + public PartitionAggregateEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java new file mode 100644 index 0000000000..26504edc2f --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/partition/PartitionSplitEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step.partition; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Partition Split") +@Description("Partition Split Event") +@Category({ "Spring Batch", "Step", "Partition" }) +public class PartitionSplitEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Partition count") + public long partitionCount; + + public PartitionSplitEvent(String stepName, long stepExecutionId) { + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java new file mode 100644 index 0000000000..97fbb7b6d7 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/observability/jfr/events/step/tasklet/TaskletExecutionEvent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.observability.jfr.events.step.tasklet; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; + +@Label("Tasklet Execution") +@Description("Tasklet Execution Event") +@Category({ "Spring Batch", "Step", "Tasklet" }) +public class TaskletExecutionEvent extends Event { + + @Label("Step Name") + public String stepName; + + @Label("Step Execution Id") + public long stepExecutionId; + + @Label("Tasklet Type") + public String taskletType; + + @Label("Tasklet Status") + public String taskletStatus; + + public TaskletExecutionEvent(String stepName, long stepExecutionId, String taskletType) { + this.taskletType = taskletType; + this.stepName = stepName; + this.stepExecutionId = stepExecutionId; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java index 104d48e995..5600cab5f1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/partition/PartitionStep.java @@ -18,6 +18,8 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.job.JobExecutionException; +import org.springframework.batch.core.observability.jfr.events.step.partition.PartitionAggregateEvent; +import org.springframework.batch.core.observability.jfr.events.step.partition.PartitionSplitEvent; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.partition.support.DefaultStepExecutionAggregator; @@ -97,10 +99,21 @@ public void afterPropertiesSet() throws Exception { protected void doExecute(StepExecution stepExecution) throws Exception { stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); - // Wait for task completion and then aggregate the results + // Split execution into partitions and wait for task completion + PartitionSplitEvent partitionSplitEvent = new PartitionSplitEvent(stepExecution.getStepName(), + stepExecution.getId()); + partitionSplitEvent.begin(); Collection executions = partitionHandler.handle(stepExecutionSplitter, stepExecution); + partitionSplitEvent.partitionCount = executions.size(); stepExecution.upgradeStatus(BatchStatus.COMPLETED); + partitionSplitEvent.commit(); + + // aggregate the results of the executions + PartitionAggregateEvent partitionAggregateEvent = new PartitionAggregateEvent(stepExecution.getStepName(), + stepExecution.getId()); + partitionAggregateEvent.begin(); stepExecutionAggregator.aggregate(stepExecution, executions); + partitionAggregateEvent.commit(); // If anything failed or had a problem we need to crap out if (stepExecution.getStatus().isUnsuccessful()) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java index b545aac50e..004fffa5b3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java @@ -42,6 +42,7 @@ import org.springframework.batch.core.observability.BatchStepObservation; import org.springframework.batch.core.observability.BatchStepObservationConvention; import org.springframework.batch.core.observability.DefaultBatchStepObservationConvention; +import org.springframework.batch.core.observability.jfr.events.step.StepExecutionEvent; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.item.ExecutionContext; @@ -205,6 +206,10 @@ public final void execute(StepExecution stepExecution) if (logger.isDebugEnabled()) { logger.debug("Executing: id=" + stepExecution.getId()); } + StepExecutionEvent stepExecutionEvent = new StepExecutionEvent(stepExecution.getStepName(), + stepExecution.getJobExecution().getJobInstance().getJobName(), stepExecution.getId(), + stepExecution.getJobExecutionId()); + stepExecutionEvent.begin(); stepExecution.setStartTime(LocalDateTime.now()); stepExecution.setStatus(BatchStatus.STARTED); Observation observation = BatchMetrics @@ -291,6 +296,8 @@ public final void execute(StepExecution stepExecution) + "This job is now in an unknown state and should not be restarted.", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e); } + stepExecutionEvent.exitStatus = stepExecution.getExitStatus().getExitCode(); + stepExecutionEvent.commit(); stopObservation(stepExecution, observation); stepExecution.setExitStatus(exitStatus); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index efdca2fc23..3aa64b4df7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -34,6 +34,8 @@ import org.springframework.batch.core.listener.ItemReadListener; import org.springframework.batch.core.listener.ItemWriteListener; import org.springframework.batch.core.listener.SkipListener; +import org.springframework.batch.core.observability.BatchMetrics; +import org.springframework.batch.core.observability.jfr.events.step.chunk.*; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.core.step.StepContribution; @@ -344,8 +346,14 @@ protected void doExecute(StepExecution stepExecution) throws Exception { while (this.chunkTracker.moreItems() && !interrupted(stepExecution)) { // process next chunk in its own transaction this.transactionTemplate.executeWithoutResult(transactionStatus -> { + ChunkTransactionEvent chunkTransactionEvent = new ChunkTransactionEvent(stepExecution.getStepName(), + stepExecution.getId()); + chunkTransactionEvent.begin(); StepContribution contribution = stepExecution.createStepContribution(); processNextChunk(transactionStatus, contribution, stepExecution); + chunkTransactionEvent.transactionStatus = transactionStatus.isRollbackOnly() + ? BatchMetrics.STATUS_ROLLED_BACK : BatchMetrics.STATUS_COMMITTED; + chunkTransactionEvent.commit(); }); } } @@ -464,9 +472,12 @@ private Chunk readChunk(StepContribution contribution) throws Exception { } @Nullable private I readItem(StepContribution contribution) throws Exception { - this.compositeItemReadListener.beforeRead(); + ItemReadEvent itemReadEvent = new ItemReadEvent(contribution.getStepExecution().getStepName(), + contribution.getStepExecution().getId()); I item = null; try { + itemReadEvent.begin(); + this.compositeItemReadListener.beforeRead(); item = doRead(); if (item == null) { this.chunkTracker.noMoreItems(); @@ -475,6 +486,7 @@ private Chunk readChunk(StepContribution contribution) throws Exception { contribution.incrementReadCount(); this.compositeItemReadListener.afterRead(item); } + itemReadEvent.itemReadStatus = BatchMetrics.STATUS_SUCCESS; } catch (Exception exception) { this.compositeItemReadListener.onReadError(exception); @@ -484,6 +496,10 @@ private Chunk readChunk(StepContribution contribution) throws Exception { else { throw exception; } + itemReadEvent.itemReadStatus = BatchMetrics.STATUS_FAILURE; + } + finally { + itemReadEvent.commit(); } return item; } @@ -533,14 +549,18 @@ private Chunk processChunk(Chunk chunk, StepContribution contribution) thr } private O processItem(I item, StepContribution contribution) throws Exception { + ItemProcessEvent itemProcessEvent = new ItemProcessEvent(contribution.getStepExecution().getStepName(), + contribution.getStepExecution().getId()); O processedItem = null; try { + itemProcessEvent.begin(); this.compositeItemProcessListener.beforeProcess(item); processedItem = doProcess(item); if (processedItem == null) { contribution.incrementFilterCount(); } this.compositeItemProcessListener.afterProcess(item, processedItem); + itemProcessEvent.itemProcessStatus = BatchMetrics.STATUS_SUCCESS; } catch (Exception exception) { this.compositeItemProcessListener.onProcessError(item, exception); @@ -550,6 +570,10 @@ private O processItem(I item, StepContribution contribution) throws Exception { else { throw exception; } + itemProcessEvent.itemProcessStatus = BatchMetrics.STATUS_FAILURE; + } + finally { + itemProcessEvent.commit(); } return processedItem; } @@ -600,23 +624,36 @@ private void doSkipInProcess(I item, RetryException retryException, StepContribu } private void writeChunk(Chunk chunk, StepContribution contribution) throws Exception { + ChunkWriteEvent chunkWriteEvent = new ChunkWriteEvent(contribution.getStepExecution().getStepName(), + contribution.getStepExecution().getId(), chunk.size()); try { + chunkWriteEvent.begin(); this.compositeItemWriteListener.beforeWrite(chunk); doWrite(chunk); contribution.incrementWriteCount(chunk.size()); this.compositeItemWriteListener.afterWrite(chunk); + chunkWriteEvent.chunkWriteStatus = BatchMetrics.STATUS_SUCCESS; } catch (Exception exception) { this.compositeItemWriteListener.onWriteError(exception, chunk); + chunkWriteEvent.chunkWriteStatus = BatchMetrics.STATUS_FAILURE; if (this.faultTolerant && exception instanceof RetryException retryException) { logger.info("Retry exhausted while attempting to write items, scanning the chunk", retryException); + ChunkScanEvent chunkScanEvent = new ChunkScanEvent(contribution.getStepExecution().getStepName(), + contribution.getStepExecution().getId()); + chunkScanEvent.begin(); scan(chunk, contribution); + chunkScanEvent.skipCount = contribution.getSkipCount(); + chunkScanEvent.commit(); logger.info("Chunk scan completed"); } else { throw exception; } } + finally { + chunkWriteEvent.commit(); + } } private void doWrite(Chunk chunk) throws Exception { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java index 6b5c01d85b..a7664d01ea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java @@ -20,6 +20,7 @@ import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.listener.ChunkListener; import org.springframework.batch.core.job.JobInterruptedException; +import org.springframework.batch.core.observability.jfr.events.step.tasklet.TaskletExecutionEvent; import org.springframework.batch.core.step.StepContribution; import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.listener.StepExecutionListener; @@ -222,9 +223,12 @@ public void setInterruptionPolicy(StepInterruptionPolicy interruptionPolicy) { */ @Override protected void doExecute(StepExecution stepExecution) throws Exception { - stepExecution.getExecutionContext().put(TASKLET_TYPE_KEY, tasklet.getClass().getName()); + String taskletType = tasklet.getClass().getName(); + stepExecution.getExecutionContext().put(TASKLET_TYPE_KEY, taskletType); stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); - + TaskletExecutionEvent taskletExecutionEvent = new TaskletExecutionEvent(stepExecution.getStepName(), + stepExecution.getId(), taskletType); + taskletExecutionEvent.begin(); stream.update(stepExecution.getExecutionContext()); getJobRepository().updateExecutionContext(stepExecution); @@ -266,6 +270,8 @@ public RepeatStatus doInChunkContext(RepeatContext repeatContext, ChunkContext c }); + taskletExecutionEvent.taskletStatus = stepExecution.getExitStatus().getExitCode(); + taskletExecutionEvent.commit(); } /** From 8f5a3cc74e62ba1144c58d673d6aed550692c014 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 12 Sep 2025 15:06:43 +0200 Subject: [PATCH 35/49] Remove System.out.println committed mistakenly --- .../java/org/springframework/batch/core/job/AbstractJob.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 1868399af5..9ca23315f4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java @@ -370,7 +370,6 @@ public final void execute(JobExecution execution) { finally { JobSynchronizationManager.release(); } - System.out.println("execution = " + execution); } From 7cc11b829368338596b18d1e6c988e142f1e673d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 12 Sep 2025 15:07:32 +0200 Subject: [PATCH 36/49] Assert that a step execution has an id before execution --- .../batch/core/step/AbstractStep.java | 2 + .../batch/core/SpringBatchVersionTests.java | 2 + .../JobBuilderConfigurationTests.java | 2 + .../DefaultBatchConfigurationTests.java | 2 +- .../core/job/builder/FlowJobBuilderTests.java | 2 + .../core/job/builder/JobBuilderTests.java | 2 + .../core/listener/ItemListenerErrorTests.java | 2 + .../core/observability/BatchMetricsTests.java | 2 + .../ObservabilitySampleStepTests.java | 2 + .../batch/core/step/AbstractStepTests.java | 2 +- .../batch/core/step/NonAbstractStepTests.java | 2 +- .../builder/RegisterMultiListenerTests.java | 3 + ...erantStepFactoryBeanNonBufferingTests.java | 4 +- .../RepeatOperationsStepFactoryBeanTests.java | 4 +- .../step/item/SimpleStepFactoryBeanTests.java | 20 --- .../step/item/TaskletStepExceptionTests.java | 4 +- .../step/tasklet/AsyncTaskletStepTests.java | 6 +- .../core/step/tasklet/TaskletStepTests.java | 129 +++++++++--------- ...RemoteChunkingManagerStepBuilderTests.java | 2 + .../chunking/ManagerConfiguration.java | 2 + .../delimited/DelimitedJobConfiguration.java | 2 + .../fixed/FixedLengthJobConfiguration.java | 2 + .../file/json/JsonJobConfiguration.java | 2 + .../multiline/MultiLineJobConfiguration.java | 2 + .../MultiRecordTypeJobConfiguration.java | 2 + .../MultiResourceJobConfiguration.java | 2 + .../samples/file/xml/XmlJobConfiguration.java | 2 + .../football/FootballJobConfiguration.java | 2 + .../jdbc/JdbcReaderBatchWriterSampleJob.java | 2 + ...onousItemProcessingWithVirtualThreads.java | 2 + ...ionForLaunchingJobsWithVirtualThreads.java | 2 + ...ningConcurrentStepsWithVirtualThreads.java | 2 + ...unningParallelStepsWithVirtualThreads.java | 2 + ...ingPartitionedStepsWithVirtualThreads.java | 2 + ...stemCommandTaskletsWithVirtualThreads.java | 2 + .../metrics/BatchMetricsApplication.java | 2 + .../samples/mongodb/MongoDBConfiguration.java | 2 + .../OwnersExportJobConfiguration.java | 2 + .../retry/RetrySampleConfiguration.java | 2 + ...SkippableExceptionDuringProcessSample.java | 2 + .../SkippableExceptionDuringReadSample.java | 2 + .../SkippableExceptionDuringWriteSample.java | 2 + .../ValidationSampleConfiguration.java | 2 + ...positeItemReaderSampleFunctionalTests.java | 2 + .../batch/test/JobLauncherTestUtilsTests.java | 2 + .../test/SpringBatchTestJUnit4Tests.java | 2 + .../test/SpringBatchTestJUnit5Tests.java | 2 + ...copeAnnotatedListenerIntegrationTests.java | 2 + 48 files changed, 158 insertions(+), 94 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java index 004fffa5b3..bcc16338fd 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java @@ -201,6 +201,8 @@ public final void execute(StepExecution stepExecution) throws JobInterruptedException, UnexpectedJobExecutionException { Assert.notNull(stepExecution, "stepExecution must not be null"); + Assert.state(stepExecution.getId() != null, + "StepExecution has no id. It must be saved before it can be executed."); stepExecution.getExecutionContext().put(SpringBatchVersion.BATCH_VERSION_KEY, SpringBatchVersion.getVersion()); if (logger.isDebugEnabled()) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBatchVersionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBatchVersionTests.java index 65287a5f89..ec9f904daf 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBatchVersionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/SpringBatchVersionTests.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; @@ -77,6 +78,7 @@ void testBatchVersionInExecutionContext() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java index 9112204db2..3d5ba56c9b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/JobBuilderConfigurationTests.java @@ -98,6 +98,7 @@ private void testJob(String jobName, BatchStatus status, int stepExecutionCount, @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public static class TestConfiguration { @@ -165,6 +166,7 @@ protected Step step3(JobRepository jobRepository) throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public static class BeansConfigurer { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java index e2a12a3ff2..e5651e9c63 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java @@ -86,7 +86,7 @@ void testDefaultInfrastructureBeansRegistration() { } @Configuration - static class MyJobConfiguration extends DefaultBatchConfiguration { + static class MyJobConfiguration extends JdbcDefaultBatchConfiguration { @Bean public Step myStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java index 1e9e13abe1..1c24477e31 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.JobInterruptedException; @@ -390,6 +391,7 @@ void testBuildWithJobScopedStep() throws Exception { @EnableBatchProcessing @Configuration + @EnableJdbcJobRepository static class JobConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java index f39dae8d02..ee1c2f83ff 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/JobBuilderTests.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.listener.JobExecutionListener; @@ -67,6 +68,7 @@ void testListeners() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class MyJobConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ItemListenerErrorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ItemListenerErrorTests.java index 7c434be37c..231f9f22a4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ItemListenerErrorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/ItemListenerErrorTests.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; @@ -127,6 +128,7 @@ void testOnProcessError() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class BatchConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/BatchMetricsTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/BatchMetricsTests.java index 2ad9f19f11..dcefc36ea1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/BatchMetricsTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/BatchMetricsTests.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; @@ -248,6 +249,7 @@ void testBatchMetrics() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class MyJobConfiguration { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/ObservabilitySampleStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/ObservabilitySampleStepTests.java index bdf9d4626b..eb3af687a3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/observability/ObservabilitySampleStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/observability/ObservabilitySampleStepTests.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; @@ -110,6 +111,7 @@ public SampleTestRunnerConsumer yourCode() { @Configuration(proxyBeanMethods = false) @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfig { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java index dce5ea67bc..ffda9d7b22 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java @@ -38,7 +38,7 @@ class AbstractStepTests { void testEndTimeInListener() throws Exception { // given StepExecution execution = new StepExecution("step", - new JobExecution(new JobInstance(1L, "job"), new JobParameters())); + new JobExecution(new JobInstance(1L, "job"), 0L, new JobParameters()), 0L); AbstractStep tested = new AbstractStep() { @Override protected void doExecute(StepExecution stepExecution) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/NonAbstractStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/NonAbstractStepTests.java index afbe781b56..672bc95e61 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/NonAbstractStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/NonAbstractStepTests.java @@ -64,7 +64,7 @@ class NonAbstractStepTests { final List events = new ArrayList<>(); final StepExecution execution = new StepExecution(tested.getName(), - new JobExecution(new JobInstance(1L, "jobName"), new JobParameters())); + new JobExecution(new JobInstance(1L, "jobName"), 0L, new JobParameters()), 0L); /** * Fills the events list when abstract methods are called. diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/RegisterMultiListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/RegisterMultiListenerTests.java index 7078fdd9e7..496c1cf2f7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/RegisterMultiListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/RegisterMultiListenerTests.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.listener.ChunkListener; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.listener.ItemWriteListener; @@ -179,6 +180,7 @@ public ItemWriter writer() { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class MultiListenerFaultTolerantTestConfiguration extends MultiListenerTestConfigurationSupport { @Bean @@ -215,6 +217,7 @@ public Step step(JobRepository jobRepository) { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class MultiListenerTestConfiguration extends MultiListenerTestConfigurationSupport { @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanNonBufferingTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanNonBufferingTests.java index 91b5342158..dcec489a5c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanNonBufferingTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanNonBufferingTests.java @@ -79,7 +79,7 @@ void setUp() throws Exception { factory.setIsReaderTransactionalQueue(true); JobInstance jobInstance = new JobInstance(1L, "skipJob"); - jobExecution = new JobExecution(jobInstance, new JobParameters()); + jobExecution = new JobExecution(jobInstance, 0L, new JobParameters()); } /** @@ -94,7 +94,7 @@ void testSkip() throws Exception { factory.setListeners(new SkipListener[] { skipListener }); Step step = factory.getObject(); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java index 4815ff7000..c4755b2fd6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java @@ -49,7 +49,7 @@ class RepeatOperationsStepFactoryBeanTests { private List list; - private final JobExecution jobExecution = new JobExecution(new JobInstance(0L, "job"), new JobParameters()); + private final JobExecution jobExecution = new JobExecution(new JobInstance(0L, "job"), 0L, new JobParameters()); @BeforeEach void setUp() { @@ -86,7 +86,7 @@ void testStepOperationsWithoutChunkListener() throws Exception { }); Step step = factory.getObject(); - step.execute(new StepExecution(step.getName(), jobExecution)); + step.execute(new StepExecution(step.getName(), jobExecution, 0L)); assertEquals(1, list.size()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java index ca18ecc295..4925db24d8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java @@ -36,7 +36,6 @@ import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; import org.springframework.batch.core.step.Step; -import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.listener.StepListener; import org.springframework.batch.core.job.SimpleJob; import org.springframework.batch.core.listener.ItemListenerSupport; @@ -54,7 +53,6 @@ import org.springframework.batch.repeat.exception.SimpleLimitExceptionHandler; import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; -import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; @@ -146,24 +144,6 @@ void testSimpleJob() throws Exception { assertTrue(written.contains("foo")); } - @Test - void testSimpleConcurrentJob() throws Exception { - - SimpleStepFactoryBean factory = getStepFactory("foo", "bar"); - factory.setTaskExecutor(new SimpleAsyncTaskExecutor()); - - AbstractStep step = (AbstractStep) factory.getObject(); - step.setName("step1"); - - JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters()); - StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); - repository.add(stepExecution); - step.execute(stepExecution); - assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); - assertEquals(2, written.size()); - assertTrue(written.contains("foo")); - } - @Test void testSimpleJobWithItemListeners() throws Exception { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java index b1caf81dfb..ecef7d0f2b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java @@ -86,8 +86,8 @@ void init() { taskletStep.setTransactionManager(new ResourcelessTransactionManager()); JobInstance jobInstance = new JobInstance(1L, "testJob"); - JobExecution jobExecution = new JobExecution(jobInstance, new JobParameters()); - stepExecution = new StepExecution("testStep", jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, new JobParameters()); + stepExecution = new StepExecution("testStep", jobExecution, 0L); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncTaskletStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncTaskletStepTests.java index 2f274009ac..09d7d1935e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncTaskletStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/AsyncTaskletStepTests.java @@ -119,7 +119,7 @@ void testStepExecutionUpdates() throws Exception { JobExecution jobExecution = jobRepository.createJobExecution("JOB", new JobParameters()); StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); - + stepExecution.setId(0L); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -148,6 +148,7 @@ void testStepExecutionFails() throws Exception { JobExecution jobExecution = jobRepository.createJobExecution("JOB", new JobParameters()); StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); + stepExecution.setId(0L); step.execute(stepExecution); @@ -178,7 +179,7 @@ void testStepExecutionFailsWithProcessor() throws Exception { JobExecution jobExecution = jobRepository.createJobExecution("JOB", new JobParameters()); StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); - + stepExecution.setId(0L); step.execute(stepExecution); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); @@ -200,6 +201,7 @@ void testStepExecutionFailsOnLastItem() throws Exception { JobExecution jobExecution = jobRepository.createJobExecution("JOB", new JobParameters()); StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); + stepExecution.setId(0L); step.execute(stepExecution); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java index e5ca60fb42..1eb9934642 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java @@ -127,8 +127,8 @@ void setUp() throws Exception { @Test void testStepExecutor() throws Exception { - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(1, processed.size()); assertEquals(1, stepExecution.getReadCount()); @@ -137,10 +137,10 @@ void testStepExecutor() throws Exception { @Test void testCommitCount_Even() throws Exception { - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); step = getStep(new String[] { "foo", "bar", "spam", "eggs" }, 2); step.setTransactionManager(transactionManager); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(4, processed.size()); assertEquals(4, stepExecution.getReadCount()); @@ -151,10 +151,10 @@ void testCommitCount_Even() throws Exception { @Test void testCommitCount_Uneven() throws Exception { - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); step = getStep(new String[] { "foo", "bar", "spam" }, 2); step.setTransactionManager(transactionManager); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(3, processed.size()); assertEquals(3, stepExecution.getReadCount()); @@ -164,8 +164,8 @@ void testCommitCount_Uneven() throws Exception { @Test void testEmptyReader() throws Exception { - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step = getStep(new String[0]); step.setTasklet(new TestingChunkOrientedTasklet<>(getReader(new String[0]), itemWriter, new RepeatTemplate())); step.setStepOperations(new RepeatTemplate()); @@ -184,8 +184,8 @@ void testEmptyReader() throws Exception { @Test void testStepExecutionUpdates() throws Exception { - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.setStepOperations(new RepeatTemplate()); @@ -205,8 +205,8 @@ void testStepExecutionUpdates() throws Exception { @Test void testStepExecutionUpdateFailure() throws Exception { - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); JobRepository repository = new JobRepositoryFailedUpdateStub(); @@ -247,8 +247,8 @@ void testIncrementRollbackCount() { }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); try { step.execute(stepExecution); @@ -268,8 +268,8 @@ void testExitCodeDefaultClassification() { }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); try { step.execute(stepExecution); @@ -296,8 +296,8 @@ public ExitStatus afterStep(StepExecution stepExecution) { return ExitStatus.FAILED.addExitDescription("FOO"); } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); try { step.execute(stepExecution); @@ -319,8 +319,8 @@ void testNonRestartedJob() throws Exception { MockRestartableItemReader tasklet = new MockRestartableItemReader(); step.setTasklet(new TestingChunkOrientedTasklet<>(tasklet, itemWriter)); step.registerStream(tasklet); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); @@ -330,8 +330,8 @@ void testNonRestartedJob() throws Exception { @Test void testSuccessfulExecutionWithExecutionContext() throws Exception { - final JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + final JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.setJobRepository(new JobRepositorySupport() { @Override public void updateExecutionContext(StepExecution stepExecution) { @@ -347,8 +347,8 @@ public void updateExecutionContext(StepExecution stepExecution) { @Test void testSuccessfulExecutionWithFailureOnSaveOfExecutionContext() throws Exception { - final JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + final JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.setJobRepository(new JobRepositorySupport() { private int counter = 0; @@ -376,8 +376,8 @@ public void updateExecutionContext(StepExecution stepExecution) { void testNoSaveExecutionAttributesRestartableJob() { MockRestartableItemReader tasklet = new MockRestartableItemReader(); step.setTasklet(new TestingChunkOrientedTasklet<>(tasklet, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); assertDoesNotThrow(() -> step.execute(stepExecution)); assertFalse(tasklet.isRestoreFromCalled()); @@ -390,8 +390,8 @@ void testNoSaveExecutionAttributesRestartableJob() { @Test void testRestartJobOnNonRestartableTasklet() throws Exception { step.setTasklet(new TestingChunkOrientedTasklet<>(() -> "foo", itemWriter)); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); } @@ -413,8 +413,8 @@ public void update(ExecutionContext executionContext) { }; step.setTasklet(new TestingChunkOrientedTasklet<>(reader, itemWriter)); step.registerStream(reader); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); assertFalse(stepExecution.getExecutionContext().containsKey("foo")); @@ -434,8 +434,8 @@ public void update(ExecutionContext executionContext) { executionContext.putString("foo", "bar"); } } }); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); assertFalse(stepExecution.getExecutionContext().containsKey("foo")); @@ -459,8 +459,8 @@ public ExitStatus afterStep(StepExecution stepExecution) { return null; } }); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); assertEquals(2, list.size()); } @@ -481,7 +481,8 @@ public void open(ExecutionContext executionContext) throws ItemStreamException { }; step.setStreams(new ItemStream[] { reader }); step.registerStepExecutionListener(reader); - StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters)); + StepExecution stepExecution = new StepExecution(step.getName(), + new JobExecution(jobInstance, 0L, jobParameters), 0L); step.execute(stepExecution); assertEquals(1, list.size()); } @@ -504,8 +505,8 @@ public ExitStatus afterStep(StepExecution stepExecution) { stepTemplate.setCompletionPolicy(new SimpleCompletionPolicy(5)); step.setStepOperations(stepTemplate); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); assertEquals(1, list.size()); ExitStatus returnedStatus = stepExecution.getExitStatus(); @@ -530,8 +531,8 @@ public String read() throws RuntimeException { throw new RuntimeException("FOO"); } }, itemWriter)); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); step.execute(stepExecution); assertEquals("FOO", stepExecution.getFailureExceptions().get(0).getMessage()); assertEquals(1, list.size()); @@ -554,8 +555,8 @@ public void update(ExecutionContext executionContext) { }; step.setTasklet(new TestingChunkOrientedTasklet<>(reader, itemWriter)); step.setStreams(new ItemStream[] { reader }); - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); + JobExecution jobExecution = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecution, 0L); assertFalse(stepExecution.getExecutionContext().containsKey("foo")); @@ -582,8 +583,8 @@ void testStatusForInterruptedException() throws Exception { step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); @@ -603,8 +604,8 @@ void testStatusForNormalFailure() throws Exception { }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -624,8 +625,8 @@ void testStatusForErrorFailure() throws Exception { }; step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -653,8 +654,8 @@ protected void doRollback(DefaultTransactionStatus status) throws TransactionExc } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -683,8 +684,8 @@ protected void doRollback(DefaultTransactionStatus status) throws TransactionExc } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -708,8 +709,8 @@ public void close() throws ItemStreamException { } } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); // The job actually completed, but the streams couldn't be closed. @@ -736,8 +737,8 @@ public void close() throws ItemStreamException { step.setTasklet(new TestingChunkOrientedTasklet<>(itemReader, itemWriter)); step.registerStream(itemReader); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); @@ -769,7 +770,8 @@ public String read() throws RuntimeException { step.setTasklet(new TestingChunkOrientedTasklet<>(reader, itemWriter)); step.registerStream(reader); - StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters)); + StepExecution stepExecution = new StepExecution(step.getName(), + new JobExecution(jobInstance, 0L, jobParameters), 0L); step.execute(stepExecution); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); @@ -788,8 +790,8 @@ void testStepToCompletion() throws Exception { template.setCompletionPolicy(new DefaultResultCompletionPolicy()); step.setStepOperations(template); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(3, processed.size()); @@ -810,7 +812,8 @@ public ExitStatus afterStep(StepExecution stepExecution) { } }; step.setStepExecutionListeners(new StepExecutionListener[] { listener }); - StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters)); + StepExecution stepExecution = new StepExecution(step.getName(), + new JobExecution(jobInstance, 0L, jobParameters), 0L); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -828,8 +831,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute() { @Override @@ -852,8 +855,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon return null; } }); - JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters); - StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); + JobExecution jobExecutionContext = new JobExecution(jobInstance, 0L, jobParameters); + StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext, 0L); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); } diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilderTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilderTests.java index f5911e16ab..704b262215 100644 --- a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilderTests.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/RemoteChunkingManagerStepBuilderTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.listener.ChunkListener; import org.springframework.batch.core.listener.ItemReadListener; import org.springframework.batch.core.listener.ItemWriteListener; @@ -356,6 +357,7 @@ else if (count < items.size()) { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class BatchConfiguration { @Bean diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/ManagerConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/ManagerConfiguration.java index e09bfe6dbe..9ba06ca753 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/ManagerConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/ManagerConfiguration.java @@ -20,6 +20,7 @@ import jakarta.jms.JMSException; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; @@ -50,6 +51,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @EnableBatchIntegration @EnableIntegration @PropertySource("classpath:org/springframework/batch/samples/chunking/remote-chunking.properties") diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java index df4ef2203b..d3b059fd36 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/delimited/DelimitedJobConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.samples.file.delimited; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -40,6 +41,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class DelimitedJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java index 9b74da95da..d9db0e8eea 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/fixed/FixedLengthJobConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.samples.file.fixed; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -41,6 +42,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class FixedLengthJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java index 47f791adfb..a00c4b9c96 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/json/JsonJobConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.samples.file.json; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -43,6 +44,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JsonJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java index 3351cb4553..981785c62b 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiline/MultiLineJobConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.samples.file.multiline; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -40,6 +41,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class MultiLineJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java index cac11c77e6..f5aaf0e463 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multirecordtype/MultiRecordTypeJobConfiguration.java @@ -17,6 +17,7 @@ import java.util.Map; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -50,6 +51,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class MultiRecordTypeJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java index 1d5fc97347..0ea241c961 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/multiresource/MultiResourceJobConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.samples.file.multiresource; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -47,6 +48,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class MultiResourceJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java index c2c0af7941..47dbe6397a 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/file/xml/XmlJobConfiguration.java @@ -20,6 +20,7 @@ import com.thoughtworks.xstream.security.ExplicitTypePermission; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.StepScope; @@ -46,6 +47,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class XmlJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java index 8eb09166bb..d6f5e9a051 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/football/FootballJobConfiguration.java @@ -17,6 +17,7 @@ import javax.sql.DataSource; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -43,6 +44,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class FootballJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java index c375c62dd8..34e6522ec8 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/jdbc/JdbcReaderBatchWriterSampleJob.java @@ -17,6 +17,7 @@ import javax.sql.DataSource; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JdbcReaderBatchWriterSampleJob { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java index bc14e70d80..d51f3efcd7 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForAsynchronousItemProcessingWithVirtualThreads.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.concurrent.Future; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -43,6 +44,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForAsynchronousItemProcessingWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForLaunchingJobsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForLaunchingJobsWithVirtualThreads.java index 2332ff4d14..8006c3fb48 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForLaunchingJobsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForLaunchingJobsWithVirtualThreads.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.samples.loom; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.step.Step; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForLaunchingJobsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java index 616fb77b1a..39a4a7f161 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningConcurrentStepsWithVirtualThreads.java @@ -19,6 +19,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -43,6 +44,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForRunningConcurrentStepsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningParallelStepsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningParallelStepsWithVirtualThreads.java index 17e183b4e1..f19c757bae 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningParallelStepsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningParallelStepsWithVirtualThreads.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.samples.loom; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForRunningParallelStepsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningPartitionedStepsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningPartitionedStepsWithVirtualThreads.java index a3fd73e4d9..742494c8e8 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningPartitionedStepsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningPartitionedStepsWithVirtualThreads.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.Map; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -45,6 +46,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForRunningPartitionedStepsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.java index e4b1feb8c0..d64985f0df 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/loom/JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.Arrays; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -42,6 +43,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class JobConfigurationForRunningSystemCommandTaskletsWithVirtualThreads { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/BatchMetricsApplication.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/BatchMetricsApplication.java index 80ea2f8211..4ae4b72d68 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/BatchMetricsApplication.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/metrics/BatchMetricsApplication.java @@ -16,6 +16,7 @@ package org.springframework.batch.samples.metrics; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.samples.common.DataSourceConfiguration; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -27,6 +28,7 @@ @EnableScheduling @EnableBatchProcessing +@EnableJdbcJobRepository @Import({ Job1Configuration.class, Job2Configuration.class, JobScheduler.class, PrometheusConfiguration.class, DataSourceConfiguration.class }) @PropertySource("metrics-sample.properties") diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java index 33dc7f94a6..a795e66789 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java @@ -19,6 +19,7 @@ import com.mongodb.client.MongoClients; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableMongoJobRepository; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; import org.springframework.beans.factory.annotation.Value; @@ -34,6 +35,7 @@ @Configuration @PropertySource("classpath:/org/springframework/batch/samples/mongodb/mongodb-sample.properties") @EnableBatchProcessing +@EnableMongoJobRepository public class MongoDBConfiguration { @Value("${mongodb.host}") diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java index 492d47d9ec..c87122f7e3 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java @@ -17,6 +17,7 @@ import javax.sql.DataSource; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; @@ -36,6 +37,7 @@ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class OwnersExportJobConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java index 3635a32007..3984bc96fa 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/retry/RetrySampleConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.samples.retry; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -37,6 +38,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class RetrySampleConfiguration { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java index a408f08e34..f1ec9c18e4 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringProcessSample.java @@ -18,6 +18,7 @@ import java.util.Arrays; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class SkippableExceptionDuringProcessSample { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java index 8f9ed81e84..2d09447828 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringReadSample.java @@ -18,6 +18,7 @@ import java.util.Arrays; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class SkippableExceptionDuringReadSample { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java index 8a3a1776da..45b15c7d20 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/skip/SkippableExceptionDuringWriteSample.java @@ -18,6 +18,7 @@ import java.util.Arrays; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class SkippableExceptionDuringWriteSample { diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java index 1e9674c257..37f15d3c17 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/validation/ValidationSampleConfiguration.java @@ -18,6 +18,7 @@ import java.util.Arrays; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; @@ -39,6 +40,7 @@ */ @Configuration @EnableBatchProcessing +@EnableJdbcJobRepository @Import(DataSourceConfiguration.class) public class ValidationSampleConfiguration { diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java index 70002751e1..e537b7a3ce 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemReaderSampleFunctionalTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; @@ -74,6 +75,7 @@ void testJobLaunch() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class JobConfiguration { @Bean diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/JobLauncherTestUtilsTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/JobLauncherTestUtilsTests.java index 2eb32abefb..60ed6feeb3 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/JobLauncherTestUtilsTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/JobLauncherTestUtilsTests.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; @@ -78,6 +79,7 @@ void getUniqueJobParameters_doesNotRepeatJobParameters() { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestJobConfiguration { @Bean diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit4Tests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit4Tests.java index 9f83750cf2..1ab1c7d1df 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit4Tests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit4Tests.java @@ -24,6 +24,7 @@ import org.junit.runner.RunWith; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.step.StepExecution; @@ -107,6 +108,7 @@ public void testJob() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class JobConfiguration { @Bean diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit5Tests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit5Tests.java index b901605e11..bd18a3d750 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit5Tests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/SpringBatchTestJUnit5Tests.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.job.parameters.JobParameters; @@ -110,6 +111,7 @@ JobExecution getJobExecution() { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository public static class JobConfiguration { @Bean diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeAnnotatedListenerIntegrationTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeAnnotatedListenerIntegrationTests.java index f12cc4bb35..57b2315fb9 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeAnnotatedListenerIntegrationTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeAnnotatedListenerIntegrationTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.job.JobExecution; import org.springframework.batch.core.launch.JobOperator; @@ -96,6 +97,7 @@ public String read() throws Exception { @Configuration @EnableBatchProcessing + @EnableJdbcJobRepository static class TestConfig { @Autowired From 4959e6077d0390530aa9d199e25e7cde44c9e79f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 12 Sep 2025 16:50:51 +0200 Subject: [PATCH 37/49] Make Micrometer dependency optional With the introduction of JFR support in #4972, users should now be able to choose which observability tool to use without necessarily including the other. Resolves #4973 --- pom.xml | 2 +- spring-batch-core/pom.xml | 22 ++++++++++++---------- spring-batch-samples/pom.xml | 10 ++++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 7858b92b28..8552ccd5b2 100644 --- a/pom.xml +++ b/pom.xml @@ -55,9 +55,9 @@ 7.0.0-SNAPSHOT 2.0.13-SNAPSHOT 7.0.0-SNAPSHOT - 1.16.0-SNAPSHOT + 1.16.0-SNAPSHOT 4.0.0-SNAPSHOT 4.0.0-SNAPSHOT 4.0.0-SNAPSHOT diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 048a1e24f1..36c346375c 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -54,18 +54,20 @@ spring-jdbc ${spring-framework.version} - - io.micrometer - micrometer-core - ${micrometer.version} - - - io.micrometer - micrometer-observation - ${micrometer.version} - + + io.micrometer + micrometer-core + ${micrometer.version} + true + + + io.micrometer + micrometer-observation + ${micrometer.version} + true + com.fasterxml.jackson.core jackson-databind diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 7196aa21b9..9d9adeaf96 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -59,6 +59,16 @@ micrometer-registry-prometheus-simpleclient ${micrometer.version} + + io.micrometer + micrometer-core + ${micrometer.version} + + + io.micrometer + micrometer-observation + ${micrometer.version} + commons-io commons-io From c2d64778fb0a16e5d3a855251b02a42125bb0e53 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 15 Sep 2025 08:30:56 +0200 Subject: [PATCH 38/49] Revert "Make Micrometer dependency optional" This reverts commit 4959e6077d0390530aa9d199e25e7cde44c9e79f. Spring Batch has a compile-time dependency to spring-context, which in turn has a compile-time dependency to micrometer-observation. Issue #4973 --- pom.xml | 2 +- spring-batch-core/pom.xml | 22 ++++++++++------------ spring-batch-samples/pom.xml | 10 ---------- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 8552ccd5b2..7858b92b28 100644 --- a/pom.xml +++ b/pom.xml @@ -55,9 +55,9 @@ 7.0.0-SNAPSHOT 2.0.13-SNAPSHOT 7.0.0-SNAPSHOT + 1.16.0-SNAPSHOT - 1.16.0-SNAPSHOT 4.0.0-SNAPSHOT 4.0.0-SNAPSHOT 4.0.0-SNAPSHOT diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 36c346375c..048a1e24f1 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -54,20 +54,18 @@ spring-jdbc ${spring-framework.version} + + io.micrometer + micrometer-core + ${micrometer.version} + + + io.micrometer + micrometer-observation + ${micrometer.version} + - - io.micrometer - micrometer-core - ${micrometer.version} - true - - - io.micrometer - micrometer-observation - ${micrometer.version} - true - com.fasterxml.jackson.core jackson-databind diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 9d9adeaf96..7196aa21b9 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -59,16 +59,6 @@ micrometer-registry-prometheus-simpleclient ${micrometer.version} - - io.micrometer - micrometer-core - ${micrometer.version} - - - io.micrometer - micrometer-observation - ${micrometer.version} - commons-io commons-io From 724874b41560da2a29da87447f7580464d0f561e Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 15 Sep 2025 10:16:34 +0200 Subject: [PATCH 39/49] Implement unsupported operations in ResourcelessJobRepository --- .../support/ResourcelessJobRepository.java | 127 +++++++++++++++--- 1 file changed, 107 insertions(+), 20 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java index 71347c3ca9..fc2dcdad91 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java @@ -27,6 +27,7 @@ import org.springframework.batch.core.step.StepExecution; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.lang.Nullable; /** * A {@link JobRepository} implementation that does not use or store batch meta-data. It @@ -50,6 +51,12 @@ public class ResourcelessJobRepository implements JobRepository { private JobExecution jobExecution; + /* + * =================================================================================== + * Job operations + * =================================================================================== + */ + @Override public List getJobNames() { if (this.jobInstance == null) { @@ -58,8 +65,41 @@ public List getJobNames() { return Collections.singletonList(this.jobInstance.getJobName()); } + /* + * =================================================================================== + * Job instance operations + * =================================================================================== + */ + + @Override + public List getJobInstances(String jobName, int start, int count) { + if (this.jobInstance == null) { + return Collections.emptyList(); + } + return Collections.singletonList(this.jobInstance); + } + + @Override + @Nullable + public JobInstance getJobInstance(@Nullable Long instanceId) { + return this.jobInstance; + } + + @Override + @Nullable + public JobInstance getLastJobInstance(String jobName) { + return this.jobInstance; + } + + @Override + @Nullable + public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { + return this.jobInstance; + } + @SuppressWarnings("removal") @Override + @Deprecated(since = "6.0", forRemoval = true) public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { return false; } @@ -75,42 +115,51 @@ public JobInstance createJobInstance(String jobName, JobParameters jobParameters return this.jobInstance; } + /* + * =================================================================================== + * Job execution operations + * =================================================================================== + */ + @Override - public JobExecution createJobExecution(String jobName, JobParameters jobParameters) { - if (this.jobInstance == null) { - createJobInstance(jobName, jobParameters); - } - this.jobExecution = new JobExecution(this.jobInstance, 1L, jobParameters); + @Nullable + public JobExecution getJobExecution(Long executionId) { return this.jobExecution; } @Override - public void update(JobExecution jobExecution) { - jobExecution.setLastUpdated(LocalDateTime.now()); - this.jobExecution = jobExecution; + @Nullable + public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { + return this.jobExecution; } @Override - public void add(StepExecution stepExecution) { - this.addAll(Collections.singletonList(stepExecution)); + @Nullable + public JobExecution getLastJobExecution(JobInstance jobInstance) { + return this.jobExecution; } @Override - public void addAll(Collection stepExecutions) { - this.jobExecution.addStepExecutions(new ArrayList<>(stepExecutions)); + public List getJobExecutions(JobInstance jobInstance) { + if (this.jobExecution == null) { + return Collections.emptyList(); + } + return Collections.singletonList(this.jobExecution); } @Override - public void update(StepExecution stepExecution) { - stepExecution.setLastUpdated(LocalDateTime.now()); - if (this.jobExecution.isStopping()) { - stepExecution.setTerminateOnly(); + public JobExecution createJobExecution(String jobName, JobParameters jobParameters) { + if (this.jobInstance == null) { + createJobInstance(jobName, jobParameters); } + this.jobExecution = new JobExecution(this.jobInstance, 1L, jobParameters); + return this.jobExecution; } @Override - public void updateExecutionContext(StepExecution stepExecution) { - stepExecution.setLastUpdated(LocalDateTime.now()); + public void update(JobExecution jobExecution) { + jobExecution.setLastUpdated(LocalDateTime.now()); + this.jobExecution = jobExecution; } @Override @@ -118,7 +167,27 @@ public void updateExecutionContext(JobExecution jobExecution) { jobExecution.setLastUpdated(LocalDateTime.now()); } + /* + * =================================================================================== + * Step execution operations + * =================================================================================== + */ + @Override + @Nullable + public StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId) { + if (this.jobExecution == null || !this.jobExecution.getId().equals(jobExecutionId)) { + return null; + } + return this.jobExecution.getStepExecutions() + .stream() + .filter(stepExecution -> stepExecution.getId().equals(stepExecutionId)) + .findFirst() + .orElse(null); + } + + @Override + @Nullable public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { return this.jobExecution.getStepExecutions() .stream() @@ -136,8 +205,26 @@ public long getStepExecutionCount(JobInstance jobInstance, String stepName) { } @Override - public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { - return this.jobExecution; + public void add(StepExecution stepExecution) { + this.addAll(Collections.singletonList(stepExecution)); + } + + @Override + public void addAll(Collection stepExecutions) { + this.jobExecution.addStepExecutions(new ArrayList<>(stepExecutions)); + } + + @Override + public void update(StepExecution stepExecution) { + stepExecution.setLastUpdated(LocalDateTime.now()); + if (this.jobExecution.isStopping()) { + stepExecution.setTerminateOnly(); + } + } + + @Override + public void updateExecutionContext(StepExecution stepExecution) { + stepExecution.setLastUpdated(LocalDateTime.now()); } } \ No newline at end of file From 76a65501f75fd0115d05d3dd6c90d085a8d8d110 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 15 Sep 2025 11:52:55 +0200 Subject: [PATCH 40/49] Make transaction manager optional in Tasklet and Chunk-Oriented steps Before this commit, it was required to provide a transaction manager in tasklet and chunk-oriented steps even if transactions are not needed. This commit makes that optional by using a no-op transaction manager by default. Resolves #4974 --- .../batch/core/step/builder/StepBuilder.java | 10 ++++++++++ .../batch/core/step/builder/TaskletStepBuilder.java | 10 ++++++++++ .../batch/core/step/item/ChunkOrientedStep.java | 13 ++++++++++--- .../batch/core/step/tasklet/TaskletStep.java | 7 +++++-- .../helloworld/HelloWorldJobConfiguration.java | 10 +++++----- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java index 2c744fa857..9442429842 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/StepBuilder.java @@ -65,6 +65,16 @@ public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager tr return new TaskletStepBuilder(this).tasklet(tasklet, transactionManager); } + /** + * Build a step with a custom tasklet, not necessarily item processing. + * @param tasklet a tasklet + * @return a {@link TaskletStepBuilder} + * @since 6.0 + */ + public TaskletStepBuilder tasklet(Tasklet tasklet) { + return new TaskletStepBuilder(this).tasklet(tasklet); + } + /** * Build a step that processes items in chunks with the size provided. To extend the * step to being fault tolerant, call the {@link SimpleStepBuilder#faultTolerant()} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java index 896fce2cea..d21ccc5a31 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/TaskletStepBuilder.java @@ -49,6 +49,16 @@ public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager tr return this; } + /** + * @param tasklet the tasklet to use + * @return this for fluent chaining + * @since 6.0 + */ + public TaskletStepBuilder tasklet(Tasklet tasklet) { + this.tasklet = tasklet; + return this; + } + @Override protected TaskletStepBuilder self() { return this; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index 3aa64b4df7..6c19d46afa 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -111,11 +111,11 @@ public class ChunkOrientedStep extends AbstractStep { /* * Transaction related parameters */ - private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); + private PlatformTransactionManager transactionManager; private TransactionTemplate transactionTemplate; - private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); + private TransactionAttribute transactionAttribute; /* * Chunk related parameters @@ -309,7 +309,14 @@ public void registerSkipListener(SkipListener skipListener) { @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); - Assert.notNull(this.transactionManager, "Transaction manager must not be null"); + if (this.transactionManager == null) { + logger.info("No transaction manager has been set. Defaulting to ResourcelessTransactionManager."); + this.transactionManager = new ResourcelessTransactionManager(); + } + if (this.transactionAttribute == null) { + logger.info("No transaction attribute has been set. Defaulting to DefaultTransactionAttribute."); + this.transactionAttribute = new DefaultTransactionAttribute(); + } Assert.isTrue(this.chunkSize > 0, "Chunk size must be greater than 0"); Assert.notNull(this.itemReader, "Item reader must not be null"); Assert.notNull(this.itemWriter, "Item writer must not be null"); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java index a7664d01ea..6b97def353 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java @@ -41,6 +41,7 @@ import org.springframework.batch.repeat.RepeatOperations; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.batch.repeat.support.RepeatTemplate; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; @@ -49,7 +50,6 @@ import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; -import org.springframework.util.Assert; import java.util.concurrent.Semaphore; @@ -118,7 +118,10 @@ public TaskletStep(String name) { @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); - Assert.state(transactionManager != null, "A transaction manager must be provided"); + if (this.transactionManager == null) { + logger.info("No transaction manager has been set. Defaulting to ResourcelessTransactionManager."); + this.transactionManager = new ResourcelessTransactionManager(); + } } /** diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java index 388e1af0de..586a77bbc7 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,10 @@ */ package org.springframework.batch.samples.helloworld; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; import org.springframework.batch.core.step.Step; -import org.springframework.batch.core.configuration.annotation.*; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; @@ -26,7 +27,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.jdbc.support.JdbcTransactionManager; @Configuration @EnableBatchProcessing @@ -35,11 +35,11 @@ public class HelloWorldJobConfiguration { @Bean - public Step step(JobRepository jobRepository, JdbcTransactionManager transactionManager) { + public Step step(JobRepository jobRepository) { return new StepBuilder("step", jobRepository).tasklet((contribution, chunkContext) -> { System.out.println("Hello world!"); return RepeatStatus.FINISHED; - }, transactionManager).build(); + }).build(); } @Bean From 7d269e94b5fcfd9e6bb507d7002ac1d8a2cea0f3 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 15 Sep 2025 17:17:33 +0200 Subject: [PATCH 41/49] Fix step execution id generation in ResourcelessJobRepository --- .../support/ResourcelessJobRepository.java | 12 +++++++++--- .../helloworld/HelloWorldJobConfiguration.java | 7 +------ .../helloworld/HelloWorldJobFunctionalTests.java | 2 ++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java index fc2dcdad91..694cd471a7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java @@ -16,7 +16,6 @@ package org.springframework.batch.core.repository.support; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -51,6 +50,8 @@ public class ResourcelessJobRepository implements JobRepository { private JobExecution jobExecution; + private long stepExecutionIdIncrementer = 0L; + /* * =================================================================================== * Job operations @@ -189,6 +190,9 @@ public StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId) @Override @Nullable public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { + if (this.jobExecution == null || !this.jobExecution.getJobInstance().getId().equals(jobInstance.getId())) { + return null; + } return this.jobExecution.getStepExecutions() .stream() .filter(stepExecution -> stepExecution.getStepName().equals(stepName)) @@ -206,12 +210,14 @@ public long getStepExecutionCount(JobInstance jobInstance, String stepName) { @Override public void add(StepExecution stepExecution) { - this.addAll(Collections.singletonList(stepExecution)); + stepExecution.setId(this.stepExecutionIdIncrementer++); } @Override public void addAll(Collection stepExecutions) { - this.jobExecution.addStepExecutions(new ArrayList<>(stepExecutions)); + for (StepExecution stepExecution : stepExecutions) { + this.add(stepExecution); + } } @Override diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java index 586a77bbc7..1be6378d09 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/helloworld/HelloWorldJobConfiguration.java @@ -16,22 +16,17 @@ package org.springframework.batch.samples.helloworld; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; -import org.springframework.batch.core.configuration.annotation.EnableJdbcJobRepository; import org.springframework.batch.core.job.Job; -import org.springframework.batch.core.step.Step; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.Step; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.repeat.RepeatStatus; -import org.springframework.batch.samples.common.DataSourceConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; @Configuration @EnableBatchProcessing -@EnableJdbcJobRepository -@Import(DataSourceConfiguration.class) public class HelloWorldJobConfiguration { @Bean diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/helloworld/HelloWorldJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/helloworld/HelloWorldJobFunctionalTests.java index d03bfe88b2..6516443138 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/helloworld/HelloWorldJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/helloworld/HelloWorldJobFunctionalTests.java @@ -42,6 +42,8 @@ public void testLaunchJob() throws Exception { // then assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + assertEquals(1, jobExecution.getStepExecutions().size()); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStepExecutions().iterator().next().getStatus()); } } From 0b4c27010776f68ebb55bcf0b3c531672fc432c2 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 15 Sep 2025 21:47:10 +0200 Subject: [PATCH 42/49] Move unused methods in DefaultBatchConfiguration to its subclasses While this creates some duplication (which can be avoided by introducing an intermediate base class), it removes the exposure of methods to clients who do not need/use them. --- .../support/DefaultBatchConfiguration.java | 33 ------------------- .../JdbcDefaultBatchConfiguration.java | 33 +++++++++++++++++++ .../MongoDefaultBatchConfiguration.java | 33 +++++++++++++++++++ 3 files changed, 66 insertions(+), 33 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 9b3bd71828..c208b06715 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -16,10 +16,7 @@ package org.springframework.batch.core.configuration.support; import org.springframework.batch.core.configuration.DuplicateJobException; -import org.springframework.batch.core.job.DefaultJobKeyGenerator; import org.springframework.batch.core.job.Job; -import org.springframework.batch.core.job.JobInstance; -import org.springframework.batch.core.job.JobKeyGenerator; import org.springframework.batch.core.configuration.BatchConfigurationException; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.converter.DefaultJobParametersConverter; @@ -39,7 +36,6 @@ import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.Isolation; /** * Base {@link Configuration} class that provides common infrastructure beans for enabling @@ -153,33 +149,4 @@ protected JobParametersConverter getJobParametersConverter() { return new DefaultJobParametersConverter(); } - /** - * Return the value of the {@code validateTransactionState} parameter. Defaults to - * {@code true}. - * @return true if the transaction state should be validated, false otherwise - */ - protected boolean getValidateTransactionState() { - return true; - } - - /** - * Return the transaction isolation level when creating job executions. Defaults to - * {@link Isolation#SERIALIZABLE}. - * @return the transaction isolation level when creating job executions - */ - protected Isolation getIsolationLevelForCreate() { - return Isolation.SERIALIZABLE; - } - - /** - * A custom implementation of the {@link JobKeyGenerator}. The default, if not - * injected, is the {@link DefaultJobKeyGenerator}. - * @return the generator that creates the key used in identifying {@link JobInstance} - * objects - * @since 5.1 - */ - protected JobKeyGenerator getJobKeyGenerator() { - return new DefaultJobKeyGenerator(); - } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java index 3b57ec1496..2b3cc40e15 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JdbcDefaultBatchConfiguration.java @@ -24,6 +24,9 @@ import org.springframework.batch.core.converter.StringToLocalDateConverter; import org.springframework.batch.core.converter.StringToLocalDateTimeConverter; import org.springframework.batch.core.converter.StringToLocalTimeConverter; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobKeyGenerator; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.ExecutionContextSerializer; import org.springframework.batch.core.repository.JobRepository; @@ -45,6 +48,7 @@ import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Isolation; import javax.sql.DataSource; import java.nio.charset.Charset; @@ -255,4 +259,33 @@ protected ConfigurableConversionService getConversionService() { return conversionService; } + /** + * Return the value of the {@code validateTransactionState} parameter. Defaults to + * {@code true}. + * @return true if the transaction state should be validated, false otherwise + */ + protected boolean getValidateTransactionState() { + return true; + } + + /** + * Return the transaction isolation level when creating job executions. Defaults to + * {@link Isolation#SERIALIZABLE}. + * @return the transaction isolation level when creating job executions + */ + protected Isolation getIsolationLevelForCreate() { + return Isolation.SERIALIZABLE; + } + + /** + * A custom implementation of the {@link JobKeyGenerator}. The default, if not + * injected, is the {@link DefaultJobKeyGenerator}. + * @return the generator that creates the key used in identifying {@link JobInstance} + * objects + * @since 5.1 + */ + protected JobKeyGenerator getJobKeyGenerator() { + return new DefaultJobKeyGenerator(); + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java index 2c4db64f6c..fdb18c63d3 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/MongoDefaultBatchConfiguration.java @@ -16,6 +16,9 @@ package org.springframework.batch.core.configuration.support; import org.springframework.batch.core.configuration.BatchConfigurationException; +import org.springframework.batch.core.job.DefaultJobKeyGenerator; +import org.springframework.batch.core.job.JobInstance; +import org.springframework.batch.core.job.JobKeyGenerator; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; @@ -23,6 +26,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.MongoTransactionManager; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.transaction.annotation.Isolation; /** * Base {@link Configuration} class that provides common MongoDB-based infrastructure @@ -115,4 +119,33 @@ protected MongoTransactionManager getTransactionManager() { return this.applicationContext.getBean("transactionManager", MongoTransactionManager.class); } + /** + * Return the value of the {@code validateTransactionState} parameter. Defaults to + * {@code true}. + * @return true if the transaction state should be validated, false otherwise + */ + protected boolean getValidateTransactionState() { + return true; + } + + /** + * Return the transaction isolation level when creating job executions. Defaults to + * {@link Isolation#SERIALIZABLE}. + * @return the transaction isolation level when creating job executions + */ + protected Isolation getIsolationLevelForCreate() { + return Isolation.SERIALIZABLE; + } + + /** + * A custom implementation of the {@link JobKeyGenerator}. The default, if not + * injected, is the {@link DefaultJobKeyGenerator}. + * @return the generator that creates the key used in identifying {@link JobInstance} + * objects + * @since 5.1 + */ + protected JobKeyGenerator getJobKeyGenerator() { + return new DefaultJobKeyGenerator(); + } + } From 4757cce7838bd9c4ee8d9192741686da7824fa3f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 15 Sep 2025 22:40:46 +0200 Subject: [PATCH 43/49] Add missing EnableMongoJobRepository in MongoDB integration tests --- .../repository/support/MongoDBIntegrationTestConfiguration.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java index 93d2be8a57..33a1d2b8b6 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java @@ -17,6 +17,7 @@ import org.springframework.batch.core.job.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.EnableMongoJobRepository; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; @@ -37,6 +38,7 @@ */ @Configuration @EnableBatchProcessing +@EnableMongoJobRepository class MongoDBIntegrationTestConfiguration { private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.11"); From a7f8c845a61b78f0e7437dac122bf207135d0402 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 16 Sep 2025 18:21:48 +0200 Subject: [PATCH 44/49] Add default implementation to Step#getName Resolves #4976 --- .../org/springframework/batch/core/step/Step.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java index 071560b3cb..03447ddb0e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/Step.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ * @author Mahmoud Ben Hassine * */ +@FunctionalInterface public interface Step { /** @@ -35,9 +36,14 @@ public interface Step { String STEP_TYPE_KEY = "batch.stepType"; /** - * @return the name of this step. + * The name of the step. This is used to distinguish between different steps and must + * be unique within a job. If not explicitly set, the name will default to the fully + * qualified class name. + * @return the name of the step (never {@code null}) */ - String getName(); + default String getName() { + return this.getClass().getName(); + } /** * @return {@code true} if a step that is already marked as complete can be started From 736aa94c21e5c8f4f9f00b077ad8239e3d213855 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 16 Sep 2025 21:10:39 +0200 Subject: [PATCH 45/49] Add Micrometer observability to ChunkOrientedStep --- .../builder/ChunkOrientedStepBuilder.java | 19 +++ .../core/step/item/ChunkOrientedStep.java | 58 ++++++++- ...ntedStepObservabilityIntegrationTests.java | 114 ++++++++++++++++++ 3 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepObservabilityIntegrationTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java index 683c7a9d56..8b38d9584c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/ChunkOrientedStepBuilder.java @@ -21,6 +21,8 @@ import java.util.LinkedHashSet; import java.util.Set; +import io.micrometer.core.instrument.MeterRegistry; + import org.springframework.batch.core.annotation.AfterChunk; import org.springframework.batch.core.annotation.AfterProcess; import org.springframework.batch.core.annotation.AfterRead; @@ -108,6 +110,8 @@ public class ChunkOrientedStepBuilder extends StepBuilderHelper parent, int chunkSize) { super(parent); this.chunkSize = chunkSize; @@ -359,6 +363,18 @@ public ChunkOrientedStepBuilder taskExecutor(AsyncTaskExecutor asyncTaskEx return self(); } + /** + * Set the meter registry to be used for collecting metrics during step execution. + * This allows for monitoring and analyzing the performance of the step. If not set, + * it will default to {@link io.micrometer.core.instrument.Metrics#globalRegistry}. + * @param meterRegistry the MeterRegistry to use + * @return this for fluent chaining + */ + public ChunkOrientedStepBuilder meterRegistry(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; + return self(); + } + @SuppressWarnings("unchecked") public ChunkOrientedStep build() { ChunkOrientedStep chunkOrientedStep = new ChunkOrientedStep<>(this.getName(), this.chunkSize, this.reader, @@ -412,6 +428,9 @@ public ChunkOrientedStep build() { }); retryListeners.forEach(chunkOrientedStep::registerRetryListener); skipListeners.forEach(chunkOrientedStep::registerSkipListener); + if (this.meterRegistry != null) { + chunkOrientedStep.setMeterRegistry(this.meterRegistry); + } try { chunkOrientedStep.afterPropertiesSet(); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java index 6c19d46afa..720aa0556a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedStep.java @@ -19,6 +19,10 @@ import java.util.List; import java.util.concurrent.Future; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; @@ -35,7 +39,11 @@ import org.springframework.batch.core.listener.ItemWriteListener; import org.springframework.batch.core.listener.SkipListener; import org.springframework.batch.core.observability.BatchMetrics; -import org.springframework.batch.core.observability.jfr.events.step.chunk.*; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ChunkScanEvent; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ChunkTransactionEvent; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ChunkWriteEvent; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ItemProcessEvent; +import org.springframework.batch.core.observability.jfr.events.step.chunk.ItemReadEvent; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.core.step.StepContribution; @@ -67,7 +75,6 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttribute; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; @@ -146,6 +153,11 @@ public class ChunkOrientedStep extends AbstractStep { */ private AsyncTaskExecutor taskExecutor; + /* + * Observability parameters + */ + private MeterRegistry meterRegistry; + /** * Create a new {@link ChunkOrientedStep}. * @param name the name of the step @@ -306,6 +318,16 @@ public void registerSkipListener(SkipListener skipListener) { this.compositeSkipListener.register(skipListener); } + /** + * Set the meter registry to use for metrics. + * @param meterRegistry the meter registry + * @since 6.0 + */ + public void setMeterRegistry(MeterRegistry meterRegistry) { + Assert.notNull(meterRegistry, "Meter registry must not be null"); + this.meterRegistry = meterRegistry; + } + @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); @@ -335,6 +357,10 @@ public void afterPropertiesSet() throws Exception { this.retryTemplate.setRetryPolicy(this.retryPolicy); this.retryTemplate.setRetryListener(this.compositeRetryListener); } + if (this.meterRegistry == null) { + logger.info("No meter registry has been set. Defaulting to the global meter registry."); + this.meterRegistry = Metrics.globalRegistry; + } } @Override @@ -481,6 +507,8 @@ private Chunk readChunk(StepContribution contribution) throws Exception { @Nullable private I readItem(StepContribution contribution) throws Exception { ItemReadEvent itemReadEvent = new ItemReadEvent(contribution.getStepExecution().getStepName(), contribution.getStepExecution().getId()); + Timer.Sample sample = startTimerSample(); + String status = BatchMetrics.STATUS_SUCCESS; I item = null; try { itemReadEvent.begin(); @@ -504,9 +532,12 @@ private Chunk readChunk(StepContribution contribution) throws Exception { throw exception; } itemReadEvent.itemReadStatus = BatchMetrics.STATUS_FAILURE; + status = BatchMetrics.STATUS_FAILURE; } finally { itemReadEvent.commit(); + stopTimerSample(sample, contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + contribution.getStepExecution().getStepName(), "item.read", "Item reading", status); } return item; } @@ -558,6 +589,8 @@ private Chunk processChunk(Chunk chunk, StepContribution contribution) thr private O processItem(I item, StepContribution contribution) throws Exception { ItemProcessEvent itemProcessEvent = new ItemProcessEvent(contribution.getStepExecution().getStepName(), contribution.getStepExecution().getId()); + Timer.Sample sample = startTimerSample(); + String status = BatchMetrics.STATUS_SUCCESS; O processedItem = null; try { itemProcessEvent.begin(); @@ -578,9 +611,12 @@ private O processItem(I item, StepContribution contribution) throws Exception { throw exception; } itemProcessEvent.itemProcessStatus = BatchMetrics.STATUS_FAILURE; + status = BatchMetrics.STATUS_FAILURE; } finally { itemProcessEvent.commit(); + stopTimerSample(sample, contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + contribution.getStepExecution().getStepName(), "item.process", "Item processing", status); } return processedItem; } @@ -633,6 +669,8 @@ private void doSkipInProcess(I item, RetryException retryException, StepContribu private void writeChunk(Chunk chunk, StepContribution contribution) throws Exception { ChunkWriteEvent chunkWriteEvent = new ChunkWriteEvent(contribution.getStepExecution().getStepName(), contribution.getStepExecution().getId(), chunk.size()); + Timer.Sample sample = startTimerSample(); + String status = BatchMetrics.STATUS_SUCCESS; try { chunkWriteEvent.begin(); this.compositeItemWriteListener.beforeWrite(chunk); @@ -644,6 +682,7 @@ private void writeChunk(Chunk chunk, StepContribution contribution) throws Ex catch (Exception exception) { this.compositeItemWriteListener.onWriteError(exception, chunk); chunkWriteEvent.chunkWriteStatus = BatchMetrics.STATUS_FAILURE; + status = BatchMetrics.STATUS_FAILURE; if (this.faultTolerant && exception instanceof RetryException retryException) { logger.info("Retry exhausted while attempting to write items, scanning the chunk", retryException); ChunkScanEvent chunkScanEvent = new ChunkScanEvent(contribution.getStepExecution().getStepName(), @@ -660,6 +699,8 @@ private void writeChunk(Chunk chunk, StepContribution contribution) throws Ex } finally { chunkWriteEvent.commit(); + stopTimerSample(sample, contribution.getStepExecution().getJobExecution().getJobInstance().getJobName(), + contribution.getStepExecution().getStepName(), "chunk.write", "Chunk writing", status); } } @@ -711,6 +752,19 @@ private void scan(Chunk chunk, StepContribution contribution) { } } + private Timer.Sample startTimerSample() { + return BatchMetrics.createTimerSample(this.meterRegistry); + } + + private void stopTimerSample(Timer.Sample sample, String jobName, String stepName, String operation, + String description, String status) { + String fullyQualifiedMetricName = BatchMetrics.METRICS_PREFIX + operation; + sample.stop(BatchMetrics.createTimer(this.meterRegistry, operation, description + " duration", + Tag.of(fullyQualifiedMetricName + ".job.name", jobName), + Tag.of(fullyQualifiedMetricName + ".step.name", stepName), + Tag.of(fullyQualifiedMetricName + ".status", status))); + } + private boolean isConcurrent() { return this.taskExecutor != null; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepObservabilityIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepObservabilityIntegrationTests.java new file mode 100644 index 0000000000..f77b961536 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepObservabilityIntegrationTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.step.item; + +import java.util.List; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.job.Job; +import org.springframework.batch.core.job.JobExecution; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.job.parameters.JobParameters; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.Step; +import org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder; +import org.springframework.batch.item.support.ListItemReader; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Integration tests for observability features in {@link ChunkOrientedStep}. + * + * @author Mahmoud Ben Hassine + */ +public class ChunkOrientedStepObservabilityIntegrationTests { + + @Test + void testChunkOrientedStepMetrics() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class); + SimpleMeterRegistry meterRegistry = context.getBean(SimpleMeterRegistry.class); + JobOperator jobOperator = context.getBean(JobOperator.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobOperator.start(job, new JobParameters()); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + Assertions.assertEquals(3, meterRegistry.getMeters().size()); + assertDoesNotThrow( + () -> meterRegistry.get("spring.batch.item.read") + .tag("spring.batch.item.read.job.name", "job") + .tag("spring.batch.item.read.step.name", "step") + .tag("spring.batch.item.read.status", "SUCCESS") + .timer(), + "There should be a meter of type TIMER named spring.batch.item.read registered in the meter registry"); + assertDoesNotThrow( + () -> meterRegistry.get("spring.batch.item.process") + .tag("spring.batch.item.process.job.name", "job") + .tag("spring.batch.item.process.step.name", "step") + .tag("spring.batch.item.process.status", "SUCCESS") + .timer(), + "There should be a meter of type TIMER named spring.batch.item.process registered in the meter registry"); + assertDoesNotThrow( + () -> meterRegistry.get("spring.batch.chunk.write") + .tag("spring.batch.chunk.write.job.name", "job") + .tag("spring.batch.chunk.write.step.name", "step") + .tag("spring.batch.chunk.write.status", "SUCCESS") + .timer(), + "There should be a meter of type TIMER named spring.batch.chunk.write registered in the meter registry"); + } + + @Configuration + @EnableBatchProcessing + static class TestConfiguration { + + @Bean + public Job job(JobRepository jobRepository, Step step) { + return new JobBuilder(jobRepository).start(step).build(); + } + + @Bean + public Step step(JobRepository jobRepository, MeterRegistry meterRegistry) { + return new ChunkOrientedStepBuilder(jobRepository, 2) + .reader(new ListItemReader<>(List.of("one", "two", "three", "four", "five"))) + .processor(String::toUpperCase) + .writer(items -> { + }) + .meterRegistry(meterRegistry) + .build(); + } + + @Bean + public SimpleMeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + } + +} \ No newline at end of file From 65916578de5125f7edb9e1c07d195324a4cc3b4d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 17 Sep 2025 09:30:23 +0200 Subject: [PATCH 46/49] Remove usage of deprecated API in ChunkOrientedStepIntegrationTests --- .../item/ChunkOrientedStepIntegrationTests.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java index b767c0c659..1153506210 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/ChunkOrientedStepIntegrationTests.java @@ -38,7 +38,7 @@ import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder; -import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy; +import org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy; import org.springframework.batch.core.step.skip.SkipLimitExceededException; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; @@ -426,9 +426,10 @@ public Step faulTolerantChunkOrientedStep(JobRepository jobRepository, // skip policy configuration int skipLimit = Integer.parseInt(System.getProperty("skipLimit")); - Map, Boolean> skippableExceptions = Map.of(FlatFileParseException.class, true, - DataIntegrityViolationException.class, true); - LimitCheckingItemSkipPolicy skipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, skippableExceptions); + Set> skippableExceptions = Set.of(FlatFileParseException.class, + DataIntegrityViolationException.class); + LimitCheckingExceptionHierarchySkipPolicy skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy( + skippableExceptions, skipLimit); return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) .processor(itemProcessor) @@ -460,9 +461,10 @@ public Step concurrentFaulTolerantChunkOrientedStep(JobRepository jobRepository, // skip policy configuration int skipLimit = Integer.parseInt(System.getProperty("skipLimit")); - Map, Boolean> skippableExceptions = Map.of(FlatFileParseException.class, true, - DataIntegrityViolationException.class, true); - LimitCheckingItemSkipPolicy skipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, skippableExceptions); + Set> skippableExceptions = Set.of(FlatFileParseException.class, + DataIntegrityViolationException.class); + LimitCheckingExceptionHierarchySkipPolicy skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy( + skippableExceptions, skipLimit); return new ChunkOrientedStepBuilder(jobRepository, 2).reader(itemReader) .processor(itemProcessor) From c20644c0185593c00f47f6f38bf169a681b0ea3b Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 17 Sep 2025 10:50:51 +0200 Subject: [PATCH 47/49] Update documentation --- spring-batch-docs/modules/ROOT/nav.adoc | 5 +-- .../modules/ROOT/pages/index.adoc | 3 +- .../pages/spring-batch-observability.adoc | 11 ++++++ .../pages/spring-batch-observability/jfr.adoc | 13 +++++++ .../micrometer.adoc} | 18 ++++++++-- .../modules/ROOT/pages/tracing.adoc | 9 ----- .../modules/ROOT/pages/whatsnew.adoc | 36 +++++++++++++++++-- 7 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 spring-batch-docs/modules/ROOT/pages/spring-batch-observability.adoc create mode 100644 spring-batch-docs/modules/ROOT/pages/spring-batch-observability/jfr.adoc rename spring-batch-docs/modules/ROOT/pages/{monitoring-and-metrics.adoc => spring-batch-observability/micrometer.adoc} (84%) delete mode 100644 spring-batch-docs/modules/ROOT/pages/tracing.adoc diff --git a/spring-batch-docs/modules/ROOT/nav.adoc b/spring-batch-docs/modules/ROOT/nav.adoc index 01406bc879..540c0f8a0d 100644 --- a/spring-batch-docs/modules/ROOT/nav.adoc +++ b/spring-batch-docs/modules/ROOT/nav.adoc @@ -53,8 +53,9 @@ ** xref:spring-batch-integration/launching-jobs-through-messages.adoc[] ** xref:spring-batch-integration/available-attributes-of-the-job-launching-gateway.adoc[] ** xref:spring-batch-integration/sub-elements.adoc[] -* xref:monitoring-and-metrics.adoc[] -* xref:tracing.adoc[] +* xref:spring-batch-observability.adoc[] +** xref:spring-batch-observability/micrometer.adoc[] +** xref:spring-batch-observability/jfr.adoc[] * Appendices ** xref:appendix.adoc[] ** xref:schema-appendix.adoc[] diff --git a/spring-batch-docs/modules/ROOT/pages/index.adoc b/spring-batch-docs/modules/ROOT/pages/index.adoc index 8557d07958..0a28755815 100644 --- a/spring-batch-docs/modules/ROOT/pages/index.adoc +++ b/spring-batch-docs/modules/ROOT/pages/index.adoc @@ -28,9 +28,8 @@ xref:common-patterns.adoc#commonPatterns[Common Patterns] :: Common batch proces and guidelines. xref:spring-batch-integration.adoc[Spring Batch Integration] :: Integration between Spring Batch and Spring Integration projects. -xref:monitoring-and-metrics.adoc[Monitoring and metrics] :: Batch jobs +xref:spring-batch-observability.adoc[Spring Batch Observability] :: Batch jobs monitoring and metrics. -xref:tracing.adoc[Tracing] :: Tracing with Micrometer. The following appendices are available: diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-observability.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability.adoc new file mode 100644 index 0000000000..5c8f4135cf --- /dev/null +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability.adoc @@ -0,0 +1,11 @@ + +[[springBatchObservability]] += Spring Batch Observability + +Observability is a critical aspect of modern applications, and Spring Batch provides robust support for monitoring and tracing batch jobs. + +This section covers the integration of Spring Batch with popular observability tools such as Micrometer and Java Flight Recorder (JFR): + +[role="xmlContent"] +* xref:spring-batch-observability/micrometer.adoc[Micrometer Support] +* xref:spring-batch-observability/jfr.adoc[Java Flight Recorder Support] \ No newline at end of file diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/jfr.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/jfr.adoc new file mode 100644 index 0000000000..411f51b6d9 --- /dev/null +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/jfr.adoc @@ -0,0 +1,13 @@ +[[jfr]] += Java Flight Recorder (JFR) support + +As of version 6, Spring Batch provides support for Java Flight Recorder (JFR) to help you monitor and troubleshoot batch jobs. JFR is a low-overhead, event-based profiling tool built into the Java Virtual Machine (JVM) that allows developers to collect detailed information about the performance and behavior of their applications. + +JFR can be enabled by adding the following JVM options when starting your Spring Batch application: + +[source, bash] +---- +java -XX:StartFlightRecording:filename=my-batch-job.jfr,dumponexit=true -jar my-batch-job.jar +---- + +Once JFR is enabled, Spring Batch will automatically create JFR events for key batch processing activities, such as job and step executions, item reads and writes, as well as transaction boundaries. These events can be viewed and analyzed using tools such as Java Mission Control (JMC) or other JFR-compatible tools. \ No newline at end of file diff --git a/spring-batch-docs/modules/ROOT/pages/monitoring-and-metrics.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/micrometer.adoc similarity index 84% rename from spring-batch-docs/modules/ROOT/pages/monitoring-and-metrics.adoc rename to spring-batch-docs/modules/ROOT/pages/spring-batch-observability/micrometer.adoc index 0d22ebcabb..0eb49fc31e 100644 --- a/spring-batch-docs/modules/ROOT/pages/monitoring-and-metrics.adoc +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-observability/micrometer.adoc @@ -1,7 +1,9 @@ +[[micrometer]] += Micrometer support -[[monitoring-and-metrics]] -= Monitoring and metrics +[[monitoring-and-metrics]] +== Monitoring and metrics Since version 4.2, Spring Batch provides support for batch monitoring and metrics based on link:$$https://micrometer.io/$$[Micrometer]. This section describes @@ -82,4 +84,14 @@ Metrics.globalRegistry.config().meterFilter(MeterFilter.denyNameStartsWith("spri ---- See Micrometer's link:$$http://micrometer.io/docs/concepts#_meter_filters$$[reference documentation] -for more details. \ No newline at end of file +for more details. + +[[tracing]] +== Tracing + +As of version 5, Spring Batch provides tracing through Micrometer's `Observation` API. By default, tracing is enabled +when using `@EnableBatchProcessing`. Spring Batch will create a trace for each job execution and a span for each +step execution. + +If you do not use `EnableBatchProcessing`, you need to register a `BatchObservabilityBeanPostProcessor` in your +application context, which will automatically setup Micrometer's observability in your jobs and steps beans. diff --git a/spring-batch-docs/modules/ROOT/pages/tracing.adoc b/spring-batch-docs/modules/ROOT/pages/tracing.adoc deleted file mode 100644 index 113190feeb..0000000000 --- a/spring-batch-docs/modules/ROOT/pages/tracing.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[tracing]] -= Tracing - -As of version 5, Spring Batch provides tracing through Micrometer's `Observation` API. By default, tracing is enabled -when using `@EnableBatchProcessing`. Spring Batch will create a trace for each job execution and a span for each -step execution. - -If you do not use `EnableBatchProcessing`, you need to register a `BatchObservabilityBeanPostProcessor` in your -application context, which will automatically setup Micrometer's observability in your jobs and steps beans. diff --git a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc index 7ec1625480..4b4991aaa0 100644 --- a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc +++ b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc @@ -3,13 +3,16 @@ This section highlights the major changes in Spring Batch 6.0. For the complete list of changes, please refer to the https://github.com/spring-projects/spring-batch/releases[release notes]. -Spring Batch 6.0 includes the following features: +Spring Batch 6.0 includes the following features and improvements: * xref:whatsnew.adoc#dependencies-upgrade[Dependencies upgrade] * xref:whatsnew.adoc#batch-infrastrucutre-configuration-improvements[Batch infrastructure configuration improvements] * xref:whatsnew.adoc#new-implementation-of-the-chunk-oriented-processing-model[New implementation of the chunk-oriented processing model] +* xref:whatsnew.adoc#new-concurrency-model[New concurrency model] * xref:whatsnew.adoc#new-command-line-operator[New command line operator] * xref:whatsnew.adoc#ability-to-recover-failed-job-executions[Ability to recover failed job executions] +* xref:whatsnew.adoc#ability-to-stop-all-kind-of-steps[Ability to stop all kinds of steps] +* xref:whatsnew.adoc#observability-with-jfr[Observability support with the Java Flight Recorder (JFR)] * xref:whatsnew.adoc#deprecations-and-pruning[Deprecations and pruning] [[dependencies-upgrade]] @@ -70,7 +73,8 @@ In this release, several changes have been made to simplify the batch infrastruc * The `JobRepository` now extends the `JobExplorer` interface, so there is no need to define a separate `JobExplorer` bean. * The `JobOperator` now extends the `JobLauncher` interface, so there is no need to define a separate `JobLauncher` bean. -* The `JobRegistry` is now smart enough to register jobs automatically, so there is no need to define a separate `JobRegistrySmartInitializingSingleton` bean. +* The `JobRegistry` is now optional, and smart enough to register jobs automatically, so there is no need to define a separate `JobRegistrySmartInitializingSingleton` bean. +* The transaction manager is now optional, and a default `ResourcelessTransactionManager` is used if none is provided. This reduces the number of beans required for a typical batch application and simplifies the configuration code. @@ -121,7 +125,7 @@ public Step faulTolerantChunkOrientedStep(JobRepository jobRepository, JdbcTrans // skip policy configuration int skipLimit = 50; var skippableExceptions = Set.of(FlatFileParseException.class); - SkipPolicy skipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, skippableExceptions); + SkipPolicy skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy(skippableExceptions, skipLimit); // step configuration int chunkSize = 100; @@ -138,6 +142,15 @@ public Step faulTolerantChunkOrientedStep(JobRepository jobRepository, JdbcTrans Please refer to the https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-6.0-Migration-Guide[migration guide] for more details on how to migrate from the previous implementation to the new one. +[[new-concurrency-model]] +== New concurrency model + +Prior to this release, the concurrency model based on the "parallel iteration" concept required a lot of state synchronization at different levels and had several limitations related to throttling and backpressure leading to confusing transaction semantics and poor performance. + +This release revisits that model and comes with a new, simplified approach to concurrency based on the producer-consumer pattern. A concurrent chunk-oriented step now uses a bounded internal queue between the producer thread and consumer threads. Items are put in the queue as soon as they are ready to be processed, and consumer threads take items from the queue as soon as they are available for processing. Once a chunk is ready to be written, the producer thread pauses until the chunk is written, and then resumes producing items. + +This new model is more efficient, easier to understand and provides better performance for concurrent executions. + [[new-command-line-operator]] == New command line operator @@ -154,6 +167,23 @@ Prior to this release, if a job execution fails abruptly, it was not possible to This release introduces a new method named `recover` in the `JobOperator` interface that allows you to recover failed job executions consistently across all job repositories. +[[ability-to-stop-all-kind-of-steps]] +== Ability to stop all kinds of steps + +As of v5.2, it is only possible to externally stop `Tasklet` steps through `JobOperator#stop`. +If a custom `Step` implementation wants to handle external stop signals, it just can't. + +This release adds a new interface, named `StoppableStep`, that extends `Step` and which can be implemented by any step that is able to handle stop signals. + +[[observability-with-jfr]] +== Observability with the Java Flight Recorder (JFR) + +In addition to the existing Micrometer metrics, Spring Batch 6.0 introduces support for the Java Flight Recorder (JFR) to provide enhanced observability capabilities. + +JFR is a powerful profiling and event collection framework built into the Java Virtual Machine (JVM). It allows you to capture detailed information about the runtime behavior of your applications with minimal performance overhead. + +This release introduces several JFR events to monitor key aspects of a batch job execution, including job and step executions, item reads and writes, as well as transaction boundaries. + [[deprecations-and-pruning]] == Deprecations and pruning From ab197fc61c0766acc361e2624b85a568091fa836 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 17 Sep 2025 11:02:19 +0200 Subject: [PATCH 48/49] Prepare milestone release 6.0.0-M3 --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 7858b92b28..7278424e6e 100644 --- a/pom.xml +++ b/pom.xml @@ -52,19 +52,19 @@ 17 - 7.0.0-SNAPSHOT - 2.0.13-SNAPSHOT - 7.0.0-SNAPSHOT - 1.16.0-SNAPSHOT + 7.0.0-M9 + 2.0.12 + 7.0.0-M3 + 1.16.0-M3 - 4.0.0-SNAPSHOT - 4.0.0-SNAPSHOT - 4.0.0-SNAPSHOT - 5.0.0-SNAPSHOT - 4.0.0-SNAPSHOT - 4.0.0-SNAPSHOT - 4.0.0-SNAPSHOT + 4.0.0-M6 + 4.0.0-M6 + 4.0.0-M6 + 5.0.0-M6 + 4.0.0-M5 + 4.0.0-M5 + 4.0.0-M3 2.19.2 1.12.0 @@ -83,7 +83,7 @@ 3.0.2 - 1.6.0-SNAPSHOT + 1.6.0-M3 1.4.21 4.13.2 From 59d4413a4c5035b02e01e5c19a3ff83a913b97df Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 17 Sep 2025 13:40:36 +0200 Subject: [PATCH 49/49] Release version 6.0.0-M3 --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7278424e6e..cbeb251c6c 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 6.0.0-SNAPSHOT + 6.0.0-M3 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index a833c69e25..7cd9f5cfc6 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-SNAPSHOT + 6.0.0-M3 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 048a1e24f1..1a26efda0d 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-SNAPSHOT + 6.0.0-M3 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 8a9524165b..bcf3a9bed9 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-SNAPSHOT + 6.0.0-M3 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index ca268e592d..da6d4c71a9 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-SNAPSHOT + 6.0.0-M3 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index fa06f58bce..0f639f5858 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-SNAPSHOT + 6.0.0-M3 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 7196aa21b9..e3a3ce43dd 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-SNAPSHOT + 6.0.0-M3 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 30ca92fe41..92196a272b 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 6.0.0-SNAPSHOT + 6.0.0-M3 spring-batch-test Spring Batch Test