Querying - jpaworkshop
Download
Report
Transcript Querying - jpaworkshop
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"));