Files
spring-boot-demo/db-migrations-flyway-liquibase/docs/05-repeatable-migrations.md
T
asmhatreandClaude Sonnet 5 3908331431 Add db-migrations-flyway-liquibase: Flyway vs Liquibase migrations, rollbacks and baselines on Spring Boot 4.1
Companion code for the Flyway vs Liquibase article: checksum validation, out-of-order
and repeatable migrations, baselining an existing schema, Flyway Community's undo/diff/deploy
stubs, concurrent-startup locking for both tools, Liquibase changeset identity and rollback
(auto-generated vs explicit), a verified Liquibase 5.0.3 filename-caching defect, the new
OSS license service, the FSL license change, and running both tools against one database.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Q6XdRjtsp4862EM44T7i9a
2026-09-15 07:08:57 +00:00

63 lines
4.4 KiB
Markdown

# 5. Repeatable migrations
[← 4. Out-of-order migrations](04-out-of-order-migrations.md) · [Index](../README.md) · Next: [6. Baselining an existing database →](06-baselining-an-existing-database.md)
Versioned migrations (`V1__...`) are one-shot: apply once, never again, ever, on this database.
Views, stored procedures and seed-reference-data scripts don't fit that model — you want them to
re-apply whenever their *content* changes, regardless of what version number anything else is at.
That's what a repeatable migration, prefixed `R__` instead of `V<n>__`, is for.
[`FlywayRepeatableTest`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayRepeatableTest.java)
ships one versioned migration (`V1__init.sql`, creating and seeding an `invoice` table) alongside
[`R__invoice_summary_view.sql`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayRepeatableTest.java),
a view definition. On the first startup it runs once, like anything else:
```
installed_rank | version | description | type | checksum
---------------+---------+-------------------------------------------+-------+------------
-1 | NULL | << Flyway Schema History table created >> | TABLE | NULL
1 | 1 | init | SQL | -192856793
2 | NULL | invoice summary view | SQL | -2025099931
```
Notice the `version` column is `NULL` — that's how the history table distinguishes a repeatable
migration from a versioned one; there is no version number to have. The test then widens the view
to also sum `amount_cents`, **without touching a filename or any version number**, and starts a
second context against the same database:
```
installed_rank | version | description | type | checksum
---------------+---------+-------------------------------------------+-------+------------
-1 | NULL | << Flyway Schema History table created >> | TABLE | NULL
1 | 1 | init | SQL | -192856793
2 | NULL | invoice summary view | SQL | -2025099931
3 | NULL | invoice summary view | SQL | 750256380
```
(both tables from
[`docs/output/04-flyway-repeatable.txt`](output/04-flyway-repeatable.txt))
A brand new row — same description, new checksum — and the view really was redefined: querying
`invoice_summary` afterward returns the new `total_cents` column. Compare this against chapter
[3](03-checksum-validation.md): for a *versioned* migration, a changed checksum is a hard failure.
For a *repeatable* one, it's the trigger to rerun. Same mechanism (a checksum comparison against
the history table), opposite consequence, and the only thing that decides which applies is the
filename prefix.
<svg viewBox="0 0 700 150" xmlns="http://www.w3.org/2000/svg" font-family="monospace" font-size="13">
<rect x="20" y="20" width="200" height="50" rx="6" fill="#eef2ff" stroke="#4f46e5"/>
<text x="120" y="42" text-anchor="middle">V1__init.sql</text>
<text x="120" y="58" text-anchor="middle" font-size="11">checksum changes → FAILS</text>
<rect x="260" y="20" width="240" height="50" rx="6" fill="#dcfce7" stroke="#16a34a"/>
<text x="380" y="42" text-anchor="middle">R__invoice_summary_view.sql</text>
<text x="380" y="58" text-anchor="middle" font-size="11">checksum changes → RERUNS</text>
<text x="20" y="110" font-size="12" fill="#475569">Same comparison against flyway_schema_history's checksum column —</text>
<text x="20" y="128" font-size="12" fill="#475569">the V/R prefix alone decides whether a mismatch is a failure or a re-run.</text>
</svg>
## Going deeper
- **Repeatable migrations run last, after every pending versioned one**, in the order they appear on the classpath (alphabetically, by default) — not interleaved by when they were last changed.
- [Flyway's repeatable migration docs](https://documentation.red-gate.com/flyway/flyway-cli-and-api/concepts/migrations#repeatable-migrations) (`rel="nofollow"`) cover ordering and the `installedOn`/checksum comparison in full.
- A view is the textbook use case, but the same mechanism works for anything idempotent — a stored procedure body, or a `MERGE`/`upsert` of reference data that should reflect whatever the file currently says, not whatever it said the first time it ran.