Introduction: Why Database Versioning Matters
Your application code is in Git. Every change is tracked, reviewed, and reversible. Your database schema is not — it lives in someone’s head, a shared wiki page, or a folder of SQL scripts with names like fix_final_v3.sql. This is the problem Liquibase solves.
The Problem: Database Drift
On a typical team without database versioning:
- Developer A adds a column locally and forgets to tell anyone
- Developer B’s tests fail on a table that exists on A’s machine but not on B’s
- Staging has 3 extra columns that production doesn’t — or vice versa
- Nobody knows what the “correct” schema state is
- Deploying to production means manually running SQL scripts while hoping nothing was missed
This is database drift — the schema diverges between environments and nobody tracks when or why.
The same team has perfect code history in Git. They can reproduce the app from any point in history. But the database from 6 months ago? Gone.
What Liquibase Does
Liquibase applies the same version-control discipline to your database that Git applies to your code.
Code changes → Git commit → Code review → Deploy
Database changes → Liquibase changeset → Code review → liquibase update
You write schema changes as changesets in a changelog file (YAML, XML, or SQL). Liquibase tracks which changesets have been applied to each database in two tracking tables it creates automatically. When you run liquibase update, it compares the changelog to the tracking table and applies only the changesets not yet run.
Every developer, every environment, every deployment runs exactly the same changesets in exactly the same order. No drift. No surprises.
How Liquibase Tracks Changes
Liquibase creates two tables in your MySQL database the first time it runs:
DATABASECHANGELOG
Every applied changeset gets a row:
SELECT ID, AUTHOR, FILENAME, DATEEXECUTED, MD5SUM, EXECTYPE, CONTEXTS, LABELS, TAG
FROM DATABASECHANGELOG
ORDER BY ORDEREXECUTED;
| Column | Purpose |
|---|---|
ID | The changeset ID from your changelog |
AUTHOR | The changeset author from your changelog |
FILENAME | The changelog file path |
DATEEXECUTED | When this changeset was applied |
MD5SUM | Checksum of the changeset content — breaks if you modify a deployed changeset |
EXECTYPE | EXECUTED, FAILED, MARK_RAN, RERAN |
CONTEXTS | Which contexts the changeset was run with |
LABELS | Which labels were active |
TAG | Rollback tag applied at this point |
The combination of ID + AUTHOR + FILENAME is the unique identifier for every changeset. Liquibase uses this to determine what’s already been applied.
DATABASECHANGELOGLOCK
A single-row table preventing concurrent Liquibase executions:
SELECT * FROM DATABASECHANGELOGLOCK;
-- LOCKED=1 means another process is currently running migrations
When Liquibase starts, it sets LOCKED=1. When it finishes, it sets LOCKED=0. If your application crashes mid-migration, the lock stays set — run liquibase release-locks to clear it.
Liquibase vs Manual SQL Scripts
| Manual SQL Scripts | Liquibase | |
|---|---|---|
| Tracking “what’s been run” | Manual, error-prone | Automatic — DATABASECHANGELOG table |
| Multi-environment consistency | Varies by convention | Guaranteed — same changeset order |
| Rollback | Manual | Built-in for many change types |
| Team collaboration | “Did you run Dave’s script?” | Git-based, ordered, conflict-resistant |
| CI/CD integration | Shell scripts + manual tracking | Native — runs as part of application startup |
| Audit trail | Whatever you write down | Automatic — who ran what, when |
| Schema review | Separate SQL review | Same PR as application code |
Liquibase vs Flyway
Both Liquibase and Flyway solve the same problem. Key differences:
| Liquibase | Flyway | |
|---|---|---|
| Changelog formats | XML, YAML, JSON, SQL | SQL (primarily), Java |
| Automatic rollback | Yes, for many change types | No — must write undo scripts manually |
| Preconditions | Yes — guard changesets with conditions | No |
| Cross-database portability | Strong — platform-agnostic change types | Limited |
| Complexity | Higher — more features | Lower — simpler |
| Spring Boot integration | spring.liquibase.* | spring.flyway.* |
For this series: Liquibase. It’s more capable for teams that need rollback support, multi-environment filtering, and complex migration patterns with MySQL.
The Sample Application
This series builds an e-commerce database incrementally:
users — accounts and authentication
product_categories — category hierarchy
products — catalog with pricing and stock
orders — customer orders
order_items — line items per order
payments — payment records
coupons — discount codes
audit_log — change tracking
Every article adds to this schema using Liquibase changesets. By the end, you’ll have a complete, production-ready Liquibase setup with Spring Boot, MySQL, CI/CD integration, and zero-downtime deployment patterns.
Installation
Option A: Standalone CLI
# macOS with Homebrew
brew install liquibase
# Or download directly
curl -L https://github.com/liquibase/liquibase/releases/download/v4.28.0/liquibase-4.28.0.tar.gz | tar xz
Verify:
liquibase --version
# Liquibase Community 4.28.0
Option B: Maven Plugin (for this series)
Add to pom.xml — no separate installation needed:
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>4.28.0</version>
</plugin>
Option C: Spring Boot (zero configuration)
Add the dependency — Liquibase runs automatically at application startup:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
What You’ve Learned
- Database drift is the real problem — schemas diverge between environments without versioning
- Liquibase tracks applied changesets in
DATABASECHANGELOG— a row per applied change DATABASECHANGELOGLOCKprevents concurrent migrations and can get stuck if a process crashes- The changeset’s unique identity is
ID + AUTHOR + FILENAME— never change these after deployment - Three ways to use Liquibase: standalone CLI, Maven plugin, Spring Boot auto-configuration
Next: Article 2 — Core Concepts: Changelog, Changeset, and Tracking Tables — the mental model you need before writing your first migration.