Über die Lebenszeit einer Anwendung ergeben sich immer wieder auch Änderungen am Schema der persistenten Daten. Das zieht im Betrieb auch oft Probleme nach sich. Datenbank Tabellen müssen angepasst und Daten migriert werden. Wenn dies nicht gewissenhaft geschieht sammelt sich über die Zeit auch immer mal eine Altlast an nicht mehr genutzten Datenbankspalten an, denn hinzufügen ist immer einfacher als wieder entfernen.
Ein populäres Tool um Datenbankmigrationen zu formalisieren und zu verwalten ist Flyway. Um Flyway zu nutzen reicht es eine Maven Dependency hinzufügen.
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
In diesem Beitrag schauen wir uns Flyway anhand eines kleinen Beispielprojekts (GitHub) an. Es beinhaltet bereits die Entität Person
.
Person.java
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String firstname;
private String lastname;
}
Als erste Datenbankmigration können wir die notwendige Datenbankstruktur erzeugen um diese Entität abzubilden. Per default können die Migrationen in src/main/resources/db/migration
angelegt werden. Die Namensgebung erfolgt dabei nach dem Schema <prefix><version>__<name>.sql
. In diesem einfachen Beispiel bleiben wir bei dem Präfix V
für einfache versionierte Migrationen. Unser Beispielprojekt verwendet eine H2 Datenbank. Daher ergibt sich folgende erste Migration.
V1_0__init_schema.sql
create sequence PERSON_SEQ
increment by 50;
create table PERSON
(
ID INTEGER not null
primary key,
FIRSTNAME CHARACTER VARYING(255),
LASTNAME CHARACTER VARYING(255)
);
In unserer Datenbank verwaltet Flyway zusätzlich eine Tabelle flyway_schema_history
in der nachgehalten wird welche Migrationen in der Datenbank bereits durchgeführt wurden. Beim Starten der Anwendung kann das Tool nun prüfen ob neue Migrationen vorhanden sind, die ausgeführt werden müssen. Weitere Möglichkeiten um Migrationen durchzuführen sind ein Maven Plugin oder eine Java API.
Wie bilden wir nun aber Änderungen ab? Nehmen wir mal an die Entität Person
soll zusätzlich ein Attribut age
enthalten. Neben der Anpassung in der Java-Entity können wir für die Anpassung der Datenbankstruktur eine entsprechende Migration hinzufügen.
V1_1__add_age.sql
ALTER TABLE PERSON ADD AGE INTEGER;
Beim nächsten Start können wir im Log sehen, dass Flyway die Migration durchgeführt hat.
[...] : Current version of schema "PUBLIC": 1.0
[...] : Migrating schema "PUBLIC" to version "1.1 - add age"
[...] : Successfully applied 1 migration to schema "PUBLIC", now at version v1.1
Naja gut, die Änderung war relativ einfach, das hätte Hibernate auch automatisch hinbekommen können (wenn auch weniger formalisiert und dokumentiert). Flyway bietet uns aber die volle Kontrolle darüber welche Anpassungen für eine Migration durchgeführt werden. Für ein etwas interessanteres Beispiel wollen wir die Attribute firstname
und lastname
zukünftig durch ein einzelnes Attribut name
abbilden. Die folgende Migration nimmt die Änderungen in der Datenbank vor.
V1_2__combine_name.sql
ALTER TABLE PERSON ADD NAME CHARACTER VARYING(255);
UPDATE PERSON SET NAME = CONCAT_WS(' ', FIRSTNAME, LASTNAME);
ALTER TABLE PERSON DROP COLUMN FIRSTNAME;
ALTER TABLE PERSON DROP COLUMN LASTNAME;
Da uns letztlich alle Möglichkeiten von SQL offen stehen, können wir in den Datenbankmigrationen auch Inhalte verändern oder hinzufügen. Dabei sind alle Veränderungen klar dokumentiert und nachvollziehbar. Eine Migration der Datenbank von einer vorhandenen Version auf die aktuelle geschieht dabei ganz automatisch.
Das Beispielprojekt gibt es natürlich auch zum selber ausprobieren auf GitHub.