Start / Seminare / Jakarta EE modernisieren
Modul
Persistence und Datenzugriff
4 Kapitel in diesem Modul-Video · Laufzeit
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
Persistence und Datenzugriff
0:00 Jetzt kommen wir zu dem Teil, bei dem eine Migration am meisten Schaden anrichten kann: den Daten. Die Regel für dieses Modul lautet deshalb schlicht — am Datenmodell erkennt man, ob eine Migration sauber war, und zwar daran, dass es sich nicht verändert hat. Wir sehen uns an, wie Persistence-Einheiten und Datenquellen künftig konfiguriert werden, welche Zugriffsmuster infrage kommen und warum Panache eine Verlockung ist, der man in diesem Schritt widerstehen sollte.
0:26 Und wir klären, wie Schemaverwaltung aussieht, wenn zwei Anwendungen eine Zeit lang parallel laufen.
Persistence und Datenzugriff
0:32 Wir arbeiten weiter am Piloten. Nach Schnittstelle und Beans folgt die Persistenzschicht — und damit die Verbindung zur Datenbank, die beide Systeme während der Übergangszeit teilen. Dieses Modul hat deshalb eine Besonderheit: Es geht nicht nur darum, dass der neue Dienst funktioniert, sondern darum, dass er den Bestand nicht stört.
Persistence-Einheiten und Datenquellen
0:51 Beginnen wir bei der Konfiguration. Sie zieht um — von der persistence.xml und der Serverkonsole in die Anwendungskonfiguration. Damit wandert auch eine Zuständigkeit: Was bisher der Datenbankbetrieb im Server gepflegt hat, gehört künftig zur Anwendung und liegt im Repository. Quarkus konfiguriert Hibernate ORM über Properties statt über eine persistence.xml.
1:14 Datenbankart, Version, Zugangsdaten und Pool stehen in der Anwendungskonfiguration; der EntityManager wird wie gewohnt per Injektion bezogen. Eine persistence.xml bleibt möglich, ist aber nicht der vorgesehene Weg — und Vorsicht: Beides gleichzeitig führt zum Fehler beim Start. Das ist eigentlich freundlich, denn es verhindert die schlimmste Variante, nämlich zwei Konfigurationsquellen, von denen nur eine wirkt und niemand weiß, welche.
1:41 Worauf es hier ankommt, sind zwei Zeilen. Erstens die Strategie für die Schemaverwaltung, hier auf validate — das heißt: Hibernate prüft beim Start, ob das vorhandene Schema zu den Entities passt, und ändert nichts. Das ist bei einer Migration die einzig richtige Einstellung, denn die Datenbank gehört noch dem Bestand. Und zweitens, wie die Fußzeile sagt: Das Passwort steht nicht in dieser Datei.
2:04 Es kommt als Umgebungsvariable oder Secret — genau die Trennung, die wir in Modul fünf für den Application Server schon eingeübt haben. Der Pool ist ein Punkt, den fast alle vertagen und fast alle bereuen. Bisher stellte der Server ihn bereit, jetzt gehört er zur Anwendung — und damit gilt eine neue Rechnung: Poolgröße mal Instanzenzahl ergibt die Last auf der Datenbank.
2:26 Wenn Sie den Wert eines einzelnen Servers übernehmen und dann fünf Instanzen betreiben, öffnen Sie fünfmal so viele Verbindungen. Datenbanken reagieren darauf unfreundlich. Und Punkt vier ist die Einsicht dahinter: Ein zu großer Pool löst nichts, er verlagert die Warteschlange nur von der Anwendung in die Datenbank — dorthin, wo sie teurer ist.
2:46 Die Logik dieser Tabelle ist ein Umzug von Zuständigkeiten. Und wie die Fußzeile sagt: Jede Zeile ist eine Stelle, an der bisher der Betrieb entschied und jetzt die Anwendung entscheidet. Das ist nicht nur technisch, das ist organisatorisch. Wenn der Datenbankadministrator bisher die Poolgröße gesetzt hat, muss jetzt jemand aus dem Entwicklungsteam diese Verantwortung übernehmen — und der Administrator muss wissen, wo der Wert steht.
3:11 Klären Sie das, bevor der erste Lasttest kommt. Und die dritte Zeile ist eine angenehme Vereinfachung: Der Dialekt ergibt sich aus Datenbankart und Version, statt von Hand gesetzt zu werden. Punkt eins ist gutartig, weil er laut scheitert: persistence.xml und Properties nebeneinander, und der Start bricht ab. Ärgerlich, aber sofort sichtbar.
3:32 Punkt drei ist die leise Variante: Der Dialekt weicht ab, weil die Datenbankversion nicht gesetzt ist. Dann funktioniert fast alles — bis eine bestimmte Abfrage anders übersetzt wird und plötzlich anders sortiert. Und Punkt vier ist der, der in Sicherheitsprüfungen auffällt: Zugangsdaten landen in der Konfigurationsdatei im Repository.
3:53 Das passiert praktisch immer beim ersten Versuch, weil es so bequem ist. Räumen Sie es sofort auf, nicht später.
Zugriffsmuster und Schema
4:01 Jetzt zur Frage, wie der Code auf die Daten zugreift. Und zur wichtigsten Empfehlung dieses Moduls: Ändern Sie hier möglichst wenig. Und zur wichtigsten Empfehlung dieses Moduls: Ändern Sie hier möglichst wenig. Jede zusätzliche Verbesserung kostet Sie die Vergleichbarkeit mit dem Bestand. Die Entities bleiben Jakarta-Persistence-Entities, unverändert.
4:22 Der Zugriff kann weiterhin über den EntityManager laufen — oder über Panache, das Entity- und Repository-Muster deutlich vereinfacht. Panache ist attraktiv, keine Frage: weniger Code, kürzere Abfragen. Aber für eine Migration ist der unveränderte Weg meist der richtige. Der Grund ist derselbe wie in Modul acht bei der JSON-Bindung: Jede zusätzliche Änderung verwässert den Vergleich mit dem Bestand.
4:47 Panache können Sie danach einführen, als eigenen Schritt, mit eigenem Test. Dann wissen Sie auch, was die Änderung bewirkt hat. Diese Gegenüberstellung zeigt, worum es bei der Entscheidung wirklich geht — nicht um Eleganz, sondern um die Anzahl gleichzeitiger Änderungen. Links bleibt alles, wie es war: kein Umbau der Aufrufe, und der Vergleich mit dem Bestand ist einfach, weil derselbe Code dieselben Abfragen erzeugt.
5:12 Rechts wird der Code kürzer und lesbarer — aber Sie ändern Migration und Zugriffsmuster gleichzeitig. Wenn danach eine Abfrage ein anderes Ergebnis liefert, haben Sie zwei mögliche Ursachen. Machen Sie es in zwei Schritten, dann haben Sie immer nur eine. Lazy Loading ist das Thema, das in Migrationen die meisten Überraschungen liefert — und zwar deshalb, weil es vorher funktioniert hat.
5:35 Punkt zwei erklärt warum: Der Application Server hielt die Sitzung oft länger offen, als die Spezifikation verlangt. Code, der außerhalb der Transaktion nachlädt, lief also jahrelang problemlos. In der neuen Laufzeit scheitert genau dieser Aufruf. Und Punkt drei macht es unangenehm: Die Fehler treten selten auf und nur auf bestimmten Pfaden — meist dort, wo ein Objekt an eine Ausgabeschicht durchgereicht wird.
5:59 Ein Test ohne echte Transaktionsgrenze findet das nicht. Das Muster hier ist absichtlich unspektakulär: ein Repository, ein injizierter EntityManager, eine lesende und eine schreibende Methode. Worauf es ankommt, steht in der Fußzeile: Schreibende Methoden brauchen die Transaktionsannotation, sonst wirft der Aufruf einen Fehler.
6:19 Das ist übrigens eine hilfreiche Strenge — im Application Server hätte derselbe Code vielleicht stillschweigend funktioniert, weil irgendwo weiter oben schon eine Transaktion lief. Hier merken Sie sofort, wenn die Grenze fehlt. Ein Fehler beim ersten Versuch ist besser als ein Datenverlust im Betrieb. Punkt zwei ist die Ausprägung dessen, worüber wir gerade gesprochen haben — und der Zusatz ist wichtig: Lazy Loading schlägt außerhalb der Transaktion fehl, also in Produktion.
6:47 Denn im Test läuft oft alles in einer Transaktion. Punkt drei ist ein Klassiker, den man leicht übersieht: Die Abfrage liefert eine andere Sortierung, weil sie ohne ausdrückliche Sortierung arbeitet. Ohne ORDER BY garantiert keine Datenbank eine Reihenfolge — sie war nur zufällig stabil. Und Punkt vier betrifft die Leistung: Ein Second-Level-Cache aus dem Server fehlt, und die Datenbanklast steigt, ohne dass jemand einen Fehler sieht.
Schema und Betrieb
7:12 Bleibt das Schema. Und hier gilt während der Migration eine sehr klare Regel: Das Schema gehört in Migrationsdateien, nicht in den Start der Anwendung. Denn während der Übergangszeit teilen sich zwei Anwendungen dieselben Tabellen — und jede Schemaänderung trifft beide. Schemaänderungen werden über Flyway oder Liquibase versioniert ausgeführt. Hibernate darf das Schema in Produktion nicht erzeugen; dort steht die Strategie auf validate oder none.
7:39 Der Gewinn ist Nachvollziehbarkeit: Jede Änderung liegt als Datei im Repository, mit Reihenfolge und Zeitpunkt, und es ist jederzeit klar, welche Umgebung auf welchem Stand ist. Das ist während einer Koexistenzphase besonders wichtig — denn dort greifen zwei Anwendungen auf dieselben Tabellen zu, und jede Änderung muss zu beiden passen.
7:59 Die Logik dieser Tabelle steht in der letzten Spalte. Und beachten Sie die zweite Zeile: update, sinnvoll in nirgends produktiv. Das ist bewusst so scharf formuliert. Update ändert das Schema so, wie Hibernate es für richtig hält — es fügt hinzu, aber es räumt nicht auf, und es sagt Ihnen nicht, was es getan hat. In einer Datenbank, die noch dem Bestand gehört, ist das gefährlich. Die dritte Zeile ist Ihr Werkzeug: validate meldet einen Unterschied und bricht ab, statt ihn zu beheben.
8:28 Genau das wollen Sie wissen. Der erste Punkt ist der harte: Während der Koexistenz greifen beide Anwendungen auf dieselben Tabellen zu. Jede Schemaänderung trifft also auch den Bestand — der davon nichts weiß. Punkt zwei ist die typische stille Variante: Eine geänderte Spaltenlänge fällt erst beim längsten Datensatz auf, also bei der einen Adresse mit dem sehr langen Straßennamen.
8:50 Punkt drei ist methodisch entscheidend: Der Vergleich alt gegen neu ist nur bei gleichem Schema aussagekräftig. Und Punkt vier ist die Zusammenfassung: Verhaltensänderung und Schemaänderung zugleich lassen sich nicht auseinanderhalten. Der rote Faden: Bestand festhalten, versionieren, prüfen, verstehen — und erst danach ändern.
9:10 Schritt vier ist der ungewöhnliche: Abweichungen einzeln prüfen und begründen, nicht anpassen. Der Reflex ist, das Entity so zu ändern, bis validate grün wird. Aber jede Abweichung sagt Ihnen etwas: Vielleicht hat die Tabelle eine Spalte, die kein Entity kennt, weil eine andere Anwendung sie füllt. Und wie die Fußzeile sagt: Ein Abgleich, der beim ersten Lauf grün ist, ist der beste Beleg für eine saubere Übernahme — er kommt nur selten vor.
9:38 Punkt eins ist der, der in Abnahmeumgebungen häufig passiert: Hibernate erzeugt das Schema und weicht dabei still vom Bestand ab. Danach testen Sie gegen ein Schema, das es in Produktion nicht gibt. Punkt zwei ist die gefährlichste Variante des Schemathemas: Die Migration ändert einen Spaltentyp, den die Altanwendung noch schreibt.
9:57 Punkt drei betrifft Zählwerk unmittelbar: Zeitstempel wechseln die Zeitzone und verschieben Abrechnungsgrenzen — bei Ableseterminen ist das kein Schönheitsfehler, sondern ein fachlicher Fehler. Und Punkt vier: Auch Datenbankversion und Sortierfolge gehören zwischen Test und Produktion angeglichen.
Übung
10:14 In der Übung übernehmen Sie Zähler und Ablesung. Und Sie weisen nach, dass sich am Datenmodell nichts geändert hat. Der Nachweis ist ein grüner Abgleich und drei Abfragen, die dieselben Datensätze liefern wie der Bestand. Mehr braucht es nicht, weniger genügt nicht. Sie übernehmen die Entities für Zähler und Ablesung, konfigurieren Datenquelle und Pool und binden die Schema-Migration ein.
10:38 Das Lernziel ist, Datenzugriff zu übertragen, ohne das bestehende Datenmodell zu verändern — die schwierigste Disziplin dieses Tages, weil die Versuchung so groß ist, gleich aufzuräumen. Erfolgreich sind Sie, wenn der Abgleich mit validate ohne Abweichung läuft und Ihre Vergleichsabfragen dieselben Datensätze in derselben Reihenfolge liefern wie der Bestand.
10:57 Und der Hinweis ist ernst gemeint: Panache bleibt in dieser Übung außen vor — eine Änderung nach der anderen. Fünf Schritte, und der letzte ist der aussagekräftigste: drei Abfragen gegen Bestand und Neubau vergleichen. Nicht nur, ob dieselbe Anzahl herauskommt — auch dieselben Datensätze in derselben Reihenfolge. Und wie die Fußzeile sagt: Weichen die Ergebnisse ab, ist die Ursache fast immer eine fehlende Sortierung. Das ist eine dieser Erfahrungen, die eine Stunde Fehlersuche sparen.
11:27 Prüfen Sie also zuerst, ob die Abfrage überhaupt eine Sortierung vorgibt, bevor Sie tiefer graben. Schritt drei — die erste Migrationsdatei aus dem Bestandsschema — ist übrigens weitgehend automatisierbar. Punkt eins ist die Rechnung, über die wir gesprochen haben: Die Poolgröße bleibt bei dem Wert, den ein einzelner Server brauchte — mal Instanzenzahl ergibt das eine unangenehme Überraschung.
11:50 Punkt zwei passiert beim ersten Durchlauf fast jedem: Schreibende Methoden vergessen die Transaktionsannotation; immerhin scheitert das laut. Punkt drei entwertet die ganze Übung: Der Vergleich läuft auf verschiedenen Datenständen und beweist nichts. Und Punkt vier ist der, der stillschweigend Schaden anrichtet: Das Schema wird beim ersten Start doch erzeugt, weil die Strategie nicht gesetzt war.
Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →