JPQL

Java Persistence Query Language (zkráceně: JPQL) je dotazovací jazyk, který se využívá v programovacím jazyce Java jako součást JPA. Je velice podobný klasickému SQL, je zde však několik podstatných rozdílů.

SQL vs JPQL

Na rozdíl od klasického SQL, kde jsou dotazy vykonávány nad tabulkami databáze, je u JPQL dotaz vykonáván nad objekty aplikace, tzv. entitami. Tyto objekty jsou namapovány na databázové tabulky pomocí JPA anotací či XML konfigurací (link na JPA). Podstatné také je, že JPQL respektuje nadefinované vztahy, které mají mezi sebou entity. Dalším důležitým znakem JPQL je, že odstiňuje programátora od specifik konkrétního datového úložiště. Jinými slovy, programátor se nestará o to, zda jsou v konečném důsledku dotazy převáděny nad Oracle nebo MySQL databází. Stále píše stejné dotazy. Tato vlastnost je velice pohodlná, může však mít i jistá omezení(-----)

Každý výrobce databází přináší do svého produktu jinou funkcionalitu, kterou se odlišuje od ostatních. Používáním standardních „univerzálních“ dotazů pomocí JQPL o tyto výhody přichází. Někdy se v této souvislosti hovoří o zákonu netěsných abstrakcí. Ten ve zkratce říká, že jakákoli abstrakce vede k zúžení funkcionality nebo k neefektivnímu využívání zdrojů. Jako příklad se často uvádí tato abstrakce SQL:
... WHERE a=b AND b=c
... WHERE a=b AND b=c AND a=c

Matematicky jsou tyto zápisy totožné. Může však u některých databází docházet v případě použití druhého zápisu k výraznému zlepšení výkonnosti zpracování.

Vytváření a vykonávání dotazů

U dotazů se rozlišují tři typy dotazů – dynamický, pojmenovaný a nativní [1]
Dynamický dotaz se vytváří pomocí metody createQuery() na objektu EntityManager. Parametrem této metody je vytvořený dotaz v textové podobě. Metoda vrací objekt s rozhraním javax.persistence.Query(dále již pouze Query).
Např.
Query query = entityManager.createQuery("SELECT c FROM Customer c ");
Vyhledaná data lze získat pomocí metod getSingleResult(), která vrací jediný objekt, nebo pomocí getResults(), která vrací seznam.
Pojmenovaný dotaz se vytváří pomocí metody createNamedQuery() opět na objektu EntityManager. Rozdíl oproti dynamickému dotazu je ten, že se statické dotazy zapisují do souboru orm.xml nebo alternativně do anotace. Tím vzniká znovu použitelnost dotazu na více místech aplikace.
Nativní dotaz se vytváří pomocí metody createNativeQuery() také na objektu EntityManager. Tento dotaz je na rozdíl od ostatních sestrojován v nativní syntaxi SQL.

Parametry v dotazech

Obdobně, jako je tomu v JDBC, i v JPQL lze dotazy parametrizovat. Hodnota parametru se nastavuje pomocí metody setParametr() na objektu Query
Existují dva základní způsoby, jakým lze definovat parametr:(-----)

  1. Pomocí jména – na místo hodnoty se ve výrazu zapíše název parametru s prefixem „:“

Např.
Query query = entityManager.createQuery("SELECT c FROM Customer c WHERE c.name LIKE :custName");
query. setParameter("custName", name);

  1. Pomocí pořadí – na místo hodnoty se ve výrazu zapíše pořadové číslo s prefixem „?“ (indexováno od 1)

Např.
Query query = entityManager.createQuery("SELECT c FROM Customer c WHERE c.name LIKE ?1 ");
query. setParameter(1, name);

Typy dotazů

Existují tři základní typy dotazů: SELECT, UPDATE a DELETE (-----).

JPQL select

Slouží k definici výběru dat z databáze. Výsledkem může být:

  • Entita
  • Atribut entity
  • Nově zkonstruovaný objekt
  • Hodnota agregační funkce
  • Sekvence výše uvedených výsledků

Pro odkazování na atributy entit se používá tečková konvence.
Syntaxe výběrového dotazu:
SELECT <select expression>
FROM <from clause>
[WHERE <conditional expression>]
[ORDER BY <order by clause>]
[GROUP BY <group by clause>
[HAVING <having clause>]]

