„Die Geschichte ist eine ewige Wiederholung.“
Thukydides
Es existiert kaum ein Software Projekt in dem nicht irgendwelche Daten in einer Datenbank gespeichert werden. Solange sich das Projekt im Entwicklungsstatus befindet, sind vielfache Änderungen am Datenbankschema kein Problem. Gerade bei der Entwicklung mit Datenbankabstraktionen wie Hibernate oder JPA machen sich Entwickler wenig Gedanken, weil das Framework sich um die Aktualisierung des Schemas kümmert.
Spätestens wenn die Software in Produktion verwendet wird, ändert sich dieser gedankenlose Umgang mit den Datenbankstrukturen. Bei Millionen von Tabelleneinträgen möchte kein Entwickler, dass ein geistloses Programm die Datenstruktur selbständig verändert und dabei vielleicht beschädigt. Häufig sind dann Skripte im Einsatz um die aktuellen Datenbankänderungen vorzunehmen. Diese müssen dann zumeist in SQL geschrieben und manuell ausgeführt werden. Das Prozedere ist aufwendig und fehleranfällig. Kommt dann der Auftraggeber auf die Idee, eine Datenbank mit einem anderen SQL Dialekt verwenden zu wollen, dann müssen alle Skripte angepasst werden.
Mit Liquibase gibt es aber seit mittlerweile 15 Jahren eine elegante Lösung für dieses Problem. Liquibase ist eine Bibliothek um Änderungen an einem Datenbankschema verfolgen, verwalten und anwenden zu können. Dazu definiert der Entwickler ein ChangeLog für seine Datenbank in XML, YAML oder JSON Format.
# db.changelog-master.xml databaseChangeLog: - include: comments: initial dabase setup with legacy data file: db/changelog/0000-init.yaml - include: comments: add ancestor image tables file: db/changelog/0001-ancestor-images.yaml
Die hier dargestellte YAML Datei db.changelog-master.xml
verweist mit den beiden include
Einträgen auf zwei weitere ChangeLog Dateien, die ihrerseits ChangeSets enthalten. Ein ChangeSet kann wiederum mehrere Changes enthalten, jedoch gehört es zu den Best Practices, nur eine Änderung pro ChangeSet zu definieren.
# 0000-init.yaml databaseChangeLog: - changeSet: id: ancestor-member author: Jens Kaiser comments: Ein Mitglied in einem Ahnenforschungsverein changes: - createTable: columns: - column: constraints: nullable: false primaryKey: true primaryKeyName: CONSTRAINT_A name: ID type: BIGINT - column: constraints: nullable: false name: NICKNAME type: VARCHAR(6) - column: name: GIVEN_NAME constraints: nullable: false type: VARCHAR(255) - column: name: SURNAME constraints: nullable: false type: VARCHAR(255) tableName: MEMBER
In diesem ChangeLog ist ein einzelnes ChangeSet enthalten, mit dem eine Datenbank Tabelle MEMBER
erstellt wird. Obwohl noch nicht viel gezeigt wurde, lassen sich schon interessante Eigenschaften der Liquibase Lösung erkennen. Ganz offensichtlich ist diese Konfiguration nicht datenbankspezifisch. Ob nun H2, MySQL, MariaDB, PostgreSQL, Oracle oder Informix, die YAML Konfiguration erzeugt auf allen Datenbanken eine entsprechende Tabelle. Auch kann der ChangeLog verwendet werden um in verschiedenen Umgebungen identische Datenbanken zu erstellen. Eine zur Produktion identische Testdatenbank kann also jederzeit erzeugt werden. Nicht ganz so offensichtlich ist es, dass diese Änderung von Liquibase automatisch zurückgenommen werden kann. In diesem Fall weiß Liquibase, dass ein createTable
durch ein dropTable
rückgängig gemacht werden kann.
Bereits existierende Projekte um Liquibase zu ergänzen klingt nach einer ganzen Menge Arbeit, da es nun heißt, ChangeSets für alle Tabellen, Sequenzen und bestehende Daten zu erzeugen. Erfreulicherweise ist dies nicht der Fall, denn Liquibase erlaubt es über die Kommandozeile oder ein Maven Plugin, ein bestehendes Datenbankschema auszulesen und als Changelog zu speichern.
liquibase --changeLogFile=changelog.yml generateChangeLog
Hier wird auf der Kommandozeile das aktuelle Datenbankschema in die ChangeLog Datei changelog.yml
geschrieben. Die Informationen zur Datenbank erhält das Tool aus einer speziellen Property-Datei.
Damit sich Liquibase um die Änderungen des Datenbankschemas in einem Spring Boot Projekt kümmert, muss die Bibliothek in den Classpath aufgenommen werden.
<dependency> <groupId>org.liquibase</groupId> <artifactId>liquibase-core</artifactId> <version>4.3.5</version> </dependency>
Zusätzlich benötigt Liquibase einen Verweis auf das ChangeLog in der application.properties
.
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.yaml spring.liquibase.enabled=true spring.jpa.hibernate.ddl-auto=validate
Hier sind zusätzlich die Properties spring.liquibase.enabled
und spring.jpa.hibernate.ddl-auto
angegeben. Die Property spring.jpa.hibernate.ddl-auto
steht auf validate
, da sich nun Liquibase um die Änderungen der Datenbank kümmert und zusätzlich ist hier die Property spring.liquibase.enabled
angegeben um Liquibase bei der Entwicklung einfach deaktivieren zu können.
Wird die Spring Boot Anwendung nun gestartet, dann prüft Liquibase ob es Änderungen an dem ChangeLog gab. Gibt es noch nicht durchgeführte ChangeSets, dann werden diese ausgeführt. Damit es keine nachträglichen Änderungen an schon ausgeführten ChangeSets gibt, prüft Liquibase diese mit einem MD5 Hash auf eventuelle Änderungen. Nachträgliche Änderungen an einem ChangeSet sind in den meisten Fällen verboten, weil sich sonst das aktuelle Datenbankschema nicht mehr aus dem ChangeLog reproduzieren lässt.
Damit ist die Umstellung eines Spring Boot Projektes auf Liquibase auch schon beendet und zukünftige Änderungen am Datenbankschema können nun automatisiert auf den Zielsystemen installiert werden.