Querying - jpaworkshop

advertisement
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
SELECT e FROM Employee e
WHERE e.department = :dept
AND e.salary > :base
Query q = ...
q.setParameter("dept", "Taxes");
q.setParameter("base", "3500");
• Positional Parameters
SELECT e FROM Employee e
WHERE 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 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) {
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 EntityMetadaten (Annotationen/XML)
@OneToMany(mappedBy = "employee", fetch = FetchType.EAGER)
private Set<Phone> phones = new HashSet<Phone>();
– Programmatisch beim Erstellen eines Queries
mittels Join Fetch
SELECT d FROM Department d LEFT JOIN FETCH d.employees
Joins & Fetching
 Es gibt unterschiedliche Joins in JPQL:
• Fetch Joins für Eager Loading
SELECT d FROM 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 e FROM Employee e where e.address.city = 'Bern'
SELECT e.address FROM 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 p FROM Project p WHERE 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();
• 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
• Stored Procedures werden in JPA nicht unterstützt. Die meisten JPAImplementationen bieten jedoch proprietäre Mechanismen zum
Einbinden von Stored Procedures.
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 ObjektOrientierte 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
 Beispiel mit Strings für Referenzen:
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 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"));
Herunterladen