Skip to content

Commit 51f7d38

Browse files
committed
Update documentation
1 parent d4c3e77 commit 51f7d38

File tree

7 files changed

+169
-235
lines changed

7 files changed

+169
-235
lines changed

spring-batch-docs/modules/ROOT/nav.adoc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
*** xref:step/chunk-oriented-processing/restart.adoc[]
1919
*** xref:step/chunk-oriented-processing/configuring-skip.adoc[]
2020
*** xref:step/chunk-oriented-processing/retry-logic.adoc[]
21-
*** xref:step/chunk-oriented-processing/controlling-rollback.adoc[]
2221
*** xref:step/chunk-oriented-processing/transaction-attributes.adoc[]
2322
*** xref:step/chunk-oriented-processing/registering-item-streams.adoc[]
2423
*** xref:step/chunk-oriented-processing/intercepting-execution.adoc[]

spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,36 @@ framework, it sets the status of the current
161161
`BatchStatus.STOPPED`, saves it, and does the same
162162
for the `JobExecution` before finishing.
163163

164+
[[graceful-shutdown]]
165+
== Handling external interruption signals
166+
167+
As of v6.0+, Spring Batch provides a `JobExecutionShutdownHook` that you can attach to the JVM runtime
168+
in order to intercept external interruption signals and gracefully stop the job execution:
169+
170+
[source, java]
171+
----
172+
Thread springBatchHook = new JobExecutionShutdownHook(jobExecution, jobOperator);
173+
Runtime.getRuntime().addShutdownHook(springBatchHook);
174+
----
175+
176+
A `JobExecutionShutdownHook` requires the job execution to track as well as a reference to a job operator
177+
that will be used to stop the execution.
178+
179+
[[recover-job]]
180+
== Recovering a job
181+
182+
If a graceful shutdown is not performed properly (ie the JVM is shutdown abruptly), Spring Batch will not
183+
have a chance to update the execution state correctly in order to restart the failed job execution. In this
184+
case, the job execution will stay in a `STARTED` state which is not restartable. In this case, it is possible
185+
to recover such a job execution with the `JobOperator` API:
186+
187+
[source, java]
188+
----
189+
JobExecution jobExecution = ...; // get the job execution to recover
190+
jobOperator.recover(jobExecution);
191+
jobOperator.restart(jobExecution);
192+
----
193+
164194
[[aborting-a-job]]
165195
== Aborting a Job
166196

spring-batch-docs/modules/ROOT/pages/retry.adoc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ Examples include remote calls to a web service that fails because of a network g
1616
====
1717
As of version 2.2.0, the retry functionality was pulled out of Spring Batch.
1818
It is now part of a new library, https://github.com/spring-projects/spring-retry[Spring Retry].
19-
Spring Batch still relies on Spring Retry to automate retry operations within the framework.
20-
See the reference documentation of Spring Retry for details about
21-
key APIs and how to use them.
19+
As of v6.0, Spring Batch does *not* use Spring Retry to automate retry operations within the framework,
20+
and is now based on the https://docs.spring.io/spring-framework/reference/7.0/core/resilience.html#resilience-annotations-retryable[retry feature] provided by Spring Framework 7.0.
2221
====

spring-batch-docs/modules/ROOT/pages/scalability.adoc

Lines changed: 119 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ These break down into categories as well, as follows:
2222

2323
* Multi-threaded Step (single-process)
2424
* Parallel Steps (single-process)
25+
* Local Chunking of Step (single-process)
2526
* Remote Chunking of Step (multi-process)
2627
* Partitioning a Step (single or multi-process)
28+
* Remote Step (multi-process)
2729

2830
First, we review the single-process options. Then we review the multi-process options.
2931

