Versionierung mit Liquibase

IFS-Logo Diese Seite ist ein Teil der IsyFact-Standards. Alle Inhalte der Seite, insbesondere Texte und Grafiken, sind urheberrechtlich geschützt. Alle urheberrechtlichen Nutzungs- und Verwertungsrechte liegen beim Bundesverwaltungsamt.

Creative Commons Namensnennung Die Nutzung ist unter den Lizenzbedingungen der Creative Commons Namensnennung 4.0 International gestattet.

Die Schemaversionierung mit Liquibase erfolgt gemäß den Richtlinien in Versionierung mit Liquibase mit Fokus auf die Integration in Spring Boot und die Nutzung mit Maven. Liquibase wird in Spring Boot über die Konfigurationsdateien eingebunden, während das zugehörige Maven-Plugin Schemaänderungen direkt in der Build-Pipeline ausführt.

Durch strukturierte Änderungsprotokolle lassen sich Schemaanpassungen kontrolliert, nachvollziehbar und automatisiert verwalten. Bestehende Datenbanken bleiben unverändert und dienen als Basis für zukünftige Änderungen. Die Kombination mit JDBC erlaubt eine flexible Anbindung an verschiedene Datenbanken.

Zudem lässt sich Liquibase nahtlos in CI/CD-Pipelines wie GitLab CI oder GitHub Actions integrieren, um Migrationen und Rollbacks zu automatisieren und so eine sichere Bereitstellung zu gewährleisten.

1. Einrichtung von Liquibase

IT-Systeme nutzen Liquibase direkt sowie als Maven-Plugin. Neben der Einbindung in Maven ist auch eine Konfiguration über Spring Boot notwendig.

1.1. Maven-Konfiguration

Liquibase benötigt JDBC, um mit der Datenbank zu kommunizieren und Änderungen auszuführen. Daher ist neben der Bibliothek liquibase-core, die Kernfunktionen von Liquibase enthält, auch der jeweilige JDBC-Treiber der verwendeten Datenbank notwendig.

Listing 1. Einbindung der Abhängigkeiten im Maven POM
<dependencies>
    <dependency>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-core</artifactId>
    </dependency>
    <dependency>
        <!-- JDBC-Treiber der Datenbank -->
    </dependency>
</dependencies>

Neben den Abhängigkeiten ist die Konfiguration des Liquibase Maven Plugin erforderlich.

Listing 2. Konfiguration des Liquibase Maven Plugin
<build>
    <plugins>
        <plugin>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
            <configuration>
                <changeLogFile>src/main/resources/db/changelog/db.changelog-master.xml</changeLogFile>
                <url>${spring.datasource.url}</url>
                <username>${spring.datasource.username}</username>
                <password>${spring.datasource.password}</password>
                <driver>${spring.datasource.driver-class-name}</driver>
            </configuration>
        </plugin>
    </plugins>
</build>

Offizielle Dokumentation von Liquibase: Using JDBC URL in Liquibase

1.2. Spring Boot Konfiguration

Um Liquibase beim Start eines IT-Systems automatisch Schemaänderungen durchführen zu lassen, kann dies in der application.properties konfiguriert werden.

Listing 3. Konfiguration: Automatische Schemaänderungen beim Start
spring.liquibase.enabled=true

Standardmäßig verwendet Liquibase das Root Changelog db/changelog/db.changelog-master.xml. Es dient als zentrale Steuerungsdatei, in der alle Schemaänderungen verwaltet werden. Es enthält entweder direkte Änderungen oder bindet weitere Changelog-Dateien ein, um eine strukturierte Verwaltung zu ermöglichen.

Falls ein anderer Ablageort genutzt werden soll, muss dies explizit angegeben werden.

Listing 4. Konfiguration: Ablageort des Root Changelogs
spring.liquibase.change-log=classpath:pfad/zur/changelog.xml
Ablage von Credentials der Datenbank

Benutzernamen und Passwörter zum Zugriff auf Datenbanken in Produktion und dazu ähnlichen Umgebungen dürfen nicht in Konfigurationsdateien (application.properties) oder im Maven POM gespeichert werden.

Stattdessen empfiehlt sich die Nutzung von:

  • Umgebungsvariablen ($DB_USER, $DB_PASSWORD),

  • einer zentralen Konfiguration (zum Beispiel Spring Cloud Config), oder

  • Secrets-Management-Tools (zum Beispiel AWS Secrets Manager, HashiCorp Vault, Kubernetes Secrets).

