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
82 lines
5.2 KiB
Markdown
82 lines
5.2 KiB
Markdown
# 2. The anatomy of a job, and why the code looks different from older tutorials
|
|
|
|
[← Previous](01-the-problem-and-mental-model.md) | [README](../README.md) | [Next: Chunk-oriented processing →](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")` — **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)` — passing the transaction manager as a second argument
|
|
to `chunk()` — **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 —
|
|
`reader`/`processor`/`writer`/`faultTolerant`/`skip` exist on both — 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 — 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 — 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`** — 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`** — 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 →](03-chunk-oriented-processing.md)
|