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
5.4 KiB
1. The problem, and the smallest correct mental model
← README | Next: Anatomy of a job →
A batch job processes a lot of records without a person watching each one: importing a CSV of
products into a database, closing out a day's transactions, re-indexing a search table. Two
things make this harder than writing a for loop over the rows:
- It has to survive being interrupted. A process gets killed, a database connection drops,
a row three-quarters of the way through is bad. A
forloop that dies at row 4,700,000 has no idea it already wrote 4,699,999 rows, and re-running it from the top either duplicates work or duplicates data. - It has to report what happened, precisely: how many rows were read, how many were written, how many were skipped and why, whether it finished. "The import ran" is not an answer anyone building on top of this can use.
Spring Batch's job is to own both of those problems so your code only has to describe three things: where the rows come from, what to do to each one, and where they go. Everything else — tracking progress, committing in batches, remembering where a failed run stopped — is the framework's job, provided you tell it enough for it to do that job. Most of this article is about that "provided."
The five nouns
The picture above names everything this article uses:
- Job — a named, ordered list of steps.
productImportJobin this repository has two: import the rows, then report a count. - Step — one unit of work within a job. A step is either a single Tasklet (run
once, do one thing —
reportStephere) or chunk-oriented: read one item, process it, repeat until you have a chunk's worth, then write and commit the whole chunk in one transaction. - ItemReader / ItemProcessor / ItemWriter — the three interfaces chunk-oriented
processing is built from. This module's reader is a
FlatFileItemReaderover a CSV, the processor validates and filters rows, the writer is aJdbcBatchItemWriter. - JobRepository — the thing that makes the first two bullet points of this page possible. It persists every job and step execution: status, timestamps, and the read / write / filter / skip / commit / rollback counts you saw in the transcripts above. Chapter 10 is entirely about a trap hiding in how this gets configured on Spring Boot 4.1.
Hold onto one fact from this page, because it is the one everything else cashes in later: a chunk commits or it doesn't, as a whole. There is no partial chunk. That single sentence is the entire explanation for why restarting a failed job does not reprocess everything, and it is why the "poisoned" row in this repository's demo data fails nine other, perfectly good rows along with it — see chapter 7.
What this repository demonstrates, and how to run it
Four Spring profiles configure the same job three different ways plus one infrastructure
variant; see the README for the full table and the exact commands. Everything
under docs/output/ was produced by scripts/capture-scenarios.sh and scripts/capture-javap.sh,
which scripts/run-all.sh runs in sequence.