Offizielle Dokumentation von Liquibase:

2. Erstellung einer Baseline

Die folgenden Schritte beschreiben die Erstellung einer Baseline zur Verwaltung eines bestehenden Schemas.

1. Erstellung der db.changelog-master.xml

Damit eine Baseline korrekt verwaltet werden kann, muss zuerst das Root Changelog db.changelog-master.xml erzeugt werden.

Diese Datei dient als Einstiegspunkt für alle Schemaänderungen und stellt sicher, dass die Baseline zuerst und dann alle weiteren Release-Changelogs in der richtigen Reihenfolge angewendet werden.

Bei Verwendung von Spring Boot sollte sich diese Datei im Ordner src/main/resources/db/changelog befinden:

Listing 5. Beispiel für das Root Changelog in XML
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
        http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.8.xsd">
    <include file="baseline.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
Listing 6. Beispiel für das Root Changelog in YAML
databaseChangeLog:
  - include:
      file: baseline.yml
      relativeToChangelogFile: true

Offizielle Dokumentation von Liquibase: Design Your Liquibase Project

2. Generierung der baseline.xml
mvn liquibase:generateChangeLog
    -Dliquibase.outputChangeLogFile=src/main/resources/db/changelog/baseline.xml

Nach der Generierung sollte die Datei manuell überprüft und bereinigt werden.

3. Markierung der Baseline

Damit Liquibase die bestehende Datenbank nicht erneut verändert, aber die bereits vorhandenen Strukturen als Referenz speichert, muss die Baseline als ausgeführt markiert werden.

Listing 7. Markierung der Baseline als ausgeführt
mvn liquibase:changelogSync

Dadurch werden alle im Changelog definierten Änderungen als bereits angewendet registriert, ohne tatsächlich Änderungen an der Datenbank vorzunehmen.

Listing 8. Optionale Vorabprüfung ohne Ausführung
mvn liquibase:changelogSyncSQL

Dieser Befehl zeigt die SQL-Befehle an, die Liquibase ausführen würde, ohne sie tatsächlich anzuwenden.

Offizielle Dokumentation von Liquibase:

4. Tabellen DATABASECHANGELOG- und DATABASECHANGELOGLOCK anlegen

Beim ersten Liquibase-Update oder Sync-Vorgang legt Liquibase automatisch zwei Tabellen an:

  • DATABASECHANGELOG: Speichert ausgeführte Changesets.

  • DATABASECHANGELOGLOCK: Sperrt die Datenbank, um gleichzeitige Änderungen zu verhindern.

Falls die Sperre aus einem vorherigen Prozess hängen geblieben ist, kann sie mit folgendem Befehl entfernt werden:

Listing 9. Entfernung der Sperre
mvn liquibase:releaseLocks

Offizielle Dokumentation von Liquibase: Tracking Tables

5. Integration der Baseline in das Versionskontrollsystem

Nachdem die Baseline erstellt wurde, sollten sowohl diese als auch das Root Changelog db.changelog-master.xml in das Versionskontrollsystem aufgenommen werden. Dies gewährleistet eine nachvollziehbare Historie der Datenbankänderungen.

3. Release-orientierte Umsetzung von Schemaänderungen

In den folgenden Schritten werden Changelogs in XML als Beispiel verwendet.

Es wird eine Baseline als Ausgangspunkt für die Datenbankstruktur genutzt, auf die alle nachfolgenden Release-Changelogs aufbauen.

Für eine genauere Steuerung, beispielsweise bei Stored Procedures, Triggern oder komplexen Optimierungen, können Changelogs auch als SQL-Dateien integriert werden.

3.1. Verzeichnisstruktur

Die Verzeichnisstruktur ist so aufgebaut, dass die Baseline als erster Schritt dient und alle nachfolgenden Releases über Changelogs (changelog-X.Y.xml) angewendet werden. Sie definiert die grundlegende Datenbankstruktur und bleibt nach der ersten Anwendung unverändert.

/db/changelog/
├── db.changelog-master.xml
├── baseline.xml
├── changelog-1.0.xml
├── changelog-1.1.xml
└── changelog-1.2.xml

Offizielle Dokumentation von Liquibase:

