Datenbank-Migrationen sind ein kritischer Bestandteil moderner Softwareentwicklung. Jede Änderung am Schema muss nachvollziehbar, reproduzierbar und rückgängig machbar sein. Während Flyway auf nummerierte SQL-Dateien setzt, verfolgt Liquibase einen deklarativen Ansatz: Änderungen werden in Changeset-Dateien in den Formaten YAML, XML, JSON oder SQL beschrieben.

Warum Liquibase?

Liquibase hat gegenüber Flyway einige entscheidende Vorteile:

  • Datenbankunabhängigkeit — ein Changeset kann (mit Einschränkungen) gegen PostgreSQL, MySQL, Oracle und MSSQL ausgeführt werden
  • Rollback-Unterstützung — Liquibase kann Änderungen automatisch oder manuell zurücksetzen
  • Mehrere Formate — YAML, XML, JSON oder SQL als Changeset-Format
  • Preconditions — Bedingungen, die vor der Ausführung geprüft werden
  • Contexts und Labels — Migrationen lassen sich zielgerichtet auf bestimmte Umgebungen anwenden

Integration mit Spring Boot 4.1

In Spring Boot 4.1 ist Liquibase per Auto-Configuration integriert. Sobald liquibase-core auf dem Classpath liegt, führt Spring Boot beim Start automatisch liquibase update aus:

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>
Code-Sprache: HTML, XML (xml)
<em># application.yml</em>
spring:
  liquibase:
    change-log: classpath:db/changelog/db.changelog-master.yaml
    enabled: true
Code-Sprache: HTML, XML (xml)

Die Master-Changelog-Datei referenziert alle einzelnen Changesets:

<em># db.changelog-master.yaml</em>
databaseChangeLog:
  - include:
      file: db/changelog/001-create-users.yaml
  - include:
      file: db/changelog/002-add-email-column.yaml
  - include:
      file: db/changelog/003-create-orders.sql
Code-Sprache: PHP (php)

Changesets in YAML

YAML ist das am einfachsten lesbare Format für Liquibase-Changesets:

<em># 001-create-users.yaml</em>
databaseChangeLog:
  - changeSet:
      id: 1
      author: rene
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: BIGINT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: username
                  type: VARCHAR(255)
                  constraints:
                    nullable: false
                    unique: true
              - column:
                  name: created_at
                  type: TIMESTAMP
                  defaultValueComputed: CURRENT_TIMESTAMP
Code-Sprache: PHP (php)

Rollbacks

Ein Rollback beschreibt, wie eine Migration rückgängig gemacht wird:

  - changeSet:
      id: 2
      author: rene
      changes:
        - addColumn:
            tableName: users
            columns:
              - column:
                  name: email
                  type: VARCHAR(320)
      rollback:
        - dropColumn:
            tableName: users
            columnName: email

Bei einfachen Änderungen wie createTable generiert Liquibase das Rollback automatisch. Bei komplexeren Operationen muss es explizit definiert werden.

SQL-basierte Changesets

Auch purer SQL-Code lässt sich verwenden — mit allen Vorteilen des Liquibase-Trackings:

<em>-- 002-add-index.sql</em>
<em>-- liquibase formatted sql</em>

<em>-- changeset rene:3</em>
CREATE INDEX idx_users_email ON users(email);

<em>-- rollback DROP INDEX idx_users_email;</em>
Code-Sprache: HTML, XML (xml)

Wichtig: Der Kommentar -- liquibase formatted sql teilt Liquibase mit, dass die SQL-Datei Changeset-Metadaten enthält.

Preconditions

Preconditions verhindern, dass Migrationen unter falschen Voraussetzungen ausgeführt werden:

  - changeSet:
      id: 4
      author: rene
      preConditions:
        - onFail: HALT
        - tableExists:
            tableName: users
        - not:
            columnExists:
              tableName: users
              columnName: phone
      changes:
        - addColumn:
            tableName: users
            columns:
              - column:
                  name: phone
                  type: VARCHAR(20)

Mit onFail: HALTMARK_RAN oder CONTINUE steuert man das Verhalten, falls die Vorbedingung nicht erfüllt ist.

Kontextabhängige Migrationen

Contexts und Labels erlauben migrationsspezifische Ausführungen je nach Umgebung:

  - changeSet:
      id: 5
      author: rene
      context: dev
      labels: seed
      changes:
        - insert:
            tableName: users
            columns:
              - column:
                  name: username
                  value: "testuser"
              - column:
                  name: email
                  value: "test@example.com"
Code-Sprache: JavaScript (javascript)
<em># Nur Changesets mit dem Kontext "dev" ausführen</em>
mvn liquibase:update -Dliquibase.contextFilter=dev

<em># Changesets mit Label "seed" ausschließen</em>
mvn liquibase:update -Dliquibase.labelFilter=!seed
Code-Sprache: HTML, XML (xml)

Fazit

Liquibase ist die ideale Wahl, wenn du datenbankunabhängige, deklarativ beschriebene Migrationen mit vollständiger Rollback-Fähigkeit benötigst. Die Unterstützung für YAML-Changelogs macht Changesets besonders lesbar und diff-bar, während Contexts und Preconditions die Steuerung in komplexen Deployment-Pipelines erlauben. In Spring Boot 4.1 ist Liquibase per Autokonfiguration sofort einsatzbereit — sobald liquibase-core auf dem Classpath liegt, werden Migrationen beim Anwendungsstart automatisch ausgeführt.