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
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.
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 theinstalledOn/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/upsertof reference data that should reflect whatever the file currently says, not whatever it said the first time it ran.