Ukázky výběrových dotazů:
SELECT u FROM User u
Vrací seznam všech uživatelů.
SELECT u.username FROM User u
Vrací seznam uživatelských jmen všech uživatelů.
SELECT u. username, u.email FROM User u
Vrací seznam uživatelských jmen a emailových adres (pole velikosti 2).
SELECT u.address FROM User u
Vrací seznam adres všech uživatelů.
SELECT u.address.country.code FROM User u
Pomocí tečkové notace lze vybírat objekty a vlastnosti souvisejících objektů. V tomto případě dotaz vrací seznam kódů zemí v adresách uživatelů.
SELECT NEW cz.wiki.javaee.UserDTO (u.username, u.email, u.address.city) FROM User u
Vrací seznam objektů UserDTO vytvořených pro každého uživatele. Do konstruktoru se předávají údaje uživatele.

JPQL update

Příkaz UPDATE slouží pro dávkovou aktualizaci dat. Syntaxe je následující (Šlajchrt, 2010):
UPDATE <entity_name> [[AS] <alias>] SET <update_statement> {, <update_statement>}* [WHERE <condition>].
Příklad využití příkazu UPDATE:.
UPDATE Account AS a SET a.deposit=a.deposit*1.01 where a.client.firstName='Josef'.
Připíše 1% ze zůstatku všem Josefům.

JPQL delete

Příkaz DELETE slouží k dávkovému odstraňování entit. Syntaxe:
DELETE FROM <entity_name> [[AS] <alias>] [WHERE <condition>]
Příklad využití příkazu DELETE
DELETE FROM User AS u WHERE u.username LIKE 'f%'
Příkaz smaže všechny uživatele, kteří začínají písmenem „f“.

Klauzule v dotazech

Každý typ dotazu má určitou strukturu (viz SELECT, UPDATE, DELETE). V této kapitole budou rozepsány jednotlivé klauzule těchto dotazů.

Klauzule FROM

Klauzule FROM definuje entity dotazu. Každá entita může mít přiřazen nějaký alias, který je možno použít i v ostatních klauzulích dotazu. Alias se může napsat rovnou za název entity, nebo se může alternativně použít příkaz „AS“. JPQL podporuje v klauzuli FROM použít také příkaz IN, který znázorňuje zkrácený zápis příkazu INNER JOIN (viz dále) .
Příklady:
FROM User u
FROM User AS u
FROM User u, Account a
FROM User u, IN(c.accounts) a

Klauzule WHERE

Klauzule WHERE slouží k omezení výsledků podle určitých kritérií. Používá se ve všech typech dotazů (SELECT, UPDATE, DELETE).
Příklady:
WHERE u.username='Josef'
Omezí výběr na uživatele s uživatelským jménem „Josef“
WHERE u.username ='Josef' AND u.email='josef@novak.cz'
Omezí výběr na uživatele „Josef“ s emailem „josef@novak.cz“
WHERE a.deposit NOT BETWEEN 1000 AND 100000
Omezí výběr na účty se zůstatkem mezi 1000 a 100000
WHERE u.address.country IN ('CZ', 'SK')
Omezí výběr na ty záznamy, kde adresa uživatele má kód „CZ“ nebo „SK“.
WHERE u.email LIKE '%novak.cz'
Zde je využit znak „%“, který zastupuje více libovolných znaků. V tomto případě omezí výběr na uživatele, jejichž email obsahuje doménu „novak.cz“. Je možné také využít znak „_“, který zastupuje právě jeden libovolný znak.

Klauzule ORDER BY

Klauzule ORDER BY slouží k seřazení výsledků výběrového dotazu. Výsledky jsou seřazeny podle atributů entity uvedené v klauzuli. Je možné definovat směr řazení za uvedené atributy – příkaz ASC pro vzestupné a DESC pro sestupné seřazení .
Příklady:
SELECT u FROM User u ORDER BY u.username, u.email
SELECT a FROM Account a ORDER BY a.deposit DESC, a.client.lastName ASC, a.client.firstName ASC

Klauzule GROUP BY

Příkaz GROUP BY se používá k seskupení entit. Skupina je tvořena entitami, které mají stejnou hodnotu atributů uvedených v GROUP BY.
Musí být dodrženo pravidlo, že výsledek může obsahovat pouze skupinové atributy uvedené v klauzuli GROUP BY a hodnoty agregačních funkcí (viz dále) aplikovaný na neskupinové atributy entit .
Příklad:
SELECT a.client, sum(a.deposit) FROM Account a GROUP BY a.client
Výsledek v seznamu je klient a součet vkladů na jeho účtech.

Klauzule HAVING

