Files
spring-boot-demo/spring-batch/docs/03-chunk-oriented-processing.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

79 lines
4.7 KiB
Markdown

# 3. Chunk-oriented processing: what "chunk(10)" actually does
[&larr; Previous](02-anatomy-of-a-job.md) | [README](../README.md) | [Next: The item processor as a filter &rarr;](04-item-processor-as-filter.md)
`chunk(10)` means: read up to 10 items (calling the reader once per item), run each through the
processor, then hand all the survivors to the writer in **one call**, inside **one transaction**.
If the writer succeeds, the transaction commits and the step's counters &mdash; read, write,
filter, commit &mdash; move forward together. If anything in that chunk throws, the whole
transaction rolls back: none of those 10 items' writes are kept, even the ones that would have
succeeded on their own.
The happy-path run of this module's `productImportJob` against clean data makes this concrete.
60 rows, chunk size 10, two rows fail the processor's validation (filtered, not written):
```console
$ SELECT read_count, filter_count, write_count, commit_count FROM BATCH_STEP_EXECUTION;
READ_COUNT | FILTER_COUNT | WRITE_COUNT | COMMIT_COUNT
60 | 2 | 58 | 6
```
Full transcript: [`docs/output/05-happy-path.txt`](output/05-happy-path.txt), source:
[`ProductValidatingProcessor.java`](../src/main/java/com/ankurm/batch/processing/ProductValidatingProcessor.java).
60 rows at chunk size 10 is 6 chunks &mdash; `COMMIT_COUNT` confirms all 6 committed, one
transaction each.
<figure>
<svg viewBox="0 0 740 220" role="img" aria-label="60 rows split into six chunks of 10; each chunk is read, processed, and written as one transaction that either commits fully or rolls back fully.">
<style>.h{font:600 12px sans-serif;fill:#1a1a1a}.c{font:11px sans-serif;fill:#4b5563}.m{font:11px monospace;fill:#1a1a1a}</style>
<text x="20" y="24" class="h">60 rows &rarr; six chunks of 10, each its own transaction</text>
<g>
<!-- 6 chunk boxes -->
<rect x="20" y="50" width="110" height="50" rx="5" fill="#e7f4ea" stroke="#4a9d63"/>
<rect x="140" y="50" width="110" height="50" rx="5" fill="#e7f4ea" stroke="#4a9d63"/>
<rect x="260" y="50" width="110" height="50" rx="5" fill="#e7f4ea" stroke="#4a9d63"/>
<rect x="380" y="50" width="110" height="50" rx="5" fill="#e7f4ea" stroke="#4a9d63"/>
<rect x="500" y="50" width="110" height="50" rx="5" fill="#e7f4ea" stroke="#4a9d63"/>
<rect x="620" y="50" width="110" height="50" rx="5" fill="#e7f4ea" stroke="#4a9d63"/>
<text x="45" y="80" class="m">1-10</text>
<text x="165" y="80" class="m">11-20</text>
<text x="285" y="80" class="m">21-30</text>
<text x="405" y="80" class="m">31-40</text>
<text x="525" y="80" class="m">41-50</text>
<text x="645" y="80" class="m">51-60</text>
</g>
<text x="20" y="130" class="c">Each box: read 10 &rarr; process 10 (0-1 filtered) &rarr; write survivors &rarr; COMMIT.</text>
<text x="20" y="150" class="c">Row 12 (chunk 2) and row 33 (chunk 4) are filtered by the processor -- see chapter 4 -- so those</text>
<text x="20" y="170" class="c">two chunks commit 9 rows instead of 10. 58 rows land in PRODUCT; every chunk still commits.</text>
</svg>
</figure>
## Why 10, and what changes if you pick a different size
Chunk size is a trade-off with no universally right answer:
- **Smaller chunks** commit more often, so a failure loses less work and transactions are
shorter (less lock contention, smaller rollback). They also mean more round trips to the
database &mdash; more commit overhead per row.
- **Larger chunks** amortize that overhead across more rows, but a failure anywhere in the chunk
costs you the whole chunk's work, and the transaction held open is bigger and longer.
10 is small enough that chapter 7's poisoned row costs you 12 rows of rework at most (one
chunk), not thousands, and small enough to demonstrate multiple commits from 60 rows without a
huge fixture file. Production jobs processing millions of rows commonly use chunk sizes in the
hundreds or low thousands; the right number depends on row size, write cost, and how expensive a
partial redo is for your specific job &mdash; there is no formula that replaces measuring it
against your own writer.
## Going deeper
- The chunk-processing reference: [Spring Batch &mdash; Chunk-oriented processing](https://docs.spring.io/spring-batch/reference/step/chunk-oriented-processing.html) (`rel="nofollow"`).
- `ChunkOrientedStep` itself, if you want to see the commit/rollback logic this chapter
describes: `org.springframework.batch.core.step.item.ChunkOrientedStep` in
`spring-batch-core-6.0.5.jar` (the stack trace in
[`docs/output/07-restart-run1-fails.txt`](output/07-restart-run1-fails.txt) names it directly).
- What happens when the processor itself decides to drop a row rather than the writer failing:
[chapter 4](04-item-processor-as-filter.md).
[Next: The item processor as a filter &rarr;](04-item-processor-as-filter.md)