Im ersten Teil von Block 2 hast du gesehen, warum ein ORM eingesetzt wird und wie Hibernate/JPA eine Java-Entity auf eine PostgreSQL-Tabelle abbildet. Damit dieses Mapping stabil funktioniert, braucht die Anwendung ein verlässliches Datenbankschema. Genau hier beginnt Schema Evolution: Software ändert sich, aber produktive Daten bleiben bestehen.
Ein Ticket-System ist am ersten Tag selten fertig modelliert. Zuerst gibt es vielleicht nur title, status und created_at. Später braucht das Team Prioritäten, SLA-Fristen, neue Statuswerte oder zusätzliche Constraints. Diese Änderungen dürfen nicht zufällig auf Entwicklerlaptops entstehen. Sie müssen nachvollziehbar, wiederholbar und reviewbar sein.
Flyway ist dafür das Migrationswerkzeug im DB-2-Starterprojekt. Es führt versionierte SQL-Dateien in einer festen Reihenfolge aus und merkt sich in einer History-Tabelle, welche Migrationen bereits gelaufen sind Redgate (2026). Dadurch wird aus “ich habe lokal SQL ausprobiert” ein nachvollziehbarer Entwicklungsstand, den alle Umgebungen gleich ausführen können.
Warum Schema Evolution?¶
Ein Datenbankschema ist Teil der Anwendung. Wenn der Code neue Felder erwartet, muss die Datenbank diese Felder kennen. Wenn die Datenbank neue Regeln erzwingt, muss der Code dazu passen. Schema und Anwendung entwickeln sich deshalb gemeinsam.
Ohne Migrationen bleibt oft unklar, woher ein Schema kommt. Vielleicht wurde eine Tabelle im Datenbankclient angepasst, vielleicht hat Hibernate lokal etwas erzeugt, vielleicht fehlt die Änderung in der CI. Flyway löst dieses Grundproblem, indem jede Schemaänderung als Datei im Projekt liegt, versioniert wird und beim Start in kontrollierter Reihenfolge läuft.
Ohne kontrollierte Migrationen entstehen typische Probleme:
Eine Entwicklerin hat lokal eine Spalte, ein anderer Entwickler nicht.
Die Testdatenbank passt nicht mehr zur CI-Umgebung.
Hibernate startet nicht, weil eine Entity eine Spalte erwartet, die in PostgreSQL fehlt.
Eine Produktivdatenbank enthält bestehende Zeilen, die einen neuen
NOT NULLConstraint verletzen würden.Niemand weiss sicher, welche SQL-Änderung auf welcher Umgebung bereits gelaufen ist.
Schema Evolution bedeutet nicht, dass jede Änderung kompliziert ist. Sie bedeutet: Jede Änderung wird so beschrieben, dass sie wiederholt, geprüft und später verstanden werden kann.
Die Rückkopplung ist wichtig. Der Code zeigt, welche Struktur gebraucht wird. Die Migration verändert die Datenbank kontrolliert. Die Datenbank schützt danach die Daten, auf die der Code angewiesen ist.
Was Flyway macht¶
Flyway sucht beim Start nach Migrationsdateien, sortiert sie nach Version und führt nur diejenigen aus, die in dieser Datenbank noch nicht gelaufen sind. Im Starterprojekt liegen diese Dateien unter:
src/main/resources/db/migration/Die erste Migration heisst:
V1__starter_ticket_schema.sqlDer Name hat Bedeutung:
| Teil | Bedeutung |
|---|---|
V1 | Version der Migration |
__ | Trennung zwischen Version und Beschreibung |
starter_ticket_schema | lesbare Beschreibung |
.sql | SQL-Migration |
Flyway legt zusätzlich eine History-Tabelle an. Im Starterprojekt läuft Flyway im Schema app_starter, deshalb liegt die Tabelle dort:
app_starter.flyway_schema_historyIn dieser Tabelle steht unter anderem, welche Version ausgeführt wurde, wie die Datei hiess und ob die Ausführung erfolgreich war. Das ist der Unterschied zwischen “ich habe irgendwo SQL ausgeführt” und einer kontrollierten Schemaänderung.
Flyway in Spring Boot¶
Spring Boot bindet Flyway automatisch ein, wenn die passenden Abhängigkeiten vorhanden sind. Im Starterprojekt findest du sie in pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>Die Datenbankverbindung und das Flyway-Schema stehen in application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5433/ticket_system
username: ticket_user
password: ticket_user
flyway:
default-schema: app_starter
schemas: app_starter
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
default_schema: app_starterDamit ist die Verantwortung klar verteilt:
spring.datasource.*beschreibt, wo PostgreSQL erreichbar ist.spring.flyway.*sagt, in welchem Schema Migrationen laufen.spring.jpa.hibernate.ddl-auto: validatesagt Hibernate, dass es das Schema nur prüfen soll.hibernate.default_schemasorgt dafür, dass Hibernate dasselbe Schema erwartet.
Das ist die zentrale Reihenfolge in diesem Kurs: Flyway erstellt und verändert das Schema. Hibernate validiert danach, ob Entity und Datenbank zusammenpassen.
Migrationen sind unveränderlich¶
Eine bereits ausgeführte Migration wird nicht nachträglich korrigiert. Wenn V1__starter_ticket_schema.sql auf einer Datenbank gelaufen ist, erwartet Flyway beim nächsten Start dieselbe Datei. Wird sie nachträglich verändert, passt die Prüfsumme nicht mehr zur History.
Für Lernprojekte kann man eine lokale Datenbank manchmal neu aufsetzen. In professionellen Projekten ist das keine Option für produktive Daten. Dort gilt:
Fehler in einer gelaufenen Migration werden mit einer neuen Migration korrigiert.
Beispiel:
V1__starter_ticket_schema.sql
V2__add_ticket_priority.sql
V3__backfill_ticket_priority.sql
V4__enforce_ticket_priority.sqlDiese Denkweise macht Reviews einfacher. Das Team sieht nicht nur den Endzustand, sondern auch den Weg dorthin.
Sicheres Evolutionsbeispiel¶
Angenommen, das Ticket-System braucht eine neue Priorität. Der naive Weg wäre:
ALTER TABLE app_starter.tickets
ADD COLUMN priority TEXT NOT NULL;Das ist riskant, sobald die Tabelle bereits Daten enthält. Bestehende Tickets haben noch keinen Wert für priority, verletzen also den neuen NOT NULL Constraint.
Sicherer ist eine Änderung in Schritten:
ALTER TABLE app_starter.tickets
ADD COLUMN priority TEXT;Danach werden bestehende Daten befüllt:
UPDATE app_starter.tickets
SET priority = 'normal'
WHERE priority IS NULL;Erst wenn die Daten sauber sind, wird die Regel aktiviert:
ALTER TABLE app_starter.tickets
ALTER COLUMN priority SET NOT NULL;
ALTER TABLE app_starter.tickets
ADD CONSTRAINT tickets_priority_check
CHECK (priority IN ('low', 'normal', 'high', 'urgent'));Diese Reihenfolge ist nicht nur technischer Formalismus. Sie schützt bestehende Daten und macht die fachliche Absicht sichtbar: Jedes Ticket braucht eine Priorität, und nur bestimmte Werte sind erlaubt.
Riskante Migrationen erkennen¶
Bei einem Migrationsreview fragst du nicht nur: “Ist das SQL syntaktisch korrekt?” Du fragst:
Was passiert mit bestehenden Zeilen?
Ist die Änderung rückwärtskompatibel zum aktuell laufenden Code?
Wird eine Pflichtregel aktiviert, bevor die Daten bereinigt sind?
Gehen Daten verloren?
Muss die Anwendung zuerst lesen können, bevor sie schreiben muss?
Typische Warnsignale:
| Migration | Risiko | Sicherere Idee |
|---|---|---|
ADD COLUMN ... NOT NULL auf bestehender Tabelle | vorhandene Zeilen haben keinen Wert | zuerst nullable ergänzen, befüllen, dann NOT NULL |
DROP COLUMN direkt nach Codeänderung | alter Code oder Reports greifen noch darauf zu | zuerst nicht mehr verwenden, später entfernen |
ALTER COLUMN TYPE ohne Prüfung | Werte können nicht konvertiert werden | Datenprofil prüfen und Konvertierung testen |
| Statuswerte nur in Java validieren | andere Schreibpfade können falsche Werte speichern | CHECK Constraint oder Referenztabelle |
ddl-auto: update im Projekt | Schema ändert sich am Review vorbei | Flyway-Migration schreiben, Hibernate validiert |
Verbindung zum Starterprojekt¶
Die Startmigration im Starterprojekt ist absichtlich klein:
CREATE TABLE app_starter.tickets (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT,
status TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);Für Block 2 war das genug, um Entity Mapping, Repository und Hibernate-Validierung zu verstehen. Für Schema Evolution ist es ein guter Ausgangspunkt, weil du erkennst, welche Regeln noch fehlen:
Darf
titlewirklichNULLsein?Welche Statuswerte sind erlaubt?
Braucht jedes Ticket eine Priorität?
Welche bestehenden Daten müssen vor einem Constraint bereinigt werden?
Wichtig: Diese Lücken werden nicht durch nachträgliches Bearbeiten von V1__starter_ticket_schema.sql geschlossen. Im Unterricht schreibst du dafür eine neue Migration, zum Beispiel:
V2__enforce_ticket_rules.sqlDiese neue Migration beschreibt den nächsten Evolutionsschritt: bestehende Daten prüfen oder befüllen, Pflichtfelder aktivieren und erlaubte Statuswerte mit einem Constraint absichern.
Im dritten Teil von Block 2 verfolgst du danach, wie diese Datenbankstruktur im Spring-Boot-Request-Ablauf genutzt wird. Flyway liefert die stabile Datenbankbasis, auf der Controller, Service, Repository und Entity arbeiten.
Unterrichtsstruktur¶
Motivation: Warum Schema Evolution nötig ist und welche Rolle Flyway übernimmt.
Flyway im Spring-Boot-Projekt lesen: Dependencies,
application.yml, Migration und History-Tabelle.Migrationsreview: riskante Schemaänderung erkennen und eine neue Migration als sicheren nächsten Schritt planen.
- Redgate. (2026). Flyway Documentation. https://documentation.red-gate.com/fd