Klauzule HAVING se používá pro filtrování výsledků dotazů sestavených pomocí GROUP BY. Podobá se klauzuli WHERE s tím rozdílem, že podmínka obsahuje funkční výrazy nad identifikátory (včetně agregačních funkcí), které se vyskytují v klauzuli SELECT daného dotazu .
Příklad:
SELECT a.client, sum(a.deposit) FROM Account a GROUP BY a.client HAVING sum(a.deposit) > 1000
Vybere klienty, kteří mají na svých účtech v souhrnu vyšší částku než 1000 Kč

Vytváření relačních dotazů

Během vytváření dotazů je často potřeba vybírat i související záznamy dané entity. Následující příkazy slouží právě pro tyto účely.

INNER JOIN

Příkaz INNER JOIN pomáhá při výběru souvisejících entit nebo jejich atributů .
Příklad:
SELECT a FROM User u, IN(u.address) a
Vrátí všechny adresy patřící nějakým uživatelům.
Alternativní zápis:
SELECT a FROM User u INNER JOIN u.address a

LEFT JOIN

Příkaz LEFT JOIN je velmi podobný příkazu INNER JOIN. Umožňuje však vybírat také entity, které nemají nastavenou požadovanou vazbu. V případě, že tuto vazbu nemají, atributy mají ve výsledku hodnotu NULL .
Příklad:
SELECT u.username, u.email, a.city FROM User u LEFT JOIN u.address a
Vybere uživatelská jména, emaily všech uživatelů a název města u těch uživatelů, kteří mají vyplněnou adresu s městem.

FETCH JOIN

Příkaz FETCH JOIN vynucuje nahrání asociovaných entit a to i v případě, že je vazba nastavena pomocí FetchType jako Lazy .
Příklad:
SELECT u FROM user u INNER JOIN FETCH u.address

Stránkování výsledků

V případě, že je počet výsledků příliš velký, lze omezit jeho počet pomocí :
Query::setFirstResult(index) – určuje, od kterého indexu se má vrátit první výsledek
Query::setMaxResult(max) – určuje maximální počet výsledků v seznamu

Operátory a funkce

Operátory

Operátory se používají nejčastěji v klauzuli WHERE, která omezuje výběr entit. Jsou to :
=, >, <, >=, <=, <>, [NOT] BETWEEN
Využití u číselných hodnot.
[NOT] LIKE
Využívané u řetězcových hodnot
[NOT] IN
Používané pro výčet hodnot
IS [NOT] NULL
Ověření, zda je hodnota NULL nebo NOT NULL
IS [NOT] EMPTY
Výběr (ne) prázdných hodnot
[NOT] MEMBER OF
Zjišťuje, zda (není) je hodnota obsažena v kolekci
AND, OR
Klasické operátory pro spojování výrazů – „A“, „Nebo“

Funkce

Funkce se používají pro úpravu vybíraných dat nebo pro jejich agregaci .
LOWER(String), UPPER(String)
Převedení na malá / velká písmena
TRIM([[LEADING|TRAILING|BOTH] [trimchar] FROM] String)
Ořezání znaků na začátku/konci řetězce
CONCAT(String1, String2)
Spojení dvou řetězců
LENGTH(String)
Zjištění délku řetězců
LOCATE(String1, String2 [, start])
Vyhledávání řetězce
SUBSTRING(String, start, length)
Výběr části řetězce
ABS(number)
Absolutní číslo
SQRT(double)
Odmocnina
MOD(int, int)
Modulo
CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP
Výběr konkrétního data/času/časové známky
Agregační funkce:
COUNT
Zjišťuje počet
MAX
Maximum
MIN
Minimum
AVG
Průměr
SUM
Suma
DISTINCT
eliminuje duplicity, přičemž hodnoty null jsou automaticky eliminovány.

Novinky v JPA 2.0

JPA 2.0 přineslo mnoho nových vylepšení, které umožňují snadnější práci s entitami propojených s databází. Co se týče JPQL, asi největší novinkou je tzv. Criteria API (DeMichiel, 2008).

Criteria API

Criteria API má přinést zjednodušení při vytváření dynamických dotazů. Dotaz má podobu objektu, na kterém je možné volat metody, které znázorňují jednotlivé klauzule dotazu. Výsledně sestavený dotaz se podobně jako u alternativního staršího způsobu předá jako parametr metody createQuery na instanci objektu EntityManager (DeMichiel, 2008).
Příklad:

EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
query.where(cb.equal(user.get("username"), "Pepa"));
List<User> result = em.createQuery(query).getResultList();

Využití Criteria API má několik výhod:

  • Klauzule dotazů mohou být přidávány dynamicky v libovolném pořadí.
  • Rychlejší kompilace (odpadají některá ověření).
  • Menší riziko výskytu chyb a překlepů – dotaz není sestavován jako řetězec znaků, nýbrž pomocí objektů.

Externí odkazy