250 likes | 393 Views
Querying. Persistente Domänenmodelle mit JPA 2.0 und Bean Validation. Entitäten laden. In JPA gibt es verschiedene Optionen Entitäten zu laden: Eine einzelne Instanz über die ID laden Navigation auf dem Objektgraphen Queries in der Java Persistence Query Language (JPQL) Queries in SQL
E N D
Querying Persistente Domänenmodelle mit JPA 2.0 und Bean Validation
Entitäten laden • In JPA gibt es verschiedene Optionen Entitäten zu laden: • Eine einzelne Instanz über die ID laden • Navigation auf dem Objektgraphen • Queries in der Java Persistence Query Language (JPQL) • Queries in SQL • In JPA2 kommt neu die Criteria API hinzu
Entity über die ID laden • Der EntityManager stellt zwei Möglichkeiten zur Verfügung: find() und getReference()EntityManager em = …Integer id = 1234;Employee john = em.find(Employee.class, id); • Falls die Entität bereits im Persistence Context geladen ist, so wird kein DB-Query ausgeführt. • find(): Wenn sich die Entity noch nicht im Persistence Context befindet, so wird sie von der DB geladen. • Resultat is null, falls die Entity nicht existiert • getReference(): Wenn sich die Entität noch nicht im Persistence Context befindet, so wird ein Proxy zurückgegeben. Es wird vorerst kein DB-Query ausgeführt. Dieses erfolgt erst wenn auf die Entität zugegriffen wird. • EntityNotFoundException erst beim Zugriff, falls die Entity nicht existiert.
Navigation des Objektgraphen • Ausgehend von einer Entity kann ein Objektgraph traversiert werden. Dabei lätdt JPA transparent alle notwendigen Daten von der DB. • Dieses Feature wird “Lazy Loading” genannt • Die Entities müssen persistent und der EntityManager muss offen sein • Dies ist ein mächtiges Feature, birgt aber auch Gefahren
Queries mit JPQL • JPQL ist eine mächtige Abfragesprache basierend auf dem Entitätenmodell: • Stark an SQL angelehnt • Unabhängig von der darunterliegenden Datenbank • Abfragen basieren auf dem Klassenmodell (Entitäten), nicht auf dem Datenmodell (Tabellen) • Unterstützt OO-Konstrukte wie Vererbung, Polymorphismus und Pfadausdrücke String queryString = “select e.address from Employee e where e.mainProject.name = ‘JPA Kurs‘“; Query query = em.createQuery(queryString); List<Address> users = query.getResultList();
Verwendung von JPQL • Typischerweise wird JPQL verwendet um Entities zu laden. JPQL unterstützt aber auch andere Szenarien: • Abfrage von skalaren Werten (Projektionen oder Aggregationen) • Bulk Updates und Deletes • Reporting Queries: Rückgabe von Daten-Tupels, nutzung von Gruppierungs- und Aggregationsfunktionen der DB • Constructor Expressions: Abfüllen von beliebigen Objekten (nicht notwendigerweise Entities) • JPQL kann entweder in Dynamischen Queries oder in Named Queries verwendet werden.
Dynamische Queries • Bei Dynamischen Queries wird der JPQL String zur Laufzeit erstellt. • Kontextabhängige Queries • String Concatenation EntityManager em = ... String queryString = “select e from Employee e where e.address.city = ‘Bern‘“; Query query = em.createQuery(queryString); List<Employee> employees = query.getResultList();
Named Queries • Named Queries werden statisch definiert und können überall in der Applikation verwendet werden. • Die JPA Infrastruktur kann Named Queries vor der eigentlichen Ausführung parsen und kompilieren (Prepared Statements) • Parsen/Kompilierung muss nur einmal durchgeführt werden • Kann beim Deployen/Startup erfolgen und überprüft werden (Fail Fast) @NamedQuery(name = "Employee.findAll", query = "SELECT e FROM Employee e") public class Employee { ... } EntityManager em = ... Query q = em.createNamedQuery("Employee.findAll"); List<Employee> employees = query.getResultList();
Parameter Binding • Queries können parametrisert werden. Es gibt zwei Arten der Parametrisierung: • Named Parameters • Positional Parameters SELECT e FROM Employee eWHERE e.department = :dept AND e.salary > :base Query q = ... q.setParameter("dept", "Taxes"); q.setParameter("base", "3500"); SELECT e FROM Employee eWHERE e.department = ?1 AND e.salary > ?2 Query q = ... q.setParameter(1, "Taxes"); q.setParameter(2, "3500");
Queries ausführen • Abholen des Resultates mit Methoden von QueryList getResultList()Object getSingleResult()int executeUpdate() • Beispiel Query q = em.createQuery("SELECT e FROM Employee e");List<Employee> emps = q.getResultList();for(Employee e : emps) { System.out.println(e);}
JPQL Sprach-Features • JPQL ist eine sehr mächtige und flexible Abfragesprache. Hier nur einige Features: • JOINS und Subqueries (IN, EXISTS) • Aggregatsfunktionen (AVG, COUNT, MIN, MAX, SUM) • GROUP BY und HAVING • Funktionen (LOWER, ABS, TRIM ...) • LIKE • Collection-Abfragen: IS EMPTY, MEMBER • ANY, ALL, SOME,
Pfad-Ausdrü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
Pagination Mit JPA ist Pagination sehr einfach: String queryString = “select e from Employee“; Query query = em.createQuery(queryString); query.setFirstResult(110); query.setMaxResults(10); List<Order> orders = query.getResultList(); • JPA schreibt nicht vor, wie Pagination umgesetzt wird! Dies kann von JPA-Implementation und DB-Dialekt abhängen. • In der Regel wird das resultierende SQL-Query ist für den entsprechenden SQL-Dialekt optimiert. • Achtung: Meist wird das SQL-Rowset limitiert, und nicht die resultierenden Entities!
Fetching & Lazy Loading • Die Idee von Lazy Loading ist es, die Daten erst dann von der DB zu laden, wenn sie auch wirklich in der Applikation benötigt werden. • Das Laden sollte für den Client transparent sein • Dem Programmierer wird viel Arbeit erspart • Nachteile: • Traversieren eines Objekt-Graphen kann in vielen einzelnen DB-Queries resultieren • “N+1 select problem”: Für eine Parent-Child Beziehung wird für jedes Kind ein eigenes DB-Query abgesetzt • Das Gegenteil von Lazy Loading ist Eager Loading
Fetching & Lazy Loading • In JPA kann das Lade-Verhalten auf zwei Weisen beeinflusst werden: • Global Fetch Plan: Konfiguriert in den Entity-Metadaten (Annotationen/XML) • Programmatisch beim Erstellen eines Queries mittels Join Fetch @OneToMany(mappedBy = "employee", fetch = FetchType.EAGER) private Set<Phone> phones = new HashSet<Phone>(); SELECT dFROM Department d LEFT JOIN FETCH d.employees
Joins & Fetching • Es gibt unterschiedliche Joins in JPQL: • Fetch Joins für Eager Loading SELECT dFROM Department d LEFT JOIN FETCH d.employees • Explizite Joins für Selektion und Projektion SELECT employee FROM Employee employee JOIN employee.projects project WHERE project.name = 'Arcos' SELECT project FROM Employee employee JOIN employee.projects project WHERE employee.name = 'John' • Implizite Joins aus Pfadausdrücken SELECT eFROM Employee e where e.address.city = 'Bern' SELECT e.addressFROM Employee e where e.name = 'John'
Polymorphe Queries JPQL unterstützt Polymorphie: Query q = em.createQuery("select p FROM Project p");List<Project> projects = q.getResultList(); Selektion aufgrund einer Subklasse: SELECT employee FROM Employee employee JOIN employee.projects project, DesignProject dproject WHERE project = dproject AND dproject.innovationLevel > 2 • JPA 1: Queries sind immer polymorph! • JPA 2: Einschränkungen des Typs mittels Type-Expression möglich SELECT pFROM Project pWHERE TYPE(p) IN (DesignProject)
Reporting Queries • Wird mehr als eine Expression in der SELECT Klausel verwendet, wird ein Object[]-Array zurückgegeben: List result = em.createQuery( "SELECT e.name, e.department.name " + "FROM Project p JOIN p.employees e " + "where p.name = "ZLD").getResultList();for (Iterator i = result.iterator(); i.hasNext()) { Object[] values = (Object[])i.next(); System.out.println(values[0] + "," + values[1]);} • Solche Queries werden typischerweise für Reporting verwendet • Das Resultat sind keine Entities und wird nicht vom Persistence Context gemanagt!
Constructor Expressions • Mit Constructor Expressions existiert eine einfache Möglichkeit um Resultate auf Klassen zu mappen: public class EmployeeTO { public String employeeName; public String deptName; public EmployeeTO(String employeeName, String deptName) {...}} 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);} • Achtung: Klasse muss vollqualifiziert angegeben werden! • Kann auch mit Entities verwendet werden. • Das Resultat wird nicht vom Persistence Kontext gemanagt.
Bulk Statements • In JPQL können UPDATE und DELETE-Statements formuliert werden, welche auf eine Menge von Entities angewendet werden. Query q = em.createQuery("DELETE from Employee e");int count = q.executeUpdate(); Query q = em.createQuery("UPDATE Employee e " + "SET e.name = 'Simon' " + "WHERE e.name = 'Peter');int count = q.executeUpdate(); Achtung: Bulk Statements umgehen den Entity Manager! Damit geladene Entities die Veränderungen mitbekommen, müssen sie mit der Datenbank synchronisiert werden.
Vorteile und Nachteile von JPQL • Vorteile • Sehr mächtig und flexibel • Stark an SQL angelehnt • Nachteile • JPQL ist eine embedded Language die in Java mittels Stings verwendet wird. • Keine Überprüfung beim Kompilieren, keine Typ-Sicherheit • Flexible Komposition eines Queries ist nicht elegant möglich (String-Manipulation) • Für nicht-triviale Anwendungen ist SQL Knowhow und Verständnis des Datenmodels ist notwendig
SQL Queries • JPA ermöglicht die Formulierung von SQL-Queries: Query q = em.createNativeQuery("SELECT * FROM emp WHERE id = ?", Employee.class);q.setParameter(1, employeeId); List<Employee> employees = q.getResultList(); • Stored Procedures werden in JPA nicht unterstützt. Die meisten JPA-Implementationen bieten jedoch proprietäre Mechanismen zum Einbinden von Stored Procedures. • SQL-Queries könne auch als NamedQuery definiert werden: @NamedNativeQuery( name = "employeeReporting", query = "SELECT * FROM emp WHERE id = ?", resultClass = Employee.class) Ausführung analog Named Queries in JPQL
SQL Queries • Update und Delete Statements: Query q = em.createNativeQuery("UPDATE emp SET salary = salary + 1");int updated = q.executeUpdate(); • Flexibles Mapping des Result-Sets @SqlResultSetMapping(name = "EmployeeWithAddress",entities = {@EntityResult(entityClass = Employee.class), @EntityResult(entityClass = Address.class)} String s = "SELECT e.*, a.* FROM emp e, address a" + "WHERE e.adress_id = a.id"; Query q = em.createNativeQuery(s, "EmployeeWithAddress");List<Employee> employees = q.getResultList();
Criteria API in JPA 2 • Mit der Criteria API wird in JPA 2 eine Objekt-Orientierte Schnittstelle zum programmatischen Erstellen von Queries standardisiert. • Die meisten JPA-Implementationen bieten bereits eine proprietäre Schnittstelle dieser Art an • Vorteile: • Dynamisches Erstellen von Queries (Komposition) • Keine String-Manipulation notwendig • OO-Konstrukte zum Erstellen komplexer Queries • Gewisse Typsicherheit
Criteria API in JPA 2 QueryBuilder qb = ... CriteriaQuery q = qb.create(); Root<Customer> cust = q.from(Customer.class); Join<Order, Item> item = cust.join("orders").join("lineitems"); q.select(cust.get("name")).where( qb.equal(item.get("product").get("productType"), "printer")); • Beispiel mit Strings für Referenzen: Beispiel mit typsicheren, statischem Metamodel: QueryBuilder qb = ... CriteriaQuery q = qb.create(); Root<Customer> cust = q.from(Customer.class); Join<Customer, Order> order = cust.join(Customer_.orders); Join<Order, Item> item = order.join(Order_.lineitems); q.select(cust.get(Customer_.name)) .where(qb.equal(item.get(Item_.product).get(Product_.productType), "printer"));