@@ -84,50 +86,7 @@ implementations. The simplest multi-threaded `TaskExecutor` is a
8486
The result of the preceding configuration is that the `Step` executes by reading, processing,
8587
and writing each chunk of items (each commit interval) in a separate thread of execution.
8688
Note that this means there is no fixed order for the items to be processed, and a chunk
87-
might contain items that are non-consecutive compared to the single-threaded case. In
88-
addition to any limits placed by the task executor (such as whether it is backed by a
89-
thread pool), the tasklet configuration has a throttle limit (default: 4).
90-
You may need to increase this limit to ensure that a thread pool is fully used.
91-
92-
93-
[tabs]
94-
====
95-
Java::
96-
+
97-
When using Java configuration, the builders provide access to the throttle limit, as
98-
follows:
99-
+
100-
.Java Configuration
101-
[source, java]
102-
----
103-
@Bean
104-
public Step sampleStep(TaskExecutor taskExecutor, JobRepository jobRepository, PlatformTransactionManager transactionManager) {
105-
return new StepBuilder("sampleStep", jobRepository)
106-
.<String, String>chunk(10).transactionManager(transactionManager)
107-
.reader(itemReader())
108-
.writer(itemWriter())
109-
.taskExecutor(taskExecutor)
110-
.throttleLimit(20)
111-
.build();
112-
}
113-
----
114-
115-
XML::
116-
+
117-
For example, you might increase the throttle-limit, as follows:
118-
+
119-
[source, xml]
120-
----
121-
<step id="loading"> <tasklet
122-
task-executor="taskExecutor"
123-
throttle-limit="20">...</tasklet>
124-
</step>
125-
----
126-
127-
====
128-
129-
130-
89+
might contain items that are non-consecutive compared to the single-threaded case.
13190

13291
Note also that there may be limits placed on concurrency by any pooled resources used in
13392
your step, such as a `DataSource`. Be sure to make the pool in those resources at least
@@ -246,6 +205,63 @@ aggregating the exit statuses and transitioning.
246205

247206
See the section on xref:step/controlling-flow.adoc#split-flows[Split Flows] for more detail.
248207

208+
[[localChunking]]
209+
== Local Chunking
210+
211+
Local chunking is a new feature that allows you to process chunks of items in parallel, locally within the same JVM using multiple threads.
212+
This is particularly useful when you have a large number of items to process and want to take advantage of multi-core processors.
213+
With local chunking, you can configure a chunk-oriented step to use multiple threads to process chunks of items concurrently.
214+
Each thread will read, process and write its own chunk of items independently, while the step will manage the overall execution and commit the results.
215+
216+
This feature is possible by using the `ChunkMessageChannelItemWriter`, which is an item writer that submits chunk
217+
requests to local workers from a `TaskExecutor`:
218+
219+
[source, java]
220+
----
221+
@Bean
222+
public ChunkTaskExecutorItemWriter<Vet> itemWriter(ChunkProcessor<Vet> chunkProcessor) {
223+
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
224+
taskExecutor.setCorePoolSize(4);
225+
taskExecutor.setThreadNamePrefix("worker-thread-");
226+
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
227+
taskExecutor.afterPropertiesSet();
228+
return new ChunkTaskExecutorItemWriter<>(chunkProcessor, taskExecutor);
229+
}
230+
----
231+
232+
The `ChunkMessageChannelItemWriter` requires a `TaskExecutor` to process chunk concurrently
233+
as well as a `ChunkProcessor` to define what to do with each chunk. Here is an example of
234+
a chunk processor that writes each chunk of items to a relational database table:
235+
236+
[source, java]
237+
----
238+
@Bean
239+
public ChunkProcessor<Vet> chunkProcessor(DataSource dataSource, TransactionTemplate transactionTemplate) {
240+
String sql = "insert into vets (firstname, lastname) values (?, ?)";
241+
JdbcBatchItemWriter<Vet> itemWriter = new JdbcBatchItemWriterBuilder<Vet>().dataSource(dataSource)
242+
.sql(sql)
243+
.itemPreparedStatementSetter((item, ps) -> {
244+
ps.setString(1, item.firstname());
245+
ps.setString(2, item.lastname());
246+
})
247+
.build();
248+
249+
return (chunk, contribution) -> transactionTemplate.executeWithoutResult(transactionStatus -> {
250+
try {
251+
itemWriter.write(chunk);
252+
contribution.incrementWriteCount(chunk.size());
253+
contribution.setExitStatus(ExitStatus.COMPLETED);
254+
}
255+
catch (Exception e) {
256+
transactionStatus.setRollbackOnly();
257+
contribution.setExitStatus(ExitStatus.FAILED.addExitDescription(e));
258+
}
259+
});
260+
}
261+
----
262+
263+
You can find an example of this scaling technique in the https://github.com/spring-projects/spring-batch/tree/main/spring-batch-samples/src/main/java/org/springframework/batch/samples/chunking/local[Local Chunking Sample].
264+
249265
[[remoteChunking]]
250266
== Remote Chunking
251267