3.2. Root Changelog

Das Root Changelog ist die db.changelog-master.xml und stellt sicher, dass die Baseline zuerst und dann alle weiteren Release-Changelogs in der richtigen Reihenfolge angewendet werden.

Listing 10. Struktur des Root Changelogs
<databaseChangeLog>
    <include file="db/changelog/baseline.xml"/>
    <include file="db/changelog/changelog-1.0.xml"/>
    <include file="db/changelog/changelog-1.1.xml"/>
    <include file="db/changelog/changelog-1.2.xml"/>
</databaseChangeLog>

3.3. Tagging von Baseline und Releases

Vor jedem neuen Release sollte ein Tag gesetzt werden, um ein sauberes Rollback des gesamten Releases zu erleichtern. Der erste Tag definiert die Baseline und markiert diesen Zustand als Referenzpunkt. Dadurch kann bei Bedarf gezielt auf die Baseline zurückgerollt werden. Eine einheitliche sinnvolle Namenskonvention ist empfehlenswert.

Listing 11. Setzen eines Tags
mvn liquibase:tag -Dliquibase.tag=v1.0-baseline
Listing 12. Befehl zum Rollback
mvn liquibase:rollback -Dliquibase.rollbackTag=v1.0-baseline

Offizielle Dokumentation von Liquibase: Maven tag

3.4. Erste Änderungen nach der Baseline

Das erste Update nach der Baseline ist Release-Changelog 1.0. Alle nachfolgenden Release-Changelogs enthalten ausschließlich inkrementelle Änderungen zur Baseline.

Listing 13. Beispiel für inkrementelle Änderungen im Changelog
<databaseChangeLog>
    <changeSet id="1.0-001" author="dev1">
        <addColumn tableName="customers">
            <column name="email" type="varchar(255)"/>
        </addColumn>
    </changeSet>

    <changeSet id="1.0-002" author="dev2">
        <createIndex indexName="idx_orders_date" tableName="orders">
            <column name="order_date"/>
        </createIndex>
    </changeSet>
</databaseChangeLog>

3.5. Preconditions

Preconditions in Liquibase prüfen vor der Ausführung eines Changesets definierte Bedingungen. Wird eine Bedingung nicht erfüllt, kann die Migration abgebrochen, übersprungen oder mit einer Warnung fortgesetzt werden. Dadurch werden Fehler frühzeitig erkannt und nur zulässige Änderungen ausgeführt.

Listing 14. Beispiel für Preconditions
<databaseChangeLog>
    <changeSet id="1.0-001" author="dev1">
        <preConditions onFail="HALT">
            <not>
                <columnExists tableName="customers" columnName="email"/>
            </not>
        </preConditions>
        <addColumn tableName="customers">
            <column name="email" type="varchar(255)"/>
        </addColumn>
    </changeSet>

    <changeSet id="1.0-002" author="dev2">
        <preConditions onFail="HALT">
            <not>
                <indexExists tableName="orders" indexName="idx_orders_date"/>
            </not>
        </preConditions>
        <createIndex indexName="idx_orders_date" tableName="orders">
            <column name="order_date"/>
        </createIndex>
    </changeSet>
</databaseChangeLog>

Offizielle Dokumentation von Liquibase: Preconditions

3.6. Manuelles Rollback im Changelog

Rollback-Anweisungen in der changelog.xml ermöglichen Rollbacks einzelner Changesets für mehr Kontrolle.

Listing 15. Beispiel für ein Rollback im Changelog
<databaseChangeLog>
    <changeSet id="1.0-001" author="dev1">
        <addColumn tableName="customers">
            <column name="email" type="varchar(255)"/>
        </addColumn>
        <rollback>
            <dropColumn tableName="customers" columnName="email"/>
        </rollback>
    </changeSet>

    <changeSet id="1.0-002" author="dev2">
        <createIndex indexName="idx_orders_date" tableName="orders">
            <column name="order_date"/>
        </createIndex>
        <rollback>
            <dropIndex indexName="idx_orders_date" tableName="orders"/>
        </rollback>
    </changeSet>
</databaseChangeLog>

3.7. Befehle zum Rollback

Befehl Bedeutung

mvn liquibase:rollback -Dliquibase.rollbackTag=TAG_NAME

Rollback auf einen zuvor gesetzten Tag (TAG_NAME).

