Files
spring-boot-demo/db-migrations-flyway-liquibase/docs/06-baselining-an-existing-database.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

3.7 KiB

6. Baselining an existing database

← 5. Repeatable migrations · Index · Next: 7. Why there is no undo →

Every migration tool eventually meets a database it didn't create. Someone hand-ran DDL for five years, and now the team wants Flyway to take over from here — without dropping the schema and replaying history that never actually happened through Flyway.

FlywayBaselineTest sets this up literally: a legacy_account table is created with plain JDBC, with one row already in it, before Flyway is ever involved. Pointing an unconfigured Flyway at that database refuses outright — this is the same safety check from chapter 1, and it's what makes baselining necessary rather than optional:

FlywayException: Found non-empty schema(s) "PUBLIC" but no schema history table. Use baseline() or set baselineOnMigrate to true to initialize the schema history table.

Turning on spring.flyway.baseline-on-migrate=true (with baseline-version=1 and a baseline-description) fixes it. V1__init.sql — written to describe the schema that already exists — never actually runs; instead Flyway inserts a BASELINE-typed row claiming version 1 is already accounted for, and only V2__add_status_column.sql executes for real:

installed_rank | version | description                               | type     | success
---------------+---------+-------------------------------------------+----------+--------
-1             | NULL    | << Flyway Schema History table created >> | TABLE    | true
1              | 1       | pre-flyway schema                          | BASELINE | true
2              | 2       | add status column                          | SQL      | true

(from docs/output/05-flyway-baseline.txt)

The pre-existing row survives untouched, and gains the new column:

ID | OWNER           | STATUS
---+-----------------+-------
1  | pre-flyway-data | ACTIVE
Trap: the baseline row's description is not a fixed string. It is exactly whatever spring.flyway.baseline-description was set to — in this test, literally "pre-flyway schema". A different, fixed marker — << Flyway Schema History table created >> — belongs to a different row (rank -1, type TABLE), created the moment the history table itself is created, whether or not baselining ever happens. Confusing the two is an easy way to write a broken assertion or a broken monitoring query — this module's own first draft did exactly that.

Going deeper

  • baselineVersion decides what "already accounted for" means, and it matters which value you pick — chapter 14 shows the same mechanism used with baseline-version=0 instead of 1, for a database whose existing schema doesn't match any of your migration files at all.
  • Flyway baseline command reference (rel="nofollow") — the one-time flyway baseline CLI/API call this test's baselineOnMigrate=true triggers automatically on first startup.
  • Baselining is a one-way door in the sense that matters: once version 1 is marked as baselined, Flyway will never again check whether the schema it describes actually matches V1__init.sql's content — that specific file's checksum is simply never looked at again for this database.