JavaHibernate
Back to current versionRestore this version

Hibernate Java ORM#

Hibernate ist ein ORM-Framework für [Java]]. ORM bedeutet: "Objekt-relationales Mapping" und heisst im Klartext, daß eine Datenstruktur von Objekten, so wie sie in Java üblich ist, in eine relationale Datenbank übertragen wird. Das bedeutet, daß eine Java-Applikation einfach und komfortabel auf eine echte Datenbank wie z.B. MySQL zugreifen kann. Die Daten werden somit persistent gemacht.

Zur Einführung ins Thema empfiehlt sich ein Blick auf die Webseite. Eine sehr tiefgehende Anleitung findet sich auf Java Persistenz mit Hibernate, das Thomas Bayen besitzt und wärmstens empfiehlt.

Wer jetzt neu anfängt, sollte überlegen, als Zugriffs-API direkt JPA zu verwenden. Das ist ein neuerer Java-Standard (seit Java6), der maßgeblich von Hibernate inspiriert wurde und deshalb ausgezeichnet unterstützt wird. Dennoch kann man jederzeit für besondere Anforderungen auch einzelne Hibernate-Spezialbefehle oder -Konfigurationen benutzen. Da JPA von verschiedenen ORM-Produkten zur Verfügung gestellt wird, kann man so Know-How (und ggf. sogar die ganze ORM-Bibliothek) austauschen. Es steht zu erwarten, daß die JPA-API langfristig die ursprüngliche Hibernate-API komplett verdrängen wird.

Tips und Tricks #

Bei der Arbeit mit Hibernate ist mir vor allem aufgefallen, wie komplex das Thema doch in Wirklichkeit ist. Früher dachte ich immer: Was regen die sich in all den SQL-Büchern so auf, die paar Tabellen können doch nicht so kompliziert zu bearbeiten sein. Im praktischen Betrieb mit Hibernate merkt man aber immer wieder, daß man auf Grenzen und "Bugs" stößt, die gar keine Bugs sind, sondern die in der Natur der Daten liegen, die man vorher evtl. nicht genau durchschaut hatte. Ich möchte betonen, daß ich bei aller Komplexität des Themas noch auf kein Problem gestossen bin, das man mit Hibernate nicht angemessen lösen konnte. Da mir einige dieser Aha-Momente schon wieder entfallen sind, möchte ich in Zukunft hier einen Platz haben, um sie festzuhalten.

eingebettete Arrays #

Man kann relativ einfach ein Array aus eingebetteten Komponenten erzeugen.

Wir erinnern uns: Komponenten sind Objekte, die keine eigene Identität besitzen und deshalb keine eigene Entity sind. Gedacht sind diese, damit man z.B. in Kunde eine Adresse einbinden kann, ohne daß diese Aufteilung innerhalb der Java-Klassen auch eine eigene Tabelle in der relationalen Datenbank erzeugt. Komponenten werden mit @Embeddable annotiert.

Nun kann man aber auch ein Array von Komponenten erzeugen. Dabei annotiert man ein Collection-Feld mit @CollectionOfElements. Jetzt kann man z.B. seinem Kunden mehrere Telefonnummern zuordnen. Intern geht das natürlich nicht ohne eigene Tabelle, das wird aber weitgehend vor dem Benutzer versteckt.

So weit - so gut. Für den normalen Benutzer reicht es bis hierhin. Ich bin nun aber auf ein besonderes Phänomen gestossen: Ändert man eines der Komponentenobjekte im Array, so werden als Antwort darauf beim nächsten flush() des Persistenzkontextes (also zumeist beim nächsten commit()) alle(!) Telefonnummern dieses Kunden aus der entsprechenden Tabelle mit DELETE gelöscht. Danach werden alle Telefonnummern wieder mit INSERT neu erzeugt. Bis dahin ist das erstmal umständlich und witzig, aber der Spaß hörte auf, als ich merkte, daß ein Zeiger auf eine Komponente, den ich zwischengespeichert hatte, nun nicht mehr gültig war. Das vorher gespeicherte Objekt war ja gelöscht worden, also konnte ich mit diesem nichts mehr machen - es war nicht mehr persistent.

Nach einigem überlegen fiel mir auf, das Hibernate gar nicht anders handeln kann. Meine Liste erlaubt ja grundsätzlich mehrere identische Telefonnummern. Wenn ich jetzt eine davon ändere: woran soll Hibernate erkennen, welche ich denn nun ändern will?!? Die fehlende Objektidentität zwingt also förmlich dazu, die ganze Collection zu ersetzen (zu löschen und neu zu erzeugen).

Nun war die Frage, wie man diese verlorene Identität wiederbekommen kann. Kann man, und zwar mit

  @CollectionId(columns = { @Column() }, generator = "sequence", type = @Type(type = "long"))

