JAVA PERSISTENCE API CAS Enterprise Application Development Java EE Dieses Skript basiert auf: Pro JPA 2: Mastering the Java Persistence API, Keith and Schincariol, ISBN 978-1-4302-1956-9 Simon Martinelli, 11.2013 [email protected] | about.me/simas_ch 1 INHALT 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. INTRODUCTION GETTING STARTED ENTERPRISE APPLICATIONS (Æ EJB KURS) OBJECTION-RELATIONAL MAPPING COLLECTION MAPPING ENTITY MANAGER USING QUERIES QUERY LANGUAGE CRITERIA API ADVANCED OBJECT-RELATIONAL MAPPING ADVANCED TOPICS XML MAPPING FILES 2 1 INTRODUCTION 3 DAS PROBLEM 4 GRUNDSÄTZE DES O/R MAPPINGS • Objekte nicht Tabellen Applikationen arbeiten grundsätzlich nur mit dem Klassenmodell • Richtig nutzen, nicht ignorieren Um "gutes" O/R-Mapping zu betreiben, muss man sich der Probleme bewusst sein und die relationale Technologie kennen • Unauffällig, nicht transparent Persistenz ist nicht transparent. Die Applikation muss die Kontrolle über den Objekt-Lifecycle haben 5 GRUNDSÄTZE DES O/R MAPPINGS • Alte Daten, neue Objekte Es ist sehr häufig, dass eine Applikation bestehende Daten verwenden muss. Deshalb ist der Support für Legacy Daten zentral. • Ausreichend, aber nicht zuviel Applikationen dürfen nicht durch ein schwergewichtiges Persistenzmodell erdrückt werden • Lokal und mobil Entitäten müssen transportiert werden können 6 MODERNE PERSISTENZ APIS • Arbeiten mit gewöhnlichen Java-Klassen für Daten (POJOs) • Objekte können transient oder persistent sein • Innerhalb und ausserhalb von Applikationsservern verwendbar • Vererbung, Aggregation, Komposition abbildbar • Transitive Persistenz oder Persistence by Reachability • Lazy Loading • Automatic Dirty Checking • Datenbank-Roundtrips minimieren, Outer Join Fetching • SQL-Generierung zur Laufzeit 7 TECHNOLOGIE STACK 8 Geschichte 9 API • Packages • javax.persistence • javax.persistence.spi • Classes • Persistence • • • • • Interfaces EntityManagerFactory EntityManager EntityTransaction Query • Exceptions ( ~8 ) • RollbackException • • • • • Annotations ( ~64 ) Entity Id OneToOne OneToMany • • • • Enumerations ( ~10 ) InheritanceType CascadeType FetchType 10 2 GETTING STARTED 11 ENTITY ÜBERBLICK • Eine Entity ist persistierbar. Der Zustand kann in einer Datenbank abgespeichert und später wieder darauf zugegriffen werden • Wie jedes andere Java Objekt hat eine Entity eine Objektidentität. Zusätzlich besitzt sie eine Datenbankidentität (Primary Key) • In Zusammenhang mit der Datenbank werden die Entities transaktional verwendet. Die Erstellung, Änderung und das Löschen wird in einer Transaktion durchgeführt 12 ENTITY METADATA • • • • • Kennzeichnung mit Annotation @Entity oder Mapping mit XML Klasse kann Basisklasse oder abgeleitet sein Klasse kann abstrakt oder konkret sein Serialisierbarkeit ist bezüglich Persistenz nicht erforderlich Anforderungen: • Standardkonstrukutor muss vorhanden sein. • Klasse darf nicht final, kein Interface und keine Enumeration sein und keine final-Methoden enthalten • Felder müssen private oder protected sein. • Zugriff von Clients auf Felder nur über get/set- oder Business-Methoden erlaubt. • Jede Entity muss einen Primärschlüssel (@Id) haben CONVENTIONS OVER CONFIGURATION 13 ENTITY, BEISPIEL @Entity public class Employee { @Id protected int id; protected String name; protected long salary; public Employee() { } public Employee(int id) { this.id = id; } } 14 ENTITY MANAGER, ÜBERBLICK 15 ENTITY MANAGER, BEISPIEL // ENTITYMANAGER ERSTELLEN EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa.emp.2008"); EntityManager em = emf.createEntityManager(); // ENTITY PERSISTIEREN em.getTransaction().begin(); Employee emp = new Employee(158); em.persist(emp); em.getTransaction().commit(); // ENTITY FINDEN Employee emp = em.find(Employee.class, 158); 16 ENTITY MANAGER, BEISPIEL // ENTITY VERÄNDERN em.getTransaction().begin(); emp.setSalary(emp.getSalary() + 1000); em.getTransaction().commit(); // ENTITY LÖSCHEN em.getTransaction().begin(); em.remove(emp); em.getTransaction().commit(); // QUERIES Query q = em.createQuery("SELECT e FROM Employee e"); Collection emps = q.getResultList(); 17 PERSISTENCE UNIT, BEISPIEL <!-- META-INF/persistence.xml --> <persistence version="2.0" ...> <persistence-unit name="jpa-testPU" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <properties> <property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/test"/> <property name="javax.persistence.jdbc.password" value="test"/> <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/> <property name="javax.persistence.jdbc.user" value="test"/> <property name="eclipselink.ddl-generation“ value="create-tables"/> </properties> </persistence-unit> </persistence> 18 ÜBUNG: GETTING STARTED 1. Erstellen Sie eine neue Derby Datenbank 2. Erstellen Sie ein neues Projekt 3. Erstellen Sie eine Persistence Unit (METAINF/persistence.xml) 4. Erstellen Sie eine Klasse Employee 5. Machen Sie die Klasse zur Entity (@Entity, @Id) 6. Implementieren Sie den JUnit-Test um Employees 1. 2. 3. 4. einzufügen zu finden zu verändern zu löschen 19 4 OBJECT-RELATIONAL MAPPING 20 ACCESS TYP /* FUER DAS PERSISTENZ-FRAMEWORK EXISTIEREN ZWEI ZUGRIFFSPUNKTE AUF DIE DATEN EINER KLASSE */ //FIELD ACCESS @Entity public class Employee { @Id private int id; } //PROPERTY ACCESS @Entity public class Employee { protected int id; @Id public int getId() { return id; } } 21 ACCESS TYP OPTIONS (JPA 2.0) /* - VERSCHIEDENE ACCESS TYPES PRO KLASSE MÖGLICH - MISCHEN VON ACCES TYPES IN EINER VERERBUNGSHIERARCHIE */ @Entity @Access(FIELD) public class Vehicle { ... @Transient double fuelEfficiency; @Access(PROPERTY) protected double getDbFuelEfficiency() { return convertToImperial(fuelEfficiency); } ... } 22 MAPPING • Es wird immer vom Defaultverhalten ausgegangen • Das Defaultverhalten kann übersteuert werden @Entity @Table(name = "EMP") public class Employee { @Id @Column(name = "EMP_ID") private int id; } 23 PERSISTENTE DATENTYPEN • Erlaubt • • • • • • • Alle primitiven Typen, String Alle Wrapperklassen und serialisierbaren Typen (z.B. Integer, BigDecimal, Date, Calendar) byte[], Byte[], char[], Character[] Enumerations Beliebige weitere Entity-Klassen Collections von Entities, welche als Collection<>, List<>, Set<> oder Map<> deklariert sind. • Nicht erlaubt • Alle Arten von Arrays ausser die obgenannten • JPA 1.0: Collections von etwas anderem als Entities, also z.B. Wrapperklassen und andere serialiserbare Typen. 24 JAVA / SQL TYPE MAPPING • Implizit durch JDBC “Data Type Conversion Table“ definiert • Explizit durch die @Column Annotation, z.B.@Column(name = "sender") protected String sender; • Produktespezifisch durch JPA-Implementation oder im JDBC-Driver für die jeweilige Datenbank 25 LARGE OBJECTS public class Employee { @Lob private byte[] picture; ... } 26 ENUMERATIONS /* Enumerations können persistiert werden. Entweder als - Ordinalwert (position) oder - Stringwert (Name der Konstante) */ // VARIANTE ORDINAL @Enumerated(EnumType.ORDINAL) private Color color; // VARIANTE STRING @Enumerated(EnumType.STRING) private Color color; /* VORSICHT BEI ÄNDERUNGEN AN DER ENUMERATION */ 27 TEMPORALE TYPEN • Erlaubte Zeittypen • java.sql.Date, java.sql.Time, java.sql.Timestamp, java.util.Date, java.util.Calendar • java.sql Typen brauchen keine weitere Definition • Bei java.util Typen muss der JDBC Typ angegeben werden • TemporalType.DATE, TemporalType.TIME, TemporalType.TIMESTAMP @Temporal(TemporalType.DATE) private Calendar dob; 28 TRANSIENTE ATTRIBUTE • Attribute können von der Persistierung ausgeschlossen werden • Entweder mittels dem Schlüsselwort transient transient private String translatedName; • oder wenn das Attribut serialisiert werden soll mittels Annotation @Transient private String translatedName; 29 ENTITY IDENTITY DER PRIMÄRSCHLÜSSEL • Jede Entity-Klasse muss einen mit @Id bezeichneten Primärschlüssel besitzen. • Eine Id kann von folgenden Typen sein Primitive Java Typen: byte, int, short, long, char Wrapper Klassen: Byte, Integer, Short, Long, Character Array von primitiven Typen oder Wrapper Klassen java.lang.String, java.math.BigInteger, java.util.Date, java.sql.Date • Floating Point Typen sind ebenfalls erlaubt, aber sind aufgrund der möglichen Rundungsfehler nicht zu empfehlen • • • • 30 PRIMÄRSCHLÜSSEL GENERIERUNG • Primärschlüssel können in Zusammenarbeit mit der Datenbank generiert werden. • Strategien sind Identity, Table, Sequence und Auto @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Integer id; } 31 5 COLLECTION MAPPING 32 BEZIEHUNGEN • Beziehungen zwischen Entities sind prinzipiell gegeben durch entsprechende Referenzen oder Collection-Member in den Entity-Klassen. • Sie müssen jedoch deklariert werden, und sehr oft sind Details zum O/R-Mapping und zum Verhalten notwendig. • Folgende Beziehungs-Charakteristiken spielen eine Rolle: • Unidirektional, bidirektional • many-to-one, one-to-one, one-to-many, many-to-many • Aggregation, Komposition DER KORREKTE UNTERHALT DER BEZIEHUNGEN IST SACHE DER APPLIKATION! 33 BEZIEHUNGEN - KONZEPTE • Richtung • Unidirektional • Bidirektional • Kardinalität • • • • one-to-one many-to-one one-to-many many-to-many 34 OWNING AND INVERSE SIDE • In JPA spricht man von owning und inverse Side • Die besitzende Seite ist verantwortlich für das führen der Beziehung in der Datenbank (Foreign Key) • Die inversen Seite wird durch das mappedBy Attribut gekenntzeichnet • Bei unidirektonalen Beziehungen fehlt die inverse Seite 35 ONE-TO-ONE, UNIDIREKTIONAL // K Klasse lass la sse ss e Employee @OneToOne @OneToOne res ess; s; private Address addr address; // entspricht: @OneToOne @JoinColumn(name="address_id", referencedColumnName = "id") private Address address; 36 MANY-TO-ONE, UNIDIREKTIONAL lass la ass s e Em E p oy pl o e ee e // K Klasse Employee @One @O neTo ne ToOn To One On @OneToOne private Address address; // entspricht: @OneToOne @JoinColumn(name="address_id", referencedColumnName = "id") private Address address; 37 ONE-TO-MANY, BIDIREKTIONAL // Klasse Phone @ManyToOne(optional = false) private Employee employee; // Klasse Employee @OneToMany(mappedBy = "employee") private Collection<Phone> phones; 38 MANY-TO-MANY, BIDIREKTIONAL // Klasse Employee @ManyToMany(mappedBy = "employees") private Collection<Project> projects; // Klasse Project @ManyToMany private Collection<Employee> employees; 39 COLLECTIONS OF NON-ENTITES AND EMBEDDABLES (JPA 2.0) @Entity public class Employee { @ElementCollection @Column(name="PHONE_NUMBER") private List<String> phoneNumbers; } 40 COLLECTIONS • java.util.Set • Eindeutig (Object.equals())@OneToMany private Set<Phone> phones;£ • java.util.List • geordnet, kann sortiert werden@OneToMany @OrderBy("phonenumber ASC") private List<Phone> phones; • java.util.Map • Key/Value Paare@OneToMany @MapKey(name = "phonenumber") private Map<String, Phone> phones; 41 PERSISTENT ORDERING (JPA 2.0) /* DIE REIHENFOLGE EINER LISTE KANN PERSISTIERT WERDEN */ @Entity public class Employee { @OneToMany @OrderColumn(name="PHONE_POS") List<Phone> phones; } 42 ENHANCED MAP SUPPORT (JPA 2.0) /* VERWENDUNG VON OBJECTS, EMBEDABBLES UND ENTITIES ALS MAP KEY UND VALUE /* @Entity public class Vehicle { @OneToMany @MapKeyJoinColumn(name="PART_ID") Map<Part, Supplier> suppliers; } 43 LAZY LOADING • Default bei one-to-one und many-to-one • FetchType.EAGER • Default bei one-to-many und many-to-many • FetchType.LAZY • Defaultverhalten kann übersteuert werden. @OneToMany(fetch = FetchType.EAGER) private Set<Phone> phones; 44 ÜBUNG: BEZIEHUNGEN • Erweitern Sie Ihr Projekt gemäss nachfolgendem Klassenmodell • Wählen Sie die ID Generierungsstrategie • Definieren Sie die Beziehungstypen • Definieren Sie die Beziehungsmappings • Testen Sie Ihre Arbeit in dem Sie einen Employee mit sämtlichen Beziehungen erstellen 45 KLASSENMODELL 46 6 ENTITY MANAGER 47 PERSISTENCE CONTEXT Der Persistence Context definiert das physische Umfeld von Entities zur Laufzeit • die Menge aller Managed Entities in der Applikation • den Entity Manager für diese Entities • die laufende Transaktion • den Contexttyp 48 KONTEXT TYPEN • TRANSACTION • Standard im Java EE Umfeld • Lesender und schreibender Zugriff nur innerhalb der Transaktion • Gelesene Objekte sind nach der Transaktion im Zustand detached • Wiedereinkopplung in eine Transaktion mit merge() • EXTENDED • • • • • Standard im Java SE Umfeld Alle Objekte sind lesend und schreibend zugreifbar Modifikationen finden lokal statt Effekt von persist(), remove() usw. wird aufbewahrt Propagation von Efffekten und Änderungen in die DB aber nur, wenn nachträglich begin()/commit() ausgeführt wird 49 OBJEKTVERWALTUNG • Der Transfer von Objekten von und zur Datenbank erfolgt automatisch: so spät wie möglich --> Lazy Access • Der Transfer von Objekten von und zur Datenbank kann manuell erzwungen werden --> synchron zum Aufruf • Selbstverständlich gilt ein Transaktionsmodell: Der Zugriff auf Objekte erfolgt ab Beginn der Transaktion, die Synchronisation mit der Datenbank wird spätestens beim Commit abgeschlossen und unterliegt der ACIDRegel • Auf Objekte kann auch ausserhalb von Transaktionen zugegriffen werden, jedoch ohne Konsistenz- und Synchronisationsgarantie 50 ZUSTÄNDE UND ÜBERGÄNGE 52 Entity persistieren /* MIT PERSIST() WIRD EINE NEUE ENTITY VOM ENTITYMANAGER VERWALTET */ Department dept = em.find(Department.class, deptId); Employee emp = new Employee(); emp.setId(empId); emp.setName(empName); emp.setDepartment(dept); dept.getEmployees().add(emp); em.persist(emp); /* DIE METHODE CONTAINS() KANN GEPRÜFT WERDEN OB EINE ENTITY MANAGED IST */ if (em.contains(emp)) { } 53 KASKADIERTE PERSISTENZ (1) • Kaskadierte Persistenz heisst: Alle von einem persistenten Objekt aus erreichbaren Objekte sind ebenfalls persistent Employee employee = new Employee(); em.persist(emp); Address address = new Address(); employee.setAddress(address); • Die Kaskadierung muss deklariert werden: • PERSIST, MERGE, REMOVE, REFRESH • ALL 54 KASKADIERTE PERSISTENZ (2) /* DIE KASKADIERUNG KANN FÜR DAS ERSTELLEN UND DAS LÖSCHEN DER PERSISTENZ SEPARAT EINGESTELLT WERDEN */ public class Employee { @OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE}) private Address address; } 55 ORPHAN REMOVAL (JPA 2.0) /* SOLLEN ABHÄNGIGE KINDELEMENTE BEI TO-MANY BEZIEHUNGEN EBENFALLS GELÖSCHT WERDEN, KANN DIES SEIT JPA 2.0 EBENFALLS DEKLARIERT WERDEN (Orphan = Weisenkind) */ @OneToMany(cascade = CascadeType.ALL, mappedBy = "customer", orphanRemoval = true) private Set<Order> orders; 56 ENTITY SUCHEN • Mit find() kann eine Entity über ihren Primary Key gefunden werden • Die gefunden Entity wird kommt automatisch in den Zustand managed • Da find() über den Primary Key sucht, kann diese Methode vom Persistence Provider optimiert werden und unter Umständen einen Datenbankzugriff vermieden werden • Soll eine one-to-one oder many-to-one Reference auf eine bestehende Entity gebildet werden, kann getReference() verwendet werden um das vollständige Laden der Target-Entity zu verhindern 57 EINLESEN • Der Objektzustand wird beim ersten Zugriff auf das Objekt eingelesen. • Wenn FetchType.EAGER gesetzt ist, werden referenzierte Objekte ebenfalls mitgeladen. • Wenn FetchType.LAZY gesetzt ist, werden referenzierte Objekte beim ersten Gebrauch eingelesen. • Der Objektzustand wird nie automatisch aufgefrischt, nur via die EntityManager.refresh()-Methode. • Eine neue Transaktion führt nicht automatisch zum erneuten Einlesen bestehender Objekte. 58 OBJEKTZUSTAND NACH COMMIT • Persistence Context EXTENDED • Entity bleibt im Zustand managed • Änderungen nach dem Commit werden berücksichtig und im Rahmen der nächsten Transaktion in die Datenbank übernommen • Persistence Context TRANSACTION • Objekt ist nach Commit im Zustand detached • Änderungen müssen mit EntityManager.merge() innerhalb der nächsten Transaktion dem EntityManager übergeben werden 59 OBJEKTZUSTAND NACH ROLLBACK • Nach einem Rollback ist jedes noch vorhandene Objekt im Zustand detached • Die Belegung der Felder wird durch den Rollback nicht geändert, jedoch der Zustand in der Datenbank • Achtung! Inkonsistenzen! Objekte mit Entity Manager neu laden 60 PERSISTENCE CONTEXT AUFRÄUMEN • Ab und zu kann es vorkommen, dass der Persistence Context gelöscht werden soll • Dies kann mit der Methode clear() des EntityManager erreicht werden • Alle Entities kommen in den Zustand detached • Vorsicht! enthält der Persistence Context Änderungen welche noch nicht mit commit() gespeichert wurden, gehen diese verloren 61 ÜBUNG: ENTITY MANAGER • Definieren Sie die Kaskadierung auf den Beziehungen im Modell • Entfernen Sie die nicht mehr benötigten persist() Aufrufe • Definieren Sie das Ladverhalten (EAGER oder LAZY) bei Ihren Beziehungen • Schauen Sie sich im Debugger das Lazy Loading an • Wiederholen Sie die Tests und achten Sie auf die generierten SQL Statements in Bezug auf das Ladeverhalten 62 7 USING QUERIES 63 QUERIES IN JPA • JPA kennt drei Möglichkeiten um Abfragen zu formulieren • Java Persistence QL • Unabhängig von der darunterliegenden Datenbank • SQL Subset • Abfragen basieren auf dem Klassenmodell, nicht auf dem Datenmodell • SQL • Criteria API seit JPA 2.0 64 EINFÜHRUNG (1) • Einfachstes Query SELECT e FROM Employee e • Pfadausdrücke, Navigation mit . SELECT e.name FROM Employee e SELECT e.department FROM Employee e • Resultate filtern SELECT e FROM Employee e WHERE e.department.name = 'NA42' AND e.address.state in ('NY','CA') • Projektion SELECT e.name, e.salary FROM Employee e 65 EINFÜHRUNG (2) • Join zwischen Entities SELECT p.number FROM Employee e, Phone p WHERE e = p.employee AND e.department.name = 'NA42' AND p.type = 'Cell' • Join mit JOIN Operator SELECT p.number FROM Employee e JOIN e.phones p WHERE e.department.name = 'NA42' AND p.type = 'Cell' 66 EINFÜHRUNG (3) • Aggregatfunktionen SELECT d, COUNT(e), MAX(e.salary), AVG(e.salary) FROM Department d JOIN d.employees e GROUP BY d HAVING COUNT(e) >= 5 • Query Parameter • Named SELECT e FROM Employee e WHERE e.department = :dept AND e.salary > :base • Positional SELECT e FROM Employee e WHERE e.department = ?1 AND e.salary > ?2 67 PARAMETERÜBERGABE // Uebergabe eines Named Parameter q.setParameter("dept", "NA42"); // Uebergabe eines Positional Parameter q.setParameter(1, "NA42"); 68 PFADAUSDRÜCKE • Ein Pfadausdruck ermöglicht die direkte Navigation von einem äusseren zu inneren, referenzierten Objekten: SELECT e.address FROM Employee e SELECT e.address.name FROM Employee e • Ein Pfadausdruck kann in einer Collection enden: SELECT e.projects FROM Employee e • Ein Pfadausdruck kann NICHT über eine Collection hinweg navigieren: SELECT e.projects.name FROM Employee e 69 QUERIES DEFINIEREN /* DYNAMISCHE QUERIES */ em.createQuery("SELECT e FROM Employee e"); /* NAMED QUERIES */ // Deklaration @NamedQuery(name = "Employee.findAll", query = "SELECT e FROM Employee e") public class Employee {...} // Verwendung Query q = em.createNamedQuery("Employee.findAll"); 70 QUERIES AUSFÜHREN /* ABHOLEN DES RESULTATES MIT METHODEN VON QUERY */ List getResultList() Object getSingleResult() int executeUpdate() // Beispiel Query q = em.createQuery("SELECT e FROM Employee e"); List<Employee> emps = q.getResultList(); for(Employee e : emps) { ... } 71 QUERY RESULTATE VERARBEITEN • Mögliche Typen im Resultat: Primitive Typen und String, Entity Typen, Object[] und Benutzertypen durch Constructor Expression • Die List von getResultList() bzw. das Object aus getSingleResult() enhält direkt diese Resulttypen • Handelt es sich beim Resultat um eine Entity kommt diese in den Zustand managed wenn das Query: • innerhalb einer Transaktion ausgeführt wird • ausserhalb einer Transaktion ausgeführt wird und der Contexttyp EXTENDED ist. • Wird ein Query ausserhalb einer Transaktion im Contexttyp TRANSACTION ausgeführt, spricht man von einem Read-only Query 72 MEHRERE RESULTAT TYPEN /* BEI EINER PROJEKTION IN DER SELECT KLAUSEL WIRD EINE LISTE VON OBJECT[]-ARRAY ZURÜCKGEGEBEN */ List<Object[]> result = em.createQuery( "SELECT e.name, e.department.name " + "FROM Project p JOIN p.employees e " + "where p.name = "ZLD").getResultList(); for (Object[] values : result) { System.out.println(values[0] + "," + values[1]); } 73 CONSTRUCTOR EXPRESSION /* MIT DER CONSTRUCTOR EXPRESSIONS EXISTIERT EINE EINFACHE MÖGLICHKEIT UM RESULTATE AUF KLASSEN ZU MAPPEN. ACHTUNG: KLASSE MUSS VOLLQUALIFIZIERT ANGEGEBEN WERDEN! */ // Beispiel TransferObject public class EmployeeTO { public EmployeeTO(String employeeName, String deptName) {...} } // Query ausführen List result = em.createQuery( "SELECT NEW jpa.util.EmployeeTO(e.name, e.department.name) " + "FROM Project p JOIN p.employees e where p.name = "ZLD").getResultList(); for (EmployeeTO emp : result) { System.out.println(emp.employeeName + "," + emp.deptName); } 74 PAGING UND SYNCHRONISATION • Paging wird in Applikationen definiert, um die Resultatmenge einzuschränken: Query.setFirstResult(int pos) Query.setMaxResults(int max) • Synchronisation Query.setFlushModeType(FlushModeType type) • FlushModeType.AUTO Ausstehende Änderungen werden ebenfalls in das Resultat einbezogen • FlushModeType.COMMIT Nur comittete Daten werden zurückgegeben 75 BULK UPDATE UND DELETE // BULK DELETE Query q = em.createQuery("DELETE from Employee e"); int count = q.executeUpdate(); // BULK UPDATE Query q = em.createQuery("UPDATE Employee e " + "SET e.name = 'Simon' WHERE e.name = 'Peter'); int count = q.executeUpdate(); /* VORSICHT damit wird der Entity Manager umgangen! Verwaltete Entities unbedingt aktualisieren*/ 76 QUERY HINTS /* MÖGLICHKEIT FÜR DEN HERSTELLER FÜR OPTIMIERUNGEN */ // Beispiel direkt beim Query q.setHint("toplink.cache-usage", "DoNotCheckCache"); // Beispiel als Named Query @NamedQuery(name = "findAll", query = "SELECT e FROM Employee e", hints = {@QueryHint(name = "toplink-cache-usage", value = "DoNotCheckCache")}) public class Employee { ... } 77 EMPFEHLUNGEN • Verwenden Sie Named Queries • Constructor Expression • Werden die Daten nur zum Anzeigen z.B. für Statistiken verwendet, brauchen diese nicht als Entites gelesen werden • Verwenden Sie Query Hints zur Optimierung • Bulk Update und Delete in isolierter Transaktion verwenden und danach den Persistence Context "aufräumen" • Provider Unterschiede beachten • Studieren Sie die generierten SQLs um ein Gefühl für Ihre JPA Implementation zu erhalten 78 8 QUERY LANGUAGE 79 KLASSENMODELL 80 SELECT SELECT <select_expression> FROM <from_clause> [WHERE <conditional_expression>] [ORDER BY <order_by_clause>] // Beispiel SELECT e FROM Employee e WHERE e.name = 'John Doe' ORDER BY e.salary 81 FROM Identifikationsvariable FROM Employee e SQL Joins werden automatisch erzeugt, wenn - zwei oder mehr Range Variablen verwendet werden FROM Employee e, Department d - der JOIN Operator verwendet wird FROM Employee e JOIN e.department d - ein Pfad Ausdruck über eine Beziehung navigiert SELECT e.department ... 82 JOINS INNER JOINS JPQL: SELECT p FROM Employee e JOIN e.phones p SQL: SELECT p.* FROM emp e, phone p WHERE e.id = p.emp_id OUTER JOINS JPQL: SELECT e, d FROM Employee e LEFT JOIN e.department d SQL: SELECT * FROM emp e, dept d WHERE e.dept_id = d.id (+) FETCH JOINS SELECT e FROM Employee e JOIN FETCH e.address 83 WHERE INPUT PARAMETER Named = :name Positional = ?1 OPERATOREN PRÄZEDENZ Navigations Operator . Unäre Operatoren +/Multiplikation (*) und Division (/) Addition (+) und Subtraktion (-) Vergleichsoperatoren =, >, >=, <, <=, [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF] Logische Operatoren (AND, OR, NOT) 84 BETWEEN SELECT e FROM Employee e WHERE e.salary BETWEEN 40000 AND 45000 entspricht SELECT e FROM Employee e WHERE e.salary >= 40000 AND e.salary <= 45000 85 IN SELECT e FROM Employee e WHERE e.address.state IN ('NY', 'CA') SELECT e FROM Employee e WHERE e.department IN (SELECT DISTINCT d FROM Department d JOIN d.employees de JOIN de.projects p WHERE p.name LIKE 'QA%') 86 COLLECTIONS IS EMPTY /* IS EMPTY ist in Bezug auf Collections equivalent zu IS NULL bei Feldern */ SELECT e FROM Employee e WHERE e.directs IS NOT EMPTY entspricht SELECT m FROM Employee m WHERE (SELECT COUNT(e) from Employee e WHERE e.manager = m) > 0 87 COLLECTIONS MEMBER OF /* MEMBER OF prüft ob das Objekt ein Element einer Collection ist */ SELECT e FROM Employee e WHERE :project MEMEBER OF e.projects entspricht SELECT e FROM Employee e WHERE :project IN (SELECT p FROM e.projects p) 88 EXISTS SELECT e FROM Employee e WHERE NOT EXISTS (SELECT p FROM e.phones p WHERE p.type = 'Cell') 89 ANY, ALL und SOME SELECT e FROM Employee e WHERE e.directs IS NOT EMPTY AND e.salary > ALL (SELECT d.salary FROM e.directs d) 90 FUNKTIONEN ABS(number) CONCAT(string1, string2) CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP LENGTH(string) LOCATE(string1, string2 [,start]) LOWER(string) MOD(number1, number2) SIZE(collection) SQRT(number) SUBSTRING(string, start, end) UPPER(STRING) TRIM 91 ORDER BY // Einfache Sortierung SELECT e FROM Employee e ORDER BY e.name DESC // Mehrfache Sortierung SELECT e FROM Employee e ORDER BY e.name, e.salary DESC /* Das beim ORDER BY verwendete Feld muss im SELECT enthalten sein! Folgendes Query ist NICHT erlaubt: */ SELECT e.name FROM Employee e ORDER BY e.salary DESC 92 AGGREGAT QUERIES SELECT <select_expression> FROM <from_clause> [WHERE <conditional_expression>] [GROUP BY <group_by_clause>] [HAVING <conditional_expression>] [ORDER BY <order_by_clause>] // Beispiel SELECT AVG(e.salary) FROM Employee e 93 AGGREGAT FUNKTIONEN AVG Durchschnitt der Gruppe COUNT Anzahl Werte in der Gruppe MAX Maximaler Wert in der Gruppe MIN Minimaler Wert in der Gruppe SUM Summe der Gruppe 94 GROUP BY /* Definiert eine Gruppierung für die Aggregation der Resultate /* SELECT d.name, COUNT(e) FROM Department d JOIN d.employee e GROUP by d.name /* Fehlt die GROUP BY Angabe, wird das ganze Query als Gruppe verwendet */ SELECT COUNT(e) FROM Department d JOIN d.employee e 95 HAVING /* Definiert einen Filter, welcher nach der Gruppierung der Resultate angewendet wird */ SELECT e, COUNT(p) FROM Employee e JOIN e.projects p GROUP BY e HAVING COUNT(p) >= 2 96 UPDATE UPDATE <entity_name> [[AS] <identification_variable>] SET <update_statement> {, <update_statement>}* [WHERE <conditional_expression>] // Beispiel UPDATE Employee e SET e.salary = 60000 WHERE e.salary = 55000 97 DELETE DELETE FROM <entity_name> [[AS] <identification_variable>] [WHERE <condition>] // Beispiel DELETE FROM Employee e WHERE e.department IS NULL 98 JPQL ERWEITERUNGEN IN JPA 2.0 // Timestamp SELECT t from BankTransaction t WHERE t.txTime > {ts ‘2008-06-01 10:00:01.0’} // Non-polymorphic Queries SELECT e FROM Employee e WHERE TYPE(e) = FullTimeEmployee OR e.wage = "SALARY" // Collection Parameters in IN Expression SELECT emp FROM Employee emp WHERE emp.project.id IN [:projectIds] 99 JPQL ERWEITERUNGEN IN JPA 2.0 // Ordered List Index SELECT t FROM CreditCard c JOIN c.transactionHistory t WHERE INDEX(t) BETWEEN 0 AND 9 CASE Statement UPDATE Employee e SET e.salary = CASE e.rating WHEN 1 THEN e.salary * 1.1 WHEN 2 THEN e.salary * 1.05 ELSE e.salary * 1.01 END 100 ÜBUNG: QUERY LANGUAGE • Erstellen Sie JUnit-Tests, um die Möglichkeiten der Query Language auszuprobieren • Abfragen von Objekten mit Bedingungen / Sortierung • Aggregatfunktionen • Gibt es Abfragen, die Schwierigkeiten bereiten, aber in SQL grundsätzlich möglich wären? • Verwenden Sie Named Queries 101 9 CRITERIA API 102 EINFACHES QUERY CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Employee> cq = cb.createQuery(Employee.class); Root<Employee> employee = cq.from(Employee.class); cq.select(employee); // entspricht JPQL SELECT e FROM Employee e; 103 PARAMETER CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Employee> cq = cb.createQuery(Employee.class); Root<Employee> employee = cq.from(Employee.class); cq.select(employee); Parameter<Integer> param = cb.parameter(Integer.class, "id"); cq.where(cb.equal(employee.get("id"), param)); // entspricht JPQL SELECT e FROM Employee e WHERE e.id = :id 104 PROJEKTION CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Object> cq = cb.createQuery(Object.class); Root<Employee> employee = cq.from(Employee.class); cq.multiselect(employee.get("id")); // entspricht JPQL SELECT e.id FROM Employee e 105 METAMODEL API @Generated(value="EclipseLink-2.5.0.v20130507-rNA", date="2013-11-05T10:51:00") @StaticMetamodel(Employee.class) public class Employee_ { public static volatile SingularAttribute<Employee, Long> id; } 106 METAMODEL API CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Object> cq = cb.createQuery(Object.class); Root<Employee> employee = cq.from(Employee.class); cq.multiselect(employee.get(Employee_.id)); // entspricht JPQL SELECT e.id FROM Employee e 107 10 ADVANCED OBJECT-RELATIONAL MAPPING 108 EMBEDDED OBJECTS • entspricht Komposition • Eingebettete Objekte haben keine eigene Identität • Mutterobjekt und eingebettetes Objekt sind in derselben Tabelle abgelegt 109 EMBEDDED OBJECTS, BEISPIEL @Embeddable public class Address { private String street; private String city; private String state; private String zip; } @Entity public class Employee { @Id private int id; private String name; private long salary; @Embedded private Address address; } 110 ENHANCED EMBEDDABLES (JPA 2.0) /* Verschachtelung von Embeddables und Embeddables mit Beziehungen */ @Embeddable public class Assembly { ... @Embedded ShippingDetail shipDetails; @ManyToOne Supplier supplier; ... } 111 ZUSAMMENGESETZTE PRIMÄRSCHLÜSSEL public class EmployeeId implements Serializable { protected String country; protected int id; } // Variante 1 @IdClass( EmployeeId.class ) @Entity public class Employee { @Id protected String country; @Id protected int id; } // Variante 2 @Entity public class Employee { @EmbeddedId public EmployeeId id; } 112 COMPOSITE PRIMARY KEY WITH RELATIONSHIPS (JPA 2.0) @Entity @IdClass(PartPK.class) public class Part { @Id int partNo; @Id @ManyToOne Supplier supplier; } public class PartPK { int partNo; int supplier; } 113 READ-ONLY MAPPINGS UND OPTIONAL /* Read-Only */ @Column(insertable = false, updateable = false) /* Es ist nicht möglich eine ganze Entity als read-only zu deklarieren */ /* Optional */ @ManyToOne(optional = true) // Default 114 MAPPING VON SQL-VIEWS /* Komplexe Abfragen werden in einer Datenbank gerne als (materialisierte) View vorgehalten. Um Performance-Vorteile zu nutzen, kann eine View wie eine Entity angesprochen werden. */ @Entity public class EmployeeStats { @Id protected long id; @Column(insertable=false, updatable=false) protected int numPhones; } CREATE VIEW employeestats (id, numPhones) AS SELECT e.id, COUNT(p.*) FROM employee e LEFT OUTER JOIN PHONES P on (…) group by e.id 115 VERERBUNG • Vererbungshierarchien können problemlos verwendet und abgebildet werden • Klassen können abstrakt oder konkret sein • Alle Klassen in der Vererbungshierarchie müssen den Primärschlüssel der Basisklasse verwenden (erben) • Es gibt vier Mappingstrategien auf die Datenbank: • • • • • Eine einzige Tabelle für die gesamte Verbungshierarchie Eine Tabelle für jede konkrete Klasse Eine Tabelle für jede Klasse Mapped Superclass 116 SINGLE_TABLE @Entity @Inheritance public abstract class Project @Entity public class DesignProject extends Project @Entity public class QualityProject extends Project 117 JOINED @Entity @Inheritance(strategy=InheritanceType.JOINED) public abstract class Project @Entity public class DesignProject extends Project @Entity public class QualityProject extends Project 118 TABLE_PER_CLASS (Optional) @Entity @Inheritance(strategy=TABLE_PER_CLASS) public abstract class Project @Entity public class DesignProject extends Project @Entity public class QualityProject extends Project 119 MAPPED_SUPERCLASS @MappedSuperclass public abstract class BaseEntity { @Id @GeneratedValue protected Integer id; protected Integer version; protected Timestamp createdAt; protected String createdFrom; protected Timestamp updatedAt; protected String updatedFrom; } @Entity public class Phone extends BaseEntity { } 120 ÜBUNG: ADVANCED ORM • Probieren Sie die verschiedenen Vererbungsmappings bei Project aus. • Achtung! Generierte Tabellen beim Wechsel löschen • Erstellen Sie ein Klasse BaseEntity für alle Entities und verwenden Sie für diese MAPPED_SUPERCLASS. • Probieren Sie die Java Persistence Query Language in Bezug auf die Vererbung aus 121 KLASSENMODELL 122 11 ADVANCED TOPICS 123 SQL QUERIES EntityManager.createNativeQuery(String sql) EntityManager.createNativeQuery(String sql, Class resultClass) // Kann auch als NamedQuery definiert werden @NamedNativeQuery( name = "employeeReporting", query = "SELECT * FROM emp WHERE id = ?", resultClass = Employee.class) // Ausführen des NamedQueries und Parameterübergabe Query q = em.createNamedQuery("employeeReporting"); q.setParameter(1, employeeId); List<Employee> list = q.getResultList(); 124 RESULT SET MAPPING // Definition @SqlResultSetMapping( name = "employeeResult", entities = @EntityResult(entityClass = Employee.class) // Verwendung Query q = em.createNativeQuery( "SELECT * FROM EMPLOYEE", "employeeResult"); /* Mapping Foreign Keys Wenn die Foreign Keys im Resultat der Abfrage enthalten sind, werden die einfachen Beziehungen ebenfalls mitgeführt */ 125 MULITPLE RESULT MAPPING SELECT e.*, a.* FROM emp e, address a WHERE e.adress_id = a.id @SqlResultSetMapping( name = "EmployeeWithAddress", entities = {@EntityResult(entityClass = Employee.class), @EntityResult(entityClass = Address.class)} 126 MAPPING INHERITANCE SELECT id, name, start_date, daily_rate, term, vacation, hourly_rate, salary, pension, type FROM employee_stage @SqlResultSetMapping( name="EmployeeStageMapping", entities= @EntityResult( entityClass=Employee.class, discriminatorColumn="TYPE", fields={ @FieldResult(name="startDate",column="START_DATE"), @FieldResult(name="dailyRate",column="DAILY_RATE"), @FieldResult(name="hourlyRate",column="HOURLY_RATE") })) 127 LIFECYCLE CALLBACKS • Callbacks sind eine gängige Methode, um Einfluss auf den Lade- oder Speichervorgang von Objekten zu nehmen. • Mögliche Events: PrePersist, PostPersist, PreRemove, PostRemove, PreUpdate, PostUpdate, PostLoad • • • • • • @Entity public class ImageMessage extends Message { @PrePersist @PreUpdate protected void compress(){ ... } @PostLoad @PostUpdate protected void uncompress() { ... } 128 Entity Listeners /* Wenn Sie dieselbe Funktionalität bei Callbacks von verschiedenen Entities verweden wollen, können Sie einen Entity Listener erstellen: /* public class BaseEntityDebugListener { @PrePersist public void debugPrePersist(BaseEntity e) { log.debug("PrePresist: " + e); } } // Entity Listener verwenden: @Entity @EntityListeners({BaseEntityDebugListener.class}) public class Employee extends BaseEntity 129 VERERBUNG UND LIFECYCLE EVENTS • Prüfen ob Default Listeners existieren • Höchste Ebene der Vererbung auf @EntityListeners Annotations prüfen • Wiederhole Schritt 2 für die nächst tiefere Ebene • Höchste Ebene der Vererbung auf Methoden mit zum Event passender Annotation prüfen • Wiederhole Schritt 4 für die nächst tiefere Ebene 130 ENTITY ZUSTAND REFRESH • Sind Sie nicht sicher, ob der Zustand Ihrer Entität mit dem Zustand in der Datenbank übereinstimmt, kann mit der Methode refresh() im EntityManager das Entity erneut geladen werden • Ist das zu aktualisierende Entity nicht im Zustand managed wird eine IllegalArgumentException geworfen 131 FRAGEN ZUM TRANSAKTIONSMANAGEMENT • Ist der Objektzustand nach dem Lesen durch die Applikation in der Datenbank eingefroren, während dem die Transaktion läuft (mit welchem Isolationsgrad wird gearbeitet)? • Ein Objekt wird geändert: Zu welchem Zeitpunkt wird es in der Datenbank gesperrt? • Kann entdeckt werden, ob Daten beim Commit in der Zwischenzeit von anderen Prozessen geändert wurden auf der DB? 132 LOST UPDATE PROBLEM 133 SPERRZEITPUNKT • Daten werden in der Datenbank im Rahmen von SQL-Befehlen gesperrt • Da Änderungen vorerst lokal durchgeführt und erst zum Commit-Zeitpunkt in die DB propagiert werden, findet das Sperren erst beim Commit statt. • Allfällige Wartesituationen, Deadlocks, Integritätsverletzungen treten effektiv erst zum Commit-Zeitpunkt auf 134 OPTIMISTIC LOCKING VERSIONIERUNG • Die Versionierung im Rahmen von JPA ist als optimistsches Locking zu verstehen. Mit der Versionierung kann ein Concurrency Control über Transaktionen hinweg realisiert werden. • Mit einer @Version können Lost Updates detektiert und vermieden werden: Das Feld kann vom Typ int, Integer, short, Short, long, Long oder java.sql.Timestamp sein @Entity public class Employee { @Version protected long version; } 135 WEITERE LOCKING STRATEGIEN • Soll REPEATABLE_READ zur Anwendung kommen, kann Read Locking verwendet werden:em.lock(LockModeType.READ) • Um eine Entität zum Schreiben zu sperren, wird Write Locking verwendetem.lock(LockModeType.WRITE) 136 ENHANCED LOCKING (JPA 2.0) • JPA 2.0 führt neue Lockingstrategien ein: • • • • • OPTIMISTIC ( = READ ) OPTIMISTIC_FORCE_INCREMENT ( = WRITE ) PESSIMISTIC_READ (Repeatable Read) PESSIMISTIC_WRITE (Serialized) PESSIMISTIC_FORCE_INCREMENT • Kombination Opitmistic/Pessimistic möglich! 137 ENHANCED LOCKING (JPA 2.0) // Beispiel mit EntityManager.refresh() public void applyCharges() { Account acct = em.find(Account.class, acctId); // calculate charges, etc. int charge = … ; if (charge > 0) { em.refresh(acct, PESSIMISTIC_WRITE); double balance = acct.getBalance(); acct.setBalance(balance - charge); } } 138 SCHEMA GENERATION • Die Erzeugung von DDL kann, aber muss nicht durch eine Implementation der JPA Spezifikation angeboten werden. • In der JPA Spezifikation sind verschiedene Angaben (Annotations und Attribute davon) vorgesehen, welche die Erzeugung von DDL-Befehlen ermöglichen public class Message { @Column(unique = false, nullable=false, columnDefinition="varchar", lenghth=64 ) public BigDecimal sender; 139 ÜBUNG: ADVANCED TOPICS • Verwenden Sie SQL • Erweiteren Sie die BaseEntity um die Attribute createdAt und updatedAt und verwenden Sie Lifecycle Callbacks, um diese Attribute vor dem Insert und Update entsprechend abzufüllen • Erweitern Sie Ihre BaseEntity mit Optimistic Locking und testen Sie das Verhalten • Erweitern Sie ihr Mapping an beliebiger Stelle mit Angaben zur Schema Generierung (@Column) 140 12 XML MAPPING 141 THE METADATA PUZZLE • Reihenfolge bei der Verarbeitung der Metadaten: • Annotations verarbeiten • Klassen aus XML Mapping hinzufügen • Attribute aus XML Mapping hinzufügen • Defaultwerte setzen 142 DAS MAPPING FILE <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd" version="2.0"> ... </entity-mappings> 143 DEFAULTS IM MAPPING FILE Annotations ausschalten <xml-mapping-metadata-complete /> Persistence Unit Defaults schema, catalog, access, cascade-persist entity-listeners Diese können auch ausgeschaltet werden exclude-default-listeners @ExcludeDefaultListeners 144 QUERIES UND GENERATORS sequence-generator table-generator named-query named-native-query sql-result-set-mapping 145 ENTITY MAPPING MIT XML <entity class="examples.model.Employee"> <attributes> <id name="id"> <generated-value strategy="SEQUENCE" /> </id> <many-to-one name="department" /> <many-to-one name="manager" /> <one-to-many name="phones" mapped-by="employee" /> <one-to-many name="directs" mapped-by="manager" /> <one-to-one name="address" /> <many-to-many name="projects" mapped-by="employees" /> </attributes> </entity> 146 ÜBUNG: XML MAPPING • Verwenden Sie für einen Teil ihres Models XML für das Mapping • Lagern Sie ihre Named Queries in XML aus • Erstellen Sie eine globalen EntityListener um Veränderungen an den Daten zu loggen 147 13 PACKAGING AND DEPLOYMENT 148 CONFIGURING PERSISTENCE UNITS • Persistence Unit Name <persistence-unit name="EmployeeService" /> • Transaction Type RESOURCE_LOCAL oder JTA • Persistence Provider <provider>org.hibernate.ejb.HibernatePersistence</provider> • Data Source nur bei JTA • Mapping Files <mapping-file>META-INF/queries.xml</mapping-file> 149 MANAGED CLASSES • Lokale Klassen Alle annotierten Klassen im Deployment Unit in welcher das persistence.xml gepackt ist • Klassen in Mapping Files Mit XML gemappte Klassen • Explicitly Listed Classes Im persistence.xml eingetragene Klassen • Zusätzliche JARs mit Managed Classes Im persistence.xml unter jar-file eingetragene JARs 150 PROPERITES ZUR LAUFZEIT ÜBERGEBEN /* Die Properties im persistence.xml können ebenfalls zur Laufzeit beim Erstellen der EntityManagerFactory übergeben werden */ Map props = new HashMap(); props.put("javax.persistence.jdbc.user", "emp"); props.put("javax.persistence.jdbc.password", "emp"); EntityManagerFactory emf = Persistence.createEntityManagerFactory( "EmployeeService", props); 151 VARIOUS 152 JSR 303: BEAN VALIDATION • Validierung und gewisse Elemente der DDL Generierung werden mit JSR 303 ersetzt • @NotNull statt @Column(nullable=false) • @Size.max statt @Column.length • @Digits statt @Column.precision/.scale • @Min / @Max bei numerischen Columns • @Future / @Past bei Datumstypen • @Size für Collections und Arrays 153 VERSCHIEDENES • 2nd Level Cache Mechanismen • Standardisierung von häufig gebrauchen Properties im persistence.xml • • • • javax.persistence.jdbc.driver javax.persistence.jdbc.url javax.persistence.jdbc.user javax.persistence.jdbc.password • Bootstraping im Java SE Umfeld mittels ServiceLoader (ab Java 1.6) 154 REFERENZEN UND LINKS • Pro JPA 2: Mastering the Java Persistence API Mike Keith and Merrick Schincariol ISBN-13: 978-1-4302-1956-9 • JSR 317: Java Persistence 2.0 www.jcp.org/en/jsr/detail?id=317 • EclipseLink (Reference implementation) www.eclipse.org/eclipselink 155