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; public interface Query { public List getresultlist(); public Object getsingleresult(); public int executeupdate(); public Query setmaxresults(int maxresult); public Query setfirstresult(int int startposition); public Query sethint(string hintname, Object value); public Query setparameter(string name, Object value); public Query setparameter(string name, Date value, TemporalType temporaltype); public Query setparameters(string name, Calendar value, TemporalType temporaltype); public Query setparameter(int position, Object value); public Query setparameter(int position, Date value, TemporalType temporaltype); public Query setparameter(int position, Calendar value, TemporalType temporaltype); public Query setflushmode(flushmodetype FlushModeType 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; public interface EntityManager { public Query createquery(string ejbqlstring); public Query createnamedquery(string name); public Query createnativequery(string sqlstring); public Query createnativequery(string sqlstring, Class resultclass); public Query createnativequery(string sqlstring, String resultsetmapping); } Poznámky Dotazy jsou vytvářeny s použitím těchto metod EntityManageru:
try { Query query = entitymanager.createquery createquery( form Customer c whwre c.firstname firstname= Bill Bill and c.lastname lastname= Burke Burke ); Customer cust = (Customer)query.getSingleResult() getsingleresult(); } catch(entitynotfoundexception notfound) { } catch(nonuniqueresultexception nonunique) { } Poznámky Využití EntityManageru a metody createquery(), která vytváří dotaz dynamicky za běhu: 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.
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
public List findbyname(string first, String last) { Query query = entitymanager.createquery( from Customer c where c.firstname firstname=: =:first and c.lastname lastname=: =:last last); query.setparameter setparameter( first, first); query.setparameter setparameter( last last, last); return query.getresultlist(); } Poznámky Znak : následovaný jménem parametru se používá v příkazech EJB QL k identifikaci jména parametru.
public List findbyname(string first, String last) { Query query = entitymanager.createquery( from Customer c where c.firstname firstname=?1 and c.lastname lastname=?2?2); query.setparameter setparameter(1, first); query.setparameter setparameter(2, last); return query.getresultlist getresultlist(); } Poznámky 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
package enum TemporalType { DATE, //java java.sql sql.date TIME, //java java.sql sql.time TIMESTAMP //java java.sql sql.timestamp } Poznámky public interafce Query { Query setparameter(string name, java.util util.date value, TemporalType temporaltype); Query setparameter(string name, java.util util.calendar value, TemporalType temporaltype); Query setparameter(int position, java.util util.date value, TemporalType temporaltype); } Query setparameter(int position, java.util util.date value, 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
List results; int first = 0; int max = 10; do { results = getcustomers(max max, first); Iteratir it = results.iterator iterator(); while(it it.hasnext hasnext()) { Customer c = (Customer)it it.getnext getnext(); System.out out.println println(c. (c.getfirstname getfirstname() + + c.getlastname getlastname()); } entitymanager.clear clear(); first = first + results.getsize getsize(); } while (results results.size size() > 0); Poznámky Fragment kódu při práci s databází: Metoda entitymanager.clear() způsobí odpojení (detach) všech zákazníků (customers), které jsme vypisovali a jejich ponechání garbage collectoru.
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..titan.domain domain; Poznámky @Entity public class Customer {...} 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..titan.domain domain; @Entity(name name= Cust Cust ) public class Customer {... } entitymanager.createquery( SELECT c FROM Cust AS c ); c
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 io.serializable { private int id; private String first; private String last; @Id public int getid() { return id; } public String getfirstname() { return first; } public String 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
Query query = manager.createquery( SELECT c.firstname firstname, c.lastname FROM customer AS c ); c List results = query.getresultlist getresultlist(); Iterator it = result.iterator iterator(); while(it it.hasnext hasnext()) { Object[] result = (Object( Object[]) [])it it.next next(); String first = (String( String)result result[0]; String last = (String( String)result result[1]); } Poznámky 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(String first, String last) { this.first = first; this.last = last; } public String getfirst() { return first; } public String 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<Phone> 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( SELECT c FROM Customer c ); c 2 List results = query.getresultlist getresultlist(); 3 Iterator it = results.iterator iterator(); 4 while (it it.hasnext hasnext()) { 5 Customer c = (Customer)it it.next next(); 6 System.out out.print print(c. (c.getfirstname getfirstname()+ ()+ + c.getlastname getlastname()); 7 for (Phone p : c.getphonenumbers getphonenumbers()) { 8 System.out out.print print(p. (p.getnumber getnumber() + ); 9 } 10 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 SELECT s FROM Ship AS s WHERE s.tonnage = 1000000.00 Klauzule WHERE a literály Klauzule WHERE pracuje velmi podobně jako v SQL SELECT c FROM Customer AS c WHERE c.hasgoodcredit = TRUE
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