Files
spring-boot-demo/db-migrations-flyway-liquibase/docs/01-the-problem-and-mental-model.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

76 lines
5.5 KiB
Markdown

# 1. The problem, and the smallest correct mental model
[Index](../README.md) · Next: [2. Anatomy of a Flyway migration run →](02-anatomy-of-a-migration-run.md)
Every app with a database eventually needs a second table added, or a column renamed, or a
default changed — and it needs that to happen the same way on the laptop where it was written,
on the CI database that only exists for four minutes, and on production, which nobody wants to
SSH into and run SQL by hand against. A migration tool's whole job is to make "the same way"
true without a human re-typing SQL in three places.
Both tools this module compares solve that with the same core idea: number or name every schema
change, keep a table *inside the database itself* that records which ones have already run, and
on startup, apply whatever is missing. That tracking table is the single most important thing to
understand before either tool's specific behaviour makes sense — it is the reason a second
`mvn spring-boot:run` doesn't re-run migration 1, and it is the reason two things you're about to
read about (checksum validation, and locking) exist at all.
<svg viewBox="0 0 720 230" xmlns="http://www.w3.org/2000/svg" font-family="monospace" font-size="13">
<rect x="10" y="10" width="220" height="60" rx="6" fill="#eef2ff" stroke="#4f46e5"/>
<text x="120" y="35" text-anchor="middle">migration files</text>
<text x="120" y="52" text-anchor="middle" font-size="11">V1__..., V2__...</text>
<path d="M230 40 L280 40" stroke="#334155" stroke-width="2" marker-end="url(#arrow)"/>
<rect x="280" y="10" width="220" height="60" rx="6" fill="#ecfeff" stroke="#0891b2"/>
<text x="390" y="35" text-anchor="middle">the tool at startup</text>
<text x="390" y="52" text-anchor="middle" font-size="11">diff files vs. history</text>
<path d="M500 40 L550 40" stroke="#334155" stroke-width="2" marker-end="url(#arrow)"/>
<rect x="550" y="10" width="160" height="60" rx="6" fill="#fef9c3" stroke="#ca8a04"/>
<text x="630" y="35" text-anchor="middle">your database</text>
<text x="630" y="52" text-anchor="middle" font-size="11">applies what's new</text>
<path d="M390 70 L390 110" stroke="#334155" stroke-width="2" marker-end="url(#arrow)"/>
<rect x="270" y="110" width="240" height="60" rx="6" fill="#fdf2f8" stroke="#be185d"/>
<text x="390" y="135" text-anchor="middle">tracking table, in that database</text>
<text x="390" y="152" text-anchor="middle" font-size="11">flyway_schema_history / DATABASECHANGELOG</text>
<path d="M270 140 L20 140 L20 40 L 10 40" stroke="#334155" stroke-width="1.5" fill="none" stroke-dasharray="4 3" marker-end="url(#arrow)"/>
<text x="130" y="190" font-size="11" fill="#475569">read back on the NEXT startup — this is what "already applied" means</text>
<defs>
<marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#334155"/></marker>
</defs>
</svg>
The tracking table is not a cache or a convenience — it is the source of truth the tool consults
before touching your schema at all, and it lives in the same database the schema lives in, so it
survives redeploys, restarts, and different machines running the same migration set. Flyway's is
called `flyway_schema_history`; Liquibase's is called `DATABASECHANGELOG`, with a second table,
`DATABASECHANGELOGLOCK`, purely for coordinating concurrent runs (chapters
[8](08-concurrent-startup-and-locking.md) and [11](11-liquibase-locking.md)).
## What this module actually runs
Everything in this article is one small Spring Boot 4.1.1 module,
[`db-migrations-flyway-liquibase`](..), with both starters on the classpath:
[`spring-boot-starter-flyway`](../pom.xml) and [`spring-boot-starter-liquibase`](../pom.xml). Two
tiny migration sets live side by side — Flyway's under
[`src/main/resources/db/migration`](../src/main/resources/db/migration), Liquibase's as one YAML
changelog at
[`db.changelog-master.yaml`](../src/main/resources/db/changelog/db.changelog-master.yaml) — and
every scenario in the chapters that follow is a real JUnit test using Spring's
[`ApplicationContextRunner`](../src/test/java/com/ankurm/dbmigrations/flyway/FlywayHappyPathTest.java),
which boots a real `ApplicationContext` against a real H2 file database in milliseconds, with no
web server needed. A small [`Transcript`](../src/test/java/com/ankurm/dbmigrations/Transcript.java)
helper writes what each test asserts to a plain text file under
[`docs/output/`](output) — every table and error message quoted in this documentation, and in the
published article, was copied out of one of those committed files, not retyped from memory.
The one part of the module that *is* a running app rather than a test is
[`DbMigrationsApplication`](../src/main/java/com/ankurm/dbmigrations/DbMigrationsApplication.java),
whose only purpose is to expose `/diag/migrations` (chapter [15](15-production-checklist.md)) so
you can watch a real startup's bookkeeping instead of only reading about it.
## Going deeper
- [Flyway: how migrations work](https://documentation.red-gate.com/flyway/flyway-cli-and-api/concepts/migrations) — the vendor's own concept page (`rel="nofollow"`).
- [Liquibase: how it works](https://docs.liquibase.com/concepts/introduction-to-liquibase.html) — the vendor's own concept page (`rel="nofollow"`).
- [Spring Boot 4.1 release notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.1-Release-Notes) — for the package relocation both tools' autoconfiguration went through (chapter [2](02-anatomy-of-a-migration-run.md) touches this).