EJB-Persistenz mit Java SE

(http://www.zdnet.de/magazin/39142744/ejb-persistenz-mit-java-se.htm)

von DJ Walker-Morgan, 11. April 2006

Seit dem Entwurf von EJB 3.0 gibt es einen einzigen, standardisierten Persistenzmechanismus für Java-Klassen, sei es für client- oder serverseitige Anwendungen. Mithilfe von Annotations, einem Feature von Java 5, ist dieser Mechanismus einfach zu benutzen. Wie das funktioniert, soll hier gezeigt werden.

Persistente Java-Klassen - zu deren Realisierung gab es so viele Verfahren, dass das Aufkommen einer standardisierten Methode für alle Einsatzbereiche von den Entwicklern sicherlich mit Erleichterung aufgenommen wird. Die Herausforderung bestand darin, die Kluft zwischen der Standard- und der Enterprise-Version von Java zu überbrücken und eine standardisierte API bereitzustellen, welche für Enterprise-Anwendungen funktioniert, die in verwalteten Containern ausgeführt werden, aber auch gleichermaßen für Standard-Anwendungen, welche sich selbst, ohne Container, verwalten. Nun gibt es eine solche API innerhalb von JSR-220, der Enterprise-Java-Beans-3.0-Spezifikation. Im Zuge der Fortentwicklung von JSR-220 wurde diese Spezifikation in zwei Teile aufgeteilt: EJB 3.0 Persistence und EJB 3.0 Core (also der ganze Rest).

EJB 3.0 Persistence unterscheidet sich von früheren EJB-Persistenzlösungen und nutzt die neuen Annotations in Java 5.0 sowie die Erfahrungen der unterschiedlichen POJO-Persistenz-Entwickler.

Derzeit hat die EJB 3.0 Persistence-Spezifikation den Status "Final Draft", das heist es sind noch nicht alle Elemente endgültig festgelegt. Trotzdem sollte man sich schon jetzt mit einigen der Implementierungen der verfügbaren Spezifikation vertraut machen. Die Referenzimplementierung findet sich bei GlassFish, eine weitere Implementierung sind die Annotation- und EntityManager-Projekte von Hibernate. Zuerst die Grundlagen. Hier die Anleitung, wie man eine Klasse unter Java SE persistierbar macht. Folgender Code zeigt ein POJO (Plain Old Java Object):

Um aus einer Klasse eine Entity zu gestalten, die persistent gemacht werden kann, fügt man am Anfang die Annotation @Entity hinzu, um sie wie folgt als persistierbar zu markieren:

Für alle, die sich nun fragen, was denn dieses @Etwas bedeutet, hier eine kurze Vorstellung der so genannten Annotations. Java fehlte früher immer eine Möglichkeit, Metadaten in den Code einzubinden. Javadoc war eine Art von Metadaten zum Erstellen von Dokumentation und funktionierte, indem man Tags mit dem Präfix @ innerhalb von Kommentarblöcken verwendete. Auf dieser Idee baut XDoclet auf, welches zur Speicherung eigener Metadaten verwendet werden konnte. Ein alternativer Ansatz bestand darin, jeder Klasse eine entsprechende XML-Datei zuzuordnen. So verwendete Hibernate zum Beispiel eine .hbm.xml-Datei zusammen mit der Klasse für die Abbildungsinformationen für die ebenso benannte Klasse.

Beide Techniken funktionieren gut, aber bei Java 5 kam der Wunsch nach einer Inline-Implementierung auf, die durch den Compiler verifizierbar und zur Laufzeit erkennbar ist. Und genau hier kommen Annotations ins Spiel. Für die meisten Zwecke reicht es, zu wissen, dass solche Annotations die Methode sind, mit deren Hilfe man Metadaten hinzufügen kann, wie man eine Klasse persistent abbildet.

Die @Entity-Annotation markiert die Klasse als persistent. Um sie einfach abrufen zu können, benötigen die POJOs jeweils einen Primärschlüssel. Eine übliche Methode hierfür besteht darin, ein Feld vom Typ Long hinzuzufügen, welches als Primärschlüssel dient. Und so fügt man den Klassen dieses Feld hinzu:

Die @Id-Annotation markiert die Getter-Methode als das Primärschlüsselfeld und die @GeneratedValue-Annotation sorgt dafür, dass die Persistenzschicht einen Wert für dieses Feld generiert. Das reicht schon. Nun kann man mit einer Adresse arbeiten.

Um das Address-Objekt persistent zu machen, ist ein EntityManager nötig, welchen man für jede Aufgabe erhält, die mit den persistenten Daten durchgeführt werden soll. Dies ist der große Unterschied zwischen Persistenz unter Java EE und Java SE. In Java EE gibt es Annotations, welche es dem umgebenden Framework ermöglichen, die Entities zu verwalten. In Java SE wird die Verantwortung hierfür dem Entwickler überlassen, der sich um einen EntityManager kümmern muss. Einen EntityManager erhält man von einer EntityManagerFactory.

Später gibt es mehr Informationen dazu, woher man eine EntityManagerFactory erhält . Erst einmal reicht es, davon auszugehen, dass diese initialisiert ist und einen EntityManager liefern kann. Wenn man mit Java SE Persistenz erreichen will, muss man sich außerdem um die Verwaltung der Datenbanktransaktionen kümmern, die man beginnen und beenden muss. Und so erstellt man eine Transaktion und beginnt sie:

Nun kann man den EntityManager auffordern, das Address-Objekt persistent zu machen:

Dann führt man den Befehl commit für die Transaktion aus und schließt den EntityManager;

Danach wird der Person eine ID zugewiesen und sie wird in die Datenbank geschrieben. Die Abfolge der Befehle get und commit für eine Transaktion ist typisch für jeden Code, der den persistenten Zustand von Objekten ändern will. Aus Gründen der Zuverlässigkeit sollte der Code wie folgt Exceptions auffangen und ein Rollback durchführen, falls es Probleme gibt:

Im Folgenden wird angenommen, dass dieser Code die Beispiele umschließt, wenn es um die Modifizierung der Datenbank geht. Zum Abrufen von Daten benötigt man nur den EntityManager.

Um die vorher gespeicherte Adresse abzurufen, kann man die ID als Referenz verwenden. Da man keine Transaktion vom EntityManager benötigt, ist die Sache ganz einfach:

Falls man eine Adresse nach Postleitzahl abrufen will, kann man die EJB Query Language (EJBQL) verwenden, um eine Abfrage zu definieren, aus welcher der EntityManager dann die Abfrage erzeugt.

Die Abfrage lässt sich erweitern, um Adressen zu finden (von allen persistenten Address-Objekten und sie gibt die Ergebnisse als Address-Objekte zurück), bei denen die Adresse eine Postleitzahl hat, welche mit dem "noch zu setzenden" Parameter namens "param" übereinstimmt. Diesen Parameter kann man jetzt festlegen:

Um das Ergebnis zu erhalten, ruft man die Methode getResultList für die Abfrage auf, wenn man mehrere Ergebnisse erhalten möchte, oder getSingleResult(), wenn man nur ein Ergebnis erwartet:

oder

Als Letztes fehlt noch die Möglichkeit, eine Adresse zu ändern und die Änderungen in die Datenbank zu übertragen. Vorausgesetzt wird, dass die obige Abfrage eine einzelne Adresse geliefert hat.

Und hier ein weiteres POJO namens Person, welches bereits mit Annotations zwecks Persistenz versehen ist:

Der wichtige Teil ist die @ManyToOne-Annotation für das Adressfeld. Die @ManyToOne-Annotation ist eine von mehreren, welche die Beziehung zwischen den einzelnen Entities beschreiben, in diesem Fall besagt sie, dass mehrere Personen dieselbe Adresse haben können. Dies muss jeweils eine Referenz auf eine bereits persistente Adresse sein.

Die merge-Methode kann auf intelligente Weise mit den übergebenen Entities umgehen: Wenn die Entity nicht gemanagt ist, wird sie persistent gemacht, andernfalls werden die Unterschiede ermittelt und die Datenbank wird entsprechend aktualisiert. Damit ist das persistent gemachte Person-Objekt entsprechend behandelt. Das wären die Grundlagen der Persistenz. Außer natürlich die EntityManagerFactory, deren Existenz einfach als gegeben angenommen wurde. Aber darauf kommt es schließlich an. Hier ein typischer Aufruf, um sie zu erzeugen:

Man fordert einfach die Persistenz-Schicht auf, eine Factory mit dem Namen "example" zu erstellen. Die hierzu verwendeten Konfigurationsinformationen stammen aus einer Datei namens persistence.xml, welche im Verzeichnis META-INF des Source-Baumes gespeichert sein sollte. Sie definierte eine benannte "persistence-unit", welche Details darüber enthält, wie Persistenz verwaltet werden sollte, welcher zugrunde liegende Persistence Provider die Aufgabe übernimmt und welche Datenbank verwendet wird. Hier ein Codeschnipsel:

Hier gibt es eine persistence-unit namens "example", welches der Name ist, der der Methode createEntityManagerFactory()übergeben wurde. An diesem Punkt kommt die Kombination Hibernate Annotations/EntityManager zum Einsatz, daher verweist das <provider>-Element auf den HibernatePersistence-Provider. Es kann ein oder mehrere <class>-Elemente geben, von denen jedes auf eine Klasse verweist, welche persistent gemacht werden soll. In diesem Beispiel gibt es Klassen-Elementen für die Person und für die Adresse. Es gibt auch ein <properties>-Element, welches <property>-Elemente zum Setzen implementierungsspezifischer Werte enthält, die der erzeugten EntityManagerFactory übergeben werden können. In diesem Beispiel werden Hibernate und HSQLDB eingebettet verwendet. Außerdem muss der JDBC-Treiber so eingerichtet werden, dass er eine URL zur Verbindung mit der Datenbank verwendet, und Hibernate muss wissen, welcher Dialekt von SQL verwendet werden soll:

Die eingebettete HSQLDB startet mit dem Benutzernamen "sa" und einem leeren Passwort. Außerdem soll begrenzt werden, bis zu welcher Tiefe Hibernate entsprechende Objekte abruft.

Und schließlich wird noch die Eigenschaft hbm2ddl.auto gesetzt. Diese steuert die Fähigkeit von Hibernate, die erforderlichen Tabellen in der SQL-Datenbank dynamisch zu erstellen. Wenn sie auf "create" gesetzt ist, wird die Datenbank bei jedem Aufruf gelöscht und die Tabellen werden neu erzeugt. Wenn sie auf "update" gesetzt ist, wird versucht, die vorhandenen Tabellen so zu modifizieren, dass sie für die aktuellen Versionen der Entities geeignet sind:

Das Erzeugen der DDL, um die Datenbank zu erstellen, sollte mit den meisten Implementierungen möglich sein, aber der Schalter hierfür ist je nach Implementierung unterschiedlich.

Wenn diese XML-Datei erstellt ist, liegen alle Informationen vor, so dass createEntityManagerFactory die einfache Datenbank erstellen kann. Diese Datei (persistence.xml) sowie der Beispielcode finden sich im Quellcode zu diesem Artikel. Um ihn ausführen zu können, braucht man allerdings die entsprechenden Bibliotheken. Da wären einmal die HSQLDB-Datenbank, Hibernate, Hibernate-Annotations und Hibernate-EntityManager von der Hibernate-Website. Die readme-Datei, welche beim Quellcode enthalten ist, führt auf, welche Bibliotheken man benötigt und wo man sie bekommt. Sobald das Beispiel läuft, kann man damit experimentieren und selbst eine einfache persistente Entity erstellen. Wenn man wissen will, was als SQL ausgeführt wird, kann man den Eigenschaften in der Datei persistence.xml einfach folgende Codezeile hinzufügen:

Hibernate wird dann auch jede SQL-Anweisung ausgeben, die es unter der Haube ausführt. Damit sind die Grundlagen für Persistenz sowie einige einfache Mappings vorhanden. Den Source Code für dieses Tutorial gibt es auch als Download[1].

Ressourcen

URLs in diesem Artikel:
[1] = http://www.zdnet.de/i/bld/program/2006/04/quick_example-dist.zip
[2] = http://hibernate.org/
[3] = http://annotations.hibernate.org/
[4] = http://entitymanager.hibernate.org/
[5] = http://glassfish.dev.java.net
[6] = http://jcp.org/aboutJava/communityprocess/pfd/jsr220/index.html