Files
spring-boot-demo/spring-batch/docs/02-anatomy-of-a-job.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

82 lines
5.2 KiB
Markdown

# 2. The anatomy of a job, and why the code looks different from older tutorials
[&larr; Previous](01-the-problem-and-mental-model.md) | [README](../README.md) | [Next: Chunk-oriented processing &rarr;](03-chunk-oriented-processing.md)
`BatchConfig` in this module builds jobs and steps like this:
```java
new JobBuilder("productImportJob", jobRepository)
.start(importStep)
.next(reportStep)
.build();
new StepBuilder("importStep", jobRepository)
.<Product, Product>chunk(10)
.transactionManager(transactionManager)
.reader(productReader)
.processor(productProcessor)
.writer(productWriter)
.build();
```
If you have seen Spring Batch code before 2023 or so, two things here might look unfamiliar,
and it is worth being precise about which is a genuine Spring Boot 4.1 change and which is just
old:
- `JobBuilderFactory` / `StepBuilderFactory` autowired as beans, then called as
`jobBuilderFactory.get("name")` &mdash; **this was removed in Spring Batch 5, years before this
article.** It is not a Boot 4.1 surprise, it is a dead end that tutorials keep copy-pasting.
`JobBuilder` and `StepBuilder` are plain classes you construct directly with a `JobRepository`,
as above.
- `.chunk(10, transactionManager)` &mdash; passing the transaction manager as a second argument
to `chunk()` &mdash; **this compiles in 6.0.5, but it returns a different builder.**
`StepBuilder.chunk(int)` returns a `ChunkOrientedStepBuilder`, the model this whole module
uses; `StepBuilder.chunk(int, PlatformTransactionManager)` returns the older
`SimpleStepBuilder`, kept for the pre-6.0 chunk-processing model. Real `javap` output for both
overloads, from this project's own `spring-batch-core-6.0.5.jar`:
[`docs/output/03-stepbuilder-chunk-overloads.txt`](output/03-stepbuilder-chunk-overloads.txt).
The two builders overlap almost entirely in the methods you would reach for &mdash;
`reader`/`processor`/`writer`/`faultTolerant`/`skip` exist on both &mdash; so the wrong choice
usually still compiles and runs, and just quietly uses the legacy step implementation. Call
`.chunk(10).transactionManager(tx)` (two calls) if you want `ChunkOrientedStep`.
<blockquote style="background:#f4f5f7;border:1px solid #e2e5ea;border-left:4px solid #b7bec9;border-radius:6px;padding:16px 20px;"><strong>The fingerprint of picking the wrong overload.</strong> Your step still builds, runs, reads, writes and commits correctly &mdash; there is no error. The tell is in what you can't reach: <code>ChunkOrientedStepBuilder</code>-only options like <code>retryPolicy(RetryPolicy)</code> using Spring Framework 7's retry API, or a stack trace mentioning <code>ChunkOrientedStep</code> versus the older <code>TaskletStep</code> wrapping a <code>ChunkOrientedTasklet</code>. If you copied a two-argument <code>chunk(10, txManager)</code> from a pre-6.0 example and it "just worked", this is why nothing complained.</blockquote>
## Where things moved
Spring Batch 6.0 (bundled with Spring Boot 4.1.1 as `spring-batch.version` `6.0.5`, confirmed
against `spring-boot-dependencies-4.1.1.pom`) reorganized packages fairly aggressively. The ones
this module's code actually hits:
| Old (Spring Batch 5.x) | New (6.0.5) |
|---|---|
| `org.springframework.batch.core.repeat.RepeatStatus` | `org.springframework.batch.infrastructure.repeat.RepeatStatus` |
| `org.springframework.batch.item.*` (readers, writers, `ExecutionContext`) | `org.springframework.batch.infrastructure.item.*` |
| `org.springframework.batch.core.JobParameters` (mutable-ish, `Map`-backed) | `org.springframework.batch.core.job.parameters.JobParameters` (immutable record, `Set`-backed) |
The `RepeatStatus` move is the one this project's own `reportTasklet` hit directly &mdash; see
[chapter 9](09-corrections.md) for the exact compiler error it produced before the import was
fixed. It is a good example of the article's verification rule in practice: rather than trusting
a description of where things moved, write the code from the old import, let `javac` say
`package ... does not exist`, and fix the import. That is faster and more reliable than
reading a migration guide's prose, and chapter 9 has a case where trusting the prose produced a
wrong claim.
## JobRepository and JobOperator
Two interfaces you inject rather than configure by hand in this module:
- **`JobRepository`** &mdash; every `JobBuilder` and `StepBuilder` above takes one as a
constructor argument. In 6.0 it also extends `JobExplorer` (querying past executions), so
there is one bean to inject instead of two.
- **`JobOperator`** &mdash; what `ImportRunner` calls to start the job (see
[chapter 5](05-launching-and-jobparameters.md)). It extends `JobLauncher` and adds operational
methods: `restart(executionId)`, `stop(executionId)`, `recover(execution)`, `getJobNames()`.
`JobLauncher`/`JobExplorer` still exist but are the deprecated half of this pair now.
Both are auto-configured by `spring-boot-starter-batch-jdbc` on this module's classpath; nothing
in `BatchConfig` declares them as beans. [Chapter 10](10-resourceless-vs-jdbc.md) explains what
you get instead if that starter is missing.
[Next: Chunk-oriented processing &rarr;](03-chunk-oriented-processing.md)