DPKOM_7 Dotazy a EJB QL
1
Obsah přednášky • • • •
API dotazů (Query API) EJB QL Nativní dotazy pojmenované dotazy (Named Queries)
2
API dotazů • EJB QL je deklarativní dotazovací jazyk, podobný SQL pro relační databáze, ale je uzpůsobený pro práci s javovskými objekty. • Při prováděním dotazu EJB QL používá správce perzistence informace získané z mapovaných metadat a převede dotaz na nativní SQL dotaz.
3
API dotazů • EJB QL a nativní dotazy SQL jsou prováděny prostřednictvím rozhraní javax.persistence.Query. • Toto dotazovací API dodává metody pro stránkování result set, stejně jako možnosti předávání javovských parametrů do dotazu. • Dotazy mohou být předdefinovány pomocí anotací nebo XML, nebo vytvořeny dynamicky za běhu prostřednictvím API EntityManageru.
4
package javax.persistence; javax.persistence; public interface Query { public List getResultList(); public Object getSingleResult(); public int executeUpdate(); executeUpdate(); public Query setMaxResults( setMaxResults(int maxResult); maxResult); public Query setFirstResult( setFirstResult(int startPosition); startPosition); public Query setHint( setHint(String hintName, hintName, Object value); value); public Query setParameter(String setParameter(String name, name, Object value); value); public Query setParameter(String setParameter(String name, name, Date value, value, TemporalType temporalType); public Query setParameters( setParameters(String name, name, Calendar value, value, TemporalType temporalType); public Query setParameter(int setParameter(int position, position, Object value); value); public Query setParameter(int setParameter(int position, position, Date value, value, TemporalType temporalType); public Query setParameter(int setParameter(int position, position, Calendar value, value, TemporalType temporalType); public Query setFlushMode( setFlushMode(FlushModeType flushMode); flushMode); }
Poznámky Query API Ve specifikaci Java Persistence je rozsáhlé rozhraní Javy, které můžete získat za běhu od entitního manažera.
package javax.persistence; javax.persistence; public interface EntityManager { public Query createQuery(String createQuery(String ejbqlString); ejbqlString); public Query createNamedQuery( createNamedQuery(String name); name); public Query createNativeQuery( createNativeQuery(String sqlString); sqlString); public Query createNativeQuery( createNativeQuery(String sqlString, sqlString, Class resultClass); resultClass); public Query createNativeQuery( createNativeQuery(String sqlString, sqlString, String resultSetMapping); resultSetMapping); }
Poznámky Dotazy jsou vytvářeny s použitím těchto metod EntityManageru:
Poznámky try { Query query = entityManager entityManager. .createQuery( createQuery( “form Customer c whwre c.firstName c.firstName= firstName=‘Bill‘ Bill‘ and c.lastName c.lastName= lastName=‘Burke‘ Burke‘”); Customer cust = (Customer)query.getSingleResult() (Customer)query.getSingleResult(); getSingleResult(); } catch( catch(EntityNotFoundException notFound) notFound) { } catch( catch(NonUniqueResultException nonUnique) nonUnique) { }
Uvedený dotaz je pro entitu jednoho jedinečného (single, unique) zákazníka jménem Bill Burke. Dotaz je vykonán vyvoláním metody getSingleResult(). Metoda předpokládá návrat pouze jednoho výsledku. V případě nenalezení entity je vyhozena výjimka EntityNotFoundException a v případě nalezení více entit je vyhozena výjimka NonUniqueResultException.
Využití EntityManageru a metody createQuery(), která vytváří dotaz dynamicky za běhu:
API dotazů • V případě, že bude výsledkem dotazu více entit, např. bude existovat několik osob se jménem Bill Burke, je možné využít metodu getResultList(): Query query = entityManager.createQuery( “from Customer c where c.firstName=’Bill’ and c.lastName=’Burke’); java.util.List bills = query.getResultList();
8
Parametry • Velmi podobně jako java.sql.PreparedStatement v JDBC, EJB QL dovoluje specifikovat parametry v query deklaracích, takže je možné query (dotaz) použít znova a několikrát na různé množiny parametrů. • Modifikujeme předchozí příklad:
9
Poznámky public List findByName( findByName(String first, String last) { Query query = entityManager.createQuery( entityManager.createQuery( “from Customer c where c.firstName c.firstName=: firstName=:first =:first and c.lastName c.lastName=: lastName=:last =:last); last); query.setParameter query.setParameter( setParameter(“first, first); query.setParameter query.setParameter( last”, last); setParameter(“last” return query.getResultList(); }
Znak ‘:‘ následovaný jménem parametru se používá v příkazech EJB QL k identifikaci jména parametru.
Poznámky public List findByName( findByName(String first, String last) { Query query = entityManager.createQuery( entityManager.createQuery( “from Customer c where c.firstName c.firstName= firstName=?1 and c.lastName c.lastName= lastName=?2); ?2); query. query.setParameter(1, setParameter(1, first); query.setParameter query.setParameter(2, setParameter(2, last); return query.getResultList query.getResultList(); getResultList(); }
Místo řetězcových jmenných parametrů umožňuje metoda setParameter() také číselné parametry pozice. Znak ‘?‘ je použit místo znaku ‘:‘, který je použit se jmenným parametrem.
Parametry datumu • Ty se používají, pokud do dotazu potřebujeme zapracovat parametry java.util.Calendar nebo java.util.Date. • Pak použijete speciální metody setParameter():
12
Poznámky package enum TemporalType { DATE, //java //java. java.sql. sql.Date TIME, //java //java. java.sql. sql.Time TIMESTAMP //java //java. java.sql. sql.Timestamp } public interafce Query { Query setParameter(String value, , TemporalType setParameter(String name, name, java. java.util. util.Date value temporalType); temporalType); Query setParameter(String setParameter(String name, name, java. java.util. util.Calendar value, value, TemporalType temporalType); temporalType); Query setParameter(int setParameter(int position, position, java. java.util. util.Date value, value, TemporalType temporalType); temporalType); Query setParameter(int setParameter(int position, position, java. java.util. util.Date value, value, TemporalType temporalType); temporalType); }
Protože parametry mohou představovat různé věci v daném čase, je třeba říci Query objektu, jak by měl použít tyto parametry. To řeší parametr javax.persistence.TemporalType. Ten říká rozhraní Query, který databázový typ použít při převodu java.util.Date nebo java.util.Calendar na nativní typ SQL.
Stránkování výsledků Paging Results
• API Query má dvě vestavěné funkce pro řešení problému stránkování příliš rozsáhlých výsledků. • Jedná se o setMaxResults() a setFirstResult(): public List getCustomers(int max, int index) { Query query = entityManager.createQuery(“from Customer c”); return query.setMaxResults(max). setFirstResult(index). getResultList(); }
14
Stránkování výsledků Paging Results
• Metoda getCustomers() provede dotaz a získá všechny zákazníky databáze. • Metoda setMaxResults() omezuje (limituje) počet předaných zákazníků. • Metoda setFirstResult() říká query, od které pozice chceme dotazované zákazníky. • Je-li max-result nastaven na 3 a first-result na 5, výsledkem budou zákazníci 5, 6, 7.
15
Poznámky List results; results; int first = 0; int max = 10; do { results = getCustomers( getCustomers(max, max, first); Iteratir it = results. results.iterator(); iterator(); while( while(it. it.hasNext()) hasNext()) { Customer c = (Customer)it (Customer)it. it.getnext(); getnext(); System. System.out. out.println(c. println(c.getFirstName (c.getFirstName() getFirstName() + “ “ + c.getLastName c.getLastName()); getLastName()); } entityManager. entityManager.clear(); clear(); first = first + results. results.getSize(); getSize(); } while (results. results.size() size() > 0);
Metoda entityManager.clear() způsobí odpojení (detach) všech zákazníků (customers), které jsme vypisovali a jejich ponechání garbage collectoru.
Fragment kódu při práci s databází:
Pomůcky Hints
• Někteří dodavatelé Java Persistence poskytují dodatečné možnosti, které se mohou v výhodou použít při dotazech. • JBoss EJB 3.0 implementace např. dovoluje definovat timeout pro query. • Tyto prostředky se dají deklarovat pomocí metody setHint(), která má dva parametry, name typu String a libovolný objektový parametr. Query query = manager.createQuery(“from Customer c”); query.setHint(“org.hiberante.timeout”,1000);
17
FlushMode • Problémem je změna flushModu entitním manažerem během provádění dotazu. • Proto rozhraní Query poskytuje metodu setFlushMode(), pro tento účel: Query query = manager.createQuery(“from Customer c”); query.setFlushMode(FlushModeType.COMMIT);
• V uvedeném příkladě říkáme správci perzistence, aby nedělal žádné automatické ukládání (flushing) před dokončením dotazu. Doporučeno: FlushModeType.AUTO 18
EJB QL • EJB QL je vyjádřeno v termínech schéma abstraktní perzistence entity: jméno abstraktního schéma, základní vlastnosti a relační vlastnosti. • EJB QL používá: – jména abstraktního schématu k identifikaci beanu, – základní vlastnosti ke specifikaci hodnot a – relační vlastnosti pro navigaci relacemi.
19
EJB QL - Abstraktní schéma jmen • Abstraktní schéma jmen může být definováno prostřednictvím metadat, nebo může mít defóltní specifikační hodnotu. • Tato defóltní hodnota je nekvalifikované jméno entity, pokud atribut name() není specifikován v anotaci. • V následujícím fragmentu není specifikován atribut @Entity.name(), proto bude použito jméno Customer, jako odkaz na danou entitu ve voláních EJB QL: 20
package com.titan. com.titan.domain .titan.domain; domain; @Entity public class Customer { . . .} entityManager.createQuery(“ entityManager.createQuery(“SELECT c FROM Customer AS c” c”);
Zde je specifikované jméno Cust, na které je možné se odkazovat v dotazech EJB QL: package com.titan. com.titan.domain .titan.domain; domain; @Entity(name @Entity(name= name=”Cust” Cust”) public class Customer { . . . } entityManager.createQuery(“ entityManager.createQuery(“SELECT c FROM Cust AS c” c”);
Poznámky
EJB QL - Jednoduché dotazy • Takový dotaz nemá klauzuli WHERE a má pouze typ abstraktního schématu. SELECT OBJECT( c ) FROM Customer AS c
• Výraz je podobný SQL které dovoluje, aby identifikátor byl asociovaný s tabulkou. • Identifikátory nemohou být stejné jako existující jména hodnot abstraktního schématu.
22
EJB QL - Jednoduché dotazy • Navíc identifikátory proměnných jmen nerozlišují malá a velká písmena. • Následující výraz je neplatný, protože Customer je jméno abstraktního schéma EJB Customer: SELECT OBJECT( customer ) FROM Customer AS customer
• Klauzule SELECT určuje typ libovolné hodnoty, která je vrácena. • Operátor OBJECT() je volitelný a je to víceméně požadavek předchozí verze EJB 2.1. 23
Struktura vazeb mezi třídami využito v příkladech dotazů
24
EJB QL- Výběr entity a relačních vlastností • Klauzule SELECT dovolují v EJB QL vrátit libovolný počet základních nebo relačních vlastností. • Příklad na navrácení jmen a příjmení všech pasažérů na Titan plavbě: SELECT c.firstName, c.lastName FROM Customer AS c
• Jména perzistentních vlastností jsou identifikována přístupovým typem třídy entitního beanu bez ohledu na to, zda jste použili anotaci mapování na metody get nebo set. 25
EJB QL - Výběr entity a relačních vlastností • Pokud použijete get nebo set metody ke specifikaci vašich perzistentních vlastností, část get je jména metody je odstraněna a následuje malé písmeno:
26
@Entity public class Customer implements java.io java.io. io.Serializable { private int id; private String first; private String last; @Id public int getId() getId() { return id; } public String getFirstName() getFirstName() { return first; } public String getLastName() getLastName() { return last; }
Poznámky
EJB QL - Výběr entity a relačních vlastností • Klauzule SELECT bude mít následující tvar: SELECT c.firstName, c.lastName FROM Customer AS c
• Pokud nejsou deklarovánu get a set metody, přistupuje se k vlastnostem přímo pomocí jmen vlastností.
28
Poznámky Query query = manager.createQuery( “SELECT c.firstName c.firstName, firstName, c.lastName c.lastName FROM customer AS c” c”); List results = query. query.getResultList(); getResultList(); Iterator it = result. result.iterator(); iterator(); while( while(it. it.hasNext()) hasNext()) { Object[] Object[] result = (Object (Object[]) next(); Object[])it [])it. it.next(); String first = (String (String) String)result[0]; result[0]; String last = (String (String) String)result[1]); result[1]); }
Pokud dotaz vrátí více než jednu položku, je třeba použít metodu Query.getResultList( ). Výsledek je agregován do pole objektů (Object[]) v List:
EJB QL - Výběr entity a relačních vlastností • Je možné také použít tzv. jedno hodnotovou relaci v prostém příkazu selekce např. při získání kreditní karty zákazníka: SELECT c.creditCard FROM Customer c
• EJB QL využije navigace viz obrázek. Podobně se můžeme dostat k položce city v Address: SELECT c.address.city FROM Customer AS c
30
EJB QL - Výběr entity a relačních vlastností • Je také možná následující cesta dotazu, ovšem za předpokladu navigace: • Customer->CreditCard->CreditCompany->Address: SELECT c.creditCard.creditCompany.address.city FROM Customer AS c
• Navigaci cesty není možné provádět za položky perzistence (perzistence properties). • Třída je uvedena jako datový atribut jiné třídy.
31
EJB QL - Výrazy konstruktoru • Jedním z nejmocnějších rysů EJB QL je schopnost specifikovat konstruktor uvnitř klauzule SELECT, který může alokovat obyčejné javovské objekty (ne entity) a předat specifikované sloupce entity do konstruktoru.
32
Poznámky public class Name { private String first; private String last; public Name( Name(String first, String last) { this.first this.first = first; this.last this.last = last; } public String getFirst() getFirst() { return first; } public String getLast() getLast() {return last; } }
Agregace jméno a příjmení z entity Customer do javovského objektu Name:
EJB QL - Výrazy konstruktoru • Nyní je možné vrátit dotazem seznam objektů třídy Name, místo pouhého seznamu řetězců: • SELECT new com.titan.domain.Name(c.firstName, c.lastName) FROM Customer c
• Objekt Query bude automaticky alokovat instanci třídy Name pro každý řádek databáze.
34
EJB QL - Operátory IN a INNER JOIN • Mnoho relací mezi entitními beany je založeno na kolekcích. Je důležité zpřístupnit a vybrat tyto relace. • Není možné vybrat prvek přímo z relace založené na kolekci. Tuto činnost zvládne operátor IN: SELECT r FROM Customer AS c, IN(c.reservations) r
• Operátor IN přiřadí jednotlivé prvky ve vlastnosti rezervace do identifikátoru r. 35
EJB QL - Operátory IN a INNER JOIN • Pomocí identifikátoru, který reprezentuje jednotlivé prvky kolekce, se na ně můžeme odkázat přímo a dokonce je vybrat v příkazu EJB QL. • Příkaz vybere každou plavbu (cruise), kterou má zákazník rezervovanou: SELECT r.cruise FROM CUSTOMER AS c, IN( c.reservations ) r
36
EJB QL - Operátory IN a INNER JOIN • Předchozí dotaz může být také vyjádřen pomocí INNER JOIN: SELECT r.cruise FROM Customer c INNER JOIN c.reservations r
37
EJB QL - Operátory IN a INNER JOIN • Operátor INNER JOIN je intuitivním operátorem hlavně pro vývojáře z relačního světa. • Příklad speciálnějších výběrů, které spíše demonstrují možnosti uváděných operátorů: SELECT cbn.ship FROM Customer AS c, IN ( c.reservations ) r, IN( r.cabin ) cbn
38
EJB QL - Operátory IN a INNER JOIN • Alternativně: SELECT cbn.ship FROM Customer c INNER JOINM c.reservations r INNER JOIN r.cabins cbn
• Tyto dotazy vyberou všechny lodě, na které má zákazník rezervace.
39
EJB QL - LEFT JOIN • Tato syntaxe umožňuje procházet množinu entit, kde hledané hodnoty příkazu join nemusí existovat. • Např. je třeba vypsat všechna jména zákazníků a všechna jejich telefonní čísla. • Některý zákazník může mít více telefonních čísel, jiný žádné. SELECT c.firstName, c.lastName, p.number FROM Customer c LEFT JOIN c.phoneNumbers p
40
EJB QL - Fetch Joins • Tato syntaxe dovoluje znova nahrát relace entity, i když vlastnost relace má nastavený FetchType na LAZY. • Např. deklarujeme relaci one-to-many mezi entitou Customer a entitou Phone: @OneToMany(fetch=FetchType.LAZY); public Collection
getPhones() {return phones; }
• Když chceme získat informace o všech zákaznících, nejdříve se dotážeme na všechny zákazníky a pak budeme traverzovat metodou getPhones() jejich telefonní čísla: 41
Poznámky 1 Query query = manager.createQuery(“ manager.createQuery(“SELECT c FROM Customer c” c”); 2 List results = query. query.getResultList(); getResultList(); 3 Iterator it = results. results.iterator(); iterator(); 4 while (it. it.hasNext()) hasNext()) { 5 Customer c = (Customer)it (Customer)it. next(); it.next(); 6 System. System.out. out.print(c. print(c.getFirstName (c.getFirstName()+ getFirstName()+” ()+” “+ c.getLastName c.getLastName()); getLastName()); 7 for (Phone p : c.getPhoneNumbers c.getPhoneNumbers()) getPhoneNumbers()) { 8 System. System.out. out.print(p. print(p.getNumber (p.getNumber() getNumber() + “ “); 9 } 10 System. System.out. out.println( println(“”); “”); 11 }
Problémy způsobuje to, že relace Phone je deklarovaná jako lazy, a proto kolekce Phone nebude instanciována při provádění úvodního dotazu viz. řádek 1. Když je volána metoda getPhoneNumbers() – řádek 7, správce persistence musí dělat dodatečný dotaz, aby dostal telefonní čísla asociovaná se zákazníkem. Nazývá se to problém N + 1, protože se musí udělat N extra dotazů za původním úvodním dotazem. Přitom je snaha omezovat počty dotazů v databázi.
EJB QL - Fetch Joins • Tento problém právě řeší JOIN FETCH. Podívejme se jak bude vypadat modifikovaný dotaz: SELECT c FROM Customer c LEFT JOIN FETCH c.phones
• Příkaz LEFT JOIN FETCH znova nahraje asociace Phone. • Místo N+1 dotazů v databázi, je vykonán pouze jeden úvodní.
43
EJB QL - Použití DISTINCT • Klíčové slovo DISTINCT zabezpečí, že dotaz nevrací duplikáty. SELECT DISTINCT cust FROM Reservation AS res, IN (res.customers) cust
44
Poznámky SELECT c FROM Customer AS c WHERE c.creditCard.creditCompany.name = ‘Capital One’ One’ SELECT s FROM Ship AS s WHERE s.tonnage = 1000000.00 SELECT c FROM Customer AS c WHERE c.hasGoodCredit = TRUE
Klauzule WHERE a literály Klauzule WHERE pracuje velmi podobně jako v SQL
EJB QL - Klauzule WHERE a IN • Klauzule IN testuje přítomnost v seznamu literálů. • Např. v následujícím příkladě operátor EJB QL IN testuje, zda zákazníci pocházení z uvedených států: SELECT c FROM Customer AS c WHERE c.address.state IN(‘FL’, ‘TX’, ‘MI’, ‘WI’, ‘MN’)
46
EJB QL - Klauzule ORDERED BY SELECT c FROM Customer AS c ORDERED BY c.lastName
47
Hromadné operace UPDATE a DELETE • Operaci uvedeme na příkladě, ve kterém chceme přidat všem zákazníkům se jménem Bill Burke $10. UPDATE Reservation res SET res.amountPaid = (res.amountPaid + 10) WHERE EXISTS ( SELECT c FROM res.customers c WHERE c.firstName = ‘Bill’ AND c.lastName = ‘Burke’ )
48
Hromadné operace UPDATE a DELETE • Dále chceme odstranit všechny rezervace vytvořené Bill Burkem: DELETE FROM Reservation res WHERE EXISTS ( SELECT c FROM res.customers c WHERE c.firstName = ‚Bill‘ AND c.lastName= ‚Burke‘ )
49
Jednoduché entitní nativní dotazy • Tyto dotazy vezmou příkaz SQL a implicitně ho mapují do jedné entity založené na mapování metadat, která byla deklarovaná pro tuto entitu. • Očekává se, že sloupce vrácené v result setu nativního dotazu, budou perfektně souhlasit s O/R entitním mapováním. Query query = manager.createNativeQuery( “SELECT p.phone_PK, p.phone_number, p.type FROM PHONE AS p”, Phone.class );
• Všechny vlastnosti entit musí být vytaženy.
50
Složitější nativní dotazy Query createNativeQuery(String sql, String mappingName)
• Tato metoda entitního manažeru dovoluje složitější mapování nativního SQL. • Vrací najednou více entit a skalár sloupcových hodnot. • Parametr mappingName odkazuje deklarovanou @javax.persistence.SQLResultSetMapping. • Uvedená anotace se používá na specifikaci, jak se nativní výsledky SQL napojí (zaháknou zpět) do modelu O/R. 51
Složitější nativní dotazy • Jména sloupců neodpovídají mapování paralelně anotovaných vlastností (properties). • Proto je pro ně možné dodat mapování field-tocolumn použitím anotace @javax.persistence.FieldResult:
52