Die Java Persistence API Seminararbeit im Studiengang Scientific Programming WS 2012 / 2013 Autor: Andreas Kuck Matrikelnummer: 846239 1. Betreuer: Prof. Dr. Bodo Kraft 2. Betreuer: Dipl.-Math. (FH) Michael Neßlinger Datum: 15. Dezember 2012 Programmierer müssen heutzutage immer größere Datenmengen verarbeiten. Dabei bekommt das dauerhafte Speichern, auch Persistieren genannt, von Daten eine wachsende Bedeutung. Die Aufgabe eines Programmierers besteht oft darin, den Zustand eines Objekts festzuhalten und an späterer Stelle wiederherzustellen. Die Java Persistence API kann den Entwickler zwar nicht komplett von dieser Arbeit befreien, aber stellt ein solides Werkzeug dar, um Arbeit einzusparen. Die vorliegende Seminararbeit gibt dem Leser einen groben Überblick über diese Technologie und ihre vielfältigen Möglichkeiten, eine Brücke zwischen Objekten und der relationalen Datenbank zu bauen. Inhaltsverzeichnis 1 Einleitung 1 1.1 Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.3 Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 Die Java Persistence API 3 2.1 Die Java Persistence API (JPA) . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2.2 Die Persistence XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.3 Entity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.4 Konfiguration des Mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.5 Annotations an Attributen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.6 Die Beziehungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.7 Die Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.8 Transaktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.9 Der EntityManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3 Schlussteil 22 3.1 Zusammenfasseung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4 Literaturverzeichnis 23 1 Einleitung 1.1 Vorwort Diese Arbeit ist im Rahmen meines Studiums im Fach Scientific Programming entstanden. Die Themengebung stammt von dem Unternehmen TravelTainment, in dem ich parallel zu meinem Studium, eine Ausbildung als Mathematisch-technischer Softwareentwickler mache. Sie soll ein Grundstein für eine auf sie aufbauende Bachelorarbeit sein. Ziel dieser Arbeit ist es, dem Leser einen groben Überblick über die Möglichkeiten und die Funktionsweise der Java Persistence API (JPA) zu geben. 1.2 Motivation Wenn der Zustand von Objekten eines Programms dauerhaft gespeichert werden soll, bietet es sich an, eine Datenbank zu benutzen. Die relationale Datenbank ist ein sehr verbreitetes Modell zum Persistieren von Daten. Ihr Prinzip ist leicht zu verstehen und man kann einfach Beziehungen zwischen Tabellen, auch Relationen genannt, darstellen. Die Programme werden meist in objektorientierten Programmiersprachen entwickelt. Jedoch lassen sich Objekte und deren Beziehungen zueinander nur schwer in einer relationalen Datenbank darstellen. Ähnlich schwierig ist es auch, relationale Datenbestände in ein objektorientiertes Programm zu integrieren. Man spricht hier häufig von dem “Impedance Mismatch”. Impedance Mismatch (wörtlich: Impedanz-Fehlanpassung) ist ein aus der Elektro” technik übernommener Begriff; auf dem Gebiet der Software bezieht er sich jedoch auf den inhärenten1 Unterschied zwischen dem relationalen und dem objektorientierten Datenmodell.“ [Fin12] In dieser Arbeit soll das Konzept des objektrelationalen Mappings anhand der JPA erläutert werden. Dieses Konzept kann den Prozess des Persistierens erheblich vereinfachen. Objektrelationales Mapping ist das Abbilden von Klassen in eine Datenbank. Es umfasst sowohl das Schreiben von einfachen Objekten in eine Datenbank, als auch das Mapping von Klassen unter Beachtung von Assoziationen und Vererbungshierarchien. 1.3 Aufbau der Arbeit Die Arbeit beginnt mit einer kurzen Einführung in die JPA. Danach wird die persistence.xml beschrieben, welche die Grundkonfiguration der JPA darstellt. Im Anschluss daran werden Begriffe 1 inhärent: einer Sache innewohnend (siehe Duden) 1 wie Entity und Embeddable eingeführt. Der Begriff Entity ist ein besonders zentraler Begriff in der JPA, denn jedes Objekt, welches in der Datenbank abgebildet werden soll, muss als Entity markiert sein. Des Weiteren wird erläutert, wie man Objekten zusätzliche Informationen im Bezug auf das Mapping mitgeben kann. Die darauf folgenden zwei Unterkapitel gehen mehr in die Tiefe und beschreiben, wie die JPA das objektrelationale Mapping, bezogen auf Vererbung und Assoziationen, umsetzt. Im Anschluss daran wird beschrieben, wie die Datenbanktransaktionen mithilfe des EntityManagers umgesetzt werden können. Im Schlussteil der Arbeit wird das gesamte Thema reflektiert und ein Ausblick vorgestellt. 2 2 Die Java Persistence API 2.1 Die Java Persistence API (JPA) Die JPA, die von der JSR 220 Expert Group enwickelt wurde, wurde im Jahr 2006 erstmals veröffentlicht. Im Jahr 2009 wurde sie durch viele Funktionen unter dem Namen JPA 2.0 erweitert. [Wik] In dieser Arbeit werden die Funktionen, die nicht JPA 1.0 konform sind, explizit gekennzeichnet. Wie der Name schon sagt, handelt es sich bei JPA um ein Interface, welches Methoden zum Schreiben und Lesen von Java Objekten in eine Datenbank definiert. Es wird ein Interface benutzt, um die Implementierung austauschbar zu machen. Wenn man sich komplett an den Standard hält, kann man mit wenigen Klicks die Implementierung wechseln. Implementiert wird die API z.B. durch Hibernate und EclipseLink. Der Nachteil, den ein solches Interface mit sich bringt, ist seine Trägheit. Neue Funktionen werden immer erst von den Implementierungen eingeführt und, wenn sie sich bewährt haben, ins Interface übernommen. Aus diesem Grund kann es passieren, dass man auf Funktionen zurückgreifen muss, die nicht dem JPA Standard entsprechen. In diesem Fall geht die Austauschbarkeit und mit ihr auch der Sinn der API verloren. Zum Mapping kann man grob sagen, dass jedes Objekt eine eigene Tabelle und jedes Attribut eines Objekts eine eigene Spalte in dieser Tabelle bekommt. Im späteren Verlauf dieser Arbeit wird man sehen, dass dieses grobe Konzept, gerade im Bezug auf Vererbung und Assoziationen, nicht sinnvoll ist und deswegen nicht immer in dieser Art umgesetzt wird. Die Datenbankabfragen werden in der Java Persistence Query Language (JPQL) formuliert. Die JPQL ähnelt SQL, basiert aber nicht auf Tabellen, sondern auf sogenannten Entities (s. Kapitel Entities). Die erzeugte JPQL Anfrage wird dann zur Laufzeit in eine SQL-Abfrage umgewandelt und an die Datenbank geschickt. Die JPA stellt einem die Möglichkeit zur Verfügung, den SQLDialekt, in den die Abfrage umgewandelt wird, auszutauschen. So kann man durch geringen Aufwand z.B. von MySQL auf ein anderes relationales Datenbankmanagementsystem umstellen. Desweiteren unterstützt die JPA die Nutzung von Annotations, um der Datenbank Informationen, z.B. über die Namen der Tabellen und Attribute, mitgeben zu können. Ergänzend oder anstatt diesen Annotations können auch XML-Dateien verwendet werden. 3 2.2 Die Persistence XML Die Hauptkonfigurationsdatei in der JPA ist die persistence.xml. In dieser werden die grundlegenden Einstellungen konfiguriert. Man findet hier eine oder mehrere Persistenceunits“. Diese ” Units werden dann im Code den für den Datenaustausch zuständigen Objekten übergeben. Sie enthalten folgende Verbindungsparameter für die Datenbank: • URL der Datenbank • Benutzername • Passwort • Verbindungstreiber (z.B. jdbc.Driver) Außerdem wird hier der sogenannte Provider angegeben. Dieser gibt an, welche Implementierung von der JPA benutzt werden soll. In dem nachstehenden Beispiel einer persistence.xml wird Hibernate verwendet. Beispiel einer persistence.xml: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version=”1.0” encoding=”UTF−8”?> <persistence version=”2.0” xmlns=”http://java.sun.com/xml/ns/persistence” xmlns:xsi =”http://www.w3.org/2001/XMLSchema−instance” xsi:schemaLocation=”http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence 2 0.xsd”> <persistence−unit name=”myBeispiel” transaction−type=”RESOURCE LOCAL”> <provider>org.hibernate.ejb . HibernatePersistence </provider> <properties> <property name=”hibernate.connection.username” value=”andi”/> <property name=”hibernate.connection.driver class” value=”com.mysql.jdbc.Driver”/> <property name=”hibernate.connection.password” value=”andi”/> <property name=”hibernate.connection.url” value=”jdbc:mysql://localhost:3306/hiberdemo”/> <property name=”hibernate.cache.provider class” value=”org.hibernate.cache.NoCacheProvider”/> <property name=”hibernate.hbm2ddl.auto” value=”update”/> </properties> </persistence−unit> </persistence> 2.3 Entity Durch eine Entity werden im Allgemeinen Elemente beschrieben, die Attribute und Beziehungen haben. Das Konzept der Entities ist älter als viele Programmiersprachen. Der Bergriff wurde in diesem Kontext erstmals von Peter Chen eingeführt. [KS09] In der objektorientierten Programmierung fügt man den Entities Verhaltensweisen bzw. Methoden hinzu und nennt sie Objekte. JPA betrachtet die Objekte, welche persistiert werden, als Entities. Jedoch müssen diese Objekte, damit sie erkannt und persistiert werden können, mit der Annotation @Entity gekennzeichnet werden. Des Weiteren muss immer ein öffentlicher, parameterloser Konstruktor existieren. Genau ein Attribut muss mit @Id als Primärschlüssel gekennzeichnet werden. Außerdem dürfen weder 4 die Klasse, noch die zu persistierenden Attribute mit dem Schlüsselwort final gekennzeichnet sein sein. [O’C12] Eine Ausnahme bilden hier Attribute, die durch die Annotation @Transient von der Persistierung ausgeschlossen werden. Beispiel einer Entity: 1 @Eintity 2 public class Person() { @Id 3 4 private Long id; 5 private String vorname; 6 private String nachname; 7 public class Person(){ } 8 //Getter, Setter ... 9 } Abgebildet auf die Datenbank könnte die dazugehörige Relation wie folgt aussehen: id 1 2 Person Vorname nachname Max Mustermann Laura Schneider Die ID Wie bereits erwähnt, muss jede Entity einer eindeutigen ID zugeordnet sein. Die API stellt die Annotation @GeneratedValue zur Verfügung, welche sich um das Setzten der ID im Falle einer Übertragung zur Datenbank kümmert. Diese Annotation wird zusammen mit der Annotation @Id der ID zugeordnet. Die JPA stellt dem Nutzer folgende Strategien zur Verfügung: • @GeneratedValue(strategy= GenerationType.AUTO)/@GeneratedValue Die Implementierung benutzt hier ihr Default-Verhalten. • @GeneratedValue(strategy= GenerationType.IDENTITY) Die Implementierung benutzt das Autoincrement der Datenbank (wird nicht von allen Datenbanken unterstützt). • @GeneratedValue(strategy= GenerationType.SEQUENCE) Die Implementierung benutzt Sequences (wird nicht von allen Datenbanken unterstützt). • @GeneratedValue(strategy= GenerationType.TABLE) Hier wird eine Sequenztabelle angelegt, in welcher die höchste ID der Entity gespeichert wird. Wenn eine neue Entität eingefügt wird, wird dieser Wert um eins erhöht und der neuen 5 Entity zugewiesen. Die Benennung der Sequenztabelle und ihrer Spalten kann individuell angepasst werden. Sequenztabelle sequenceName sequenceNextHiValue Mustertabelle 2 Embeddables Eine Entity kann auch Objekte von selbst definierten Klassen als Attribute besitzen. Grundbedingung dafür ist, dass diese entweder ebenfalls Entities sind oder aber sogenannte Embeddables. Diese werden mit der Annotation @Embeddable gekennzeichnet und sind immer existenzabhängig von einer Entity. Existenzabhängig bedeutet, dass diese ohne Entity, welche sie benutzt, keine eigene Repräsentation in der Datenbank haben können. Die Beziehung zwischen Entity und Embeddable ist in Folge dessen eine Komposition. Dadurch, dass ein Embeddable Objekt immer zu einer Entity gehört, ist es hier nicht nötig, einen Primärschlüssel zu definieren. Seit JPA 2 ist es möglich Embeddables ineinander zu schachteln.[rei10] Beispiel zur Benutzung eines Emeddables: 1 @Embeddable 2 public class Anrede{ 3 private String vorname; 4 private String nachname; //Getter, Setter und parameterloser Kostrunktor ... 5 6 } 1 @Entity 2 public class Person{ @Id 3 private long id ; 4 @Embedded 5 6 private Anrede anrede; 7 private int lebensdauer ; //Getter und Setter und parameterloser Konstrunktor ... 8 9 } In der Datenbank wird eine Person dann wie folgt abgebildet: id 1 Person lebensdauer vorname 40 Max nachname Mustermann 6 2.4 Konfiguration des Mappings Die JPA bietet einem Anwender die Möglichkeit über die persistence.xml hinaus einzustellen, wie die Entities in der Datenbank repräsentiert werden sollen. Dafür gibt es zwei Möglichkeiten, welche im Folgenden aufgelistet werden. • Die Mappinginformationen in eine XML auslagern • Dem Quellcode Annotations hinzufügen Welche dieser Möglichkeiten man lieber benutzen möchte, hängt von den individuellen Gegebenheiten ab. Die Annotations haben den Vorteil, dass man beim Lesen des Quellcodes sofort sieht, auf welche Art und Weise die Klassen abgebildet werden. Andererseits hat die XML den Vorteil, dass man eine Datei nutzen kann, in der ausschließlich Metainformationen zu finden sind. Dies ist besonders hilfreich, wenn man JPA mit einer anderen Technologie zusammen benutzt welche ebenfalls mit Annotations arbeitet. Im Fall, dass man beispielsweise JAXB1 parallel nutzt wird der Code durch eine vielzahl von Annotations schnell unübersichtlich. Ein anderer Vorteil den das nutzen von XML mit sich bringt ist, dass im falle von änderungen bezüglich des Mappings nicht neu compiliert werden muss. Alles was sich über das Mapping durch eine XML darstellen lässt, kann auch über Annotations abgebildet werden. Aufgrund dieses Sachverhalts, wird in dieser Arbeit nicht weiter auf die Konfiguration über XML eingegangen. 2.5 Annotations an Attributen Um Attribute mit Annotations zu versehen gibt es zwei Möglichkeiten, welche sich auch kombinieren lassen. Die beiden Darstellungen erzeugen ein unterschiedliches Verhalten bezüglich des Zugriffs auf die Attribute durch den Provider. Der Zugriffstyp einer Entity wird durch die Stelle, an der die Annotation der @Id steht, bestimmt. Dieser ist innerhalb einer Entity für alle Attribute einheitlich. Sollen verschiedene Zugriffstypen in einer Entity verwendet werden, muss dies explizit mithilfe von Annotations gekennzeichnet werden. Entities, die von anderen Entities erben, übernehmen den Zugriffstyp der Vaterklasse. Des Weiteren ist der Zugriffstyp von Embeddables gleich dem Zugriffstyp der Entity, welche diese benutzt. Field Access Hier werden die Annotations direkt über die Attribute geschrieben. Ein Beispiel: 1 @Id 2 @Column(name=”Identifier”) 3 private long id ; 1 JAXB - Programmierschnittstelle zur Transformation zwischen Java-Objekten und XML. 7 Diese Art der Annotation sorgt dafür, dass die Getter und Setter überflüssig für die Persistierung der Daten werden. Der JPA-Provider spricht die Attribute in diesem Fall über Reflexion an. Reflexion ist ein komplexes Thema. In dem folgenden Zitat wird die Idee hinter dem Konzept skizziert. Reflexion ist die Möglichkeit einer Programmiersprache, zur Laufzeit Informationen ” über die Struktur und das Verhalten des Programms selbst herauszufinden und/oder abzuändern.“[Zie08] Standardmäßig bekommen die Attribute, welche mit Field Access angesprochen werden, den Namen aus der Klassendefinition. Wenn ein Attribut einen anderen Namen haben soll, muss dies über Annotation @Column“ gekennzeichnet werden. In diesem Fall wird die ID in die Tabellenspalte ” IDENTIFIER abgebildet. Property Access Hier werden die Annotations über die Getter-Methoden der Attribute geschrieben. Ein Beispiel: 1 @Id 2 public void getIdentifier (){ return this . id ; 3 4 } Diese Art der Annotation sorgt dafür, dass der Provider direkt auf die Getter und Setter der Attribute zugreift. Der Name der Spalte, welche beim Abbilden in die Datenbank angelegt wird, wird anders als beim Field Access durch die Getter und Setter ermittelt. Das bedeutet für das gerade genannte Beispiel, dass die Spalte in der Datenbank nicht ID, sondern IDENTIFIER heißt. Ferner ist zu beachten, dass die Namen der Getter und Setter immer mit get“ bzw. set“ ” ” anfangen müssen. Darüber hinaus muss der Rückgabetyp der Getter-Methode gleich dem Typ des Übergabeparameters des Setters sein. Mixed Access (JPA 2) Manchmal ist es sinnvoll beide Zugriffsarten zu verwenden. Gegeben sei der Fall, dass von einer Entity geerbt wird, die mit Field Access arbeitet und in der geerbten Entity Property Access benutzt werden soll. Wenn in diesem Fall die Entity, von der geerbt wird, auf Field Access umgestellt wird, kann es zu Problemen bei anderen Entities, die sich in derselben Vererbungshierarchie befinden, kommen. Es bietet sich also an, nur die betroffene Entity auf Property Access umzustellen. Hier gibt es zwei Möglichkeiten: • Gesamte Entity kennzeichnen. 8 1 @Entity 2 @Access(AccessType.PROPERTY) 3 //oder 4 //@Access(AccessType.FIELD) 5 public class TestKlasse{ // ... 6 7 } • Betroffenes Attribut bzw. Getter kennzeichnen. 1 @Access(AccessType.PROPERTY) 2 public getTestAttribut { // ... 3 4 } 5 //bzw 6 //@Access(AccessType.FIELD) 7 // private String testAttribut ; 2.6 Die Beziehungen Sowohl in der relationalen Datenbank als auch in der objektorientierten Programmierung stehen Entities in Beziehungen zueinander. Dabei unterscheidet man grob zwischen drei Arten von Beziehungen zwischen zwei Entities. Im Folgenden werden diese aufgezählt: • 1:1 (Annotaion: OneToOne): Eine Ente hat einen Schnabel. • 1:n und n:1 (Annotation: ManyToOne/OneToMany): Eine Ente hat viele Federn./ Viele Enten haben einen See. • n:m (Annotation: ManyToMany): Viele Enten besuchen viele Teiche. Diese Beziehungen können einseitig, wenn nur eine Entität von der Beziehung weiß, und zweiseitig, wenn beide von der Beziehung wissen, sein. Gebräuchlich sind hier auch die Begriffe unidirektional (einseitig) und bidirektional (zweiseitig), die im weiteren Verlauf verwendet werden.[KH06] Bei einer unidirektionalen 1:1 Beziehung könnte man beispielsweise von einer Ente zu einem Schnabel navigieren, nicht aber von einem Schnabel zu einer Ente. Unidirektionale Beziehung Bei der unidirektionalen Beziehung besitzt nur eine der beiden Eintities ein Attribut, welches die mit ihr in Beziehung stehende Entity enthält. Dieses Attribut wird dann mit einer der folgenden Annotationen versehen: 9 • @OneToOne - Mit @OneToOne wird ein einfaches Attribut vom Typ der Entity, zu der die Beziehung hergestellt wird, versehen. • @OneToMany - Das Attribut muss hier vom Typ Collection sein • @ManyToOne - Ermöglicht der Gegenseite in einer OneToMany Beziehung mit der Entity zu stehen. Ergibt bei einer unidirektionalen Beziehung jedoch keinen Sinn. • @ManyToMany - Das Attribut muss hier vom Typ Collection sein. Ergibt aus demselben Grund wie ManyToOne bei einer unidirektionalen Beziehung keinen Sinn. Die Entity, von der die Beziehung nicht ausgeht, enthält keine Informationen über die Beziehung und braucht deswegen auch nicht angepasst zu werden. Bidirektionale Beziehung Die bidirektionale Beziehung ist etwas komplizierter. Hier müssen beide Entities ein Attribut der Gegenseite der Beziehung besitzen. Hinzu kommt, dass angegeben werden sollte, welche Seite der Beziehung die Daten am Ende in die Datenbank abbildet. Man kann dies über die Ergänzung des Parameters mappedBy“ zu der Verbindungsannotation angeben ” 1 @OneToOne(mappedBy = ”<Name des Beziehungsattributs der Gegenseite>”). Dieser Parameter wird auf der Seite gesetzt, auf der das Mapping der Beziehung nicht erfolgen soll. Wenn diese Angabe fehlt, werden die Beziehungen doppelt abgebildet und es entstehen Redundanzen in der Datenbank. Beispiel Im Folgenden wird das Benutzen von Beziehunge, anhand eines einfachen Beispiels erläutert. Es handelt sich um ein kleines Programm, welches Autoren und ihre Bücher verwalten soll. In dem hier konstruierten Modell, kann ein Buch nur von genau einem Autor verfasst werden. Ein Autor kann jedoch beliebig viele Bücher schreiben. Daraus ergibt sich zwischen dem Autor und dem Buch eine 1:n Beziehung. Die Klasse Buch: 1 @Entity 2 public class Buch { 3 @Id 4 @GeneratedValue 5 private long id ; 6 private String titel ; 7 //Leerer Konstruktor, Getter und Setter 8 // ... 9 } 10 Momentan ist die Klasse Buch noch eine einfache Entity mit einem über Autoinkrement erstellten Primärschlüssel und mit dem Titel des Buches als Attribut. Es wird noch keine Beziehung dargestellt. Die Klasse Autor: 1 @Entity 2 public class Autor { 3 @Id 4 @GeneratedValue 5 private long id ; 6 private String name; 7 private String vorname; 8 //Leerer Konstruktor, Getter und Setter 9 // ... 10 } Die Klasse Autor enthält eine automatisch generierte ID und den Vor- und Nachnamen des Autors. 1 @Entity 2 public class Autor { 3 // ... 4 @OneToMany private Collection <Buch> buch = new ArrayList<Buch>(); 5 6 //Leerer Konstruktor, Getter und Setter 7 // ... 8 } Um von einem Autor auf seine Bücher navigieren zu können, wurde der Klasse Autor das Attribut buch“ hinzugefügt. Dieses wurde mit der passenden @OneToMany Annotation versehen um eine ” 1:n Beziehung herzustellen. Damit haben wir unsere erste Beziehung aufgebaut. Ein einfaches Anwendungsbeispiel soll an dieser Stelle zeigen, wie die JPA nun mit diesen Entities umgeht. 1 Autor autor = new Autor(); 2 autor .setName(”Mustermann”); 3 autor .setVorname(”Max”); 4 em.getTransaction() .begin() ; 5 Buch buch = new Buch(); 6 buch. setTitel (”Das erste Buch”); 7 Buch weiteresBuch = new Buch(); 11 8 em. persist (weiteresBuch); 9 weiteresBuch. setTitel (”Das zweite Buch”); 10 autor .getBuch().add(buch); 11 autor .getBuch().add(weiteresBuch); 12 //Objekte werden dem Persistenzkontext hinzugefügt 13 em. persist (buch); 14 em. persist (weiteresBuch); 15 em. persist (autor) ; 16 em.getTransaction() .commit(); Erzeugte Tabellen: id 1 2 buch titel Das erste Buch Das zweite Buch id 1 name Mustermann autor autor autor id 1 1 vorname Max buch buch id 1 2 Auffällig ist, dass eine extra Verbindungstabelle aufgebaut wird, obwohl es sich bei dieser 1:n Beziehung eher anbieten würde nur eine Verbindungsspalte bei Buch einzufügen. Das Problem ist, dass die Beziehung von Autor ausgeht. Die einzige Möglichkeit, die bleibt um auf eine Verbindungstabelle zu verzichten, ist die Verbindungsinformationen in die Tabelle autor“ ” zu schreiben. Dies würde wie folgt aussehen: id 1 name Mustermann autor vorname Max buch 1,2 In der Spalte Buch“ ist die Atomarität der Daten nicht mehr gewährleistet. Ein Verstoß gegen ” die Atomarität ist ein unerwünschter Konflikt mit der ersten Normalform. Mit dem zugrundeliegenden Quellcode besteht für die Implementierung nur die Möglichkeit eine Verbindungstabelle zu erzeugen. Zusammenfassend bezeichnet man die Beziehung, die wir hier erzeugt haben, als unidirektionale 1:n Beziehung. Man kann zwar vom Autor auf seine Bücher navigieren, jedoch nicht von einem 12 Buch auf den Autor. Um dies zu verändern müssen Anpassungen an der Entity Buch vorgenommen werden. 1 @Entity 2 public class Buch { 3 // ... 4 @ManyToOne private Autor autor ; 5 6 //Leerer Konstruktor, Getter und Setter 7 // ... 8 } Die Entity Buch wurde um ein Attribut vom Typ Autor erweitert. Es wurde bereits angesprochen, dass sich bei einer bidirektionalen Beziehung beide Seiten um das Mapping der Beziehung kümmern. Im Beispiel würde das bedeuten, dass wir sowohl eine Verbindungstabelle als auch eine Verbindungsspalte in der Datenbank erzeugen. Um redundante Datenhaltung auf der Datenbankseite zu vermeiden, müssen wir einem der beiden Verbindungsattribute den Parameter mappedBy“ übergeben. MappedBy zeigt an, dass das Mapping des Attributs an einer anderen ” Stelle behandelt wird. Bei dem ersten Entwurf ist aufgefallen, dass eine Verbindungstabelle erstellt wurde. Um das Erstellen einer solchen Tabelle und damit auch überflüssige Joins zu verhindern, sollte darauf geachtet werden, dass bei einer 1:n Beziehung immer die n-Seite für das Mapping der Beziehung zuständig ist. Die Entity Autor muss also, wie folgt, angepasst werden. 1 @Entity 2 public class Autor { 3 // ... 4 @OneToMany (mappedBy = ”autor”) private Collection <Buch> buch = new ArrayList<Buch>(); 5 6 //Leerer Konstruktor, Getter und Setter 7 // ... 8 } Nach dieser Anpassung ist jetzt lediglich das Attribut autor“ in der Entity Buch für das Mapping ” zuständig. Das Beispiel sieht dann, wie folgt, aus: Buch id 1 2 titel Das erste Buch Das zweite Buch autor id 1 1 Autor id 1 name Mustermann vorname Max 13 Man findet hier eine bidirektionale 1:n Beziehung vor. 2.7 Die Vererbung Die JPA bietet dem Programmierer auch die Möglichkeit eine Vererbungsstruktur in der Datenbank abzubilden. Insgesamt bietet die JPA drei verschiedene Möglichkeiten eine Vererbungshierarchie in der Datenbank abzubilden. SINGLE TABLE Zum einen gibt es die Möglichkeit die Klassen einer Vererbungshierarchie von Entities in dieselbe Tabelle zu schreiben. Dies führt dazu, dass man eine Tabelle mit sehr vielen Spalten hat und dementsprechend auch viele Nullwerte vorliegen. Dieses Verhalten ist das Defaultverhalten, es nennt sich SINGLE TABLE. Beispiel SINGLE TABLE: id 1 2 DTYPE Hoerbuch EBook Titel Pro JPA 2 Pro JPA 2 Buch Autor ID 1 1 Dateigroesse NULL 2 Dauer 120 NULL Eine Besonderheit ist hier die Spalte DTYPE, auf diese wird an späterer Stelle eingegangen. JOINED Ein weiteres Verhalten, welches die JPA zur Verfügung stellt, ist das sogenannte JOINEDVerhalten. Hierbei wird die Entity aufgesplittet. Die Attribute, die von der Vaterklasse geerbt werden, werden bei diesem Verhalten auch in die Tabelle der Vaterklasse geschrieben. Die Attribute, die darüber hinaus in der geerbten Klasse definiert werden, werden dann in eine gesonderte Tabelle ausgelagert. Wenn später auf ein so persistiertes Objekt zugegriffen wird, holt sich der Provider über einen Join die Daten wieder aus der Datenbank. Der Join, der beim Laden einer Entity durchgeführt werden muss, gibt diesem Verhalten seinen Namen. Beispiel JOINED: Buch id 1 2 Titel Pro JPA 2 Pro JPA 2 id 1 EBook Dateigroesse 2 Autor Id 1 1 14 id 2 Hoerbuch Dauer 120 TABLE PER CLASS Eine weitere Möglichkeit besteht darin, pro Entity eine eigene Tabelle zu benutzen. Jede Entity bekommt hier ihre eigene Tabelle, in der sie auch die Attribute, die sie von der Vaterklasse übernommen hat, abbildet. Diese Strategie nennt sich TABLE PER CLASS. Um eine eindeutige ID zu gewährleisten ist an dieser stelle eine Strategie wie IDENTITY zum Generieren der ID nicht zu empfehlen. Identity benutzt das Autoinkrement der Datenbank und kann deswegen nicht tabellenübergreifend für eine eindeutige ID sorgen. Beispiel TABLE PER CLASS: id 1 Hoerbuch Titel Autor ID Pro JPA 2 1 Dauer 120 id 2 EBook Titel Autor ID Pro JPA 2 1 Groesse 2 DiscriminatorValue Bei SINGLE TABLE werden Entities unterschiedlicher Klassen in dieselbe Tabelle einer Datenbank gemappt. Es ist dringend nötig zu wissen, zu welcher Klasse eine Entität gehört. An dieser Stelle greift die JPA auf den sogenannten Discriminatorvalue zurück, welcher eine zusätzliche Spalte in der Datenbank einnimmt. Er kann, wie folgt, einer Entity zugewiesen werden: 1 @Entity 2 @DiscriminatorValue(value=”Hörbuch”) 3 public class Hoerbuch extends Buch{ // ... 4 5 } Standartmäßig ist der DTYPE Name der Spalte, die die JPA für den Discriminatorvalue verwendet. Dies kann aber auch über die Verwendung der Annotation @DiscriminatorColumn angepasst werden. Vergleich Welche dieser Methoden die beste für eine bestimmte Anwendung ist hängt stark von dem Verwendungszweck der Entity ab. Bei Entities mit sehr vielen Attributen ist beispielsweise die Methode SINGLE TABLE nicht zu empfehlen, weil dadurch sehr viele Spalten entstehen. Wenn 15 jedoch nur wenig Attribute in die Datenbank geschrieben werden, sind SINGLE TABLE und TABLE PER CLASS die beste Wahl, weil sie, im Gegensatz z.B. zum JOINED-Mapping, komplett auf rechenaufwändige Joins verzichten. Als Polymorphe Abfrage bezeichnet man Abfragen, welche verschiedene Entity” Instanzen, die zu einer gemeinsamen Vererbungshierarchie gehören, als Ergebnis liefern.“ [KH06] Im Fall, dass man viele polymorphe Abfragen hat, kann es sinnvoll sein statt TABLE PER CLASS SINGLE TABLE zu wählen, weil so keine Abfragen über mehrere Tabellen nötig sind. Pauschal zu sagen, dass eine Methode die Beste ist, ist hier nicht möglich. 2.8 Transaktion Abbildung 2.1: Die Zustände einer Entity (Lifecycle) Für die Transaktion, sprich die Kommunikation mit der Datenbank, ist der sogenannte EntityManager zuständig. Dieser arbeitet nach folgendem Prinzip. Der EntityManager leitet die Phase der Transaktion ein. In dieser Phase können der Transaktion Entities hinzugefügt werden. Diese Entities befinden sich dann im sogenannten Persistenzkontext. Der Persistence Context (PC) ist keine API sondern ein Konzept bei der Arbeit mit ” JPA. Ein PC ist die Abstraktion für einen Bereich, in dem Objektinstanzen und deren persistente Repräsentierung konsistent gehalten werden. Aus einer etwas technischeren Sichtweise ist der PC eine Session begrenzter Lebensdauer, in der im Rahmen 16 einer oder mehrerer Transaktionen Änderungen an verwalteten Objektinstanzen vorgenommen werden. Der Lifecycle dieser Objektinstanzen bezieht sich auf den entsprechenden PC.“ [Dür] Entities des Persistenzkontexts können als Zustände Managed und Removed annehmen. Entities, welche sich im Persistenzkontext befinden, werden nach Abschluss der Transaktionsphase oder bei Aufruf der Funktion Flush in der Form, wie sie im Programm vorliegen, in die Datenbank geschrieben oder, wenn sie sich im Zustand Removed befinden, entfernt. Die sich im Transaktionskontext befindlichen Entities können aus der Datenbank oder aus dem Programm stammen. Nach Abschluss der Transaktionsphase befinden sich die involvierten Objekte im Detached-Zustand. Sie befinden sich nicht mehr im Persistenzkontext, aber haben sowohl eine Repräsentation in der Datenbank als auch im Programm. Lazy Loading Die JPA gibt einem die Möglichkeit Attribute einer Entity über Lazy Loading“ zu laden. Attribu” te, die mit Hilfe von Lazy Loading aus der Datenbank geladen werden, werden nicht automatisch beim Laden einer Entity mitgeladen. Wenn das Attribut dann später benötigt wird, wird es dynamisch nachgeladen. Das Benutzen von Lazy Loading kann bei großen Entities sinnvoll sein, wenn sie über eine lange Zeit im Managed-Zustand bleiben. In diesem Fall kann LazyLoading sich positiv auf die Auslastung des Arbeitsspeichers auswirken. Jedoch kann es im Bezug auf Detached-Entities zu Problemen führen. Wenn man im Detached-Zustand auf ein noch nicht geladenes Attribut zugreifen möchte, kann es nicht nachgeladen werden. An dieser Stelle wird noch einmal auf das Beipiel mit den Autoren verwiesen: 1 @Entity 2 public class Autor { 3 // ... 4 @OneToMany (mappedBy = ”autor”, fetch = FetchType.LAZY) private Collection <Buch> buch = new ArrayList<Buch>(); 5 6 //Leerer Konstruktor, Getter und Setter 7 // ... 8 } Aus Performancegründen wird hier darauf verzichtet, alle Bücher eines Autors direkt beim Laden aus der Datenbank mit zu laden. Wenn eine Autoren Entity in den Detached-Zustand gebracht wird, können nur noch Bücher angesprochen werden, auf die man bereits zugegriffen hat. Um dadurch verbundene Probleme zu verhindern, könnte man entweder auf das defaultmäßig eingestellte Eager Loading“ zurückgreifen oder, bevor man in den Zustand Detached wechselt, die ” Attribute, die benötigt werden, einmal ansprechen damit sie geladen werden. 17 Kaskadierung Jede Entity, die persistiert werden soll, muss standardmäßig explizit dem Persistenzkontext hinzugefügt werden. Dies führt dazu, dass auch alle Entities, die mit einer zu persistierenden Entity in Beziehung stehen, explizit dem Persistenzkontext hinzugefügt werden müssen. Häufig ist es sinnvoll, dass automatisch alle mit einer Entity in Beziehung stehenden Entities persistiert werden. Hier kann der der Parameter cascade“ einem die Arbeit abnehmen. ” 1 // ... 2 @OneToMany (mappedBy = ”autor”, cascade= CascadeType.PERSIST) 3 4 private Collection <Buch> buch = new ArrayList<Buch>(); // ... Wenn der CascadeTyp z.B. für die Beziehung von einer Ente zu einem Teich auf PERSIST steht, braucht man lediglich die Ente zu Persistieren und alle mit ihr verbundenen Teiche werden automatisch in die Datenbank geschrieben. Die Arbeit, die beim Persistieren gespart werden kann, kann auch beim Löschen von Einträgen eingespart werden. In diesem Fall muss der CacadeTyp auf DELETE gesetzt werden. Zu beachten ist, dass die Kaskadierung in beiden Fällen unidirektional abläuft. Was bedeutet, dass, wenn der Parameter auf der einen Seite gesetzt wird, sich das Verhalten beim Persistieren der anderen Seite nicht verändert. Diese Möglichkeit sollte jedoch mit Bedacht benutzt werden. Wenn z.B. cascade auf DELETE gesetzt ist und dann eine Ente gelöscht wird, werden alle Teiche, die zu dieser Ente gehören mit gelöscht. Dadurch kann es sein, dass ein Teich gelöscht wird, obwohl eine andere Ente ihn noch benutzt. Um Fehler zu vermeiden sollte der CascadeType remove nur in OneToMany und OneToOne Beziehungen zum Einsatz kommen. 2.9 Der EntityManager Wie bereits im Kapitel Transaktion 2.8 erwähnt, gibt es für das Durchführen von Transaktionen mit JPA eine Klasse EntityManager. Sie wird von der EntityManagerFactory erzeugt. Die EntityManagerFactory bekommt bei ihrer Erstellung einen String mitgegeben, über diesen String wird den EntityManager Objekten, die von ihr erzeugt werden, eine PersistenceUnit 2.2 zugeordnet. 18 Abbildung 2.2: Die Erzeugung eines EntityManagers Der EntityManager weiß dadurch mit welcher Datenbank er kommunizieren soll. Er stellt dem Programmierer einige Funktionen zur Verfügung. Die wichtigsten dieser Funktionen werden im Folgenden vorgestellt. Persist Eine neu erzeugte Entity muss, um sie persistieren zu können, über die Funktion persist“, ” welcher man diese Entity übergibt, dem Persistenzkontext hinzugefügt werden. In dem Fall, dass kein Primärschlüssel existiert, wird, je nachdem wie die Entity konfiguriert ist, ein Primärschlüssel erzeugt oder eine Exception geworfen. In dem Fall, dass man der Entity einen Primärschlüssel zugeteilt hat, der bereits in der Datenbank vorhanden ist, wird eine Exception geschmissen. Find Eine sehr grundlegende Funktion ist die find“ Funktion. Sie ermöglicht dem Programmierer eine ” Entity anhand ihres Primärschlüssels aus der Datenbank zu laden. Der Funktion wird zum einen die Klasse der Entity übergeben und zum anderen der Primärschlüssel. Der Rückgabetyp ist der Typ der Klasse die übergeben wird. Entities, die über find aus der Datenbank geladen werden, befinden sich automatisch im Persistenzkontext. Queries Wenn eine Entity nicht anhand ihres Primärschlüssels aus der Datenbank geladen werden soll, ist es auch möglich eine Entity mithilfe einer Anfrage (Eng.: query) zu erhalten. Der EntityManager stellt in diesem Fall verschiedene Funktionen zur Verfügung. 19 Clear Close und Detach Bei größeren Programmen kann es vorkommen, dass sich sehr viele Entities im Persistenzkontext befinden und damit den Speicher belegen. Um alle Entitties aus dem Kontext zu entfernen, dienen die Funktionen clear“ und close“. Die sich im Persistenzkontekt befindlichen Entities ” ” gehen nach dem Aufruf alle in den Zustand Detached über. Eine Übertragung zur Datenbank findet nicht statt. Nach einem Close ist zudem der EntityManager nicht mehr nutzbar. Soll dem Persistenzkontext nur eine Entity entzogen werden, wird der Funktion detach“ diese übergeben. ” Flush Ein Flush wird immer im Falle eines Commits der Transaktion des EntityManagers aufgerufen. Ein Flush kann aber auch manuell während der Transaktion über die Funktion flush()“ aufge” rufen werden. Alle Objekte, die sich zum Zeitpunkt des Flushs im Persistenzkontext befinden, werden dann mit der Datenbank synchronisiert. Außerdem erfolgt das Hinzufügen der Objekte zum Persistenzkontext durch die Kaskadierung an dieser Stelle. Refresh Der Zustand einer sich im Persistenzkontext befindlichen Entity wird im Falle eines Flushes in die Datenbank übertragen. Wenn man aber den Zustand der Entity im Programm durch den in der Datenbank ersetzen möchte, kann man der Funktion refresh“ die betroffene Entity übergeben. ” Der Zustand, den die Entity vorher hatte, geht dabei verloren. ??? Merge Der EntityManager bietet eine Funktion merge“, mit deren Hilfe ein Detached-Entity wieder ” in den Zustand Managed gebracht werden kann. Die Änderungen, die während dem DetachedZustand von der Anwendung an der Entity gemacht wurden, werden dann in die Datenbank übertragen. Anders als z.B. beim persist“ wird nicht die übergebene Entity in den Zustand ” Managed gebracht. Stattdessen wird eine Entity zurückgegeben, welche sich im Zustand Managed befindet. Wenn sich die zu mergende Entity bereits im Managed Zustand befindet, ist das Objekt der übergebenen Entity nach dem Merge identisch mit dem, welches beim Merge zurückgegeben wurde. Transaktion Der EntityManager kann eine Transaktion durchführen. Dafür benutzt er ein Objekt vom Typ EntityTransansaction. Dieses Objekt kann durch Aufruf der Methode getTransaction“ des Entity” Managers angesprochen werden. Um eine Transaktion einzuleiten wird auf dem EntityTransactionObjekt die Methode begin“ aufgerufen. Um die Transaktion durchzuführen wird die Funktion ” commit“ aufgerufen. Die EntityTransaction-Klasse bietet einem außerdem die Möglichkeit über ” die Funktion rollback“ die laufende Transaktion rückgängig zu machen. ” Eine Implementierung könnte wie folgt aussehen: 20 1 EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory( ” myBeispiel” ) ; 2 EntityManager em = entityManagerFactory.createEntityManager(); 3 try{ em.getTransaction() .begin() ; 4 //Funktioenen die während der Transaktion aufgerufen werden 5 em.getTransaction() .commit(); 6 7 }catch(Exception e){ em.getTransaction() . rollback () ; 8 9 10 } em.close() ; 21 3 Schlussteil 3.1 Zusammenfasseung In der Arbeit wurde auf die wichtigsten Funktionen der API eingegangen. Mithilfe von zahlreichen Code- und Tabellenbeispielen wurde ein Einblick in die praktische Verwendung der JPA gegeben. Beginnend bei Entities und der persistence.xml, wurden am Anfang der Arbeit die Grundlagen zum Verwenden der JPA Geschaffen. Des Weiteren wurden Möglichkeiten zur Steuerung des Mappings aufgezeigt. In diesem Kontext wurden auch die Zugriffsarten Property Access und Field Access vorgestellt. Darauf aufbauend wurde beschrieben auf welche Art und Weise Vererbungshierarchien und Beziehungen zwischen Entities in die Datenbank abgebildet werden. Unter anderem wurden Begriffe wie unidirektional und bidirektional zur Beschreibung der Richtung einer Beziehung eingeführt. Die Annotationen @OneToOne, @ OneToMany, @ManyToMany und @ManyToOne, die zum Beschreiben der Kardinalitäten verwendet werden, wurden ebenfalls eingeführt und anhand eines Beispiels illustriert. In den letzten Unterkapiteln liegt das Augenmerk auf der Transaktion. Es wurden die Zustände von Entities und die Möglichkeiten der Steuerung durch den EntityManager erläutert. Zusammenfassend kann man sagen, dass die wichtigsten Facetten der JPA erwähnt wurden. Mithilfe des vermittelten Wissens ist es möglich, Vererbungshierarchien und Beziehungen mithilfe der JPA in eine Datenbank zu schreiben. 3.2 Ausblick In dieser Arbeit wurde nicht auf die Eigenheiten der Implementierungen eingegangen. Diese sind für das Verständnis der JPA nicht erforderlich. Wer sich jedoch genauer mit dem objektrelationalen Mapping mit Java beschäftigen möchte, sollte einen Blick auf die Implementierungen nicht scheuen. Diese bieten einige hilfreiche Funktionen die noch nicht ins Interface übernommen wurden. Es muss immer abgewogen werden ob einem die implementierungsspezifischen Funkionen oder die Austauschbarkeit der Implementierungen wichtiger ist. 22 4 Literaturverzeichnis [Dür] Dr. Christian Dürr. JPA Einleitung. http://www.prozesse-und- systeme.de/jpaEinleitung.html. [Online; accessed 04-September-2012]. [Fin12] Mary A. Finn. Den Impedance Mismatch auf der Datenbankebene vermeiden. http://www.intersystems.de/cache/whitepapers/matchwp.html, 2012. [Online; accessed 04-September-2012]. [KH06] M. Kehle and R. Hien. Hibernate und das Java Persistence API: Einstieg und professioneller Einsatz. Entwickler.Press, 2006. [KS09] M. Keith and M. Schincariol. Pro JPA 2: Mastering the Java Persistence API. Apresspod Series. Apress, 2009. [O’C12] John O’Conner. Using the Java Persistence API in Desktop Applications. http://java.sun.com/developer/technicalArticles/J2SE/Desktop/persistenceapi/, 2012. [Online; accessed 03-September-2012]. [rei10] Reihe Java-Magazin: Fachwissen für Programmierer. Software- und Support-Verlag, 2010. [Wik] Wikipedia. Java Persistence API. http://en.wikipedia.org/wiki/Java Persistence API. [Online; accessed 04-September-2012]. [Zie08] Mathias Ziehmayer. Vergleich der Programmierkonzepte Vererbung, Generizität und Reflexion in Java und Eiffel. Diplomarbeit, Institut für Computersprachen Technische Universität Wien, 2008. 23