Daniel Röder JPA mit Hibernate Daniel Röder JPA mit Hibernate Java Persistence API in der Praxis Daniel Röder: JPA mit Hibernate Java Persistence API in der Praxis ISBN: 978-3-86802-240-7 © 2010 entwickler.press Ein Imprint der Software & Support Verlag GmbH Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar. Ihr Kontakt zum Verlag und Lektorat: Software & Support Verlag GmbH entwickler.press Geleitsstraße 14 60599 Frankfurt am Main Tel: +49(0) 69 63 00 89 - 0 Fax: +49(0) 69 63 00 89 - 89
[email protected] http://www.entwickler-press.de Lektorat: Sebastian Burkart Korrektorat: Frauke Pesch Layout: SatzWERK, Siegen (www.satz-werk.com) Umschlaggestaltung: Maria Rudi Belichtung, Druck & Bindung: M.P. Media-Print Informationstechnologie GmbH, Paderborn Alle Rechte, auch für Übersetzungen, sind vorbehalten. Reproduktion jeglicher Art (Fotokopie, Nachdruck, Mikrofilm, Erfassung auf elektronischen Datenträgern oder andere Verfahren) nur mit schriftlicher Genehmigung des Verlags. Jegliche Haftung für die Richtigkeit des gesamten Werks kann, trotz sorgfältiger Prüfung durch Autor und Verlag, nicht übernommen werden. Die im Buch genannten Produkte, Warenzeichen und Firmennamen sind in der Regel durch deren Inhaber geschützt. Inhaltsverzeichnis V V.1 V.2 V.3 1 1.1 Vorwort Aufbau des Buches Webseite zum Buch Danksagung Einleitung Impedance Mismatch 1.1.1 Granularität 1.1.2 Vererbung 1.1.3 Objektidentität 1.1.4 Beziehungen 1.1.5 Graphennavigation Hibernate Java Persistence API Hibernate, Java und das Java Persistence API – Ein Überblick Java 5 und höher 2.1.1 Annotations 2.1.2 Generics Das Java Persistence API 2.2.1 Entities 2.2.2 Entity Manager und Persistenzkontext 2.2.3 Java Persistence QL und die EJB QL Hibernate 2.3.1 Architektur im Überblick 2.3.2 Schnittstellen 2.3.3 Module Zusammenfassung 15 15 16 16 17 17 17 18 18 18 19 19 20 21 21 21 23 27 27 28 29 30 30 32 33 38 1.2 1.3 2 2.1 2.2 2.3 2.4 JPA mit Hibernate 5 Inhaltsverzeichnis 3 3.1 Einführung in Hibernate und JPA Beispielprojekt 3.1.1 Die Anwendungsfälle 3.1.2 Das Klassendiagramm 3.1.3 Projekt einrichten 3.1.4 Testen Klassisches Hibernate 3.2.1 Hibernate-Konfiguration 3.2.2 Entity „User“ 3.2.3 HibernateUtil 3.2.4 Data Access Object 3.2.5 Testen des DAOs Hibernate mit Annotations 3.3.1 Hibernate-Konfiguration 3.3.2 Entity „User“ 3.3.3 HibernateUtil 3.3.4 Data Access Object 3.3.5 Testen des DAOs Hibernate als JPA Persistence Provider 3.4.1 Konfiguration des Persistence Providers 3.4.2 Die Entity „User“ 3.4.3 HibernateUtil 3.4.4 Data Access Object 3.4.5 Testen des DAOs Hibernate als Persistenzschicht im Application Server 3.5.1 Konfiguration des Persistence Providers 3.5.2 Die Entity „User“ 3.5.3 HibernateUtil 3.5.4 SessionBean als Data Access Object 3.5.5 Testen der SessionBean mit Web-Service-Schnittstelle Zusammenfassung Der Aufbau und das Mapping von Entities Anforderungen an eine Entity 4.1.1 Definition der Tabellen- und Spaltennamen 4.1.2 Erweiterungen der Entity mit Hibernate 39 39 39 40 41 44 44 45 46 48 49 53 54 54 54 55 55 55 56 56 57 57 58 60 60 60 61 61 61 63 63 65 65 67 68 3.2 3.3 3.4 3.5 3.6 4 4.1 6 Inhaltsverzeichnis 4.2 Primärschlüssel 4.2.1 Anforderungen an den Primärschlüssel 4.2.2 Datenbankidentität, Objektidentität und -gleichheit 4.2.3 Generatoren für den Primärschlüssel Komponenten Assoziationen 4.4.1 1-zu-1-Beziehungen 4.4.2 1-zu-n- und n-zu-1-Beziehungen 4.4.3 N-zu-m-Beziehungen 4.4.4 Transitive Persistenz Vererbung 4.5.1 SINGLE_TABLE 4.5.2 TABLE_PER_CLASS 4.5.3 JOINED Collections 4.6.1 Persistente Collections 4.6.2 Collections mit Index oder Schlüssel 4.6.3 Sortierte Collections Enumerations Zusammenfassung Lebenszyklus einer Entity Die Zustände einer Entity 5.1.1 Transient 5.1.2 Persistent 5.1.3 Detached Zustandsänderungen einer Entity 5.2.1 Allgemeines zum Synchronisieren von Entities 5.2.2 Methoden des EntityManagers 5.2.3 Besonderheiten der Hibernate Session Verwendung von Detached Entities Callback-Methoden und Entity Listener 5.4.1 Beschreibung der Callback-Methoden 5.4.2 Verwendung von Entity-Listener-Klassen 5.4.3 Default Entity Listener 5.4.4 Ausführungsreihenfolge gleicher Callback-Methoden Zusammenfassung 70 70 71 72 74 78 78 83 87 88 92 93 94 96 97 97 100 104 106 107 109 109 109 110 110 111 111 111 114 115 118 118 122 123 124 125 7 4.3 4.4 4.5 4.6 4.7 4.8 5 5.1 5.2 5.3 5.4 5.5 JPA mit Hibernate Inhaltsverzeichnis 6 6.1 Transaktionen, EntityManager und Persistenzkontext Transaktionen 6.1.1 Was ist eine Transaktion? 6.1.2 Isolationsebenen 6.1.3 Transaktionssteuerung in JPA Locking 6.2.1 Optimistisches Locking 6.2.2 Lock-Modi von JPA und Hibernate 6.2.3 Erweiterte Einstellungen für das Locking in Hibernate Entity Manager und Persistenzkontext 6.3.1 Arten und Lebenszyklus des Persistenzkontexts 6.3.2 Erzeugen eines EntityManagers Patterns für EntityManager und Hibernate Session 6.4.1 „EntityManger/Session per Request“-Pattern 6.4.2 „EntityManager/Session per Conversation“-Pattern 6.4.3 „EntityManager/Session per Operation“- und „EntityManager/Session per Application“-Antipattern Zusammenfassung Datenbankabfragen mit JPA und Hibernate Das Query Interface 7.1.1 Ausführung der Abfragen 7.1.2 Parameter Binding 7.1.3 Definition von benannten Abfragen in den Metadaten Die Java Persistence Query Language 7.2.1 Allgemeines 7.2.2 Übersicht der Beispieldaten 7.2.3 Grundaufbau der Abfragen 7.2.4 Einschränkung der Ergebnismenge mit „where“ 7.2.5 Sortierung mit „order by“ 7.2.6 Joins 7.2.7 Die „select“-Anweisung im Detail 7.2.8 Aggregatfunktionen 7.2.9 Die „group by“-Anweisung 7.2.10 Polymorphe Abfragen 7.2.11 Subqueries 7.2.12 Massen-Update und -Delete 127 127 127 128 129 130 133 136 138 139 139 140 143 143 145 147 148 149 149 149 152 153 154 154 154 155 156 162 163 165 166 167 168 169 169 6.2 6.3 6.4 6.5 7 7.1 7.2 8 Inhaltsverzeichnis 7.3 7.4 Native SQL Criteria API in Hibernate 7.4.1 Ausführung der Abfragen 7.4.2 Einschränkung der Ergebnismenge mit Restrictions 7.4.3 Sortierung mit org.hibernate.criterion.Order 7.4.4 Assoziationen 7.4.5 Abfragen mit org.hibernate.criterion.Example 7.4.6 Die Klasse org.hibernate.criterion.DetachedCriteria Hibernate-Filter Criteria API und Metamodell in JPA 2.0 7.6.1 Das statische Metamodell 7.6.2 Das dynamische Metamodell 7.6.3 Das Criteria API Zusammenfassung Fetching-Strategien und Caches Fetching-Strategien 8.1.1 Fetch Joins 8.1.2 Batch Fetching mit Hibernate 8.1.3 Subselect-Fetching mit Hibernate Hibernate Query und Second Level Cache 8.2.1 Strategien und Konfiguration 8.2.2 Second Level Cache Provider Zusammenfassung Hibernate Types Hibernate Mapping Types Benutzerdefinierte Mapping Types Zusammenfassung Referenz der Annotationen Metadata-Annotationen A.1.1 Entity 169 172 172 173 176 177 178 178 179 180 180 182 183 185 187 187 190 192 194 195 197 198 198 199 199 201 208 209 209 209 7.5 7.6 7.7 8 8.1 8.2 8.3 9 9.1 9.2 9.3 A A.1 JPA mit Hibernate 9 Inhaltsverzeichnis A.2 Callback-Annotationen A.2.1 EntityListeners A.2.2 ExcludeSuperclassListeners A.2.3 ExcludeDefaultListeners A.2.4 PrePersist A.2.5 PostPersist A.2.6 PreRemove A.2.7 PostRemove A.2.8 PreUpdate A.2.9 PostUpdate A.2.10 PostLoad Annotationen für Datenbankabfragen A.3.1 NamedQuery A.3.2 QueryHint A.3.3 NamedQueries A.3.4 NamedNativeQuery A.3.5 NamedNativeQueries Abbilden der SQL-Abfrageergebnisse A.4.1 SQLResultSetMapping A.4.2 SQLResultSetMappings A.4.3 EntityResult A.4.4 FieldResult A.4.5 ColumnResult Referenzen auf den EntityManager und die EntityManagerFactory A.5.1 PersistenceContext A.5.2 PersistenceProperty A.5.3 PersistenceContexts A.5.4 PersistenceUnit A.5.5 PersistenceUnits Annotationen für die Definition der Abbildungen der Entitäten A.6.1 Table A.6.2 UniqueConstraint A.6.3 SecondaryTable A.6.4 SecondaryTables A.6.5 CollectionTable 210 210 210 210 211 211 211 212 212 212 212 213 213 213 213 214 214 215 215 215 216 216 216 217 217 217 218 218 218 219 219 219 220 220 221 A.3 A.4 A.5 A.6 10 Inhaltsverzeichnis A.7 Definieren von Primärschlüsseln A.7.1 Id A.7.2 GeneratedValue A.7.3 EmbeddedId A.7.4 IdClass A.7.5 SequenceGenerator A.7.6 TableGenerator A.7.7 MapsId Annotationen zum Überschreiben bestehender Abbildungen A.8.1 AttributeOverride A.8.2 AttributeOverrides A.8.3 AssociationOverride A.8.4 AssociationOverrides Annotationen für Entitätseigenschaften A.9.1 Transient A.9.2 Column A.9.3 Basic A.9.4 Lob A.9.5 Temporal A.9.6 Enumerated A.9.7 Version A.9.8 Access A.9.9 Cacheable 222 222 222 222 223 223 224 224 225 225 225 226 226 227 227 227 228 228 229 229 229 230 230 231 231 231 232 232 233 233 234 234 235 235 236 236 237 237 237 A.8 A.9 A.10 Annotationen für Assoziationen A.10.1 JoinColumn A.10.2 JoinColumns A.10.3 ManyToOne A.10.4 OneToOne A.10.5 OneToMany A.10.6 ManyToMany A.10.7 JoinTable A.10.8 MapKey A.10.9 MapKeyClass A.10.10 MapKeyColumn A.10.11 MapKeyEnumerated A.10.12 MapKeyJoinColumn A.10.13 MapKeyJoinColumns A.10.14 MapKeyTemporal A.10.15 OrderBy JPA mit Hibernate 11 Inhaltsverzeichnis A.10.16 A.10.17 A.10.18 A.10.19 OrderColumn PrimaryKeyJoinColumn PrimaryKeyJoinColumns ElementCollection 238 238 239 239 240 240 240 241 241 242 242 242 243 243 244 244 244 245 245 245 246 246 247 247 247 248 248 248 249 249 249 250 250 250 251 251 251 252 252 A.11 Annotationen für Vererbung A.11.1 Inheritance A.11.2 DiscriminatorColumn A.11.3 DiscriminatorValue A.11.4 MappedSuperclass A.12 Annotationen für eingebettete Komponenten A.12.1 Embeddable A.12.2 Embedded A.13 Hibernate-spezifische Annotationen A.13.1 Entity A.13.2 Table A.13.3 Index A.13.4 Tables A.13.5 Proxy A.13.6 AccessType A.13.7 BatchSize A.13.8 Cache A.13.9 Cascade A.13.10 Check A.13.11 CollectionOfElements A.13.12 Columns A.13.13 DiscriminatorFormula A.13.14 Fetch A.13.15 Filter A.13.16 Filters A.13.17 FilterDef A.13.18 ParamDef A.13.19 FilterDefs A.13.20 Formula A.13.21 Generated A.13.22 GenericGenerator A.13.23 Parameter A.13.24 IndexColumn A.13.25 LazyCollection A.13.26 LazyToOne 12 Inhaltsverzeichnis A.13.27 A.13.28 A.13.29 A.13.30 A.13.31 A.13.32 A.13.33 A.13.34 A.13.35 A.13.36 A.13.37 A.13.38 A.13.39 A.13.40 A.13.41 B MapKey MapKeyManyToMany NamedNativeQuery NamedNativeQueries NamedQuery NamedQueries NotFound OnDelete OrderBy Parent Sort Type TypeDef TypeDefs Where 252 253 253 254 254 255 255 255 256 256 256 257 257 257 258 259 261 Literaturverzeichnis Stichwortverzeichnis JPA mit Hibernate 13 V Vorwort In jedem Softwareprojekt muss man sich über die Speicherung der Daten Gedanken machen. Für Konfigurationen und wenige Daten reicht es meistens aus, diese in einfachen Property oder XML-Dateien abzulegen. Jedoch kommt man bei größeren Datenmengen meistens nicht an der Verwendung einer Datenbank vorbei. Mit Java gibt es verschiedene Ansätze Daten in einer Datenbank abzulegen. Dabei ist die Verwendung von JDBC1 die Basis von vielen Möglicheiten, da damit SQL Statements direkt an die Datenbank geschickt werden können. Um aber nicht in jedem Projekt das Rad bzw. die Persistenz der Daten neu erfinden zu müssen, gibt es zahlreiche Frameworks, wie bspw. Hibernate, die die typischen Funktionen in der Verwendung von JDBC kapseln. Da aber jedes Framework verschieden ist, wurde mit der Java Persistence API (JPA) eine einheitliche Schnittstelle (API) für die Persistenz in Java spezifiziert, die mittlerweile von zahlreichen Frameworks unterstützt wird. Das Buch zeigt anhand von vielen Beispielen die Möglichkeiten und die Verwendung der Java Persistence API. Dabei wird mit Hibernate auf eine solide Implementierung der JPA gesetzt. V.1 Aufbau des Buches In Kapitel 1 werden zunächst die Schwierigkeiten beim Speichern von Objekten in relationalen Datenbanken beleuchtet und erklärt, welche Herausforderungen sich dadurch ergeben. Des weiteren werden die „Protagonisten“ des Buches, JPA und Hibernate, vorgestellt. Kapitel 2 gibt einen tieferen Überblick über die Java Persistence API und deren Begrifflichkeiten sowie die Hibernate Projekte. Außerdem werden Annotations und Generics vorgestellt, deren Kenntnis für die Verwendung der JPA unerlässlich ist. In Kapitel 3 wird das im Buch durchgängig verwendete Beispiel vorgestellt. Dabei wird besonders auf die möglichen Anwendungsszenarien von Hibernate eingegangen. Kapitel 4 befasst sich mit dem grundlegenden Aufbau von Entites. Dabei wird auch die Verwendung von Komponenten, Assoziationen, Collections und Vererbung diskutiert. Der Lebenszyklus der Entities findet in Kapitel 5 Beachtung. Der EntityManager und die Möglichkeiten zur Transaktionssteuerung und für das Locking werden in Kapitel 6 beleuchtet. In Kapitel 7 wird der Frage nachgegangen wie gespeicherte Entites in der Datenbank gesucht und gefunden werden können. Zu diesem Zweck werden die Java Persistence Query Language (JPQL) und die Criteria APIs von JPA und Hibernate vorgestellt. 1. JDBC Überblick, http://java.sun.com/products/jdbc/overview.html JPA mit Hibernate 15 V – Vorwort Die Strategien für das Fetching und Caching von Entites werden in Kapitel 8 erläutert. In Kapitel 9 kommen zum Schluss die Hibernate Custom Types zum Zug. Im Anhang befindet sich eine Auflistung der Annotationen zur Angabe der Metadaten in JPA und Hibernate. V.2 Webseite zum Buch Auf der Webseite http://www.entwickler-press.de/jpa befindet sich der gesamte Quellcode, der in den einzelnen Kapiteln verwendet wurde. V.3 Danksagung Ich bedanke mich bei Markus Kehle und Robert Hien, die mit dem Buch „Hibernate und die Java Persistence API“ die Grundlage für mein Buch lieferten. Sie gaben mir die Chance aus dem Ihren das Meinige zu machen. Weiterhin möchte ich mich bei Erik Bens bedanken, der mir durch sein Review wertvolle und hilfreiche Tipps gegeben hat. Mein Dank gilt auch meinem Arbeitgeber der Saxonia Systems AG, der mir den Rahmen für die Arbeit an dem Buch zur Verfügung stellte. Vielen Dank auch an die Lektoren Christiane Auf, Sandra Michel, Maike Möws und Sebastian Burkart, die mir während der langen Arbeitsphase mit viel Geduld sowie Rat und Tat zur Seite standen. Schließlich möchte ich mich noch bei meiner Frau Nadine für ihre Rücksicht und Unterstützung während der langen Entstehungszeit des Buches bedanken. 16 1 1.1 Einleitung In diesem einleitenden Kapitel soll auf die Herausforderungen eingegangen werden, die bei der Speicherung von Objekten in relationalen Datenbanken entstehen können. Außerdem wird die Java Persistence API und Hibernate kurz vorgestellt. Impedance Mismatch In der Softwareentwicklung ist mit Impedance Mismatch der Unterschied in der Struktur zwischen normalisierten relationalen Datenbanken und objektorientierten Klassenhierarchien gemeint. Die Unterschiede liegen in der Granularität, in der Vererbung, bei der Objektidentität, in den Beziehungen und in der Graphennavigation. Diese Unterschiede werden in den nächsten Abschnitten erläutert. Relationale Datenbanken repräsentieren Daten in zweidimensionalen Tabellen. Ein Eintrag in einer Tabelle hat einen Primärschlüssel, mit dem der Eintrag eindeutig identifiziert werden kann. Weiterhin gibt es Fremdschlüssel, die Tabellen miteinander in Beziehung bringen, indem sie auf Primärschlüssel einer anderen Tabelle zeigen. 1.1.1 Granularität Ein objektorientiertes Modell ist typischerweise sehr feingranular, so kann es beispielsweise eine Entity Person geben, die eine Entity Adresse als Attribut hat (siehe Abbildung 1.1). Person Adresse Abbildung 1.1: Klasse Person hat eine Adresse Objekte können jegliche Granularität haben, Tabellen hingegen sind bezüglich der Granularität beschränkt. In einer relationalen Datenbank sind die Daten der Person inklusive den Adressdaten normalerweise in einer Tabelle abgelegt (siehe Tabelle 1.1). ID 1 2 Vorname Max ... Nachname Mustermann ... ... ... ... Adresse_PLZ 01234 ... Adresse_Stadt Musterstadt ... ... ... ... Tabelle 1.1: Rationale Datenbanktabellen mit Person- und Adressdaten JPA mit Hibernate 17 1 – Einleitung Mit welchen Mitteln man ein feingranulares Objektmodell in zweidimensionalen Tabellen abbilden kann, wird in Kapitel 4.3 gezeigt. 1.1.2 Vererbung Vererbung ist in Programmiersprachen wie Java selbstverständlich. Relationale Datenbanken kennen aber keine Vererbung. In Kapitel 4.5 wird gezeigt, welche Strategien es zur Abbildung von Vererbungshierarchien gibt und welche Vor- und Nachteile die jeweilige mit sich bringt. 1.1.3 Objektidentität In Java sind zwei Objekte identisch, wenn beide dasselbe Objekt sind. Wenn zwei Objekte identische Werte enthalten, dann sind die Objekte gleich, aber nicht unbedingt identisch. Objektidentität wird in Java mit dem == Operator überprüft, Objektgleichheit mit equals(). Objektidentität in Java: objektA == objektB; Objektgleichheit in Java: objektA.equals(objektB); In relationalen Datenbanken wird ein Eintrag in einer Tabelle über die Daten, die er enthält, identifiziert; damit können gleiche Datensätze gefunden werden. Allerdings kann man nicht sicherstellen, dass diese identisch sind. Um nun für ein Objekt den entsprechenden identischen Eintrag in der Datenbank zu finden, muss ein eindeutiger Primärschlüssel eingeführt werden. In den Objekten wird dieser Primärschlüssel ebenso eingefügt und somit kann über diesen die Identität zwischen Objekt und Eintrag in der Datenbank gewährleistet werden. 1.1.4 Beziehungen Beziehungen gibt es auch in relationalen Datenbanken. Mit einem Fremdschlüssel in der einen Tabelle wird ein Primärschlüssel in einer anderen Tabelle referenziert und somit die Tabellen in Beziehung gebracht. In der objektorientierten Welt gibt es mehrere Arten von Beziehungen: 1-zu-1-, 1-zu-viele-, viele-zu-1- und viele-zu-viele-Beziehungen. 18 Hibernate Diese können letztendlich alle mit Fremdschlüsseln abgebildet werden. Etwas komplizierter ist die 1-zu-viele-Beziehung, da dort ein Primärschlüssel einen Fremdschlüssel referenziert und bei der viele-zu-viele-Beziehung muss eine Beziehungstabelle (JoinTabelle) eingeführt werden. Die Beziehungstabelle enthält zwei Fremdschlüssel, die jeweils auf eine Seite der Beziehung zeigen. In der viele-zu-viele-Beziehung in Abbildung 1.2 kann ein Student mehrere Dozenten (oder Professoren) haben und ein Dozent kann ebenso mehrere Studenten haben. Beziehungen werden in Kapitel 4.4 behandelt. Student -id Student_Dozent -student_id -dozent_id Dozent -id Abbildung 1.2: Viele-zu-viele-Beziehung in einer relationalen Datenbank 1.1.5 Graphennavigation Über Objekte mit Java zu navigieren ist sehr leicht. Wenn beispielsweise auf alle Vorlesungen eines Dozenten zugriffen werden soll, so wird einfach dozent.getVorlesungen(); aufgerufen. Zur Datenbank können bis dahin bereits zwei Zugriffe erfolgt sein. Einer für die Abfrage des Dozenten und ein weiterer für die Vorlesungen des Dozenten. Als Alternative kann ein SQL-Join verwendet werden: select * from DOZENT left outer join VORLESUNG where ... Damit reduziert sich die Anzahl der Datenbankzugriffe auf einen. Aber wie verhält sich der objekt-relationale Mapper? Wie kann verhindert werden, dass, wenn über alle Dozenten iteriert und dabei auf die Vorlesungen zugriffen wird, nicht jedesmal eine Abfrage an die Datenbank erfolgt (N+1-Problem)? Antworten auf diese Fragen werden in Kapitel 8 gegeben. 1.2 Hibernate Hibernate ist ein Open-Source-Produkt und beschreibt sich auf http://www.hibernate.org als objekt-relationaler Persistenz- und Query-Service: Hibernate is a powerful, high performance object/relational persistence and query service. Hibernate lets you develop persistent classes following object-oriented idiom- including association, inheritance, polymorphism, composition, and collections. Hibernate allows you to express queries in its own portable SQL extension (HQL), as well as in native SQL, or with an object-oriented Criteria and Example API. JPA mit Hibernate 19 1 – Einleitung Hibernate ist eine feste Größe unter den O/R1-Mappern und war mit Gavin King (Gründer und Entwickler von Hibernate) auch maßgeblich an der Spezifikation der ersten Version der Java Persistence API (JPA)2 beteiligt. Der Erfolg von JPA 1.0, die vollständig durch Hibernate implementiert wird, ist sicherlich auch den fundierten Grundlagen von Hibernate zu verdanken. 1.3 Java Persistence API Im Mai 2006 wurde das Final Release der EJB-3.0-Spezifikation (JSR-220) veröffentlicht. Ziel des JSR-220 war es, Java EE zu vereinfachen. Die Java Persistence API wurde als eigenständiger Teil der EJB-3.0-Spezifikation entwickelt und löste die Entity Beans ab. Die wichtigsten Eigenschaften von JPA sind: die Entities sind einfache POJOs (Plain Old Java Objects) objektorientierte Klassenhierarchien mit Vererbung, Assoziationen, Polymorphismus usw. werden unterstützt die objektorientierte Abfragesprache Jave Persistence Query Language (JPQL) die API ist nicht nur in Java Enterprise Umgebungen (Applikationserver) einsetzbar, sondern auch in normalen Java Standard Umgebungen lauffähig Die Java Persistence API 2.0 (JSR-317) wurde im Dezember 2009 als Final Release veröffentlicht. Im Verlauf des Buches werden die Neuerungen an den entsprechenden Stellen vorgestellt. Hier nur die wesentlichsten Punkte in Kürze: Collections von Basistypen eine Criteria API mit Metamodell ein Cache Interface Erweiterungen der JPQL Query API Unterstützung der Bean Validation API3 1. 2. 3. Abkürzung für objekt-relational. JSR-220 (Java Specifcation Request), http://jcp.org/en/jsr/detail?id=220 Bean Validation API, http://jcp.org/en/jsr/summary?id=303 20 2 2.1 2.1.1 Hibernate, Java und das Java Persistence API – Ein Überblick Java 5 und höher Mit Java 5 sind eine Reihe von hilfreichen und wichtigen Neuerungen in den Java-Standard aufgenommen worden. In den beiden folgenden Abschnitten werden die zwei für Hibernate und JPA wichtigsten näher vorgestellt. Annotations Die in Java 5 neu hinzugekommenen Annotations bieten die Möglichkeit, Metadaten direkt im Sourcecode zu hinterlegen. Die Notwendigkeit für zusätzliche Dateien zur Speicherung der Metadaten wie z. B. XML- oder Property-Dateien entfällt somit. Die per Annotation hinterlegten Metadaten können unter anderem von Codegenerierungstools ausgelesen oder zur Laufzeit per Reflection abgefragt werden. Für den Entwickler haben Annotations den Vorteil, dass alle Informationen zentral im Sourcecode abgelegt sind und deren Zuordnung zum Sourcecode sofort ersichtlich ist. In JPA und Hibernate können Metadaten, z. B. Mapping-Informationen, in Annotations angegeben werden. Die Verwendung von externen XML-Dateien oder von Javadoc in Verbindung mit XDoclet ist somit nicht mehr notwendig. Eine Übersicht der verfügbaren Annotations von JPA und Hibernate befindet sich im Anhang. Verwendung von Annotations Annotations werden direkt im Sourcecode vor dem zu markierenden Element eingefügt. Zur Kennzeichnung muss jeder Annotation ein @ vorangestellt werden. Zwischen dem @-Zeichen und dem Namen der Annotation sind Leerzeichen erlaubt. Parameter einer Annotation werden, wie bei Methoden auch, in Klammern an den Annotation-Namen angehängt. Annotation-Parameter werden als Name-Wert-Paar angegeben, wobei die Reihenfolge der Parameter keine Rolle spielt (z. B. @MyAnnotation(para1=“hello“, para2=“world“) ). Hat die Annotation nur einen Parameter, kann der Name weggelassen werden. Bei parameterlosen Annotations ist die Angabe der Klammern optional. Annotations können sich auf alle wesentlichen Java-Elemente, wie Packages, Klassen, Interfaces, Enumerations, Methoden, Variablen und Methodenparameter, beziehen. JPA mit Hibernate 21 2 – Hibernate, Java und das Java Persistence API – Ein Überblick Annotations in der Java SE 5.0 In der Java Standard Edition 5.0 wurden bereits sieben Annotations definiert. Dabei wird zwischen Standard-Annotation und Meta-Annotation unterschieden. Die StandardAnnotations sind zur normalen Verwendung beim Programmieren vorgesehen, während die Meta-Annotations zur Definition neuer Annotation-Typen verwendet werden können. Folgende Annotations stehen standardmäßig zur Verfügung: @Deprecated: Mittels dieser Annotation kennzeichnet man Methoden und Klassen, die nicht mehr verwendet werden sollen. Sie ist eine Alternative zur bisherigen Verfahrensweise, veraltete Elemente über Javadoc-Kommentare zu markieren. @Override: Diese Annotation wird zur Markierung einer Methode verwendet. Der Compiler stellt dann sicher, dass eine Methode einer Basisklasse überschrieben wird. Andernfalls wird eine Compiler-Fehlermeldung ausgegeben. Dadurch wird ein Überschreiben sichergestellt und Fehler aufgrund falsch geschriebener Methoden werden vermieden. @SuppressWarnings: Dient zur Unterdrückung von Compiler-Warnungen. Die War- nungen müssen als Parameter angegeben werden. Es werden alle Meldungen unterdrückt, die sich auf Elemente beziehen, die durch das markierte Element (z. B. eine Methode) eingeschlossen werden. Listing 2.1 zeigt die Klasse AnnotationsHelloWorld. Die Methode sayHello() ist als veraltet markiert und löst bei Verwendung eine Compiler-Warnung aus. Die Markierung von toString() mit @Override stellt sicher, dass die Methode auch wirklich toString() aus Object überschreibt. public class AnnotationsHelloWorld { @Deprecated public String sayHello() { return "Hello World"; } @Override public String toString() { return "Hello World"; } } Listing 2.1: Verwendung von Annotations Als Meta-Annotations stehen folgende Elemente zur Verfügung: @Documented: Markierte Annotations werden automatisch bei der Verwendung von Javadoc, zur Erzeugung der Dokumentation, berücksichtigt. 22 Java 5 und höher @Inherited: Der Annotation-Typ wird automatisch vererbt und gilt automatisch auch für das entsprechende Element in allen abgeleiteten Subklassen. @Retention: Gibt an, wie lange die Annotation verfügbar ist. Es stehen folgende Werte zur Verfügung: SOURCE: Die Informationen stehen nur bis zur Compile-Zeit zur Verfügung und werden dann vom Compiler entfernt. CLASS: Die Metadaten werden in den Class-Dateien abgespeichert, aber nicht von der VM geladen. RUNTIME: Annotations werden in der Class-Datei abgelegt und von der VM geladen und stehen somit zur Auswertung per Reflection zur Verfügung. @Target: Mit Target wird definiert, welchen Elementen (Klasse, Methode, Parameter etc.) eine Annotation zugeordnet werden kann. Eigene Annotations definieren Listing 2.2 zeigt die Definition eines eigenen Annotation-Typs. Die Annotation MyAnnotation enthält drei Parameter: param1, param2 und counter. Für counter wurde ein DefaultWert von 0 definiert. Eine Angabe dieses Parameters bei Verwendung der Annotation ist somit optional. Mit @Retention(RetentionPolicy.Source) wurde die Verfügbarkeit auf die Compile-Zeit eingeschränkt, ein Zugriff auf diese Annotation zur Laufzeit oder ein Auslesen aus der Class-Datei ist daher nicht möglich. Durch Setzen von @Target({ElementType.METHOD, ElementType.TYPE}) wird festgelegt, dass MyAnnotation nur auf Methoden- und Klassenebene verwendet werden kann. import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { String param1(); String param2(); int counter() default 0; } Listing 2.2: Definition eines eigenen Annotation-Typs 2.1.2 Generics Die in der Java Standard Edition 5.0 neu eingeführten Generics erlauben die Abstraktion von Typen. Damit ist es möglich, Klassen und Methoden zu definieren, die generisch, also unabhängig von einem konkreten Typ, sind. Da JPA und Hibernate die Verwendung von Generics unterstützt, werden diese im folgenden Abschnitt näher erläutert. JPA mit Hibernate 23 2 – Hibernate, Java und das Java Persistence API – Ein Überblick Verwendung von Generics Ein gutes Beispiel ist das Collection Framework: List v = new Vector(); v.add(new Double(1.0)); Double d = (Double)v.get(0); Bisher war der Cast auf Double in der letzten Zeile notwendig, da sonst ein Fehler durch den Compiler ausgegeben worden wäre, denn der Rückgabewert von get() ist Object. Außerdem birgt diese Art der Verwendung von Collections das Risiko, dass ein Objekt in die Liste eingeführt wird, dessen Typ inkompatibel ist. Dies wird nicht durch den Compiler erkannt, sondern tritt erst zur Laufzeit durch eine ClassCastException zutage. Mithilfe der neuen generischen Collections lässt sich der obige Code folgendermaßen ausdrücken: List v = new Vector(); v.add(new Double(1.0)); Double d = v.get(0); Mit List v wird angegeben, dass v eine Liste von Double ist. Der in spitzen Klammern angegebene Typ (hier Double) definiert den konkreten Typparameter, der für diese Liste verwendet wird. Die korrekte Verwendung dieser Liste wird durch den Compiler sichergestellt. Daher ist das Einfügen eines unpassenden Typs nicht mehr möglich und ein Verstoß wird bereits zur Compile-Zeit erkannt. Der vorher notwendige Cast kann entfallen, denn der Rückgabewert von get() ist nun Double. Falls mehrere Typparameter angegeben werden müssen, so werden sie durch Komma getrennt (z. B. Hashtable). Die Verwendung von Generics verbessert somit die Lesbarkeit und hilft durch die erhöhte Typsicherheit, die Zuverlässigkeit des Codes zu erhöhen. Einschränkungen und Besonderheiten Bei der Verwendung von Generics muss man aber auch eine Reihe von Einschränkungen und Besonderheiten beachten, die unter anderem daher rühren, dass die Unterstützung von Generics nur auf Basis des Compilers umgesetzt wurde. Der VM selbst sind Generics unbekannt, das heißt, die Typinformationen bei Generics stehen nur zur CompileZeit zur Verfügung. Als Konsequenz hieraus liefert z. B. der Vergleich des Typs von Vector und Vector true, denn zur Laufzeit gibt es nach wie vor nur den Typ Vector. Außerdem gibt es zwischen z. B. List und List keine Vererbungsbeziehung. Das heißt, eine Liste von String ist somit keine Liste von Object. 24 Java 5 und höher Folgender Code führt zu einem Compile-Fehler: Vector v1 = new Vector(); Vector v2 = new Vector(); v1 = v2; //Compilefehler ! Wildcards und Bounds Um dieselben Möglichkeiten wie in dem bisherigen Collection-API zu haben, wie beispielsweise die Implementierung von Such- oder Sortieralgorithmen, die unabhängig von der verwendeten Collection sind, gibt es die so genannten Wildcards. Als Wildcardzeichen wird ? verwendet. Ein Vector steht für einen Vector mit beliebigem Inhalt. Folgender Code führt somit nicht mehr zu einem Compile-Fehler: Vector v1; Vector v2 = new Vector(); v1 = v2; Allerdings gibt es auch bei der Verwendung von Wildcards einige Einschränkungen. So ist das Erzeugen einer Collection mit unbekanntem Typ genauso wenig möglich wie das Hinzufügen eines Objekts zu einer solchen Collection, denn der Typ, für den ? steht, ist ja eben nicht bekannt. Wildcards können darüber hinaus mit so genannten Bounds angegeben werden. Durch Angabe einer oberen Schranke mittels extends kann festgelegt werden, dass ein Typ vom angegebenen oder einem abgeleiteten Typ sein muss. Eine mittels super definierte untere Schranke legt fest, dass ein Typ vom angegebenen oder einem Supertyp sein muss. List org.hibernate.dialect.PostgreSQLDialect org.hibernate.cache.NoCacheProvider true update Listing 3.2: hibernate.cfg.xml JPA mit Hibernate 45 3 – Einführung in Hibernate und JPA In der hibernate.cfg.xml in Listing 3.2 werden im Wesentlichen die Datenbankeinstellungen definiert: Die JDBC-Treiber-Klasse, der Datenbank-URL, der Username und das Passwort müssen angegeben werden. Des Weiteren wird ein entsprechender SQL-Dialekt ausgewählt, zum Beispiel für PostgreSQL: org.hibernate.dialect.PostgreSQLDialect MySQL (InnoDB): org.hibernate.dialect.MySQLInnoDBDialect Oracle 9i/10g: org.hibernate.dialect.Oracle9Dialect HypersonicSQL: org.hibernate.dialect.HSQLDialect Eine komplette Liste aller Dialekte, die Hibernate zur Verfügung stellt, gibt es hier10. Einige Beispiele zur Datenbankkonfiguration sind auch in der hibernate.properties zu sehen, diese ist im Ordner /etc von Hibernate Core zu finden. Mit show_sql = true gibt Hibernate alle generierten SQLs auf der Konsole aus und mit hbm2ddl.auto = update erstellt Hibernate beim Anwendungsstart automatisch die Tabellen, falls sie noch nicht existieren. Verwenden Sie hbm2ddl.auto = create, um alle Tabellen vor dem Erstellen zu löschen. Mit mapping werden die XML-Mappings für die Entities angegeben. Wie man diese Mappings erstellt, wird im nächsten Abschnitt erläutert. Die Datei hibernate.cfg.xml muss sich im Klassenpfad befinden, damit Hibernate darauf zugreifen kann. Wenn die alternative Konfigurationsdatei hibernate.properties für die Konfiguration verwendet werden soll, muss sie dann ebenfalls im Klassenpfad vorhanden sein. 3.2.2 Entity „User“ Nachdem Hibernate konfiguriert ist, kann die erste Klasse erstellt werden. Die Entity User soll den vollständigen Namen und die E-Mail des Benutzers enthalten. package booksonline.bo; public class User { private Long id; private String firstname; private String lastname; private String email; public User() { } public Long getId() { return id; } Listing 3.3: User.java 10. Hibernate-SQL-Dialekte: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/session-configuration. html#configuration-optional-dialects 46 Klassisches Hibernate private void setId(Long id) { this.id = id; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } ... } Listing 3.3: User.java (Forts.) Die Entity User (Listing 3.3) ist ein einfaches POJO11 und hat keine externen Abhängigkeiten. Die Eigenschaften der Klasse haben Getter- und Setter-Methoden entsprechend der JavaBeans-Nameskonventionen, was empfohlen wird, aber kein Muss ist. Hibernate kann auch direkt auf die Felder zugreifen. Das Attribut id enthält einen eindeutigen Bezeichner, den alle Klassen, die persistent gespeichert werden sollen, benötigen. Des Weiteren schreibt Hibernate vor, dass ein Standard-Konstruktor vorhanden sein muss. Der Konstruktor kann in Hibernate auch private sein, doch für eine JPA Entity ist lediglich public und protected erlaubt. Hibernate XML-Mapping Mit dem XML-Mapping wird Hibernate mitgeteilt, wie die Attribute des Entities in den Tabellen der Datenbank abgebildet werden sollen. Die XML-Dateien haben die Endung .hbm.xml und es wird empfohlen, für jede Entity eine eigene XML-Datei zu erstellen, da es sonst sehr schnell unübersichtlich werden kann. In Listing 3.4 ist das Mapping User.hbm.xml für die Entity User zu sehen. Listing 3.4: User.hbm.xml 11. Plain Old Java Object: http://www.martinfowler.com/bliki/POJO.html JPA mit Hibernate 47 3 – Einführung in Hibernate und JPA Das Mapping beginnt immer mit , dort wird unter anderem das package der Klasse definiert. Bei Nichtangabe des package muss der vollqualifizierte Klassenname im Element angegeben werden. Das Attribut table gibt den Name der Tabelle für die Speicherung der User an. Das Element definiert das Feld der Klasse User, das als Primärschlüssel verwendet werden soll. Mit wird die Strategie zur Generierung des eindeutigen Schlüssels gesetzt, damit er nicht „per Hand“ verwaltet werden muss. Im Beispiel wird mit native der Default-Generator spezifiziert. Dieser wählt entsprechend der darunter liegenden Datenbank eine geeignete Strategie zur Generierung des Primärschlüssels. Bei Oracle oder PostgreSQL kommen in diesem Fall Sequences zum Einsatz. Mit werden alle Felder angegeben, die persistent gesichert werden sollen. Weiterhin gibt es die Möglichkeit mittels XDoclet12 aus speziellen Tags im JavaDoc einer Klasse das XML-Mapping zu generieren. Mit Einführung von Annotations für das Mapping ist dieses Vorgehen allerdings nicht mehr nötig. 3.2.3 HibernateUtil Damit durch Hibernate ein Objekt gesichert, geladen oder aktualisiert werden kann, wird eine Session benötigt. Eine Session erhält man von einer SessionFactory. Die SessionFactory wird aus der Konfiguration von Hibernate beim Start der Anwendung einmal erzeugt und sollte zur gesamten Laufzeit der Anwendung erreichbar sein. Eine Session ermöglicht den Zugriff auf Datenbankverbindungen und Transaktionen und bietet die CRUD-Operationen (create, read, update und delete) an. Damit zur Laufzeit der Zugriff auf eine Session möglichst leicht ist, wird eine Klasse HibernateUtil erstellt (Listing 3.5), die mit einer statischen Methode eine Session zurückgibt. package booksonline.util; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { // Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { System.err.println("SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } Listing 3.5: HibernateUtil.java 12. XDoclet: http://xdoclet.sourceforge.net/xdoclet/index.html 48 Klassisches Hibernate public static SessionFactory getSessionFactory() { return sessionFactory; } } Listing 3.5: HibernateUtil.java (Forts.) 3.2.4 Data Access Object Das Anlegen, Lesen, Aktualisieren und Löschen von Entities sollte zentral über ein Data Access Object (DAO) vorgenommen werden. Dabei ist es üblich, pro Entity ein entsprechendes DAO anzulegen, damit die Übersichtlichkeit gewahrt bleibt. Die DAOs unterscheiden sich in den meisten Fällen lediglich um den Typ der zu speichernden Klasse. Aus diesem Grund ist das Anlegen eines generischen DAOs oftmals sinnvoll. Entity „User“ sichern Mit der Methode persist aus dem Beispielcode in Listing 3.6 wird eine Entity User persistent in der Datenbank gesichert. Dafür wird eine Session über die Hilfsklasse HibernateUtil geöffnet. Eine Transaktion wird gestartet und das Objekt User wird der Session übergeben. Nach dem Beenden der Transaktion und der Session ist das Objekt in der Datenbank abgelegt. package booksonline.dao; import booksonline.bo.User; import booksonline.util.HibernateUtil; import org.hibernate.Session; import org.hibernate.Transaction; public class UserDao { public void persist(User user) { Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = session.beginTransaction(); session.persist(user); tx.commit(); session.close(); } ... } Listing 3.6: UserDao.java persist() Der entscheidende Aufruf ist session.persist(user), da an dieser Stelle das Objekt der Session übergeben wird. Hibernate generiert und setzt die ID und schreibt das Objekt in die Datenbank. JPA mit Hibernate 49 3 – Einführung in Hibernate und JPA Der Aufruf der persist-Methode des DAOs kann wie im Folgenden gezeigt durchgeführt werden: UserDao userDao = new UserDao(); User user = new User("Moritz", "Muster", "
[email protected]"); userDao.persist(user); Das SQL, das Hibernate an die Datenbank im obigen Beispiel weitergeben wird, sieht wie folgt aus: insert into T_USER (firstname, lastname, email, id) values ('Moritz', 'Muster', '
[email protected] ', 1) Das Insert-Statement wird nicht schon beim Aufruf von session.persist() an die Datenbank geschickt, sondern erst beim Beenden der Transaktion. Alternativ kann auch Session.save() oder Session.saveOrUpdate() zur Sicherung eines Objekts aufgerufen werden. Während sich save() wie persist() verhält, überprüft saveOrUpdate(), ob die ID gesetzt ist oder nicht und führt dann entsprechend ein save() oder ein update() zum Aktualisieren eines Objekts durch. Das Aktualisieren von Objekten wird im übernächsten Abschnitt erläutert. Entity „User“ laden Ebenso einfach wie das Sichern von Objekten funktioniert das Laden von Objekten. In Listing 3.7 wird wiederum eine Session geöffnet und eine Transaktion gestartet. Mit der Session wird mittels get(Class class, Serializable id) oder load(Class class, Serializable id) ein vorher gesicherter User geladen. Beide Methoden benötigen die ID des zu ladenden Datensatzes. Falls die ID nicht bekannt ist, kann mit einem Datenbanktool, zum Beispiel dem DbVisualizer Free13, in der Datenbank nachgesehen werden. In der Praxis ist natürlich die Verwendung eines Datenbanktools zum Ermitteln des Primärschlüssels nicht nötig. Stattdessen kommt zum Beispiel die komfortable Java Persistence Query Language (JPQL) zum Einsatz (Kapitel 7). ... public class UserDao { ... public User findById(Long id) { Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = session.beginTransaction(); User user = (User) session.get(User.class,id); tx.commit(); session.close(); Listing 3.7: UserDao.java get() 13. DbVisualizer Free: http://www.dbvis.com/products/dbvis/download/ 50 Klassisches Hibernate return user; } ... Listing 3.7: UserDao.java get() (Forts.) Beim beispielhaften Aufruf der Methode findById wird im Folgenden angenommen, dass der User aus dem vorherigen Beispiel die ID „1“ bekommen hat. UserDao userDao = new UserDao(); User user = userDao.findById(1L); In Listing 3.7 wird get() zum Laden eines Objekts verwendet, mit load() können ebenfalls Objekte per ID geladen werden. Es gibt allerdings ein paar wesentliche Unterschiede zwischen get() und load(), die unbedingt beachtet werden sollten: get() gibt immer null zurück, falls das Objekt nicht existiert, load() hingegen wirft eine Exception. load() kann einen so genannten Proxy zurückgeben, darunter versteht man einen Platzhalter für das eigentliche Objekt. Sobald ein Zugriff auf den Proxy erfolgt, wird das eigentliche Objekt nachgeladen (erst dann gibt es einen Datenbankzugriff, falls nötig). Falls das Proxy aus dem Cache gebildet wurde, kann beim Nachladen immer noch festgestellt werden, dass das Objekt nicht existiert, worauf eine Exception geworfen wird. Die Frage ist nun, wann get() und wann load() benutzt werden sollte. Dies hängt ganz von der Situation ab; wenn sicher ist, dass das Objekt existiert, kann load() ohne Bedenken verwendet werden. Falls nicht, ist man mit get() auf der sicheren Seite. Entity „User“ aktualisieren In Listing 3.8 wird mit der Methode update(Object entity) ein bereits persistentes Objekt aktualisiert. ... public class UserDao { ... public void update(User user) { Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = session.beginTransaction(); session.update(user); tx.commit(); session.close(); } ... Listing 3.8: UserDao.java update() JPA mit Hibernate 51 3 – Einführung in Hibernate und JPA Der Aufruf von session.update() ist nötig, da sich die Entity User noch nicht in der Verwaltung durch JPA und Hibernate befindet. Näheres zu dem Lebenszyklus und der Verwaltung der Entities durch Hibernate befindet sich in Kapitel 5. In Listing 3.9 wurde bereits die Entity User geladen. Dadurch erkennt Hibernate von selbst, dass das Objekt aktualisiert werden muss, und führt das Update beim Commit der Transaktion automatisch aus. User user = (User) session.get(User.class, 1L); user.setEmail("newemail@..."); // session.update(user); // unnötig Listing 3.9: UserDao.java update() nach dem Laden nicht nötig Unabhängig davon, ob das Update automatisch oder explizit angestoßen wurde, Hibernate generiert folgendes Update-Statement und gibt es an die Datenbank weiter: update T_USER set firstname=?, ... where id=? Entity „User“ löschen Auch das Löschen einer Entity kann einfach mit einem Methodenaufruf auf der Session durchgeführt werden. Der entsprechende Code wird in Listing 3.10 gezeigt. ... public class UserDao { ... public void delete(User user) { Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = session.beginTransaction(); session.delete(user); tx.commit(); session.close(); } ... Listing 3.10: UserDao.java delete() Zu beachten ist, dass die übergebene Entity User mindestens einen entsprechenden Primärschlüssel gesetzt haben muss, damit das gewünschte Objekt gelöscht werden kann, da das SQL-Delete-Statement anhand des Primärschlüssels ausgeführt wird. Folgendes Codefragment zeigt beispielhaft den Aufruf der Löschmethode: UserDao userDao = new UserDao(); User user = user.setId(1L); userDao.delete(user); 52 Klassisches Hibernate 3.2.5 Testen des DAOs Die Testklasse für das DAO wird in Listing 3.11 gezeigt. Der Test geht davon aus, dass die entsprechende Tabelle für die Entity User in der Datenbank leer ist, und somit als erste ID „1“ vergeben wird. In der Praxis sind solche „starken“ Testvoraussetzungen natürlich nicht geeignet, jedoch zum Zeigen der CRUD-Methoden an dieser Stelle durchaus akzeptabel. package booksonline.test; public class UserTest { public UserTest() { } ... @Test public void testUser() { UserDao userDao = new UserDao(); User user = null; //Prüfen, dass kein User mit ID 1 in DB assertNull(userDao.findById(1L)); //neuen User anlegen und speichern user = new User("Moritz", "Muster", "
[email protected]"); userDao.persist(user); //generierten Schlüssel überprüfen assertEquals(1L, user.getId().longValue()); //User in DB finden User = userDao.findById(1L); assertEquals("Moritz", user.getFirstname()); //User ändern und erneut speichern user.setFirstname("Max"); userDao.update(user); //User erneut finden und überprüfen user = userDao.findById(1L); assertEquals("Max", user.getFirstname()); //User löschen User userDelete = new User(); userDelete.setId(1L); userDao.delete(userDelete); //User wird nicht mehr gefunden assertNull(userDao.findById(1L)); } } Listing 3.11: Testklasse für das DAO JPA mit Hibernate 53 3 – Einführung in Hibernate und JPA 3.3 Hibernate mit Annotations Die mit Java 5 eingeführten Annotations haben seit Version 3.1 auch in Hibernate Einzug gehalten. Durch die Annotations wird die XML-Konfiguration für das Mapping unnötig. Ein weiterer Vorteil ist die so genannte „Convention over Configuration“, wodurch nur die von den Standardparametern abweichenden Angaben mit Annotations angegeben werden müssen. Alle weiteren Einstellungen werden als gegeben angenommen. In Kapitel 4 werden die entsprechenden Defaults noch näher beleuchtet. 3.3.1 Hibernate-Konfiguration Die Hibernate-Konfiguration bleibt wie gehabt identisch zu der in Kaptitel 3.2.1. Jedoch wird das Resource-Mapping nicht mehr auf die XML-Konfigurationsdatei bezogen, sondern direkt auf die Klasse, da dort die Konfigurationen mittels der Annotations stattfinden. In Listing 3.12 wird der betroffene Ausschnitt aus der hibernate.cfg.xml gezeigt. ... ... Listing 3.12: Änderungen an der Hibernate-Konfiguration 3.3.2 Entity „User“ In Listing 3.13 wird die Entity User mit durch Annotations hinzugefügten Mapping-Informationen vorgestellt. package booksonline.bo; ... @Entity @Table(name="T_User") public class User { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private String firstname; private String lastname; private String email; public User() { } Listing 3.13: User.java mit Annotations 54 Hibernate mit Annotations public Long getId() { return id; } private void setId(Long id) { this.id = id; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } ... } Listing 3.13: User.java mit Annotations (Forts.) Mit der Annotation @Entity wird die Klasse User als Entity markiert. Die Annotation @Table spezifiziert Eigenschaften der Datenbanktabelle, in die Objekte der Klasse User gespeichert werden. In unserem Beispiel wird der Name der Tabelle auf T_User festgelegt. Die Annotation @Id entspricht dem gleichlautenden Element der XML-MappingDatei und spezifiziert den Primärschlüssel der Entity. Mit @GeneratedValue wird die Generatorstrategie angegeben, welche mit GenerationType.AUTO denselben Default-Generator wie referenziert. Besonders wichtig ist, dass die Attribute nicht speziell annotiert werden müssen, damit sie persistiert werden. Dies geschieht ohne weiteres Zutun automatisch. Möchte man ein Attribut jedoch als transient markieren, muss man die Annotation @Transient verwenden. Transiente Attribute werden nicht persistent in der Datenbank gespeichert. 3.3.3 HibernateUtil Die Klasse HibernateUtil ändert sich bei Verwendung von Annotations nur marginal zum klassischen Hibernate. Lediglich die Klasse Configuration zum Aufruf der statischen Methode configure() muss in AnnotationConfigration geändert werden. In folgendem Codebeispiel wird die kleine Änderung gezeigt: // Erzeugen der sessionFactory bei der Verwendung von Annotations sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); 3.3.4 Data Access Object Das Data Access Object ändert sich nicht durch die Verwendung der Annotations für das Mapping. Die Session kann wie beim klassischen Hibernate (Kapitel 3.2.4) mit den Methoden persist, update, delete und get zum Speichern, Aktualisieren, Löschen und Finden von Entities verwendet werden. 3.3.5 Testen des DAOs Das Testen des DAOs erfolgt analog zum Test aus Kapitel 3.2.5. JPA mit Hibernate 55 3 – Einführung in Hibernate und JPA 3.4 Hibernate als JPA Persistence Provider Hibernate implementiert vollständig die JPA-1.0-Spezifikation, die mit EJB3 als Standard-API für die Persistenz eingeführt wurde. JPA lässt sich sowohl „standalone“ in einer Desktopanwendung verwenden als auch als Persistenzschicht in einem Application Server. Im folgenden Kapitel soll zuerst die von einem Application Server unabhängige Verwendung vorgestellt werden. In Kapitel 3.5 wird dann Hibernate als Persistenzschicht im Glassfisch v3 Application Server benutzt. 3.4.1 Konfiguration des Persistence Providers Die Konfiguration des Persistence Providers für JPA wird in der Datei persistence.xml vorgenommen. Neben den Einstellungen für JPA werden auch die Hibernate-spezifischen Attribute hier gesetzt, um Hibernate an die Bedürfnisse des Projekts anzupassen. In Listing 3.14 wird die Datei persistence.xml gezeigt, die sich im Verzeichnis META-INF befinden muss.