mvn liquibase:rollback -Dliquibase.rollbackCount=1

Rollback um eine bestimmte Anzahl an Changesets (hier: 1 Changeset).

mvn liquibase:rollback -Dliquibase.rollbackDate=YYYY-MM-DDTHH:MM:SS

Rollback auf einen spezifischen Zeitpunkt im Format YYYY-MM-DDTHH:MM:SS.

mvn liquibase:rollbackToDate -Dliquibase.rollbackDate=YYYY-MM-DD

Rollback auf ein bestimmtes Datum (YYYY-MM-DD).

Offizielle Dokumentation von Liquibase: Liquibase Rollback Workflow

4. Einsatz von CI/CD-Pipelines für automatisierte Migrationen

Liquibase kann nahtlos in GitLab CI/CD-Pipelines integriert werden, um Datenbankmigrationen automatisiert, sicher und kontrolliert auszuführen. Die erforderlichen Konfigurationen werden in der Datei .gitlab-ci.yml definiert.

1. Validierung der Changelogs

Vor dem Deployment sollte sichergestellt werden, dass alle Changelogs validiert und fehlerfrei sind. Tritt ein Fehler auf, wird der Prozess abgebrochen, um fehlerhafte Migrationen zu verhindern.

Listing 16. Beispiel für ein Validierungsskript
liquibase --url=$DB_URL --username=$DB_USER --password=$DB_PASSWORD validate
2. Deployment auf Staging

Die Datenbankmigration wird durchgeführt und das Release mit einem Tag versehen, um Rollbacks zu ermöglichen. Mit liquibase history werden die durchgeführten Änderungen angezeigt. Falls alle Tests erfolgreich sind, wird das Deployment für die Produktion freigegeben.

Listing 17. Beispiel für ein Deployment-Skript
liquibase --url=$DB_URL --username=$DB_USER --password=$DB_PASSWORD update
liquibase --url=$DB_URL --username=$DB_USER --password=$DB_PASSWORD tag $RELEASE_VERSION
liquibase --url=$DB_URL --username=$DB_USER --password=$DB_PASSWORD history
3. Deployment in Produktion

Die gleichen Deployment-Befehle wie für Staging werden auf der Produktionsdatenbank ausgeführt.

4. Automatisches Rollback bei Fehlern

Falls die Pipeline einen Fehler erkennt, kann ein automatischer Rollback erfolgen:

Listing 18. Beispiel für ein Rollback-Skript
liquibase --url=$DB_URL --username=$DB_USER --password=$DB_PASSWORD rollbackToTag $RELEASE_VERSION
liquibase --url=$DB_URL --username=$DB_USER --password=$DB_PASSWORD history

Ähnliche Konfigurationen können zum Beispiel für GitHub Actions erstellt werden.

Offizielle Dokumentation von Liquibase: Using Liquibase with GitLab CI/CD

5. Nützliche Liquibase-Befehle zur Schema-Verwaltung

Liquibase bietet verschiedene Befehle zur Überprüfung der Aktualität des Schemas und zur Verwaltung von Änderungen. Hier sind einige der wichtigsten Befehle:

Befehl Bedeutung

liquibase validate

Überprüft, ob alle Changesets korrekt formatiert und konsistent sind. Falls Probleme auftreten, gibt Liquibase eine Fehlermeldung aus, die auf fehlerhafte oder fehlende Changesets hinweist.

liquibase update

Überträgt alle noch nicht angewendeten Änderungen aus den Changelogs auf die Datenbank.

liquibase status

Zeigt an, welche Änderungen noch nicht auf die Datenbank angewendet wurden. Liquibase gibt eine Liste der ausstehenden Changesets zurück.

liquibase diff

Vergleicht zwei Datenbanken und zeigt Unterschiede an. Dies ist nützlich, um zu überprüfen, ob und wie sich die Datenbanken in verschiedenen Umgebungen unterscheiden.

liquibase snapshot

Erstellt eine Momentaufnahme der aktuellen Datenbankstruktur, die zum Beispiel für Analysen oder den Vergleich mit späteren Versionen verwendet werden kann.

liquibase rollback

Führt ein Rollback aus. Dies stellt den Zustand der Datenbank auf einen definierten Punkt zurück und entfernt alle nachfolgenden Änderungen.

Offizielle Dokumentation von Liquibase: Liquibase-Befehlen.