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
This commit is contained in:
Claude
2026-09-13 06:37:46 +00:00
parent a9867c0423
commit b81af72bc3
40 changed files with 2001 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
# spring-batch
Companion code for **[Spring Batch on Boot 4.1: Jobs, Steps, Chunk Processing and Restartability](https://ankurm.com/)**
on [ankurm.com](https://ankurm.com).
Verified against Spring Boot **4.1.1**, Spring Batch **6.0.5**, Spring Framework **7.0.9**, on
Temurin JDK **25.0.4.1+1**.
One job (`productImportJob`: import a CSV of products, then report a count), configured three
different ways by Spring profile, plus one infrastructure variant that is not a profile:
| Profile / flag | What it demonstrates | Docs |
|---|---|---|
| `clean` | The happy path: chunk-oriented reading, filtering, writing | [ch. 3](docs/03-chunk-oriented-processing.md), [ch. 4](docs/04-item-processor-as-filter.md) |
| `broken` | No fault tolerance: a bad row fails the whole chunk and the job; restart resumes exactly where it left off | [ch. 7](docs/07-restartability.md) |
| `skip` | `faultTolerant().skip(...)`: the same bad row is skipped instead, job completes in one pass | [ch. 8](docs/08-skip-vs-restart.md) |
| `--spring.autoconfigure.exclude=...BatchJdbcAutoConfiguration` | Resourceless job repository: a restart forgets everything happened | [ch. 10](docs/10-resourceless-vs-jdbc.md) |
## Documentation chapters
1. [The problem, and the smallest correct mental model](docs/01-the-problem-and-mental-model.md)
2. [The anatomy of a job](docs/02-anatomy-of-a-job.md)
3. [Chunk-oriented processing](docs/03-chunk-oriented-processing.md)
4. [The item processor as a filter](docs/04-item-processor-as-filter.md)
5. [Launching a job, and why restart is not a separate API](docs/05-launching-and-jobparameters.md)
6. [The JDBC writer, and why it is not `beanMapped()`](docs/06-jdbc-writer-and-records.md)
7. [Restartability: what actually resumes, and from where](docs/07-restartability.md)
8. [Skip vs. restart](docs/08-skip-vs-restart.md)
9. [Corrections found while writing this](docs/09-corrections.md)
10. [Resourceless vs. JDBC-backed job repositories](docs/10-resourceless-vs-jdbc.md)
11. [Production checklist](docs/11-production-checklist.md)
## Captured output
Everything under [`docs/output/`](docs/output) was produced by a real run and is quoted verbatim
in the article and the chapters above:
| File | What produced it |
|---|---|
| `01-processor-filter.txt`, `02-exception-hierarchy.txt` | JUnit tests, via `mvn test` |
| `03-stepbuilder-chunk-overloads.txt`, `04-two-executioncontext-classes.txt`, `09-commandlinejobrunner-deprecated-not-removed.txt` | `javap` against the real 6.0.5 jars, via `scripts/capture-javap.sh` |
| `05-happy-path.txt` | The `clean` profile, via `scripts/capture-scenarios.sh` |
| `07-restart-run1-fails.txt`, `08-restart-run2-resumes.txt` | The `broken` profile, run twice in separate JVMs against the same database |
| `10-skip-instead-of-fail.txt` | The `skip` profile |
| `11-resourceless-forgets-everything.txt` | The `clean` profile with `BatchJdbcAutoConfiguration` excluded, run twice |
## Running it
Needs a JDK 25 and Maven 3.9.
```bash
export JAVA_HOME=/path/to/jdk-25
mvn -DskipTests package
./scripts/run-all.sh # regenerates everything under docs/output/
```
Or run one scenario by hand:
```bash
java -jar target/spring-batch-1.0.0.jar \
--spring.profiles.active=clean \
--import.file=file:./scenario-data/clean-demo/input.csv \
--spring.datasource.url=jdbc:h2:file:./scenario-data/clean-demo/db
```
`scripts/run-scenario.sh <name> <profile> <source-csv>` wraps that for repeat use. Each scenario
gets its own file-based H2 database under `scenario-data/<name>/`, gitignored, so runs never
interfere with each other and a restart persists across separate `java -jar` invocations the way
it would across a real process restart.
There is a diagnostic-adjacent endpoint nowhere in this module by design &mdash; everything
observable here comes from the standard `BATCH_STEP_EXECUTION` table via a plain SQL query,
which is deliberately how [chapter 10](docs/10-resourceless-vs-jdbc.md) suggests checking your
own job repository in production.