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
This commit is contained in:
2026-09-15 07:08:57 +00:00
co-authored by Claude Sonnet 5
parent b02fbe1416
commit 3908331431
61 changed files with 3128 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# db-migrations-flyway-liquibase
Companion code for **[Flyway vs Liquibase for Spring Boot 4: Migrations, Rollbacks and
Baselines](https://ankurm.com/)** — every claim in that post traces to a test in this module and a
transcript in [`docs/output/`](docs/output).
## Versions
| Component | Version |
|---|---|
| Spring Boot | 4.1.1 |
| Flyway (via `spring-boot-starter-flyway`) | 12.4.0 |
| Liquibase (via `spring-boot-starter-liquibase`) | 5.0.3 |
| Database | H2 2.4.240 (file-based, `AUTO_SERVER=TRUE`) |
| JDK | 25 |
## Quickstart
```bash
mvn -DskipTests package
./scripts/run.sh # default profile: Flyway only, clean startup
./scripts/run.sh both-naive # both enabled, no baseline config — fails to start on purpose
./scripts/run.sh both-fixed # both enabled, Flyway baselined at version 0 — starts cleanly
mvn test # regenerates every transcript in docs/output/
```
With the app running, hit the diagnostics endpoint to see both tools' bookkeeping tables live:
```bash
curl -s localhost:8080/diag/migrations | jq .
```
## Scenarios (Spring profiles)
| Profile | What it demonstrates | Config |
|---|---|---|
| *(default)* | Flyway-only startup against a fresh database | `spring.flyway.enabled=true`, `spring.liquibase.enabled=false` |
| `both-naive` | Enabling both starters with no other configuration — fails on startup | see [chapter 14](docs/14-running-both-at-once.md) |
| `both-fixed` | Both enabled, Flyway told to baseline at version 0 — coexists correctly | `spring.flyway.baseline-on-migrate=true`, `spring.flyway.baseline-version=0` |
## Endpoints
| Endpoint | Purpose |
|---|---|
| `GET /diag/migrations` | Plain-JDBC dump of both tools' tracking tables and the live table list — **delete before shipping** (see [chapter 15](docs/15-production-checklist.md)) |
| `GET /actuator/flyway`, `GET /actuator/liquibase` | Boot's own actuator endpoints, exposed in [`application.yml`](src/main/resources/application.yml) |
## Documentation
1. [The problem and the mental model](docs/01-the-problem-and-mental-model.md)
2. [Anatomy of a migration run](docs/02-anatomy-of-a-migration-run.md)
3. [Checksum validation](docs/03-checksum-validation.md)
4. [Out-of-order migrations](docs/04-out-of-order-migrations.md)
5. [Repeatable migrations](docs/05-repeatable-migrations.md)
6. [Baselining an existing database](docs/06-baselining-an-existing-database.md)
7. [Why there is no undo](docs/07-why-there-is-no-undo.md)
8. [Concurrent startup and locking](docs/08-concurrent-startup-and-locking.md)
9. [Liquibase: anatomy of an update](docs/09-liquibase-anatomy-of-an-update.md)
10. [Rollback: auto-generated vs explicit](docs/10-rollback-auto-generated-vs-explicit.md)
11. [Liquibase locking](docs/11-liquibase-locking.md)
12. [The OSS license service](docs/12-the-oss-license-service.md)
13. [The FSL license change](docs/13-the-fsl-license-change.md)
14. [Running both at once](docs/14-running-both-at-once.md)
15. [Production checklist](docs/15-production-checklist.md)
## Captured output
Every number quoted in the post and in the chapters above comes from a committed transcript in
[`docs/output/`](docs/output), regenerated by `mvn test` via the [`Transcript`](src/test/java/com/ankurm/dbmigrations/Transcript.java)
helper — the tests assert the same numbers they print, so a transcript going stale fails the build:
| File | Test |
|---|---|
| [`01-flyway-happy-path.txt`](docs/output/01-flyway-happy-path.txt) | [`FlywayHappyPathTest`](src/test/java/com/ankurm/dbmigrations/flyway/FlywayHappyPathTest.java) |
| [`02-flyway-checksum-mismatch.txt`](docs/output/02-flyway-checksum-mismatch.txt) | [`FlywayChecksumMismatchTest`](src/test/java/com/ankurm/dbmigrations/flyway/FlywayChecksumMismatchTest.java) |
| [`03-flyway-out-of-order.txt`](docs/output/03-flyway-out-of-order.txt) | [`FlywayOutOfOrderTest`](src/test/java/com/ankurm/dbmigrations/flyway/FlywayOutOfOrderTest.java) |
| [`04-flyway-repeatable.txt`](docs/output/04-flyway-repeatable.txt) | [`FlywayRepeatableTest`](src/test/java/com/ankurm/dbmigrations/flyway/FlywayRepeatableTest.java) |
| [`05-flyway-baseline.txt`](docs/output/05-flyway-baseline.txt) | [`FlywayBaselineTest`](src/test/java/com/ankurm/dbmigrations/flyway/FlywayBaselineTest.java) |
| [`06-flyway-undo-teams-required.txt`](docs/output/06-flyway-undo-teams-required.txt) | [`FlywayUndoTest`](src/test/java/com/ankurm/dbmigrations/flyway/FlywayUndoTest.java) |
| [`07-flyway-proprietary-stub-commands.txt`](docs/output/07-flyway-proprietary-stub-commands.txt) | [`FlywayCommunityCommandSurfaceTest`](src/test/java/com/ankurm/dbmigrations/flyway/FlywayCommunityCommandSurfaceTest.java) |
| [`08-flyway-concurrent-lock.txt`](docs/output/08-flyway-concurrent-lock.txt) | [`FlywayConcurrentMigrateTest`](src/test/java/com/ankurm/dbmigrations/flyway/FlywayConcurrentMigrateTest.java) |
| [`09-liquibase-happy-path.txt`](docs/output/09-liquibase-happy-path.txt) | [`LiquibaseHappyPathTest`](src/test/java/com/ankurm/dbmigrations/liquibase/LiquibaseHappyPathTest.java) |
| [`10-liquibase-rollback-auto.txt`](docs/output/10-liquibase-rollback-auto.txt) | [`LiquibaseRollbackAutoTest`](src/test/java/com/ankurm/dbmigrations/liquibase/LiquibaseRollbackAutoTest.java) |
| [`11-liquibase-rollback-no-inverse.txt`](docs/output/11-liquibase-rollback-no-inverse.txt) | [`LiquibaseRollbackFailTest`](src/test/java/com/ankurm/dbmigrations/liquibase/LiquibaseRollbackFailTest.java) |
| [`12-liquibase-rollback-explicit.txt`](docs/output/12-liquibase-rollback-explicit.txt) | [`LiquibaseRollbackExplicitTest`](src/test/java/com/ankurm/dbmigrations/liquibase/LiquibaseRollbackExplicitTest.java) |
| [`13-liquibase-lock-contention.txt`](docs/output/13-liquibase-lock-contention.txt) | [`LiquibaseConcurrentUpdateTest`](src/test/java/com/ankurm/dbmigrations/liquibase/LiquibaseConcurrentUpdateTest.java) |
| [`14-liquibase-oss-license-service.txt`](docs/output/14-liquibase-oss-license-service.txt) | [`LiquibaseLicenseServiceTest`](src/test/java/com/ankurm/dbmigrations/liquibase/LiquibaseLicenseServiceTest.java) |
| [`15-both-together-same-datasource.txt`](docs/output/15-both-together-same-datasource.txt) | [`BothTogetherTest`](src/test/java/com/ankurm/dbmigrations/BothTogetherTest.java) |
## Findings worth the trip
- Flyway Community's `undo`, `diff`, `check`, `deploy`, `generate`, `model`, `prepare` and `auth` commands all **compile fine** and throw `FlywayRedgateEditionRequiredException` only at runtime — there is no working rollback in Flyway Community at all ([chapter 7](docs/07-why-there-is-no-undo.md)).
- H2 case-folds unquoted identifiers to uppercase, so an unquoted query against `flyway_schema_history` (created and queried by Flyway using quoted lowercase) silently finds nothing — a real bug this module's own diagnostics endpoint had and fixed ([chapter 2](docs/02-anatomy-of-a-migration-run.md)).
- Liquibase's default lock-poll rate is 10 seconds, confirmed by decompiling `GlobalConfiguration`'s bytecode — a losing instance can wait up to ten seconds for sub-second work, unlike Flyway's near-instant row-lock release ([chapter 11](docs/11-liquibase-locking.md)).
- A genuine Liquibase 5.0.3 defect: reusing the same simple changelog filename for two logically different changelogs causes a phantom "successful" run where the changeset never actually executes ([chapter 11](docs/11-liquibase-locking.md)).
- Liquibase Community 5.0 shipped under the Functional Source License, not Apache 2.0 — a real, ongoing compliance question for projects like Apache Fineract (ASF LEGAL-721) and Keycloak (GitHub #43391) ([chapter 13](docs/13-the-fsl-license-change.md)).
- Enabling both Flyway and Liquibase against one database fails to start by default, and fixing it does not integrate them — it just gets you two independent bookkeepers, each blind to the other's tables ([chapter 14](docs/14-running-both-at-once.md)).