Was sind die Herausforderungen bei komplexen Enterprise-Anwendungen mit Hibernate und JPA?
Die meisten Herausforderungen sind performanzbedingt und können grundsätzlich in zwei Gruppen eingeteilt werden: Probleme, die bei allen datenbankbasierten Anwendung auftreten und JPA- und Hibernate-spezifische Probleme.
Allgemeine Probleme
In die erste Kategorie gehören unter anderem langsame Abfragen, zum Beispiel weil Indizes fehlen oder zu viele Daten selektiert werden. Solche Probleme können immer auftreten, wenn man mit einer Datenbank arbeitet. Bei der Verwendung von JPA und Hibernate werden diese Probleme aber noch dadurch verstärkt, dass die Datenbankinteraktionen vom Framework hinter API-Aufrufen und einer eigenen Abfragesprache (JPQL und HQL) versteckt werden. Das macht es für die Entwickler häufig schwer, die erzeugten Datenbankabfragen zu verstehen und zu optimieren. Die Probleme werden dadurch nicht rechtzeitig erkannt und finden ihren Weg bis zum Produktionssystem.
Um das zu verhindern, sollten während der Entwicklung die erzeugten Datenbankabfragen überwacht und kontrolliert werden. Zusätzlich sollte ein möglichst produktionsnaher Datenbestand für die Tests verwendet werden. Das erlaubt es, problematische Abfragen zu erkennen bevor sie zu Beeinträchtigungen auf dem Produktionssystem führen.
JPA- und Hibernate-spezifische Probleme
Die von Hibernate und JPA implementierte Objekt-Relationale-Abbildung bietet während der Entwicklung viele Vorteile. Durch die Abbildung der Datensätze auf Entitäten und die Abstraktion der Datenbankinteraktion können sich die Entwickler auf die Erstellung der Geschäftslogik konzentrieren und sind dadurch viel produktiver.
Aber diese Abbildung bietet nicht nur Vorteile. Trotz der erfolgten Abstraktion darf man nie vergessen, dass alle Daten in einer Datenbank persistiert und von dort gelesen werden. Es wird daher ein gutes Verständnis der verwendeten Datenbank benötigt.
Des Weiteren sollte die Anzahl und Art der erzeugten Datenbankabfragen während der Entwicklung überwacht werden. Kleine Änderungen in den Annotationen oder in der Verwendung eines Abfrageergebnisses können Hibernate dazu zwingen, zusätzliche Abfragen auszuführen.
Ein typisches Beispiel dafür ist das „n+1 select“-Problem. Dabei führt Hibernate zusätzliche Abfragen aus, um die Beziehungen von bereits geladenen Entitäten aus der Datenbank zu lesen. Auf kleineren Testsystemen wird dieses Problem häufig nicht sofort bemerkt, da die Tests nur wenig Entität laden und Hibernate somit nur einige wenige zusätzliche Abfragen ausführt. Das ändert sich aber auf dem Produktionssystem, wenn plötzlich hunderte oder sogar tausende Abfragen benötigt werden.
Dies ist nur eines der Beispiele bei denen über die mangelnde Performanz von JPA und Hibernate geklagt wird. Dabei liegt die Ursache in einer falschen Annotation oder einer unvorteilhaft definierten Abfrage und könnte leicht verhindert werden.
Warum sind Konzepte für die Performanceoptimierung bei Hibernate so wichtig?
JPA und Hibernate machen es sehr einfach, ein paar Datensätze in einer Datenbank zu speichern. Mit Hilfe intelligenter Default-Werte und generierter Datenbankabfragen kann man mit wenig Aufwand eine Persistenzschicht für eine neue Anwendung erstellen.
Für eine Enterprise-Anwendung, die von vielen Nutzern parallel verwendet werden soll, reicht dies aber nicht aus. Die Anwendung die in der lokalen Testumgebung schnell und fehlerfrei alle Anfragen beantwortet hat, hält häufig im Produktivbetrieb den gesteigerten Anforderungen nicht mehr stand. Dann ist meistens ein aufwändiges Refactoring notwendig.
Hibernate ist allerdings nicht grundlos zu einer der meist verbreiteten Persistenzlösungen im Java-Umfeld geworden. Mit ein wenig Erfahrung und durch Einhaltung grundlegender Konzepte, können diese Probleme vermieden werden. Damit kann Hibernate auch höchste Performanzanforderungen erfüllen.
Gibt es ein Patentrezept, um Performanceprobleme zu lösen?
Ein allgemeingültiges Patentrezept lässt sich nur schwer formulieren. Dafür sind die Anforderungen der Anwendungen zu unterschiedlich. Daher ist es wichtig die verschiedenen Konzepte und deren Verwendung genau zu verstehen, um den jeweils optimalen Ansatz für die vorliegende Anwendung auszuwählen.
Grundsätzlich sollte außerdem jeder Entwickler darauf achten, die folgenden nicht Hibernate-spezifischen Prinzipien einzuhalten:
- Bei jeder Änderung oder Neuentwicklung sollten die erzeugten Datenbankabfragen auf ihre Notwendigkeit und Geschwindigkeit geprüft werden.
- Es sollten nur die Datensätze und Spalten selektiert werden, die von der Anwendung auch wirklich benötigt werden.
- Die meisten Datenbanken bieten mit Funktionen und Stored Procedures verschiedene Möglichkeiten, komplexere Operationen abzubilden. Für besonders datenintensive Aufgaben können diese deutlich effizienter sein als eine vergleichbare Implementierung innerhalb der Anwendung.
Was sind die wichtigsten Punkte, um beim lesenden Datenbankzugriff eine ordentliche Geschwindigkeit hinzubekommen?
Die beiden Hauptursachen langsamer Select-Abfragen sind meistens fehlende Indizes in der Datenbank und das Laden zu großer Datenmengen durch Hibernate. Bei Ersterem handelt es sich um kein Hibernate-spezifisches Problem. Es kann mit den üblichen Datenbankwerkzeugen analysiert und behoben werden. Das Laden zu großer Datenmengen ist jedoch ein Hibernate-spezifisches Problem. Grundsätzlich generiert Hibernate sehr gute und effiziente SQL-Abfragen. Wenn Probleme auftreten, liegt das meistens an der unglücklichen Verwendung von Mapping-Annotationen oder ineffizient formulierten JPQL-Abfragen, die von Hibernate in ebenso ineffiziente SQL-Abfragen übersetzt werden.
Um dies zu vermeiden, sollte bereits bei der Erstellung der Entitäten deren spätere Verwendung und die benötigte Performanz berücksichtigt werden. Das bedeutet zum Beispiel, dass eine möglichst einfache Abbildung der Tabellen auf Entitäten bevorzugt und Beziehungen immer mit FetchType.LAZY geladen werden sollten.
Die effizient verwendeten Mapping-Annotationen bilden dann die Grundlage, um gute JPQL-Abfragen zu erstellen. Hierbei gilt es, genau wie bei SQL, alle benötigten aber keine unnötigen Daten mit möglichst wenigen Abfragen zu selektieren. Dazu bieten Hibernate und JPA einige interessante Features. Verschiedene Caches vermeiden zum Beispiel die Ausführung redundanter Abfragen. Außerdem können Beziehungen innerhalb einer Abfrage initialisiert werden, um das „n+1 select“-Problem zu vermeiden. Des Weiteren können Abfragen mit unterschiedlichen Projektionen verwendet werden, die nur die benötigten Informationen laden. Wann und wie diese Features angewendet werden können um den Lesezugriff zu beschleunigen, werden wir im Seminar genauer betrachten.
Und wie sieht das bei schreibenden Datenbankoperationen aus?
Schreibende Datenbankoperationen sind in den meisten Anwendungen nur dann problematisch, wenn sehr viele Datensätze angelegt, bearbeitet oder gelöscht werden sollen. Hierbei hilft es häufig, die Operationen nicht auf einzelnen Entitäten auszuführen oder mehrere Operationen mit Hilfe von JDBC Batching zusammenzufassen. Was beachtet werden muss, um Dateninkonsistenzen zu vermeiden, werden wir uns ebenfalls im Seminar genauer ansehen.
Was lernen die Teilnehmer darüber hinaus in Ihrem Seminar?
Die Teilnehmer lernen in dem Seminar, wie sie mit Hilfe von Hibernate-eigenen Mitteln potentielle Perfomanzprobleme schon während der Entwicklung erkennen können, damit sie diese beheben können, bevor sie in der Produktionsumgebung auftreten. Basierend darauf werden wir verschiedene Konzepte erarbeiten mit denen sowohl lesende als auch schreibende Datenbankzugriffe beschleunigt werden können, wann und wie datenintensive Operationen durch die Datenbank durchgeführt werden können, wie Caches unnötige Abfragen verhindern können und wie negative Auswirkungen durch konkurrierende Datenbankzugriffe vermindert werden können.



