Files
spring-boot-demo/db-migrations-flyway-liquibase/docs/05-repeatable-migrations.md
T
asmhatreandClaude Sonnet 5 67e1b2d8a9 db-migrations-flyway-liquibase: fix dead Redgate/Liquibase doc links and back the lock-defaults claim with a real transcript
Several documentation.red-gate.com and liquibase.com URLs added in the previous commit had
since moved (Redgate restructured its docs under /flyway/reference/, Liquibase Pro pricing
moved to /pricing); this repoints them at the current, verified-200 pages. Also captures
docs/output/16-liquibase-lock-defaults-javap.txt, the trimmed javap output backing chapter
11's claim about Liquibase's default lock-poll and lock-wait settings, which chapter 11
previously asserted without a committed artifact to back it.

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

4.3 KiB

5. Repeatable migrations

← 4. Out-of-order migrations · Index · Next: 6. Baselining an existing database →

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 ships one versioned migration (V1__init.sql, creating and seeding an invoice table) alongside R__invoice_summary_view.sql, 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)

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: 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.

V1__init.sql checksum changes → FAILS R__invoice_summary_view.sql checksum changes → RERUNS Same comparison against flyway_schema_history's checksum column — the V/R prefix alone decides whether a mismatch is a failure or a re-run.

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 (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.