vor dem Collection-Feld. Diese Annotation ist laut Javadoc noch experimentell. Dementsprechend ist es zu verschmerzen, daß obige Zeile einige Informationen enthält, die ich eigentlich gar nicht einstellen will. (Für Neulinge: Hibernate wählt sonst immer sehr schön Standard-Parameter aus, so daß man vieles gar nicht konfigurieren muss.)

Diese Zeile führt dazu, daß die interne Tabelle, in der die Komponenten stehen, eine Primärschlüsselspalte bekommt. Bei der BEarbeitung der Persistenz merkt Hibernate das sofort und macht statt obigem DELETE & INSERT ein (richtigeres) UPDATE. gleichzeitig bleiben dann auch die vorhandenen Objekte persistent und deren Zeiger somit gültig.

Meine letzte Frage - die ein wenig offenbleibt - ist nun noch die, was denn nun eigentlich im Ergebnis der Unterschied zwischen dieser Lösung und der Verwendung einer echten Entity für meine Telefonnummern ist. Durch die Modellierung per Komponente hat mein Objekt offiziell (über die Java-Objekte) immer noch keine Datenbank-Identität (also keinen Primärschlüssel). Ich kann da also gar nichts falsch machen. Es ist immer sicher, daß das Löschen eines Kunden auch das Löschen einer Telefonnummer nach sich zieht. Andererseits kann man diese Beziehung per Kaskadierungs-Parametern auch für Entity-Beziehungen erzeugen. Letztlich ist es also vielleicht eine Geschmackssache.

-- ThomasBayen

Performance #

Obwohl Hibernate erstaunlich schnell sein kann, so kann man natürlich jedes Programm immer noch verbessern und beschleunigen. Ich entwickle gerade eine Datenbank-Anwendung, deren Datenbank über das Internet geschieht. Das ist so richtig schön langsam, so daß man dabei einiges über Performance lernt, was natürlich auch einem normalen Programm gut zu Gesicht steht. Ich werde versuchen, hier zu notieren, was denn nun wirklich hilft.

Zuerst mal habe ich nur einige Ideen und Stichworte gesammelt, mit denen man sich beschäftigen kann:

Logging des erzeugten SQL-Codes #

Über die normale Logging-Konfiguration kann man alle von Hibernate erzeugten SQL-Befehle loggen. Das sieht in meiner logback.xml so aus:

  <!-- Ausgabe des generierten SQL-Codes (...SQL) ggf. mit übergebenen Parameter-Werten (...type) -->
  <logger name="org.hibernate.SQL" level="TRACE"/>
  <logger name="org.hibernate.type" level="ERROR"/>

Mit Hilfe dieses Loggings bin ich z.B. auf das oben beschriebene Problem mit den eingebetteten Arrays gestossen.

Auf http://www.p6spy.com/ gibt es einen JDBC-Treiber, der sich zwischen die Applikation und einen "echten" JDBC-Treiber schiebt und alle Zugriffe loggt. Ob das aufschlußreicher ist als das normale Logging, habe ich noch nicht ausprobiert.

Optimieren des Zugriffs #

Logging alleine hilft natürlich noch nichts. Dem steht die Frage gegenüber: Wie hätte ich selbst das in SQL gemacht und warum macht Hibernate das anders. Ich muss vorausschicken, daß ich es bisher immer geschafft habe, Hibernate denselben SQL-Code verwenden zu lassen, den ich auch selbst geschrieben hätte. Also das Logging betrachten und nachdenken! In dem Zusammenhang kann man übrigens auch über Lazy Loading von Unterkomponenten bzw. Unter-Collections nachdenken. Das Stichwort hier heisst fetching-Strategie.

Ein anderes Beispiel ist mir untergekommen, als das Model einer Swing-Komponente (eine JList) mit einer Hibernate-Abfrage unterlegte. Einerseits ist das eine tolle Sache, weil in der Liste immer aktuell der Inhalt der Datenbank angezeigt wird. Andererseits bemerkte ich, daß die Datenbank unendlich oft nach der Größe der Liste gefragt wurde. Sowas muss einem natürlich auffallen! Beim Debugging stellte sich heraus, daß dieser Wert beim Berechnen des Layouts immer wieder abgefragt wurde. Das zu ändern, hätte bedeutet, Swing neu zu programmieren. Als Lösung habe ich nun in meinem Model ein Flag eingebaut, um diese Größe zu cachen. Diesen Cache schalte ich in meiner Listenkomponente jeweils vor den Methoden paint() und validateTree() ein und nachher wieder aus. Und schon habe ich zwei Drittel der Datenbankabfragen eingespart!

Links zum Thema Hibernate-Performance #


Tags:  Java, Datenbank