Part 1 of 18

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;
ColumnPurpose
IDThe changeset ID from your changelog
AUTHORThe changeset author from your changelog
FILENAMEThe changelog file path
DATEEXECUTEDWhen this changeset was applied
MD5SUMChecksum of the changeset content — breaks if you modify a deployed changeset
EXECTYPEEXECUTED, FAILED, MARK_RAN, RERAN
CONTEXTSWhich contexts the changeset was run with
LABELSWhich labels were active
TAGRollback 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 ScriptsLiquibase
Tracking “what’s been run”Manual, error-proneAutomatic — DATABASECHANGELOG table
Multi-environment consistencyVaries by conventionGuaranteed — same changeset order
RollbackManualBuilt-in for many change types
Team collaboration“Did you run Dave’s script?”Git-based, ordered, conflict-resistant
CI/CD integrationShell scripts + manual trackingNative — runs as part of application startup
Audit trailWhatever you write downAutomatic — who ran what, when
Schema reviewSeparate SQL reviewSame PR as application code

Liquibase vs Flyway

Both Liquibase and Flyway solve the same problem. Key differences:

LiquibaseFlyway
Changelog formatsXML, YAML, JSON, SQLSQL (primarily), Java
Automatic rollbackYes, for many change typesNo — must write undo scripts manually
PreconditionsYes — guard changesets with conditionsNo
Cross-database portabilityStrong — platform-agnostic change typesLimited
ComplexityHigher — more featuresLower — simpler
Spring Boot integrationspring.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
  • DATABASECHANGELOGLOCK prevents 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.