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
72 lines
4.8 KiB
Markdown
72 lines
4.8 KiB
Markdown
# 10. Resourceless vs. JDBC-backed: the dependency that makes chapter 7 possible
|
|
|
|
[← Previous](09-corrections.md) | [README](../README.md) | [Next: Production checklist →](11-production-checklist.md)
|
|
|
|
Everything in [chapter 7](07-restartability.md) depends on one dependency choice that is easy to
|
|
get wrong on Spring Boot 4.1, because getting it wrong does not produce an error — the
|
|
application starts, the job runs, nothing complains.
|
|
|
|
Spring Boot 4.1 split Batch autoconfiguration into separate modules rather than one monolith:
|
|
|
|
| Starter | Autoconfiguration class | What it provides |
|
|
|---|---|---|
|
|
| `spring-boot-starter-batch` | `BatchAutoConfiguration` | Batch infrastructure basics; a **resourceless** (in-memory, non-persistent) `JobRepository` if nothing more specific is configured |
|
|
| `spring-boot-starter-batch-jdbc` | `BatchJdbcAutoConfiguration` | A real JDBC-backed `JobRepository`, plus `spring.batch.jdbc.initialize-schema` to create the `BATCH_*` tables |
|
|
|
|
`spring-boot-starter-batch-jdbc` (this module's actual dependency, in
|
|
[`pom.xml`](../pom.xml)) pulls in `spring-boot-starter-batch` transitively, so **both**
|
|
autoconfiguration classes are normally on the classpath together, and `BatchJdbcAutoConfiguration`
|
|
wins by providing the more specific `JobRepository` bean. Nothing about compiling or starting the
|
|
application tells you which one actually won.
|
|
|
|
## Proving it, by turning the JDBC half off
|
|
|
|
`--spring.autoconfigure.exclude=...BatchJdbcAutoConfiguration` removes the JDBC-backed
|
|
`JobRepository` from the picture, leaving `BatchAutoConfiguration`'s resourceless one as the only
|
|
candidate. Same job, same identifying job parameters, same `PRODUCT` table, run twice, in two
|
|
separate JVMs:
|
|
|
|
```console
|
|
-- run 1 --
|
|
REPORT: 58 products now in the PRODUCT table
|
|
JOB FINISHED: status=COMPLETED exitCode=COMPLETED
|
|
|
|
-- run 2: a second, completely fresh JVM, same jar, same job parameters, same PRODUCT table --
|
|
org.springframework.dao.DuplicateKeyException: ... Unique index or primary key violation ...
|
|
VALUES ( /* 1 */ 'ABC-0001' )
|
|
JOB FINISHED: status=FAILED exitCode=FAILED
|
|
```
|
|
|
|
Full transcript: [`docs/output/11-resourceless-forgets-everything.txt`](output/11-resourceless-forgets-everything.txt).
|
|
Run 2 does not throw `JobInstanceAlreadyCompleteException`, the exception you would expect for
|
|
re-running an already-completed job (see [chapter 5](05-launching-and-jobparameters.md)). It does
|
|
not know there was a run 1 at all. The resourceless `JobRepository` lives only in that JVM's
|
|
heap; when the process exits, every `JobInstance`, `JobExecution`, and `StepExecution` it ever
|
|
recorded goes with it. Run 2 starts `productImportJob` as if for the first time, tries to insert
|
|
`ABC-0001` again, and collides with what run 1 already committed to the `PRODUCT` table —
|
|
a table that, unlike the job repository, *is* backed by the same file-based H2 database both
|
|
times.
|
|
|
|
This is the practical failure mode: **the job repository's persistence and your own application
|
|
data's persistence are two separate decisions**, and it is entirely possible to get the second
|
|
one right (a real database, a real table) while getting the first one wrong (resourceless,
|
|
because `-jdbc` was left off a dependency list or excluded by a profile without realizing what it
|
|
carried). The symptom is not a startup error. It is "my restart didn't restart" or "why did this
|
|
job double-insert everything," discovered in whatever environment happens to restart the JVM
|
|
between runs — which, for anything running in a container orchestrator, is any redeploy,
|
|
crash-restart, or scale-down/scale-up cycle.
|
|
|
|
<blockquote style="background:#f4f5f7;border:1px solid #e2e5ea;border-left:4px solid #b7bec9;border-radius:6px;padding:16px 20px;"><strong>How to check which one you actually have.</strong> Query <code>information_schema.tables</code> (or your database's equivalent) for a table named <code>BATCH_JOB_INSTANCE</code> after your application starts. If it is not there, you have the resourceless job repository, whatever your dependency list looks like on paper — check for an exclusion in <code>spring.autoconfigure.exclude</code>, a conflicting <code>JobRepository</code> bean of your own, or simply a missing <code>-jdbc</code>/<code>-mongodb</code> starter.</blockquote>
|
|
|
|
## Going deeper
|
|
|
|
- `BatchProperties` and `BatchJdbcProperties`, including `spring.batch.job.enabled`,
|
|
`spring.batch.jdbc.initialize-schema`, and `spring.batch.jdbc.table-prefix`: the
|
|
`spring-configuration-metadata.json` inside `spring-boot-batch-4.1.1.jar` and
|
|
`spring-boot-batch-jdbc-4.1.1.jar` is the authoritative list, more current than any blog post
|
|
including this one.
|
|
- MongoDB-backed job repositories follow the identical pattern via
|
|
`spring-boot-starter-batch-data-mongodb`, not covered by this module.
|
|
|
|
[Next: Production checklist →](11-production-checklist.md)
|