Files
spring-boot-demo/spring-batch/docs/05-launching-and-jobparameters.md
T
Claude b81af72bc3 Add spring-batch: jobs, steps, chunk processing and restartability on Boot 4.1
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
2026-09-13 06:37:46 +00:00

66 lines
3.7 KiB
Markdown

# 5. Launching a job, and why restart is not a separate API
[&larr; Previous](04-item-processor-as-filter.md) | [README](../README.md) | [Next: The JDBC writer and Java records &rarr;](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 &mdash; 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 &mdash; 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 &mdash;
`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 &mdash; the common pattern
for "run this daily" jobs &mdash; 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 &mdash; 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 &rarr;](06-jdbc-writer-and-records.md)