@@ -543,3 +559,62 @@ The following example shows how to define late binding in XML:
543559
544560
====
545561

562+
[[remoteStep]]
563+
== Remote Step execution
564+
565+
As of v6.0, Spring Batch provides support for remote step executions, allowing you to execute steps of a batch job on remote machines or clusters.
566+
This feature is particularly useful for large-scale batch processing scenarios where you want to distribute the workload across multiple nodes to improve performance and scalability.
567+
Remote step execution is provided by the `RemoteStep` class, which uses Spring Integration messaging channels to enable communication between the local job execution environment and the remote step executors.
568+
569+
A `RemoteStep` is configured as a regular step by providing the remote step name and a messaging template to send step execution requests to remote workers:
570+
571+
[source, java]
572+
----
573+
@Bean
574+
public Step step(MessagingTemplate messagingTemplate, JobRepository jobRepository) {
575+
return new RemoteStep("step", "workerStep", jobRepository, messagingTemplate);
576+
}
577+
----
578+
579+
On the worker side, you need to define the remote step to execute (`workerStep` in this example) and configure
580+
a Spring Integration flow to intercept step execution requests and invoke the `StepExecutionRequestHandler`:
581+
582+
[source, java]
583+
----
584+
@Bean
585+
public Step workerStep(JobRepository jobRepository, JdbcTransactionManager transactionManager) {
586+
return new StepBuilder("workerStep", jobRepository)
587+
// define step logic
588+
.build();
589+
}
590+
591+
/*
592+
* Configure inbound flow (requests coming from the manager)
593+
*/
594+
@Bean
595+
public DirectChannel requests() {
596+
return new DirectChannel();
597+
}
598+
599+
@Bean
600+
public IntegrationFlow inboundFlow(ActiveMQConnectionFactory connectionFactory, JobRepository jobRepository,
601+
StepLocator stepLocator) {
602+
StepExecutionRequestHandler stepExecutionRequestHandler = new StepExecutionRequestHandler();
603+
stepExecutionRequestHandler.setJobRepository(jobRepository);
604+
stepExecutionRequestHandler.setStepLocator(stepLocator);
605+
return IntegrationFlow.from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("requests"))
606+
.channel(requests())
607+
.handle(stepExecutionRequestHandler, "handle")
608+
.get();
609+
}
610+
611+
@Bean
612+
public StepLocator stepLocator(BeanFactory beanFactory) {
613+
BeanFactoryStepLocator beanFactoryStepLocator = new BeanFactoryStepLocator();
614+
beanFactoryStepLocator.setBeanFactory(beanFactory);
615+
return beanFactoryStepLocator;
616+
}
617+
----
618+
619+
You can find a complete example in the https://github.com/spring-projects/spring-batch/tree/main/spring-batch-samples/src/main/java/org/springframework/batch/samples/remotestep[Remote Step Sample].
620+

spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc

