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

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