U prethodnom poglavlju smo diskutovali o korišćenju jezika HQL, specifičnog za potrebe aplikacija sa Hibernate podrškom, za izdvajanje skupova podataka iz relacione baze podataka. Videli smo da se HQL konceptualno zasniva za jeziku SQL, sa razlikom da, umesto baratanja tabelama u relacionoj bazi podataka, sve restrikcije, projekcije, spajanja itd. koriste Java klase za postavljanje ograničenja koja rezultati upita moraju da ispunjavaju.
Sličan koncept, ali nešto opštiji u smislu upotrebe, predstavlja JPA Criteria API. Ideja je da se umesto navođenja imena pod niskom (što je bio slučaj u HQL upitima), koriste napredniji koncepti za konstrukciju dinamičkih upita. Prednost ovog načina korišćenja jeste upravo u tome što se umesto korišćenja niski za zadavanje upita, koriste inteligentne programerske tehnike, kao što je ulančavanje metoda. Dodatno, koriste se prednosti programskog jezika Java.
Započnimo ovo poglavlje davanjem primera sa ciljem da ilustrujemo promenu u razmišljanju između konstrukcije HQL upita i korišćenja JPA Criteria API. Naredni HQL upit izdvaja sve zaposlene u kompaniji čije je ime “John Smith”:
String hql = "FROM Employee e WHERE e.name = \"John Smith\"";
org.hibernate.query.Query<Employee> upit =
session.createQuery(hql, Employee.class);
Ekvivalentan rezultat koji se dobija korišćenjem JPA Criteria API je:
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp)
.where(cb.equal(emp.get("name"), "John Smith"));
Očigledno, ovi pristupi se jasno razlikuju. HQL upit se konstruiše na isti način kao i SQL upit - navođenjem odgovarajuće sintakse u cilju konstrukcije niske koja sadrži sve informacije. Za razliku od toga, pristup koji se oslanja na JPA Criteria API koristi metode i refleksiju za konstrukciju uslova koje rezultati upita moraju da zadovolje. Kroz ovo poglavlje ćemo se detaljno upoznati sa ovim konceptima.
Radi kompletnosti, objasnimo nešto detaljnije prethodni kod. Za početak primećujemo da se kreira instanca interfejsa CriteriaBuilder
tako što se nad objektom klase Session
poziva metod getCriteriaBuilder()
. Instanca cb
interfejsa CriteriaBuilder
sadrži veliki broj metoda koji se koriste za definisanje ograničenja rezultata upita. Na primer, pozivom metoda createQuery
nad objektom cb
kreira jedan novi objekat klase CriteriaQuery
koji reprezentuje kostur JPA Criteria API upita. Drugi primer upotrebe klase CriteriaBuilder
jeste u kreiranju jednakosnog ograničenja koji odgovara izrazu e.name = \"John Smith\"
u HQL upitu, pozivom metoda equal()
.
Kada imamo kostur novog upita, ostatak koda je manje-više pravolinijski. Potrebno je ustanoviti koren upita, odnosno, klasu od koje se započinje zadavanje ograničenja - u našem primeru to je klasa Employee
- što se izvršava pozivanjem metoda from()
nad upitom i prosleđivanjem klase koja predstavlja koren. Ovo je ekvivalentno deklarisanju alijasa e
u HQL upitu i objekat klase Root
predstavlja osnovu za izgradnju ostatka upita. Naredni korak jeste izvršavanje projekcije pozivom metoda select()
nad upitom i prosleđivanjem konstruisanog korena upita. Konačno, potrebno je zadati odgovarajuće restrikcije nad upitom, što se izvodi ulančavanjem metoda where()
nad projekcijom upita i navođenjem ograničenja restrikcije. S obzirom da se vrši restrikcija nad poljem name
klase Employee
, sve što je potrebno uraditi jeste pronaći to ime pozivom metoda get()
nad korenom upita.
Iako smo napisali više koda u odnosu na pristup zasnovan na HQL jeziku, primetimo sledeće. Ukoliko bismo želeli da dinamički postavljamo uslove restrikcije u upitu, u pristupu koji koristi HQL jezik morali bismo da upravljamo niskama za konstruisanje upita, na primer:
String hql = "FROM Employee e WHERE ";
if (nameRestriction) {
hql += "e.name = \"John Smith\"";
}
else if (salaryRestriction) {
hql += "e.salary = 20000";
}
else {
// ...
}
org.hibernate.query.Query<Employee> upit =
session.createQuery(hql, Employee.class);
Ovakav pristup za kreiranje dinamičkih upita ima veliki broj problema. Umesto toga, mnogo je prirodnije osloniti se na mogućnosti samog programskog jezika u kojem se vrši razvoj aplikacije, što nam JPA Criteria API omogućava:
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
String attribute;
String restriction;
if (nameRestriction) {
attribute = "name";
restriction = "John Smith";
}
else if (salaryRestriction) {
attribute = "salary";
restriction = "20000";
}
else {
// ...
}
c.select(emp)
.where(cb.equal(emp.get(attribute), restriction));
Na ovaj način ne moramo da vodimo računa da li smo ispravno konstruisali niske od kojih upit zavisi zato što se način izdvajanja rezultata u poslednjoj liniji nikad ne menja!
Kao što smo demonstrirali u prethodnoj sekciji, srž JPA Criteria API-ja je u interfejsu CriteriaBuilder
, koji se dobija pozivom metoda getCriteriaBuilder()
nad objektom sesije. Ovaj interfejs je poprilično širok i služi u razne svrhe u okviru JPA Criteria API-ja. Cilj ovog poglavlja jeste da se upoznamo sa njim.
Interfejs CriteriaBuilder
nudi tri metoda za kreiranje novog upita, u zavisnosti od željenog tipa rezultata upita:
createQuery(Class<T>)
prihvata klasu koja odgovara rezultatu upita.createQuery()
, bez parametara, odgovara rezultatu tipa klase Object
.createTupleQuery()
koristi se za projekcije ili u slučaju kada SELECT
klauza sadrži više od jednog izraza i želimo da radimo sa rezultatom u strogo tipiziranom maniru. Zapravo, taj metod predstavlja skraćenicu za poziv prvog metoda createQuery(Tuple.class)
. Primetimo da je Tuple
interfejs koji sadrži različite objekte ili podatke i primenuje tipiziranost nad agregatnim delovima. Može biti korišćen kada je rezultat upita više podataka, pri čemu želimo da ih kombinujemo u jedinstveni tipizirani objekat.JPA Criteria API se sastoji od velikog broja interfejsa koji rade zajedno u cilju modeliranja strukture JPA upita. Kako budemo prolazili kroz ovo poglavlje, biće korisno pratiti dijagram na narednoj slici i posmatrati odnose između interfejsa.
U prethodnom poglavlju smo videli da u jeziku HQL možemo koristiti narednih šest klauza za konstrukciju klasičnih SELECT
upita: SELECT
, FROM
, WHERE
, ORDER BY
, GROUP BY
and HAVING
. Svaka od ovih HQL klauza ima ekvivalentan metod nad nekim od JPA Criteria API interfejsa. Naredna tabela sumira ove metode.
HQL klauza | Interfejs iz JPA Criteria API | Metod kojim se ostvaruje |
---|---|---|
SELECT |
CriteriaQuery |
select() |
Subquery |
select() |
|
FROM |
AbstractQuery |
from() |
WHERE |
AbstractQuery |
where() |
ORDER BY |
CriteriaQuery |
orderBy() |
GROUP BY |
AbstractQuery |
groupBy() |
HAVING |
AbstractQuery |
having() |
A newly created CriteriaQuery
object is basically an empty shell. With the exception of defining the result type of the query, no additional content has yet been added to fill out the query. As with HQL queries, the developer is responsible for defining the various clauses of the query necessary to fetch the desired data from the database. Semantically speaking, there is no difference between HQL and Criteria API query definitions. Both have SELECT
, FROM
, WHERE
, GROUP BY
, HAVING
, and ORDER BY
clauses; only the manner of defining them is different. Before we can fill in the various clauses of the query definition, let’s first revisit two key concepts and look at the equivalent Criteria API syntax for those concepts.
The first fundamental concept to revisit is the identification variable used in the FROM
clause of HQL queries to alias declarations that cover entity, embeddable, and other abstract schema types. In HQL, the identification variable takes on a central importance, as it is the key to tying the different clauses of the query together. But with the Criteria API we represent query components with objects and therefore rarely have aliases with which to concern ourselves. Still, in order to define a FROM
clause, we need a way to express which abstract schema types we are interested in querying against.
Interfejs AbstractQuery
(roditelj interfejsa CriteriaQuery
) nudi metod from()
za definisanje apstraktne sheme koja formira osnovu našeg upita. Ovaj metod prihvata tip entiteta kao parametar i dodaje novi koren u upitu. Koren u upitu koresponsira identifikacionoj promenljivoj u HQL-u, koja dalje koresponsira deklaraciji ranga (tj. skupu iz kojeg se podaci izdvajaju) ili izrazu spajanja. Naredni kod ilustruje kako možemo dobiti koren upita:
CriteriaQuery<Student> c = cb.createQuery(Student.class);
Root<Student> emp = c.from(Student.class);
Metod from()
vraća objekat interfejsa Root
koja odgovara tipu entiteta. Interfejs Root
je dete interfejsa From
, koji nam nudi funkcionalnosti spajanja. Interfejs From
je dete interfejsa Path
, koji je dalje dete Expression
, pa Selection
, kojim se omogućava da se koren koristi u drugim delovima definicije upita. Uloga svih ovih interfejsa će biti detaljnije opisana u kasnijim sekcijama.
Pozivi metoda from()
su aditivni. To znači da svaki poziv dodaje novi koren u upitu, što rezultuje u Dekartovom proizvodu kada je više od jednog korena definisan, ako nema daljih ograničenja u WHERE
klauzi. Naredni primer demonstrira višekoreni upit, koji zamenjuju savremenija spajanja tradacionalnijim SQL pristupom:
SELECT DISTINCT s
FROM Student s,
Ispit i
WHERE s = i.indeks
Da bismo pretvorili ovaj upit u JPA Criteria API, potrebno je da pozovemo metod from()
dvaput, dodajući entitete Student
i Ispit
kao korene upita:
CriteriaQuery<Student> c = cb.createQuery(Student.class);
Root<Student> stud = c.from(Student.class);
Root<Ispit> ispit = c.from(Ispit.class);
c.select(stud)
.distinct(true)
.where(cb.equal(stud, ispit.get("indeks")));
The second fundamental concept to revisit is the path expression. The path expression is the key to the power and flexibility of the HQL language, and it is likewise a central piece of the Criteria API.
Govorili smo o korenima upita u prethodnoj podsekciji, ali svi koreni zapravo predstavljaju specijalni tip izraza putanje. Posmatrajmo naredni jednostavan HQL upit koji vraća sve studente koji su upisani na studijskom programu Informatika:
SELECT s
FROM Student s
WHERE s.studijskiProgram.naziv = 'Informatika'
Razmišljajući u terminima JPA Criteria API, koren ovog izraza je entitet Student
i neka je on predstavljen objektom stud
. Ovaj upit takođe sadrži izraz putanje u WHERE
klauzi. Da bismo predstavili ovaj izraz putanje korišćenjem JPA Criteria API, koristimo naredni izraz:
stud.get("studijskiProgram").get("naziv")
Metod get()
je izveden iz interfejsa Path
proširenog interfejsom Root
i ekvivalentan je operatoru tačke koji se koristi u HQL izrazima putanje radi navigacije kroz putanju. S obzirom da metod get()
vraća objekat interfejsa Path
, pozivi metoda se mogu ulančavati, što će nam biti veoma korisna tehnika pri izgradnji upita, jer time izbegavamo uvođenje privremenih lokalnih promenljivih (koji nam ne služe praktično ničemu). Argument metoda get()
je naziv atributa entiteta za koji smo zainteresovani. Zbog toga što je rezultat konstruisanja izraza putanje objekat interfejsa Expression
koji mi možemo da koristimo za izgradnju uslovnih izraza, možemo prethodni HQL upit izraziti na sledeći način:
CriteriaQuery<Student> c = cb.createQuery(Student.class);
Root<Student> stud = c.from(Student.class);
c.select(stud)
.where(cb.equal(stud.get("studijskiProgram").get("naziv"), "Informatika"));
Much like HQL, path expressions may be used throughout the different clauses of the query definition. With the Criteria API, it is necessary to hold onto the root object in a local variable and use it to form path expressions where required. Once again it is worth emphasizing that the from()
method of AbstractQuery
should never be invoked more than once for each desired root. Invoking it multiple times will result in additional roots being created and a Cartesian
product if not careful. Always store the root
objects locally and refer to them when necessary.
SELECT
Postoji nekoliko formi koje klauza SELECT
može da uzima u upitu. Najjednostavnija forma predstavlja izdvajanje jednog izraza, dok neke druge forme rade sa više izraza ili koriste konstruktorskih izraza radi kreiranja novih instancnih objekata. Svaka forma je na drugi način predstavljena u JPA Criteria API.
Metod select()
interfejsa CriteriaQuery
se koristi radi formiranja SELECT
klauze u JPA Criteria API definicijama upita. Sve forme SELECT
klauze se mogu predstaviti metodom select()
, premda postoje bolji načini korišćenjem specijalizovanih metoda radi pojednostavljivanja kodiranja. Metod select()
prihvata argument interfejsa Selection
, što je zapravo roditelj interfejsa Expression
, kao i CompoundSelection
za slučaj kada je rezultat upita Tuple
ili niz rezultata.
Do sada, mi smo prosleđivali koren upita metodu select()
, čime smo indikovali da želimo da odgovarajući entitet bude rezultat upita. Moguće je proslediti i izraz koji predstavlja jednu vrednost, poput izdvajanja atributa iz entiteta ili bilo kog kompatibilnog skalarnog izraza. Naredni primer ilustruje ovaj pristup izdvajanjem atributa koji predstavlja ime entiteta Student
:
CriteriaQuery<String> c = cb.createQuery(String.class);
Root<Student> stud = c.from(Stud.class);
c.select(stud.<String>get("ime"));
Ovakav upit će vratiti sva imena studenata, uključujući sve duplikate. O izdvajanju upita bez duplikata će biti reči kasnije.
Primetimo neobičnu sintaksu koju koristimo da bismo deklarisali da atribut ime
predstavlja instancu tipa String
. Tip izraza koji se prosleđuje metodu select()
mora biti kompatibilan sa rezultujućim tipom koji se koristi za kreiranje CriteriaQuery
objekta. Na primer, ako je CriteriaQuery
objekat kreiran pozivom createQuery(Ispit.class)
nad objektom interfejsa CriteriaBuilder
, onda će biti greška ukoliko bismo pokušali da postavimo da se dobija izraz entiteta Student
korišćenjem metoda select()
. Kada poziv metoda poput select()
koristi šablonsku tipiziranost da bi omogućio ograničenja kompatibilnosti, onda možemo prefiksovati tip imenu metoda kako bismo ga kvalifikovali u slučajevima kada tip ne može biti automatski zaključen. U ovom slučaju, moramo koristiti ovaj pristup s obzirom da je metod select()
deklarisan kao:
CriteriaQuery<T> select(Selection<? extends T> selection);
Argument metoda select()
mora biti tipa koji je kompatibilan rezultujućim tipom definicije upita. Metod get()
vraća objekat Path
, ali taj objekat je uvek tipa Path<Object>
zato što kompilator ne može da zaključi odgovarajući tip samo na osnovu imena atributa. Zbog toga, da bismo deklarisali da je atribut ime
u entitetu Student
tipa String
, moramo da preciziramo poziv metoda kao što smo videli.
This syntax has to be used whenever the Path
is being passed as an argument for which the parameter has been strongly typed, such as the argument to the select()
method and certain CriteriaBuilder
expression methods.
We have not had to use them so far in our examples because we have been using them in methods like equal()
, where the parameter was declared to be of type Expression<?>
.
Because the type is wildcarded, it is valid to pass in an argument of type Path<Object>
.
Later in the chapter, we look at the strongly typed versions of the Criteria API methods that remove this requirement.
Kada definišemo SELECT
klauzu koja sadrži izdvajanje više od jednog izraza, onda ćemo koristiti različite pristupe u zavisnosti od načina na koji je definicija upita kreirana:
Ako je rezultujući tip Tuple
, onda moramo metodu select()
proslediti objekat CompoundSelection<Tuple>
.
Ako je rezultujući tip neperzistentna klasa koja će biti kreirana korišćenjem konstruktorskih izraza, tada argument mora biti objekat CompoundSelection<[T]>
, gde je [T]
tip te neperzistentne klase.
Konačno, ako je rezultujući tip niz objekata, onda se metodu prosleđuje objekat CompoundSelection<Object[]>
.
Ovi objekti se kreiraju pozivima metoda tuple()
, construct()
i array()
nad objektom interfejsa CriteriaBuilder
, redom. Neka je potrebno izdvojiti godine i oznake svih ispitnih rokova, što se izvodi narednim HQL upitom:
SELECT ir.godina,
ir.oznaka
FROM IspitniRok ir
Naredni primer demonstrira kako možemo imati višestruke izraze korišćenjem upita nad Tuple
:
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<IspitniRok> ir = c.from(IspitniRok.class);
c.select(cb.tuple(ir.get("godina"),
ir.get("oznaka")));
Kao pogodnost, metod multiselect()
iz interfejsa CriteriaQuery
može se takođe koristiti za ispunjavanje istog zahteva. Metod multiselect()
će kreirati odgovarajući tip argumenata za dati rezultujući tip upita, odnosno, zavisno od načina kreiranja upita.
Prva forma je za upite koje imaju Object
ili Object[]
kao njihov rezultujući tip. Metodu multiselect()
se u ovom slučaju može proslediti lista izraza koje predstavljaju svaki od izraza:
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<IspitniRok> ir = c.from(IspitniRok.class);
c.multiselect(ir.get("godina"),
ir.get("oznaka"));
Note that, if the query result type is declared as Object
instead of Object[]
, the behavior of multiselect()
in this form changes slightly. The result is always an instance of Object
, but if multiple arguments are passed into multiselect()
then the result must be cast to Object[]
in order to access any particular value. If only a single argument is passed into multiselect()
, then no array is created and the result may be cast directly from Object
to the desired type. In general, it is more convenient to be explicit about the query result type. If you want to work with an array of results, then declaring the query result type to be Object[]
avoids casting later and makes the shape of the result more explicit if the query is invoked separately from the code that creates it.
Druga forma je slična prvoj formi, samo se odnosi na upite koji rezultuju tipom Tuple
. Ponovo, metodu multiselect()
se u ovom slučaju može proslediti lista izraza koje predstavljaju svaki od izraza:
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<IspitniRok> ir = c.from(IspitniRok.class);
c.multiselect(ir.get("godina"),
ir.get("oznaka"));
Treća i konačna forma je za upite sa konstruktorskih izrazima čiji rezultat je neperzistentnog tipa. Metod multiselect()
se ponovo poziva sa listom izraza, ali koristi tip upita da odredi i automatski kreira odgovarajući konstruktorski izraz, u ovom slučaju primarni ključ klase IspitniRokId
:
CriteriaQuery<IspitniRokId> c = cb.createQuery(IspitniRokId.class);
Root<IspitniRok> ir = c.from(IspitniRok.class);
c.multiselect(ir.get("godina"),
ir.get("oznaka"));
Ovo je ekvivalentno narednom kodu:
CriteriaQuery<IspitniRokId> c = cb.createQuery(IspitniRokId.class);
Root<IspitniRok> ir = c.from(IspitniRok.class);
c.select(cb.construct(IspitniRokId.class, // Izdvoji primarni kljuc na osnovu kolona:
ir.get("godina"), // 1. godina
ir.get("oznaka"))); // 2. godina
Međutim, iako je metod multiselect()
izuzetno pogodan za upotrebu konstruktorskih izraza, postoje slučajevi kada je neophodno korišćenje metoda construct()
iz interfejsa CriteriaBuilder
. Na primer, ukoliko je potrebno da upit izračuna i naziv ispitnih rokova pored godine i oznake rokova. U tom slučaju, ispitni rokovi koji se konstruišu predstavljaju samo deo rezultata, tako da rezultujući tip upita mora biti tipa Object[]
i onda on mora da sadrži konstruktorski izraz za konstruisanje instanci klase IspitniRokId
:
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<IspitniRok> ir = c.from(IspitniRok.class);
c.multiselect(ir.get("naziv"), // Izdvoji naziv roka
cb.construct(IspitniRokId.class, // Izdvoji primarni kljuc na osnovu kolona:
ir.get("godina"), // 1. godina
ir.get("oznaka"))); // 2. oznaka
Like HQL, aliases may also be set on expressions in the SELECT
clause, which will then be included in the resulting SQL statement. They are of little use from a programming perspective as we construct the ORDER BY
clause through the use of the Selection
objects used to construct the SELECT
clause.
Aliases are useful when the query has a result type of Tuple
. The aliases will be available through the resulting Tuple
objects. To set an alias, the alias()
method of the Selection
interface (parent to Expression
) must be invoked. The following example demonstrates this approach:
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<Student> stud = c.from(Student.class);
c.multiselect(stud.get("indeks").alias("indeks"),
stud.get("mesto_rodjenja").alias("mesto"));
This example actually demonstrates two facets of the alias()
method. The first is that it returns itself, so it can be invoked as part of the call to select()
or multiselect()
. The second is, once again, that it returns itself, and is therefore mutating what should be an otherwise immutable object. The alias()
method is an exception to the rule that only the query definition interfaces—CriteriaQuery
and Subquery
—contain mutating operations. Invoking alias()
changes the original Selection
object and returns it from the method invocation. It is invalid to set the alias of a Selection
object more than once.
Using the alias when iterating over the query results is as simple as requesting the expression by name. Executing the previous query would allow it to be processed as follows:
TypedQuery<Tuple> q = session.createQuery(c);
for (Tuple t : q.getResultList()) {
Integer indeks = t.get("indeks", Integer.class);
String mesto = t.get("mesto", String.class);
// ...
}
FROM
U sekciji 12.3.1, diskutovali smo o metodu from()
iz interfejsa AbstractQuery
kao i o ulogama korenova upita u formiranju definicije upita. Sada ćemo proširiti tu diskusiju i prikazati načine na koje se izrazi spajanja izražavaju u JPA Criteria API.
Izrazi spajanja se kreiraju korišćenjem metoda join()
iz interfejsa From
koji predstavlja roditelj interfejsa Root
, o kojem smo diskutovali ranije, kao i interfejsa Join
, koji predstavlja tip objekata koji se vraćaju kreiranjem izraza spajanja. Ovo znači da proizvoljan koren upita može da se spaja, kao i da spajanja mogu da se ulančavaju. Metod join()
zahteva izraz putanje kao argument i opciono, argument kojim se specifikuje tip spajanja: JoinType.INNER
ili JoinType.LEFT
, za unutrašnja ili spoljašnja spajanja, redom.
Kada se vrše spajanja unakrst kolekcijskih tipova (sa izuzetkom Map
), spajanje će imati dva parametarska tipa: tip izvora i tip cilja. Ovim se održava sigurnost tipova sa obe strane spajanja i
Metod join()
je aditivan, što znači da svaki poziv rezultuje novim spajanjem koji se kreira. Zbog toga, instanca tipa Join
koja se vraća kao povratna vrednost metoda bi trebalo biti sačuvana kao lokalna promenljiva radi formiranja izraza putanja kasnije.
Naredni primer ilustruje spoljašnje spajanje od Student
ka Ispit
:
Root<Student> stud = c.from(Student.class);
Join<Student,Ispit> ispit = stud.join("indeks", JoinType.LEFT);
Da smo izostavili argument JoinType.LEFT
, spajanje bi podrazumevano bilo unutrašnje. Poput HQL-a, višestruka spajanja se mogu asocirati za istu From
instancu. Na primer, za navigaciju kroz Ispit
i StudijskiProgram
, potrebno je koristiti naredni kod, koji pretpostavlja unutrašnja spajanja:
Root<Student> stud = c.from(Student.class);
Join<Student,Ispit> ispit = stud.join("indeks");
Join<Student,StudijskiProgram> studijskiProgram = stud.join("studijskiProgram");
Spajanja se takođe mogu ulančavati u jednu naredbu. Rezultujuće spajanje će biti tipizirano tipom izvora i tipom cilja poslednjeg spajanja u naredbi:
Root<StudijskiProgram> studijskiProgram = c.from(StudijskiProgram.class);
Join<Student,Ispit> ispiti = studijskiProgram.join("studenti").join("ispiti");
Joins across collection relationships that use Map
are a special case. HQL uses the KEY
and VALUE
keywords to extract the key or value of a Map
element for use in other parts of the query. In the JPA Criteria API, these operators are handled by the key()
and value()
methods of the MapJoin
interface. Consider the following example assuming a Map
join across the phones relationship of the Employee
entity:
SELECT e.name,
KEY(p),
VALUE(p)
FROM Employee e JOIN
e.phones p
To create this query using the JPA Criteria API, we need to capture the result of the join as a MapJoin
, in this case using the joinMap()
method. The MapJoin
object has three type parameters: the source type, key type, and value type. It can look a little more daunting, but makes it explicit what types are involved.
CriteriaQuery<Object> c = cb.createQuery();
Root<Employee> emp = c.from(Employee.class);
MapJoin<Employee,String,Phone> phone = emp.joinMap("phones");
c.multiselect(emp.get("name"),
phone.key(),
phone.value());
We need to use the joinMap()
method in this case because there is no way to overload the join()
method to return a Join
object or MapJoin
object when all we are passing in is the name of the attribute. Collection
, Set
, and List
relationships are likewise handled with the joinCollection()
, joinSet()
, and joinList()
methods for those cases where a specific join interface must be used. The strongly typed version of
the join()
method, which we demonstrate later, is able to handle all join types though the single join()
call.
WHERE
Kao što smo videli u nekim primerima, klauza WHERE
u JPA Criteria API upitu se postavlja metodom where()
iz interfejsa AbstractQuery
. Metod where()
ima dve varijante u zavisnosti od toga šta prihvata:
Predicate
objekataExpression<Boolean>
Svaki poziv metoda where()
će poništiti sve druge WHERE
klauze koje su postavljene prethodnim pozivima metoda where()
i konstruisaće nov skup ograničenja na osnovu novoprosleđenih kriterijuma.
Definicija WHERE
klauze je data u nastavku:
where_clause ::= WHERE conditional_expression
Sada ćemo videti na koje načine je moguće konstruisati ograničenja koja se prosleđuju metodu where()
.
Ključ za konstruisanje izraza u okviru JPA Criteria API je interfejs CriteriaBuilder
. Ovaj interfejs sadrži metode za sve predikate, izraze i funkcije koje HQL podržava kao i druge karakteristike specifične za JPA Criteria API. Naredne tabele sumiraju preslikavanja između HQL operatora, izraza i funkcija u njihove ekvivalentne metode interfejsa CriteriaBuilder
. Primetimo da u nekim slučajevima ne postoji direktna jednakost između HQL operatora i metoda, već je neophodno koristiti kombinaciju metoda iz CriteriaBuilder
kako bismo ostvarili isti rezultat. U nekim drugim slučajevima, ekvivalentan metod za JPA Criteria API se nalazi u interfejsu koji nije CriteriaBuilder
.
HQL to CriteriaBuilder Predicate Mapping:
HQL Operator | CriteriaBuilder Method |
---|---|
AND | and() |
OR | or() |
NOT | not() |
= | equal() |
<> | notEqual() |
> | greaterThan(),gt() |
>= | greaterThanOrEqualTo(),ge() |
< | lessThan(),lt() |
<= | lessThanOrEqualTo(),le() |
BETWEEN | between() |
IS NULL | isNull() |
IS NOT NULL | isNotNull() |
EXISTS | exists() |
NOT EXISTS | not(exists()) |
IS EMPTY | isEmpty() |
IS NOT EMPTY | isNotEmpty() |
MEMBER OF | isMember() |
NOT MEMBER OF | isNotMember() |
LIKE | like() |
NOT LIKE | notLike() |
IN | in() |
NOT IN | not(in()) |
HQL to CriteriaBuilder Scalar Expression Mapping:
HQL Expression | CriteriaBuilder Method |
---|---|
ALL | all() |
ANY | any() |
SOME | some() |
- | neg(),diff() |
+ | sum() |
* | prod() |
/ | quot() |
COALESCE | coalesce() |
NULLIF | nullif() |
CASE | selectCase() |
HQL to CriteriaBuilder Function Mapping:
HQL Function | CriteriaBuilder Method |
---|---|
ABS | abs() |
CONCAT | concat() |
CURRENT_DATE | currentDate() |
CURRENT_TIME | currentTime() |
CURRENT_TIMESTAMP | currentTimestamp() |
LENGTH | length() |
LOCATE | locate() |
LOWER | lower() |
MOD | mod() |
SIZE | size() |
SQRT | sqrt() |
SUBSTRING | substring() |
UPPER | upper() |
TRIM | trim() |
HQL to CriteriaBuilder Aggregate Function Mapping:
HQL Aggregate Function | CriteriaBuilder Method |
---|---|
AVG | avg() |
SUM | sum(),sumAsLong(),sumAsDouble() |
MIN | min(),least() |
MAX | max(),greatest() |
COUNT | count() |
COUNT DISTINCT | countDistinct() |
Pored direktnog prevođenja HQL operatora, izraza i funkcija, postoje neke tehnike koje su specifične za JPA Criteria API koje je potrebno uzeti u obzir prilikom konstrukcije izraza. Sledeće sekcije detaljno razmatraju ove tehnike.
U narednom primeru, prosledili smo niz objekata Predicate
metodu and()
. Ovim se dobija ponašanje koje kombinuje sve izraze operatorom AND
. Ekvivalentno ponašanje, samo za operatom OR
, ostvaruje se metodom or()
. Postoji prečica koja radi za AND
operator, a to je da se svi izrazi proslede kao argumenti metodu where()
. Ovaj pristup će implicitno kombinovati izraze korišćenjem semantike operatora AND
.
List<Predicate> criteria = new ArrayList<Predicate>();
if (name != null) {
ParameterExpression<String> p = cb.parameter(String.class, "name");
criteria.add(cb.equal(emp.get("name"), p));
}
if (deptName != null) {
ParameterExpression<String> p = cb.parameter(String.class, "dept");
criteria.add(cb.equal(emp.get("dept").get("name"), p));
}
if (criteria.size() == 0) {
throw new RuntimeException("no criteria");
} else if (criteria.size() == 1) {
c.where(criteria.get(0));
} else {
c.where(cb.and(criteria.toArray(new Predicate[0])));
}
TypedQuery<Employee> q = em.createQuery(c);
if (name != null) { q.setParameter("name", name); }
if (deptName != null) { q.setParameter("dept", deptName); }
// ...
The Criteria API also offers a different style of building AND
and OR
expressions for those who wish to build things incrementally rather than as a list. The conjunction()
and disjunction()
methods of the CriteriaBuilder
interface create Predicate
objects that always resolve to true
and false
, respectively. Once obtained, these primitive predicates can then be combined with other predicates to build up nested conditional expressions in a tree-like fashion. The following example rewrites the predication construction portion of the previous example using the conjunction()
method. Note how each conditional statement is combined with its predecessor using an and()
call.
Predicate criteria = cb.conjunction();
if (name != null) {
ParameterExpression<String> p =
cb.parameter(String.class, "name");
criteria = cb.and(criteria, cb.equal(emp.get("name"), p));
}
if (deptName != null) {
ParameterExpression<String> p =
cb.parameter(String.class, "dept");
criteria = cb.and(criteria,
cb.equal(emp.get("dept").get("name"), p));
}
// ...
Što se tiče ostalih predikata, u tabelama za predikate iznad treba napomenuti da postoje dva skupa metoda koji su dostupni za relaciona poređenja. Na primer, postoje metodi greaterThan()
i gt()
. Metodi koji imaju naziv od dva karaktera su specifični za numeričke vrednosti i strogo su tipizirani da rade sa brojevima. Metodi koji pripadaju skupu sa dužim nazivom se mogu koristiti u svim ostalim slučajevima.
Literal values may require special handling when expressed with the JPA Criteria API. In all the cases encountered so far, methods are overloaded to work with both Expression
objects and Java literals. However, there may be some cases where only an Expression
object is accepted (in cases where it is assumed you would never pass in a literal value or when any of a number of types would be acceptable). If you encounter this situation then, to use these expressions with Java literals, the literals must be wrapped using the literal()
method.
NULL
doslovne vrednosti se kreiraju pozivom metoda nullLiteral()
, koji prihvata tip klase kao parametar i proizvodi tipiziranu verziju vrednosti NULL
za datu prosleđenu klasu. Ovo je neophodno radi proširivanja stroge tipiziranosti API-ja na NULL
vrednosti.
Upravljanje parametrima u JPA Criteria API upitima se razlikuje od pristupa u HQL-u. Ako se prisetimo, u HQL-u upitima se parametri navode dvotačkom koju prati naziv parametra. U JPA Criteria API-u ovo ne funkcioniše. Umesto toga, moramo da eksplicitno kreiramo ParameterExpression
korektnog tipa koji se može koristiti u uslovnim izrazima. Ovo se izvodi metodom parameter()
interfejsa CriteriaBuilder
. Ovaj metod zahteva tip klase (kako bi postavio tip objekta ParameterExpression
) i opciono ime koje će koristiti u slučaju imenovanih parametara. Neka je potrebno izdvojiti podatke narednim upitom:
SELECT s
FROM Student s
WHERE s.studijskiProgram.naziv = :ns
Naredni kod ilustruje korišćenje ovog metoda:
CriteriaQuery<Student> c = cb.createQuery(Student.class);
Root<Student> stud = c.from(Student.class);
c.select(stud);
ParameterExpression<String> nazivStudijskogPrograma =
cb.parameter(String.class, "ns");
c.where(cb.equal(stud.get("studijskiProgram").get("naziv"),
nazivStudijskogPrograma));
Ako se parametar neće koristiti u drugim delovima upita, onda ga je moguće ugnezditi direktno u predikatskom izrazu da bismo učinili ceo upit konciznijim. Naredni kod ilustruje ovu tehniku:
CriteriaQuery<Student> c = cb.createQuery(Student.class);
Root<Student> stud = c.from(Employee.class);
c.select(stud)
.where(cb.equal(stud.get("studijskiProgram").get("naziv"),
cb.parameter(String.class, "ns")));
Da bismo izvršili ovaj upit, očigledno, neophodno je da postavimo vrednost datog parametra. U prvom pristupu, možemo koristiti preopterećenje koje prihvata ParameterExpression
objekat:
TypedQuery<Student> query = session.createQuery(c);
query.setParameter(nazivStudijskogPrograma, "Informatika");
List<Student> results = query.getResultList();
U drugom pristupu, možemo koristiti preopterećenje koje prihvata String
koji sadrži naziv imenovanog parametra:
TypedQuery<Student> query = session.createQuery(c);
query.setParameter("ns", "Informatika");
List<Student> results = query.getResultList();
The AbstractQuery
interface provides the subquery()
method for creation of subqueries. Subqueries may be correlated (meaning that they reference a root, path, or join from the parent query) or non-correlated. The JPA Criteria API supports both correlated and non-correlated subqueries, again using query roots to tie the various clauses and expressions together. The argument to subquery()
is a class instance representing the result type of the subquery. The return value is an instance of Subquery
, which is itself an extension of AbstractQuery
. With the exception of restricted methods for constructing clauses, the Subquery
instance is a complete query definition like CriteriaQuery
that may be used to create both simple and complex queries.
To demonstrate subquery usage, let’s look at a more significant example which uses subqueries to eliminate duplicates. The Employee
entity has relationships with four other entities: single-valued relationships with Department
and Address
, and collection-valued relationships with Phone
and Project
. Whenever we join across a collection-valued relationship, we have the potential to return duplicate rows; therefore, we need to change the criteria expression for Project
to use a subquery. The following code fragment shows the required code:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp);
// ...
if (projectName != null) {
Subquery<Employee> sq = c.subquery(Employee.class);
Root<Project> project = sq.from(Project.class);
Join<Project,Employee> sqEmp = project.join("employees");
sq.select(sqEmp)
.where(cb.equal(project.get("name"),
cb.parameter(String.class, "project")));
criteria.add(cb.in(emp).value(sq));
}
// ...
We introduced a new non-correlated subquery against Project
. Because the subquery declares its own root and does not reference anything from the parent query, it runs independently and is therefore non-correlated. The equivalent HQL query with only Project
criteria would be:
SELECT e
FROM Employee e
WHERE e IN (
SELECT emp
FROM Project p JOIN
p.employees emp
WHERE p.name = :project
)
Whenever we write queries that use subqueries, there is often more than one way to achieve a particular result. For example, we could rewrite the previous example to use EXISTS
instead of IN
and shift the conditional expression into the WHERE
clause of the subquery:
if (projectName != null) {
Subquery<Project> sq = c.subquery(Project.class);
Root<Project> project = sq.from(Project.class);
Join<Project,Employee> sqEmp = project.join("employees");
sq.select(project)
.where(cb.equal(sqEmp, emp),
cb.equal(project.get("name"),
cb.parameter(String.class,"project")));
criteria.add(cb.exists(sq));
}
By referencing the Employee
root from the parent query in the WHERE
clause of the subquery, we now have a correlated subquery. This time the query takes the following form in HQL:
SELECT e
FROM Employee e
WHERE EXISTS (
SELECT p
FROM Project p JOIN
p.employees emp
WHERE emp = e AND
p.name = :name
)
We can still take this example further and reduce the search space for the subquery by moving the reference to the Employee
root to the FROM
clause of the subquery and joining directly to the list of projects specific to that employee. In HQL, we would write this as follows:
SELECT e
FROM Employee e
WHERE EXISTS (
SELECT p
FROM e.projects p
WHERE p.name = :name
)
In order to re-create this query using the JPA Criteria API, we are confronted with a dilemma. We need to base the query on the Root
object from the parent query but the from()
method only accepts a persistent class type. The solution is the correlate()
method from the Subquery
interface. It performs a similar function to the from()
method of the AbstractQuery
interface, but does so with Root
and Join
objects from the parent query. The following example demonstrates how to use correlate()
in this case:
if (projectName != null) {
Subquery<Project> sq = c.subquery(Project.class);
Root<Employee> sqEmp = sq.correlate(emp);
Join<Employee,Project> project = sqEmp.join("projects");
sq.select(project)
.where(cb.equal(project.get("name"),
cb.parameter(String.class,"project")));
criteria.add(cb.exists(sq));
}
Before we leave subqueries in the JPA Criteria API, there is one more corner case with correlated subqueries to explore: referencing a join expression from the parent query in the FROM
clause of a subquery. Consider the following example that returns projects containing managers with direct reports earning an average salary higher than a userdefined threshold:
SELECT p
FROM Project p JOIN
p.employees e
WHERE TYPE(p) = DesignProject AND
e.directs IS NOT EMPTY AND
(SELECT AVG(d.salary)
FROM e.directs d) >= :value
When creating the Criteria API query definition for this query, we must correlate the employees attribute of Project
and then join it to the direct reports in order to calculate the average salary. This example also demonstrates the use of the type()
method of the Path
interface in order to do a polymorphic comparison of types:
CriteriaQuery<Project> c = cb.createQuery(Project.class);
Root<Project> project = c.from(Project.class);
Join<Project,Employee> emp = project.join("employees");
Subquery<Number> sq = c.subquery(Number.class);
Join<Project,Employee> sqEmp = sq.correlate(emp);
Join<Employee,Employee> directs = sqEmp.join("directs");
c.select(project)
.where(cb.equal(project.type(), DesignProject.class),
cb.isNotEmpty(emp.<Collection>get("directs")),
cb.ge(sq.select(cb.avg(directs.get("salary"))),
cb.parameter(Number.class, "value")));
IN
Za razliku od drugih operatora, operator IN
zahteva specijalno upravljanje u JPA Criteria API. Metod in()
interfejsa CriteriaBuilder
prihvata samo jedan argument-jednovrednosni izraz koji će biti testiran nad skupom vrednosti u IN
izrazu. Da bismo postavili skup vrednosti, potrebno je da koristimo CriteriaBuilder.In
objekat koji se dobija kao povratna vrednost metoda in()
. Posmatrajmo naredni HQL upit:
SELECT s
FROM Student s
WHERE s.studijskiProgram.naziv IN ('Informatika', 'Matematika', 'Astronomija')
Da bismo pretvorili ovaj upit u JPA Criteria API upit, potrebno je da pozovemo metod value()
iz interfejsa CriteriaBuilder.In
da bismo postavili skup vrednosti za nazive studijskih programa za koje smo zainteresovani, kao u narednom kodu:
CriteriaQuery<Student> c = cb.createQuery(Student.class);
Root<Student> stud = c.from(Student.class);
c.select(stud)
.where(cb.in(stud.get("studijskiProgram").get("naziv"))
.value("Informatika").value("Matematika").value("Astronomija"));
Primetimo ulančane pozive metoda value()
kako bismo postavili skup vrednosti za IN
izraz. Argument metoda in()
je izraz koji će biti upoređen sa vrednostima iz skupa vrednosti koje su postavljene metodom value()
.
Specijalno, u slučajevima kada postoji veliki broj poziva value()
metoda od kojih su sve vrednosti istog tipa, interfejsa Expression
omogućava prečicu za kreiranje IN
izraza. Metod in()
iz ovog interfejsa dozvoljava jednu ili više vrednosti da budu postavljene jednim pozivom, što naredni kod ilustruje:
CriteriaQuery<Student> c = cb.createQuery(Student.class);
Root<Student> stud = c.from(Student.class);
c.select(stud)
.where(stud.get("studijskiProgram").get("naziv")
.in("Informatika", "Matematika", "Astronomija"));
U ovom pristupu, poziv metoda in()
predstavlja ulančani sufiks nad izrazom umesto prefiksni, kao što je to bilo u prethodnom primeru. Primetimo razliku u tipovima argumenata između dve verzije metoda in()
iz interfejsa CriteriaBuilder
i Expression
. Verzija iz interfejsa Expression
prihvata skup vrednosti, a ne izraz koji će biti upoređen. Sa druge strane, verzija iz interfejsa CriteriaBuilder
dozvoljava više tipizirani pristup. Odabir između ova dva pristupa je uglavnom stvar preference ili konvencije koju kompanija koristi.
IN
expressions that use subqueries are written using a similar approach. For a more complex example, in the previous chapter, we demonstrated a HQL query using an IN
expression in which the department of an employee is tested against a list generated from a subquery. The example is reproduced here.
SELECT e
FROM Employee e
WHERE e.department IN (
SELECT DISTINCT d
FROM Department d JOIN
d.employees de JOIN
de.project p
WHERE p.name LIKE 'QA%'
)
We can convert this example to the JPA Criteria API, as shown in the following code:
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
Subquery<Department> sq = c.subquery(Department.class);
Root<Department> dept = sq.from(Department.class);
Join<Employee,Project> project = dept.join("employees").join("projects");
sq.select(dept.<Integer>get("id"))
.distinct(true)
.where(cb.like(project.<String>get("name"),
"QA%"));
c.select(emp)
.where(cb.in(emp.get("dept").get("id"))
.value(sq));
The subquery is created separately and then passed into the value()
method as the expression to search for the Department
entity. This example also demonstrates using an attribute expression as a value in the search list.
CASE
Like the IN
expression, building CASE
expressions with the Criteria API requires the use of a helper interface. We begin with the general form of the CASE
expression, the most powerful but also the most complex.
SELECT p.name,
CASE
WHEN TYPE(p) = DesignProject THEN 'Development'
WHEN TYPE(p) = QualityProject THEN 'QA'
ELSE 'Non-Development'
END
FROM Project p
WHERE p.employees IS NOT EMPTY
The selectCase()
method of the CriteriaBuilder
interface is used to create the CASE
expression. For the general form, it takes no arguments and returns a CriteriaBuilder.Case
object that we may use to add the conditional expressions to the CASE
statement. The following example demonstrates this approach:
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Project> project = c.from(Project.class);
c.multiselect(project.get("name"),
cb.selectCase()
.when(cb.equal(project.type(), DesignProject.class),
"Development")
.when(cb.equal(project.type(), QualityProject.class),
"QA")
.otherwise("Non-Development"))
.where(cb.isNotEmpty(project.<List<Employee>>get("employees")));
The when()
and otherwise()
methods correspond to the WHEN
and ELSE
keywords from HQL. Unfortunately, else
is already a keyword in Java, so otherwise
must be used as a substitute.
The next example simplifies the previous example down to the simple form of the CASE
statement.
SELECT p.name,
CASE TYPE(p)
WHEN DesignProject THEN 'Development'
WHEN QualityProject THEN 'QA'
ELSE 'Non-Development'
END
FROM Project p
WHERE p.employees IS NOT EMPTY
In this case, we pass the primary expression to be tested to the selectCase()
method and use the when()
and otherwise()
methods of the CriteriaBuilder.SimpleCase
interface. Rather than a predicate or boolean expression, these methods now accept single-valued expressions that are compared to the base expression of the CASE
statement:
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Project> project = c.from(Project.class);
c.multiselect(project.get("name"),
cb.selectCase(project.type())
.when(DesignProject.class, "Development")
.when(QualityProject.class, "QA")
.otherwise("Non-Development"))
.where(cb.isNotEmpty(project.<List<Employee>>("employees")));
COALESCE
The next example we cover in this subsection concerns the HQL COALESCE
expression:
SELECT COALESCE(d.name, d.id)
FROM Department d
Building a COALESCE
expression with the JPA Criteria API requires a helper interface like the other examples we have looked at in this section, but it is closer in form to the IN
expression than to the CASE
expressions. Here we invoke the coalesce()
method without arguments to get back a CriteriaBuilder.Coalesce
object that we then use the value()
method of to add values to the COALESCE
expression. The following example demonstrates this approach:
CriteriaQuery<Object> c = cb.createQuery();
Root<Department> dept = c.from(Department.class);
c.select(cb.coalesce()
.value(dept.get("name"))
.value(dept.get("id")));
Convenience versions of the coalesce()
method also exist for the case where only two expressions are being compared.
CriteriaQuery<Object> c = cb.createQuery();
Root<Department> dept = c.from(Department.class);
c.select(cb.coalesce(dept.get("name"),
dept.get("id")));
A final note about case expressions is that they are another exception to the rule that the CriteriaBuilder
methods are non-mutating. Each when()
method causes another conditional expression to be added incrementally to the case expression, and each value()
method adds an additional value to the coalesce list.
Not to be confused with the built-in functions of HQL, criteria function expressions are the JPA Criteria API equivalent of the FUNCTION
keyword in HQL. They allow native SQL stored functions to be mixed with other JPA Criteria API expressions. They are intended for cases where a limited amount of native SQL is required to satisfy some requirement but you don’t want to convert the entire query to SQL.
Function expressions are created with the function()
method of the CriteriaBuilder
interface. It requires as arguments the database function name, the expected return type, and a variable list of arguments, if any, that should be passed to the function. The return type is an Expression
, so it can be used in many other places within the query. The following example invokes a database function to capitalize the first letter of each word in a department name:
CriteriaQuery<String> c = cb.createQuery(String.class);
Root<Department> dept = c.from(Department.class);
c.select(cb.function("initcap",
String.class,
dept.get("name")));
As always, developers interested in maximizing the portability of their applications should be careful in using function expressions. Unlike native SQL queries, which are clearly marked, function expressions are a small part of what otherwise looks like a normal portable JPA query that is actually tied to database-specific behavior.
ORDER BY
Metod orderBy()
interfejsa CriteriaQuery
postavlja uređenje u definiciji upita. Ovaj metoda prihvata jedan ili više Order
objekata, koji se kreiraju pozivom metoda asc()
ili desc()
interfejsa CriteriaBuilder
, za kreiranje rastućih ili opadajućih uređenja, redom. Naredni primer ilustruje metod orderBy()
:
CriteriaQuery<Tuple> c = cb.createQuery(Tuple.class);
Root<Student> stud = c.from(Student.class);
Join<Student,StudijskiProgram> studijskiProgram = stud.join("studijskiProgram");
c.multiselect(studijskiProgram.get("naziv"),
stud.get("ime"),
stud.get("prezime"));
c.orderBy(cb.desc(studijskiProgram.get("naziv")),
cb.asc(stud.get("prezime")));
Argumenti metoda asc()
i desc()
moraju biti jednovrednosni izrazi, tipično formirani na osnovu atributa entiteta. Redosled u kojem se argumenti prosleđuju metodu orderBy()
definišu generisani SQL. Ekvivalentan HQL upit za prethodni primer je:
SELECT sp.naziv
s.ime,
s.prezime
FROM Student s JOIN
s.studijskiProgram sp
ORDER BY sp.naziv DESC,
s.prezime
GROUP BY
i HAVING
Metodu groupBy()
i having()
interfejsa AbstractQuery
predstavlja JPA Criteria API ekvivalente klauzama GROUP BY
i HAVING
u HQL-u, redom. Oba metoda prihvataju jedan ili više izraza koji se koriste za grupisanje i filtriranje podataka.
Pogledajmo kako je GROUP BY
klauza definisana:
groupby_clause ::= GROUP BY groupby_item {, groupby_item}*
groupby_item ::= single_valued_path_expression | identification_variable
Pogledajmo kako je HAVING
klauza definisana:
having_clause ::= HAVING conditional_expression
Do ovog trenutka u poglavlju, trebalo bi da čitaocu bude intuitivan način korišćenja ovih metoda. Pogledajmo naredni HQL upit:
SELECT s.indeks,
COUNT(i)
FROM Student s JOIN
s.ispiti i
GROUP BY s.indeks
HAVING COUNT(i) BETWEEN 20 AND 30
Da bismo rekreirali ovaj primer u JPA Criteria API, potrebno je da koristimo i agregatne funkcije i metode za grupisanje. Naredni primer demonstrira ovu konverziju:
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<Student> stud = c.from(Student.class);
Join<Student,Ispit> ispit = stud.join("ispiti");
c.multiselect(stud.get("indeks"), cb.count(ispit))
.groupBy(stud)
.having(cb.between(cb.count(ispit),
cb.literal(new Long(20)),
cb.literal(new Long(30))));
Zadatak 12.1: Napisati Java aplikaciju koja korišćenjem biblioteke Hibernate i JPA Criteria API-ja izdvaja podatke o studentima bez duplikata čija imena ili prezimena počinju slovom P
i koji su rođeni u Beogradu ili Kragujevcu. Rezultat urediti po mestu rođenja opadajuće, pa po imenu i prezimenu rastuće. Ispisati mesto stanovanja, indeks, ime i prezime za date studente.
Rešenje: Dajemo dva rešenja. Prvo od njih koristi pristup sa metodom select()
u kojem se izdvajaju objekti klase Student
. Drugo od njih koristi pristup sa metodom multiselect()
da izdvoji samo informacije od značaja.
Datoteka: vezbe/primeri/poglavlje_12/src/zadatak_12_1/Main.java
:
package zadatak_12_1;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class Main {
public static void main(String[] args) {
System.out.println("Pocetak rada\n");
System.out.println("--------------------------------");
System.out.println("Izdvajanje podataka na 1. nacin:");
readStudentiInfo();
System.out.println("--------------------------------");
System.out.println("Izdvajanje podataka na 2. nacin:");
readStudentiInfoMultiselect();
System.out.println("Zavrsetak rada\n");
HibernateUtil.getSessionFactory().close();
}
private static void readStudentiInfo() {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction TR = null;
try {
TR = session.beginTransaction();
CriteriaBuilder cb = session.getCriteriaBuilder();
// Izdvojiti podatke o studentima
CriteriaQuery<Student> criteria = cb.createQuery(Student.class);
Root<Student> st = criteria.from(Student.class);
criteria.select(st);
// Bez duplikata
criteria.distinct(true);
// Cije ime ili prezime pocinju na slovo 'P'
Predicate p1 = cb.or(cb.like(st.get("ime"), "P%"),
cb.like(st.get("prezime"), "P%"));
// Zive u Beogradu ili Kragujevcu
Predicate p2 = cb.isNotNull(st.get("mesto"));
Predicate p3 = cb.in(st.get("mesto")).value("Beograd").value("Kragujevac");
criteria.where(cb.and(p1, p2, p3));
// Rezultat urediti po mestu rodjenja opadajuce
Order o1 = cb.desc(st.get("mesto"));
// pa po imenu i prezimenu rastuce
Order o2 = cb.asc(st.get("ime"));
Order o3 = cb.asc(st.get("prezime"));
criteria.orderBy(o1, o2, o3);
// Dobijanje podataka o studentima koji zadovoljavaju kriterijume
List<Student> studenti = session.createQuery(criteria).getResultList();
for (Student s : studenti) {
System.out.println(
s.getMesto().trim() + " " +
s.getIndeks() + " " +
s.getIme().trim() + " " +
s.getPrezime().trim()
);
}
TR.commit();
} catch (Exception e) {
System.err.println("Postoji problem sa ispisivanjem informacija o studentima! Ponistavanje transakcije!");
if (TR != null) {
TR.rollback();
}
} finally {
session.close();
}
}
private static void readStudentiInfoMultiselect() {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction TR = null;
try {
TR = session.beginTransaction();
CriteriaBuilder cb = session.getCriteriaBuilder();
// Za svakog studenta
CriteriaQuery<Object[]> criteria = cb.createQuery(Object[].class);
Root<Student> st = criteria.from(Student.class);
// Izdvojiti informacije o mestu rodjenja, imenu, prezimenu i indeksu
criteria.multiselect(st.get("mesto"), st.get("ime"), st.get("prezime"), st.get("indeks"));
criteria.distinct(true);
// Cije ime ili prezime pocinju na slovo 'P'
Predicate p1 = cb.or(cb.like(st.get("ime"), "P%"),
cb.like(st.get("prezime"), "P%"));
// Zive u Beogradu ili Kragujevcu
Predicate p2 = cb.isNotNull(st.get("mesto"));
Predicate p3 = cb.in(st.get("mesto")).value("Beograd").value("Kragujevac");
criteria.where(cb.and(p1, p2, p3));
// Rezultat urediti po mestu rodjenja opadajuce
Order o1 = cb.desc(st.get("mesto"));
// pa po imenu i prezimenu rastuce
Order o2 = cb.asc(st.get("ime"));
Order o3 = cb.asc(st.get("prezime"));
criteria.orderBy(o1, o2, o3);
// Dobijanje podataka o studentima koji zadovoljavaju kriterijume
List<Object[]> studenti = session.createQuery(criteria).getResultList();
for (Object[] info : studenti) {
System.out.println(
((String)info[0]).trim() + " " +
((String)info[1]).trim() + " " +
((String)info[2]).trim() + " " +
((Integer)info[3])
);
}
TR.commit();
} catch (Exception e) {
System.err.println("Postoji problem sa ispisivanjem informacija o studentima! Ponistavanje transakcije!");
if (TR != null) {
TR.rollback();
}
} finally {
session.close();
}
}
}
Zadatak 12.2: Napisati Java aplikaciju koja korišćenjem biblioteke Hibernate i JPA Criteria API-ja izdvaja nazive svih položenih predmeta za studenta čiji se indeks učitava sa standardnog ulaza. Ispisati prvo ime i prezime studenta, pa zatim spisak njegovih položenih ispita.
Rešenje: Dodajemo implementaciju u vidu klase Predmet
za tabelu PREDMET
kako bismo mogli da pristupimo nazivu predmeta. Potrebno je dopuniti i klasu Ispit
radi ostvarivanja veze između ove dve klase. Konačno, dodajemo klasu Predmet
u listu anotiranih klasa.
Datoteka: vezbe/primeri/poglavlje_12/src/zadatak_12_2/Predmet.java
:
package zadatak_12_2;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
@Entity
@Table (name = "DA.PREDMET")
public class Predmet {
@Id
@Column
private Integer id;
@Column
private String naziv;
@OneToMany(mappedBy="predmet")
private List<Ispit> ispiti = new ArrayList<Ispit>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNaziv() {
return naziv;
}
public void setNaziv(String naziv) {
this.naziv = naziv;
}
public List<Ispit> getIspiti() {
return ispiti;
}
public void setIspiti(List<Ispit> ispiti) {
this.ispiti = ispiti;
}
}
Datoteka: vezbe/primeri/poglavlje_12/src/zadatak_12_2/Ispit.java
:
package zadatak_12_2;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Table;
@Entity
@Table(name = "DA.ISPIT")
public class Ispit {
// Primarni kljuc
@Id
private IspitId idIspita;
// Ostale kolone
@Column
private Integer ocena;
@Column
private String status;
@ManyToOne
@MapsId("indeks")
@JoinColumn(name="INDEKS", referencedColumnName="INDEKS")
private Student student;
@MapsId("idRoka")
@JoinColumns({ @JoinColumn(name = "skgodina", referencedColumnName = "skgodina"),
@JoinColumn(name = "oznakaroka", referencedColumnName = "oznakaroka") })
@ManyToOne
private IspitniRok ispitniRok;
@ManyToOne
@JoinColumn(name="idPredmeta",referencedColumnName="id", insertable=false, updatable=false)
private Predmet predmet;
// Get/set metodi
public IspitId getIdIspita() {
return idIspita;
}
public void setIdIspita(IspitId idIspita) {
this.idIspita = idIspita;
}
public Integer getOcena() {
return ocena;
}
public void setOcena(Integer ocena) {
this.ocena = ocena;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public Predmet getPredmet() {
return predmet;
}
public void setPredmet(Predmet predmet) {
this.predmet = predmet;
}
}
Datoteka: vezbe/primeri/poglavlje_12/src/zadatak_12_2/HibernateUtil.java
:
package zadatak_12_2;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
class HibernateUtil {
private static SessionFactory sessionFactory = null;
static {
try {
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
sessionFactory = new MetadataSources(registry)
.addAnnotatedClass(StudijskiProgram.class)
.addAnnotatedClass(IspitniRok.class)
.addAnnotatedClass(Student.class)
.addAnnotatedClass(Ispit.class)
.addAnnotatedClass(Predmet.class)
.buildMetadata().buildSessionFactory();
} catch (Throwable e) {
System.err.println("Session factory error");
e.printStackTrace();
System.exit(1);
}
}
static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
Datoteka: vezbe/primeri/poglavlje_12/src/zadatak_12_2/Main.java
:
package zadatak_12_2;
import java.util.List;
import java.util.Scanner;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Root;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class Main {
public static void main(String[] args) {
System.out.println("Pocetak rada\n");
try (Scanner ulaz = new Scanner(System.in)) {
System.out.printf("Unesite indeks studenta: ");
Integer indeks = ulaz.nextInt();
readPolozeniPredmetiCriteria(indeks);
}
System.out.println("Zavrsetak rada\n");
HibernateUtil.getSessionFactory().close();
}
public static void readPolozeniPredmetiCriteria(Integer indeks){
System.out.println("---------------------------------");
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction TR = null;
try {
TR = session.beginTransaction();
Student trazeniStudent = session.load(Student.class, indeks);
String ime = trazeniStudent.getIme();
String prezime = trazeniStudent.getPrezime();
System.out.println("Student: " + indeks + " " + ime + " " + prezime);
/* Postavljanje kriterijuma tako da kriterijume ispunjavaju polozeni
* ispiti studenta sa brojem indeksa indeks */
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Ispit> criteria = cb.createQuery(Ispit.class);
Root<Ispit> ispit = criteria.from(Ispit.class);
Join<Ispit, Student> student = ispit.join("student");
Join<Ispit, Predmet> predmet = ispit.join("predmet");
criteria.select(ispit);
criteria.where(cb.and(
cb.gt(ispit.get("ocena"), new Integer(5)),
cb.like(ispit.get("status"), "o"),
cb.equal(student.get("indeks"), indeks)
)
);
criteria.orderBy(cb.asc(predmet.get("naziv")));
List<Ispit> results = session.createQuery(criteria).getResultList();
results.stream()
.map(i -> i.getPredmet().getNaziv())
.forEach(naziv -> System.out.println("Ispit: " + naziv));
TR.commit();
} catch (Exception e) {
System.err.println("Postoji problem sa ispisivanjem informacija o studentima! Ponistavanje transakcije!");
if (TR != null) {
TR.rollback();
}
} finally {
session.close();
}
}
}
Zadatak 12.3: Napisati Java aplikaciju koja korišćenjem biblioteke Hibernate i JPA Criteria API-ja izdvaja sve podatke o studentima koji su upisali fakultet godine koja se unosi sa standardnog ulaza.
Zadatak 12.4: Napisati Java aplikaciju koja korišćenjem biblioteke Hibernate i JPA Criteria API-ja za svaki predmet izdvaja oznaku i naziv, a zatim i spisak uslovnih predmeta. Za svaki uslovni predmet izdvojiti oznaku, naziv i broj ESPB bodova.
Zadatak 12.5: Napisati Java aplikaciju koja korišćenjem biblioteke Hibernate i JPA Criteria API-ja za studenta, čiji se indeks unosi sa standardnog ulaza, pravi izveštaj o položenim ispitima i uspehu po upisanim školskim godinama. Za svaku upisanu godinu ispisati naziv predmeta, datum polaganja i ocenu, a zatim prosečnu ocenu.