Lines changed: 8 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,21 @@ The following Java example shows an example of using a skip limit:
2121
----
2222
@Bean
2323
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
24+
int skipLimit = 10;
25+
var skippableExceptions = Set.of(FlatFileParseException.class);
26+
SkipPolicy skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy(skippableExceptions, skipLimit);
27+
2428
return new StepBuilder("step1", jobRepository)
2529
.<String, String>chunk(10).transactionManager(transactionManager)
2630
.reader(flatFileItemReader())
2731
.writer(itemWriter())
2832
.faultTolerant()
29-
.skipLimit(10)
30-
.skip(FlatFileParseException.class)
33+
.skipPolicy(skipPolicy)
3134
.build();
3235
}
3336
----
3437
+
35-
Note: The `skipLimit` can be explicitly set using the `skipLimit()` method. If not specified, the default skip limit is set to 10.
38+
Note: The `skipLimit` can be explicitly set using the `skipLimit()` method.
3639
3740
XML::
3841
+
@@ -55,8 +58,6 @@ The following XML example shows an example of using a skip limit:
5558
5659
====
5760

58-
59-
6061
In the preceding example, a `FlatFileItemReader` is used. If, at any point, a
6162
`FlatFileParseException` is thrown, the item is skipped and counted against the total
6263
skip limit of 10. Exceptions (and their subclasses) that are declared might be thrown
@@ -66,81 +67,8 @@ the step execution, but the limit applies across all skips. Once the skip limit
6667
reached, the next exception found causes the step to fail. In other words, the eleventh
6768
skip triggers the exception, not the tenth.
6869

69-
One problem with the preceding example is that any other exception besides a
70-
`FlatFileParseException` causes the `Job` to fail. In certain scenarios, this may be the
71-
correct behavior. However, in other scenarios, it may be easier to identify which
72-
exceptions should cause failure and skip everything else.
73-
74-
[tabs]
70+
[NOTE]
7571
====
76-
Java::
77-
+
78-
The following Java example shows an example excluding a particular exception:
79-
+
80-
.Java Configuration
81-
[source, java]
82-
----
83-
@Bean
84-
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
85-
return new StepBuilder("step1", jobRepository)
86-
.<String, String>chunk(10).transactionManager(transactionManager)
87-
.reader(flatFileItemReader())
88-
.writer(itemWriter())
89-
.faultTolerant()
90-
.skipLimit(10)
91-
.skip(Exception.class)
92-
.noSkip(FileNotFoundException.class)
93-
.build();
94-
}
95-
----
96-
+
97-
Note: The `skipLimit` can be explicitly set using the `skipLimit()` method. If not specified, the default skip limit is set to 10.
98-
99-
XML::
100-
+
101-
The following XML example shows an example excluding a particular exception:
102-
+
103-
.XML Configuration
104-
[source, xml]
105-
----
106-
<step id="step1">
107-
<tasklet>
108-
<chunk reader="flatFileItemReader" writer="itemWriter"
109-
commit-interval="10" skip-limit="10">
110-
<skippable-exception-classes>
111-
<include class="java.lang.Exception"/>
112-
<exclude class="java.io.FileNotFoundException"/>
113-
</skippable-exception-classes>
114-
</chunk>
115-
</tasklet>
116-
</step>
117-
----
118-
119-
====
120-
121-
122-
123-
By identifying `java.lang.Exception` as a skippable exception class, the configuration
124-
indicates that all `Exceptions` are skippable. However, by "`excluding`"
125-
`java.io.FileNotFoundException`, the configuration refines the list of skippable
126-
exception classes to be all `Exceptions` __except__ `FileNotFoundException`. Any excluded
127-
exception class is fatal if encountered (that is, they are not skipped).
128-
129-
For any exception encountered, the skippability is determined by the nearest superclass
130-
in the class hierarchy. Any unclassified exception is treated as 'fatal'.
131-
132-
133-
[tabs]
134-
====
135-
Java::
136-
+
137-
The order of the `skip` and `noSkip` method calls does not matter.
138-
139-
XML::
140-
+
141-
The order of the `<include/>` and `<exclude/>` elements does not matter.
142-
72+
The skip limit applies to all skips (read, process and write).
14373
====
14474

145-
146-

0 commit comments

Comments
 (0)