Companion code for "Spring Batch on Boot 4.1: Jobs, Steps, Chunk Processing and Restartability". A productImportJob configured three ways by profile against a poisoned CSV row, run as real java -jar processes (not just JUnit) so the restart story is genuine: a chunk fails and rolls back, the process exits, a brand-new JVM against the same file-based H2 database resumes at the exact next unread row (READ_COUNT 20, not 60) and completes. Findings the build pins: - StepBuilder.chunk(int, PlatformTransactionManager) still compiles in Batch 6.0.5 but returns the legacy SimpleStepBuilder; chunk(int) returns the new ChunkOrientedStepBuilder, and only the latter is used here. - Two different ExecutionContext classes now exist in two different packages (infrastructure.item vs core.repository.persistence) with different shapes. - spring-boot-starter-batch alone gives a resourceless JobRepository that forgets every JobInstance the moment the JVM exits; spring-boot-starter- batch-jdbc is what makes the restart demo possible at all, demonstrated by excluding BatchJdbcAutoConfiguration and watching a "restart" collide with the previous run's own data instead of resuming it. - A migration-guide summary claiming CommandLineJobRunner was removed in 6.0 is wrong -- javap against the real jar shows @Deprecated(forRemoval=true), not removed. - RepeatStatus moved from core.repeat to infrastructure.repeat, caught by the compiler rather than by reading docs. 11 documentation chapters, 10 captured transcripts (unit tests, javap output, and real two-JVM scenario runs), all regenerated by scripts/run-all.sh. Fixed after push: three dead docs.spring.io links in the doc chapters (readersAndWriters/* and chunk-oriented-processing/*.html paths moved when Spring Batch 6 reorganized its reference docs; corrected to the current readers-and-writers/*, processor.html and chunk-oriented-processing.html paths, verified 200 via curl before committing). Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019DXsJ1zpikbA1MQJN6RqFA
66 lines
3.7 KiB
Markdown
66 lines
3.7 KiB
Markdown
# 5. Launching a job, and why restart is not a separate API
|
|
|
|
[← Previous](04-item-processor-as-filter.md) | [README](../README.md) | [Next: The JDBC writer and Java records →](06-jdbc-writer-and-records.md)
|
|
|
|
[`ImportRunner`](../src/main/java/com/ankurm/batch/runner/ImportRunner.java) launches the job
|
|
with:
|
|
|
|
```java
|
|
var params = new JobParametersBuilder()
|
|
.addString("batch.run", "demo") // identifying
|
|
.toJobParameters();
|
|
try {
|
|
var execution = jobOperator.start(job, params);
|
|
} catch (JobInstanceAlreadyCompleteException e) {
|
|
// already succeeded; nothing to do
|
|
}
|
|
```
|
|
|
|
`JobParametersBuilder` and `addString(...)` look exactly like Spring Batch 4 and 5. What changed
|
|
underneath is the type it builds: `JobParameters` is now an immutable record holding a
|
|
`Set<JobParameter<?>>`, and each `JobParameter<T>` is itself a record carrying its own name,
|
|
value, type, and an `identifying` flag (default `true` for the `addX` methods used here). None of
|
|
that shows up in this code — it matters if you ever construct a `JobParameters` by hand
|
|
instead of through the builder, or serialize one.
|
|
|
|
## "Identifying" is the whole restart mechanism
|
|
|
|
A **JobInstance** is identified by a job name plus the set of parameters marked `identifying`.
|
|
Calling `jobOperator.start(job, params)` with parameters that match an existing JobInstance does
|
|
not create a second, independent run:
|
|
|
|
- If that instance's last execution is not `COMPLETED` (it `FAILED`, or never finished), Spring
|
|
Batch creates a new `JobExecution` **against the same instance** and each step resumes from
|
|
its own last-committed position. This is a restart, and it happens through the exact same
|
|
`start()` call as a first attempt — see [chapter 7](07-restartability.md) for what
|
|
"resumes from" means concretely.
|
|
- If that instance's last execution `COMPLETED`, `start()` throws
|
|
`JobInstanceAlreadyCompleteException` (unless the step allows re-running —
|
|
`allowStartIfComplete`, used on `reportStep`; see [chapter 8](08-skip-vs-restart.md)).
|
|
|
|
There is a separate `JobOperator.restart(long executionId)` method for restarting by execution
|
|
ID explicitly, useful for an operational tool that lists failed executions and lets someone pick
|
|
one. This module does not need it: every run uses the same identifying parameter
|
|
(`batch.run=demo`), so plain `start()` already does the right thing whether this is attempt one
|
|
or attempt two. If you want a fresh JobInstance on every run instead — the common pattern
|
|
for "run this daily" jobs — add a parameter that changes each time, typically a timestamp
|
|
or an incrementer, and mark it identifying (the default).
|
|
|
|
## `spring.batch.job.enabled`
|
|
|
|
Spring Boot auto-configures a `JobLauncherApplicationRunner` that launches every `Job` bean it
|
|
finds using empty parameters, on every application startup. This module turns that off
|
|
(`spring.batch.job.enabled: false` in [`application.yml`](../src/main/resources/application.yml))
|
|
because `ImportRunner` needs to control the parameters (the identifying `batch.run` value) and
|
|
which profile's `Job` bean is active. Leaving both runners on would launch the job twice per
|
|
startup with two different parameter sets. If you only ever need "run the one job on startup
|
|
with no arguments," the auto-configured runner is simpler and this whole class is unnecessary.
|
|
|
|
## Going deeper
|
|
|
|
- `JobParameters` and identifying parameters: [Spring Batch reference — running a job](https://docs.spring.io/spring-batch/reference/job/running.html) (`rel="nofollow"`).
|
|
- `spring.batch.job.name` for selecting which job the auto-configured runner launches, when you
|
|
have more than one `Job` bean and want to keep using it.
|
|
|
|
[Next: The JDBC writer and Java records →](06-jdbc-writer-and-records.md)
|