28 marca 2007

Java Persistence - Rozdziały 4.5 Klauzula WHERE oraz 4.6 Wyrażenia warunkowe

0 komentarzy
Rozdział omawiający klauzulę WHERE - 4.5 Klauzula WHERE - jest wyjątkowo skromny jak na specyfikację Java Persistence 1.0. Biorąc pod uwagę, że większość z rozdziałów zawiera po 5-10 stron liczba 0,5 strony nie jest imponująca. Okazało się jednak, że jest to preludium do większej partii materiału jaka została zawarta w rozdziale kolejnym - 4.6 Wyrażenia warunkowe. Być może ilość stron - 10 - nie przekonuje do takiego myślenia, ale mając rozdział 4.6 za sobą, przygotowane przykłady i zestawianie środowiska do wykonania testów równolegle z kilkoma dostawcami JPA - Apache OpenJPA 0.9.7-SNAPSHOT, TopLink Essentials 2.0 BUILD 40 oraz Hibernate EntityManager 3.3.0.GA okazało się, że zabrało mi to wyjątkowo dużo czasu. Na prawdę nie spodziewałem się tego! Wszystko testowałem w środowisku, które opisałem w moim ostatnim artykule Nauka Java Persistence z Apache Maven 2 i dostawcami JPA: OpenJPA, Hibernate i TopLink, do którego zachęcam, ponieważ znacznie upraszcza zrozumienie specyfikacji w praktyce.

4.5 Klauzula WHERE

Klauzula WHERE składa się z wyrażenia warunkowego służącego do wyboru obiektów lub wartości spełniających go. Klauzula WHERE zawęża wynik zapytania SELECT lub obszar działania zapytań UPDATE lub DELETE.

Składnia klauzuli WHERE:

klauzula_WHERE ::= WHERE wyrażenie_warunkowe

Wyrażenie GROUP BY umożliwia grupowanie wartości ze względu na własności encji. Wyrażenie HAVING umożliwia dalsze zawężenie wyniku zapytania do warunków spełnianych przez grupy (będących produktem GROUP BY).

Składnia wyrażenia HAVING:

wyrażenie_HAVING ::= HAVING wyrażenie_warunkowe

GROUP_BY oraz HAVING są opisane szczegółowo w dalszej części specyfikacji - rozdział 4.7, który jest przede mną i będzie tematem kolejnej relacji.

Od razu widoczne jest dlaczego połączyłem relację z lektury specyfikacji rozdziału 4.5 o klauzuli WHERE z rozdziałem kolejnym 4.6 o wyrażeniach warunkowych. Wyrażania warunkowe są integralną częścią klauzuli WHERE i w zasadzie bez ich poznania nie ma co marzyć o zrozumieniu znaczenia WHERE (oczywiście dla użytkowników języka SQL będzie to jedynie przedstawienie możliwości, a może nawet i braku możliwości, wyrażania warunków w JPQL).

4.6 Wyrażenia warunkowe

Rozdział 4.6 omawia konstrukcje, które są dozwolone w klauzulach WHERE oraz HAVING.

Pojawia się uwaga odnośnie stanowych pól (pól-stanów, ang. state-fields, tj. pól, które są "nośnikiem" informacji z bazy danych, albo jeszcze inaczej, pól mapowanych na odpowiednie kolumny w bazie danych), które są odwzorowane (zmapowane) w postaci LOBów lub serializowanej (ang. serialized form). Jako, że nie za bardzo rozumiem problemu wysłałem pytanie na forum EJB na forum.java.sun.com - Conditional Expressions and the note about state-field's serialized form.

4.6.1 Literały

Literał łańcuchowy jest ograniczony przez pojedyńcze cudzysłowy, np. 'literał' (podczas, gdy w Javie korzysta się z cudzysłowów, a pojedyńcze cudzysłowy są zarezerwowane dla literałów znakowych - pojedyńczych liter, tj. obiektów typu char lub Character).
Specjalność pojedyńczych cudzysłowów wyłącza się poprzez poprzedzenie ich kolejnym apostrofem, np. 'I''m lovin'' it'. Literały w zapytaniach, jak literały znakowe w Javie, zapisane są w Unicode. Korzystanie z mechanizmu usuwania specjalności pojedyńczego cudzysłowa w Javie, tj. korzystanie z odwrotnego ukośnika (ang. backslash), nie jest wspierane (dla przypomnienia o literałach w Javie polecam część specyfikacji języka Java - 3.10.5 String Literals.

Literał liczbowy zapisywany jest w notacji języka Java oraz SQL (na chwilę obecną nie jest mi znana różnica między nimi, ale wynika to z mojej nikłej znajomości języka SQL).

Literał liczbowy zmiennoprzecinkowy jest wspierany w notacji języka Java oraz SQL (podobnie jak przy literałach liczbowych, nie mam pojęcia jaką to robi różnicę - wyjdzie podczas korzystania z JPQL).

Ciekawostką dla mnie jest literał wyliczeniowy (ang. enum literal), który wykorzystuje składnię literału typu wyliczeniowego w Javie. Klasa typu wyliczeniowego musi zostać wymieniona.

SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE p.rodzajProjektu = RodzajProjektu.OTWARTY

UWAGA: Powyższy przykład działa jedynie dla Apache OpenJPA 0.9.7-SNAPSHOT. Hibernate EntityManager 3.3.0.GA oraz TopLink Essentials 2.0 BUILD 40 wymagają, aby typ wyliczeniowy był podawany wraz z nazwą pakietu, czyli powyższy przykład zakończy się pomyślnie ze wszystkimi trzema dostawcami JPA, jeśli zostanie podany pakiet typu wyliczeniowego:

SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE p.rodzajProjektu = pl.jaceklaskowski.jpa.entity.RodzajProjektu.OTWARTY

Wspierane są sufiksy, które wskazują typ literału liczbowego zgodnie z zasadami języka Java. Wsparcie dla literałów liczbowych szestnastkowych oraz ósemkowych nie jest wymagany (ciekawe, co mogłyby modelować pola encji, którym odpowiadałyby literały szesnastkowe i ósemkowe?).

Literały logiczne (boolowskie) to TRUE oraz FALSE (na chwilę obecną wydaje mi się, że bardziej wyrafinowane jest stosowanie literałów wyliczeniowych, które są samoopisujące się i jedyne zastosowanie dla literałów logicznych to odwzorowanie danych już istniejących w bazach danych - dane "spadkowe", ang. legacy data).

Wielkość liter nie jest istotna w predefiniowanych literałach, np. TrUe jest tak samo dobre jak TRUE czy true.

4.6.2 Zmienne identyfikujące (ang. identification variables)

Wszystkie zmienne identyfikujące wykorzystywane w klauzulach WHERE i HAVING zapytań SELECT i DELETE muszą zostać zdefiniowane w klauzuli FROM. Klauzula FROM była opisywana wcześniej (patrz Java Persistence - Rozdział 4.4 Klauzula FROM i deklaracje nawigacyjne). Zmienne identyfikujące używane w klauzuli WHERE zapytania UPDATE muszą być zadeklarowane w klauzuli UPDATE.

Zmienne identyfikujące są "istniejąco definiowane" (ang. existentially quantified) w klauzulach WHERE lub HAVING, co oznacza, że są reprezentacją pojedyńczego elementu kolekcji lub pojedyńczego egzemplarza encyjnego AST i nigdy nie reprezentują kolekcji jako całości.

4.6.3 Wyrażenia ścieżkowe (ang. path expressions)


Zabronione jest korzystanie z wyrażenia ścieżkowego reprezentującego kolekcję (ang. collection valued path expression) w ramach klauzul WHERE i HAVING jako część wyrażenia warunkowego oprócz wyrażenia porównania pustej kolekcji (ang. empty collection comparision expression), wyrażenia operującego na elemencie kolekcji (ang. collection member expression) czy wyrażenie będące argumentem operatora SIZE. Wszystkie wymienione wyrażenia są częścią składni BNF wyrażenia warunkowego i są wyjaśnione w późniejszych sekcjach.

Dla przybliżenia tematu podam operatory odpowiadające wymienionym wyżej wyrażeniom:
  • Wyrażenie porównania pustej kolekcji - operator IS EMPTY
  • Wyrażenie operujące na elemencie kolekcji - operator [NOT] MEMBER [OF]
  • Wyrażenie będące argumentem operatora SIZE - operator SIZE
Wyłącznie wymienione operatory, które operują na kolekcjach encji, mogą być wykorzystane w klauzulach WHERE oraz HAVING.

4.6.4 Parametry wejściowe (ang. input parameters)

Wyrażenia warunkowe mogą korzystać z parametrów pozycyjnych (ang. positional parameters) lub nazwanych (ang. named parameters). Nie mogą być one jednak używane równocześnie w pojedyńczym zapytaniu.

Parametry wejściowe (pozycyjne i nazwane) mogą być jedynie wykorzystywane w klauzulach WHERE lub HAVING.

Specyfikacja podkreśla, że jeśli parametr wejściowy ma wartość NULL, wtedy operacje porównania lub arytmetyczne, w których ten parametr występuje zwróci wartość nieokreśloną. Więcej informacji ma być podanych w sekcji 4.11 (jeszcze przede mną).

4.6.4.1 Parametry pozycyjne

Następujące reguły rządzą parametrami pozycyjnymi:
  • Parametry wejściowe są wyznaczane przez znak zapytania (?), po którym następuje liczba całkowita, np. ?1.
  • Parametry wejściowe rozpoczynają się od 1.
I na potwierdzenie, podczas wykonania następującego zapytania:

Query query = em
.createQuery("SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE p.rodzajProjektu = ?0");
query.setParameter(0, RodzajProjektu.OTWARTY);

otrzymałem komunikat błędu (dostawca JPA to Apache OpenJPA):

FAILED: testPositionalParameters
<4|false|0.9.7-incubating-snapshot> org.apache.openjpa.persistence.InvalidStateException: The parameter index 0 is invalid. Parameters must be integers starting at 1.
at org.apache.openjpa.persistence.QueryImpl.setParameter(QueryImpl.java:410)
at org.apache.openjpa.persistence.QueryImpl.setParameter(QueryImpl.java:49)
at pl.jaceklaskowski.jpa.chapter4_6.ConditionalExpressionsTest.testPositionalParameters(ConditionalExpressionsTest.java:99)

, co zgadza się z drugim warunkiem, gdzie parametry pozycyjne są liczone począwszy od 1.

UWAGA: Hibernate EntityManager 3.3.0.GA akceptuje numerowanie parametrów pozycyjnych począwszy od 0 (co jest niezgodne ze specyfikacją).

Specyfikacja wyraźnie zaznacza, że dany parametr może być wykorzystany wielokrotnie oraz kolejność użycia parametrów w zapytaniu nie musi odpowiadać kolejności przypisywania im wartości (co okazało się być błędnie zaimplementowane w Apache OpenJPA. Zgłosiłem błąd w bazie błędów OpenJPA - OPENJPA-188 Positional parameters (in)order within query and query.setParameter (in)order, co może ostatecznie okazać się moim niezrozumieniem tematu).

Query query = em
.createQuery("SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE o.imie LIKE ?2 AND o.nazwisko LIKE ?2 AND p.rodzajProjektu = ?1");
query.setParameter(1, RodzajProjektu.OTWARTY);
query.setParameter(2, "J%");

4.6.4.2 Parametry nazwane

Parametrem nazwanym nazywamy identyfikator poprzedzony znakiem dwukropka (:).

Parametry nazwane są rozróżniane ze względu na wielkość liter.

Query query = em
.createQuery("SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE o.imie LIKE :imie AND p.rodzajProjektu = :rodzajProjektu");
query.setParameter("rodzajProjektu", RodzajProjektu.OTWARTY);
query.setParameter("imie", "J%");

Dokładne omówienie reguł rządzących parametrami nazwanymi znajduje się w rozdziałach 3.6.1 oraz 4.4.1, który były już relacjonowane w Notatniku - Java Persistence - Rozdział 3.6 Query API oraz Java Persistence - Rozdział 4.4 Klauzula FROM i deklaracje nawigacyjne, odpowiednio.

4.6.5 Składanie wyrażeń warunkowych

Wyrażenia warunkowe mogą składać się z innych wyrażeń warunkowych, operacji porównania, logicznych, wyrażeń ścieżkowych, których wynikiem jest wartość logiczna, literałów logicznych i logicznych parametrów wejściowych.

Wyrażenia arytmetyczne (liczbowe) mogą być używane w wyrażeniach porównujących. Wyrażenia arytmetyczne mogą składać się z innych wyrażeń arytmetycznych, operacji arytmetycznych, wyrażeń ścieżkowych, których wynikiem jest wartość liczbowa, literałów liczbowych oraz liczbowych parametrów wejściowych.

Operacje arytmetyczne korzystają z promocji liczbowej, tj. "sprowadzania" argumentów operacji do wspólnego typu.

Możliwe jest korzystanie z nawiasów zwykłych '()' do grupowania i porządkowania wyrażeń.

Słowa kluczowe występujące podczas składania wyrażeń to OR, AND, NOT. Pozostałe zostaną omówione w nadchodzących rozdziałach. Cała notacja BNF wyrażeń warunkowych przedstawiona została na stronie 90. specyfikacji JPA i wszystkie będą opisane dalej, więc nic straconego.

Funkcje agregujące mogą jedynie być używane w wyrażeniach warunkowych w klauzuli HAVING (więcej w nadchodzącym rodziale).

4.6.6 Operatory i ich kolejność wykonania (siła wiązania)

Rozdział przedstawia ważność operatorów w malejącym porządku, tj. pierwszy jest najsilniejszy.
  • Operator nawigacyjny - symbol kropki (.)
  • Operatory arytmetyczne
    • Unarne - + (plus) oraz - (minus)
    • Binarne - * (mnożenie), / (dzielenie), + (dodawanie), - (odejmowanie)
    • Operatory porównania - = (równy), > (większy), >= (większy bądź równy), < (mniejszy), <= (mniejszy bądź równy), <> (nierówny), [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF]
  • Operatory logiczne - NOT, AND oraz OR
Opis operatorów znajduje się w kolejnych rozdziałach.

4.6.7 Wyrażenia należenia BETWEEN (pomiędzy)

Operator BETWEEN działa na wyrażeniach tego samego typu - wyrażeniach liczbowych, wyrażeniach łańcuchowych lub wyrażeniach kalendarzowych.

Składnia wyrażenia BETWEEN składa się z kilku wariantów odpowiadających typom wyrażeń. Pierwszy wariant wyrażenia BETWEEN z wyrażeniami arytmetycznymi:

wyrażenie_liczbowe [NOT] BETWEEN wyrażenie_arytmetyczne AND wyrażenie_arytmetyczne

Przykład: Wyszukaj osoby, które uczestniczą w 2 lub 3 projektach.

SELECT DISTINCT o FROM Osoba o WHERE SIZE(o.projekty) BETWEEN 2 AND 3

Przy tworzeniu wspomnianego przykładu ujawniła się istotna różnica między Apache OpenJPA a TopLink Essentials - raportowanie błędów.

Początkowo stworzyłem zapytanie następującej postaci:

SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE COUNT(p) BETWEEN 2 AND 3 AND p.rodzajProjektu = :rodzajProjektu

TopLink zareagował na niepoprawne zastosowanie COUNT w klauzuli WHERE w następujący sposób:

java.lang.IllegalArgumentException: An exception occured while creating a query in EntityManager
at oracle.toplink.essentials.internal.ejb.cmp3.EntityManagerImpl.createQuery(EntityManagerImpl.java:194)
at pl.jaceklaskowski.jpa.chapter4_6.ConditionalExpressionsTest.testExpressions(ConditionalExpressionsTest.java:155)

podczas, gdy OpenJPA był bardziej wylewny i wskazał dokładne źródło błędu.

Caused by: org.apache.openjpa.lib.jdbc.ReportingSQLException: Invalid use of an aggregate function. {SELECT DISTINCT t0.numer, t0.dzienImienin, t0.dzienUrodzin, t0.imie, t0.kraj, t
0.nazwisko, t0.tytul FROM Osoba t0 INNER JOIN Osoba_Projekt t1 ON t0.numer = t1.Osoba_numer INNER JOIN Projekt t2 ON t1.projekty_nazwa = t2.nazwa WHERE (COUNT(t2.nazwa) >= ? AND CO
UNT(t2.nazwa) <= ? AND t2.rodzajProjektu = ?)} [code=30000, state=42903]
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.wrap(LoggingConnectionDecorator.java:197)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.access$000(LoggingConnectionDecorator.java:53)
at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator$LoggingConnection.prepareStatement(LoggingConnectionDecorator.java:224)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:160)
at org.apache.openjpa.lib.jdbc.ConfiguringConnectionDecorator$ConfiguringConnection.prepareStatement(ConfiguringConnectionDecorator.java:137)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:160)
at org.apache.openjpa.jdbc.kernel.JDBCStoreManager$RefCountConnection.prepareStatement(JDBCStoreManager.java:1305)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.prepareStatement(DelegatingConnection.java:149)
at org.apache.openjpa.jdbc.sql.SQLBuffer.prepareStatement(SQLBuffer.java:471)
at org.apache.openjpa.jdbc.sql.SQLBuffer.prepareStatement(SQLBuffer.java:451)
at org.apache.openjpa.jdbc.sql.SelectImpl.execute(SelectImpl.java:323)
at org.apache.openjpa.jdbc.sql.SelectImpl.execute(SelectImpl.java:296)
at org.apache.openjpa.jdbc.sql.LogicalUnion$UnionSelect.execute(LogicalUnion.java:402)
at org.apache.openjpa.jdbc.sql.LogicalUnion.execute(LogicalUnion.java:213)
at org.apache.openjpa.jdbc.sql.LogicalUnion.execute(LogicalUnion.java:203)
at org.apache.openjpa.jdbc.kernel.SelectResultObjectProvider.open(SelectResultObjectProvider.java:91)
at org.apache.openjpa.lib.rop.EagerResultList.(EagerResultList.java:31)
... 31 more

Drugi wariant wyrażenia BETWEEN z wyrażeniami łańcuchowymi:

wyrażenie_łańcuchowe [NOT] BETWEEN wyrażenie_łańcuchowe AND wyrażenie_łańcuchowe

Przykład: Wyszukaj osoby, których nazwisko jest krótsze niż 5 i dłuższe niż 10 znaków.

SELECT DISTINCT o FROM Osoba o WHERE LENGTH(o.nazwisko) NOT BETWEEN 5 AND 10

I kolejny, ostatni, wariant wyrażenia BETWEEN dla wyrażeń kalendarzowych:

wyrażenie_kalendarzowe [NOT] BETWEEN wyrażenie_kalendarzowe AND wyrażenie_kalendarzowe

Przykład: Wyszukaj osoby, których imieniny są w nadchodzących 15-tu dniach.

SELECT DISTINCT o FROM Osoba o WHERE o.dzienImienin BETWEEN :dzisiaj AND :za15dni

Uwaga na wyrażenia, które operują na wartościach nieokreślonych lub NULL (opisane w nadchodzącym rozdziale 4.11).

BETWEEN jest analogiczny do odpowiedniego zapytania korzystającego z operatorów <= (mniejsze bądź równe) i >= (większe bądź równe).

4.6.8 Wyrażenia należenia IN (w)

Składnia użycia operatora porównania [NOT] IN w wyrażeniach warunkowych:

wyrażenie_in ::= wyrażenie_ścieżkowe_pola_stanu [NOT] IN ( element_in {, element_in}* | podzapytanie)
element_in ::= literał | parametr_wejściowy

wyrażenie_ścieżkowe_pola_stanu musi być wartością łańcuchową, liczbową bądź wyliczeniową.

literał bądź parametr_wejściowy musi być tego samego typu co odpowiadające wyrażenie_ścieżkowe_pola_stanu (więcej informacji będzie w rozdziale 4.12).

Wynik podzapytania musi być tego samego typu jak typ odpowiadającego wyrażenie_ścieżkowe_pola_stanu (więcej w rozdziale 4.6.15).

Wyrażenie IN jest równoznacze z wyrażeniem korzystającym z operatora równości oraz OR.

Przykład: Wyszukaj osoby o imieniu Jacek bądź Agata.

SELECT DISTINCT o FROM Osoba o WHERE o.imie IN ('Jacek', 'Agata')

Powyższe zapytanie jest analogiczne do następującego:

SELECT DISTINCT o FROM Osoba o WHERE (o.imie = 'Jacek') OR (o.imie = 'Agata')

Musi istnieć co najmniej jeden element w liście, która definiuje zbiór wartości operatora IN.

Jeśli wartość wyrażenie_ścieżkowe_pola_stanu wyrażenia jest NULL albo nieokreślona, wartość całego wyrażenia jest nieokreślona.

4.6.9 Wyrażenia porównania LIKE (jak)

Składnia operatora porównania [NOT] LIKE jest następująca:

wyrażenie_łańcuchowe [NOT] LIKE wzorzec [ESCAPE znak_wyłączający_specjalność]

wyrażenie_łańcuchowe musi być ciągiem znaków (żeby się nie powtarzać i nie napisać, że musi być wyrażeniem łańcuchowym). wzorzec jest literałem łańcuchowym lub łańcuchowym parametrem wejściowym, w którym niektóre znaki mają specjalne znaczenie:
  • podkreślnik '_' reprezentuje dowolny znak
  • procent '%' reprezentuje dowolny ciąg znaków, włączając ciąg pusty
Pozostałe znaki reprezentują same siebie. Opcjonalny parametr znak_wyłączający_specjalność jest literałem znakowym z jednym znakiem lub parametr wejściowy typu znakowego (char lub Character), który wyłącza specjalność znaków podkreślnika oraz procenta we wzorcu.

Przykład: Wyszukaj osoby, których imię rozpoczyna się 'Jac' i kończy 'k' (np. Jacek, Jack, czy ostatecznie Jaculek), a nazwisko nie rozpoczyna się literą 'K'.

SELECT DISTINCT o FROM Osoba o WHERE o.imie LIKE 'Jac%k' AND o.nazwisko NOT LIKE 'K%'

Jeśli wartość wyrażenie_łańcuchowe lub wzorca jest NULL lub nieokreślona, wartość wyrażenia LIKE jest nieokreślona. Jeśli znak_wyłączający_specjalność jest podany i jest NULL, wartość wyrażenia LIKE jest nieokreślona.

4.6.10 Wyrażenia porównania NULL


Wyrażenie porównania IS [NOT] NULL sprawdza, czy argument jest NULL.

Składnia operatora porównania IS [NOT] NULL jest następująca:

{wyrażenie_ścieżkowe_pojedyńczej_wartości | parametr_wejściowy } IS [NOT] NULL

4.6.11 Wyrażenia porównania pustej kolekcji IS EMPTY

Składnia operatora porównania IS [NOT] EMPTY jest następująca:

wyrażenie_ścieżkowe_reprezentujące_kolekcję IS [NOT] EMPTY

Wyrażenie porównania IS [NOT] EMPTY sprawdza, czy wyrażenie_ścieżkowe_reprezentujące_kolekcję jest pusty, tj. kolekcja nie posiada elementów.

Przykład: Wyszukaj osoby, które uczestniczą w projektach.

SELECT DISTINCT o FROM Osoba o WHERE o.projekty IS NOT EMPTY

Jeśli wartość wyrażenie_ścieżkowe_reprezentujące_kolekcję jest nieokreślona, wartość również będzie nieokreślona.

4.6.12 Wyrażenia porównania elementu kolekcji MEMBER OF

Składnia operatora porównania [NOT] MEMBER [OF] jest następująca:

wyrażenie_encyjne [NOT] MEMBER [OF] wyrażenie_ścieżkowe_reprezentujące_kolekcję
wyrażenie_encyjne ::= wyrażenie_ścieżkowe_pojedyńczej_wartości_asocjacji | wyrażenie_pojedyńczej_encji
wyrażenie_pojedyńczej_encji ::= zmienna_identyfikacyjna | parametr_wejściowy

Wyrażenie porównania [NOT] MEMBER [OF] sprawdza, czy wyrażenie_encyjne jest elementem kolekcji wskazanej przez wyrażenie_ścieżkowe_reprezentujące_kolekcję.

Jeśli wyrażenie_ścieżkowe_reprezentujące_kolekcję jest pustą kolekcją, wartość MEMBER [OF] jest FALSE, a [NOT] MEMBER [OF] jest TRUE.

Jeśli wyrażenie_ścieżkowe_reprezentujące_kolekcję lub wyrażenie_ścieżkowe_pojedyńczej_wartości_asocjacji jest NULL lub nieokreślona, wartość wyrażenia jest nieokreślona.

Przykład: Wyszukaj osoby, które należą do projektu Apache OpenEJB.

Query query = em.createQuery("SELECT DISTINCT o FROM Osoba o WHERE :projekt MEMBER OF o.projekty");
query.setParameter("projekt", new Projekt("Apache OpenEJB", RodzajProjektu.OTWARTY));

UWAGA: Powyższy przykład nie działa na Apache OpenJPA 0.9.7-SNAPSHOT podczas, gdy TopLink 2.0 BUILD 40 oraz Hibernate EntityManager 3.3.0.GA wykonują zapytanie poprawnie.

4.6.13 Wyrażenia istnienia EXISTS

Wyrażenie EXISTS ma wartość TRUE jeśli wartość podzapytania składa się z jednego bądź kilku wartości. W przeciwnym przypadku wartość wyrażenia EXISTS wynosi FALSE.

Składnia operatora istnienia EXISTS jest następująca:

wyrażenie_exists ::= [NOT] EXISTS (podzapytanie)


Przykład: Wyszukaj projekty, które mają zależności od projektów otwartych (wersja bardzo skomplikowana, bo możnaby to prościej obsłużyć).

SELECT DISTINCT p FROM Projekt p WHERE EXISTS (SELECT zaleznosc FROM Projekt zaleznosc WHERE zaleznosc MEMBER OF p.zaleznosci AND zaleznosc.rodzajProjektu = pl.jaceklaskowski.jpa.entity.RodzajProjektu.OTWARTY)

UWAGA: Apache OpenJPA 0.9.7-SNAPSHOT nie zwróci żadnych elementów (niepoprawne działanie) podczas, gdy TopLink Essentials 2.0 BUILD 40 oraz Hibernate EntityManager 3.3.0.GA radzą sobie znakomicie.

UWAGA: Jedynie Apache OpenJPA akceptuje podanie typu wyliczeniowego w skróconej postaci - bez pakietu. TopLink oraz Hibernate wymagają podania pełnej kwalifikowanej nazwy klasy (wraz z pakietem), tj. pl.jaceklaskowski.jpa.entity.RodzajProjektu.OTWARTY vs RodzajProjektu.OTWARTY.

4.6.14 Wyrażenia ALL, ANY, SOME

Wyrażenie warunkowe ALL ma wartość TRUE, jeśli operator porównania przyjmuje wartość TRUE dla wszystkich wartości będących elementami wyniku podzapytania lub jeśli wynik podzapytania jest pusty. Wyrażenie ALL przyjmuje wartość FALSE, jeśli dowolne porównanie zwróci FALSE. Wyrażenie ALL przyjmuje wartość nieokreśloną, jeśli nie jest TRUE albo FALSE.

Wyrażenie warunkowe ANY ma wartość TRUE, jeśli operator porównania ma wartość TRUE dla dowolnej wartości będącej elementem wyniku podzapytania. Wyrażenie ANY jest FALSE, jeśli wynik podzapytania jest pusty lub jeśli operator porównania jest FALSE dla wszystkich wartości będących elementami wyniku podzapytania. Wyrażenie ANY przyjmuje wartość nieokreśloną, jeśli nie jest TRUE albo FALSE.

Operator SOME jest synonimem dla operatora ANY.

Operatory porównania używane z ALL/ANY/SOME to = (równa się), < (mniejszy), <= (mniejszy równy), > (większy), >= (większy równy), <> (nierówny). Wynik podzapytania musi być tego samego typu jak drugi argument operatora porównania (więcej informacji w nadchodzącym rozdziale 4.12).

Składnia wyrażenia ALL/ANY/SOME jest następująca:

wyrażenie_ALL_ANY_SOME ::= { ALL | ANY | SOME} (podzapytanie)

Przykład: Wyszukaj pracowników, których zarobki są większe od wszystkich kierowników w dziale pracownika.

SELECT emp FROM Employee emp WHERE emp.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = emp.department)

4.6.15 Podzapytania

Podzapytania mogą jedynie występować w klauzulach WHERE oraz HAVING.

Składnię podzapytań przedstawia specyfikacja na stronie 95 (i wydaje się, że jest podobna, jeśli nie identyczna ze składnią zapytań).

Warto pamiętać, że w niektórych przypadkach podzapytanie musi zwracać pojedyńczą wartość, np.

SELECT o FROM Osoba o WHERE SIZE(o.projekty) > (SELECT AVG(SIZE(o1.projekty)) FROM Osoba o1)

Powyższe zapytanie zwraca osoby, których zaangażowanie w projektach przekracza średnią wśród wszystkich pracowników.

UWAGA: Zapytanie nie jest akceptowane przez TopLink Essentials 2.0 BUILD 40 oraz Hibernate EntityManager 3.3.0.GA. Apache OpenJPA 0.9.7-SNAPSHOT wykonuje zapytanie bez zarzutu.

4.6.16 Wyrażenia funkcjonalne

JPQL posiada następujące wbudowane funkcje, które mogą być używane w klauzulach WHERE oraz HAVING.

Jeśli wartość dowolnego argumentu wyrażenia funkcjonalnego jest NULL albo nieokreślona, wartość wyrażenia funkcjonalnego jest również nieokreślona.

4.6.16.1 Funkcje łańcuchowe


Funkcje zwracające ciąg znaków:

CONCAT(pierwszy_ciąg_znaków, drugi_ciąg_znaków) - zwraca ciąg znaków będący sklejeniem swoich argumentów.

SUBSTRING(ciąg_znaków, proste_wyrażenie_arytmetyczne, proste_wyrażenie_arytmetyczne) - zwraca ciąg znaków będący podciągiem ciąg_znaków od pozycji wyznaczonej przez pierwsze proste_wyrażenie_arytmetyczne o długości określonej przez drugie proste_wyrażenie_arytmetyczne.

TRIM([[LEADING | TRAILING | BOTH] [znak_do_usunięcia] FROM] ciąg_znaków) - zwraca ciąg znaków z usuniętymi znakami z ciąg_znaków. Jeśli nie podano znak_do_usunięcia domyślnie będzie to spacja. Parametr znak_do_usunięcia jest pojedyńczym znakiem bądź znakowym parametrem wejściowym o długości 1 (typu char bądź Character). Domyślnym sposobem usuwania - LEADING, TRAILING, BOTH - jest BOTH.

UWAGA: Specyfikacja ostrzega, że korzystanie z parametru znak_do_usunięcia może spowodować, że zapytanie nie będzie przenośne, gdyż nie wszystkie bazy danych wspierają usuwanie znaków innych niż spacja.

LOWER(ciąg_znaków) - zwraca ciąg znaków będący kopią ciąg_znaków ze wszystkimi literami zamienionymi na małe litery.

UPPER(ciąg_znaków) - zwraca ciąg znaków będący kopią ciąg_znaków ze wszystkimi literami zamienionymi na wielkie litery.

Funkcje zwracające wartości liczbowe:

LENGTH(ciąg_znaków) - zwraca długość ciąg_znaków

LOCATE(ciąg_znaków_do_odszukania, ciąg_znaków[, proste_wyrażenie_arytmetyczne]) - zwraca pierwszą pozycję podanego ciąg_znaków_do_odszukania w ciąg_znaków od pozycji proste_wyrażenie_arytmetyczne, włącznie (domyślnie początek ciąg_znaków). Pierwsza pozycja w ciągu to 1. Jeśli ciąg_znaków_do_odszukania nie został odnaleziony zwracana jest wartość 0.

UWAGA: Podobnie jak miało to miejsce w przypadku funkcji TRIM, specyfikacja ostrzega, że korzystanie z parametru proste_wyrażenie_arytmetyczne może spowodować, że zapytanie nie będzie przenośne, gdyż nie wszystkie bazy danych wspierają LOCATE z trzema parametrami.

4.6.16.2 Funkcje arytmetyczne

ABS(proste_wyrażenie_arytmetyczne) - zwraca wartość bezwzględną liczby proste_wyrażenie_arytmetyczne tego samego typu.

SQRT(proste_wyrażenie_arytmetyczne) - zwraca kwadrat proste_wyrażenie_arytmetyczne. Zwracany typ jest double.

MOD(proste_wyrażenie_arytmetyczne, proste_wyrażenie_arytmetyczne) - zwraca resztę z dzielenia pierwszego proste_wyrażenie_arytmetyczne przez drugie proste_wyrażenie_arytmetyczne. Zwracany typ jest integer.

SIZE(wyrażenie_ścieżkowe_reprezentujące_kolekcję) - zwraca numer elementów w wyrażenie_ścieżkowe_reprezentujące_kolekcję. Jeśli wyrażenie_ścieżkowe_reprezentujące_kolekcję jest puste, SIZE zwróci 0.

Argumenty funkcji mogą być liczbowymi typami prostymi jak i ich obiektowymi odpowiednikami.

4.6.16.3 Funkcje kalendarzowe

JPQL dostarcza trzech funkcji kalendarzowych:
  • CURRENT_DATE zwraca aktualną datę w bazie danych.
  • CURRENT_TIME zwraca aktualny czas w bazie danych.
  • CURRENT_TIMESTAMP zwraca aktualny stempel czasowy w bazie danych.
Wszystkie są funkcjami bezparametrowymi.

27 marca 2007

Nauka Java Persistence z Apache Maven 2 i dostawcami JPA: OpenJPA, Hibernate i TopLink

4 komentarzy
Nic nie zastąpi nauki poprzez praktykę. Nie wszyscy mają czas, aby wszystko praktycznie sprawdzić, więc część rzeczy uczymy się wierząc słowu pisanemu. W przypadku mojej lektury specyfikacji Java Persistence API (JPA) było inaczej - zdecydowałem się na zestawienie środowiska, które zautomatyzowałoby mi wykonywanie testów jednostkowych pisanych z TestNG i jednocześnie uatrakcyjniałyby poznawanie JPA.

Najpierw powstał artykuł Java Persistence API z OpenJPA i Derby oraz TestNG z Eclipse IDE w tle, po którym dzisiejszego wieczoru napisałem kolejny będący kontynuacją wspominanego - Nauka Java Persistence z Apache Maven 2 i dostawcami JPA: OpenJPA, Hibernate i TopLink. W ten sposób nie tylko, że mogę uruchamiać proste programy (testy) bez konieczności każdorazowego zestawiania środowiska, ale również wykonywać je z wybranym dostawcą JPA - Apache OpenJPA, Hibernate oraz TopLink Essentials. A wszystko zaczęło się tak niewinnie...

Podczas lektury specyfikacji JPA rozdział 4.6 Wyrażenia warunkowe, natrafiłem na pewne zapisy, których nie mogłem zrozumieć, a może po prostu chciałem zaprezentować je w postaci kawałka kodu? Nieważne! Jak to bywało do tej pory, tak i zrobiłem i tym razem - napisałem test. Wykonałem go i ku mojemu zdziwieniu zakończył się on błędnie (tak, teraz pamiętam, to była chęć zaprezentowania tematu, a nie jego niezrozumienie ;-)). Do tej pory wszystkie testy wykonywałem z Apache OpenJPA i widząc efekt testu zacząłem podejrzewać błąd w implementacji. Postanowiłem to zweryfikować. Tylko jak?! Nie pozostało nic innego jak skorzystać z profili w Apache Maven 2 i zdefiniować trzy profile, odpowiadające poszczególnym dostawcom JPA. Po całodniowej walce w końcu się udało zestawić środowisko i dodatkowo je opisać. Okazało się również, że to, co mnie zaskoczyło, może być faktycznie błędem w OpenJPA, ponieważ uruchomienie dokładnie tego samego testu z Hibernate i TopLink kończy się pomyślnie (!) Wysłałem zapytanie na listę dyskusyjną OpenJPA-dev i oczekuję cierpliwie na odpowiedź.

Możliwość uruchamiania testów z wieloma dostawcami uzmysłowiła mi również braki w poszczególnych implementacjach specyfikacji JPA. Weźmy najpierw pod lupę Apache OpenJPA 0.9.7-SNAPSHOT. Do tej pory nie zauważyłem potencjalnie błędnego zachowania OpenJPA, aż do dnia dzisiejszego. Ważny jest jednak do zanotowania fakt, że jest to pierwsza wpadka OpenJPA. Z Hibernate EntityManager 3.3.0.GA jest znacznie, znacznie gorzej. Ot, weźmy jako przykład wsparcie dla adnotacji @NamedNativeQuery. Próba uruchomienia encji ze zdefiniowanym natywnym zapytaniem kończy się następującym błędem:

javax.persistence.PersistenceException: org.hibernate.cfg.NotYetImplementedException: Pure native scalar queries are not yet supported
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:258)
at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:120)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:83)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:60)
at pl.jaceklaskowski.jpa.chapter4_6.ConditionalExpressionsTest.setUp(ConditionalExpressionsTest.java:38)

Albo jeszcze inny błąd, a właściwie niezgodność ze specyfikacją JPA, na którą natrafiłem wykonując testy, które do tej pory pomyślnie wykonywałem z OpenJPA - numerowanie parametrów pozycyjnych. Wykonanie poniższego zapytania

SELECT DISTINCT o FROM Osoba o, IN(o.projekty) p WHERE p.rodzajProjektu = ?0

zgodnie ze specyfikacją - strona 90, gdzie napisano Input parameters are numbered starting from 1, powinno zakończyć się wyjątkiem (np. InvalidArgumentException) podczas, gdy jest akceptowane przez Hibernate. Dodatkowo, biorąc pod uwagę brak najnowszych wersji Hibernate w repozytoriach Maven 2, nie widzę powodów dla jego stosowania.

W przypadku TopLink Essentials 2.0 BUILD 40 sprawa jest najmniej kłopotliwa. Wszystko działa zgodnie z opisem w specyfikacji, co sugeruje, że chyba powinienem podmienić domyślny profil na toplink (patrz dzisiejszy artykuł).

Miłej lektury i poznawania JPA!

22 marca 2007

Konferencja, broszura o EJB3 i pomysł na książkę - temat nieznany jeszcze

2 komentarzy
Dzisiaj będzie mniej technicznie. Kilka ciekawostek, z którymi zmierzyłem się ostatnio. Zacznę od mojej prelekcji Więcej, szybciej i prościej z Java EE 5 i Apache Geronimo 2 na konferencji Software Development GigaCon 2007 w Warszawie. To już jutro - piątek 23.03.2007 o godzinie 13:40. Wcześniej jest prelekcja zatytułowana JBoss Seam: framework nowej generacji, w której zamierzam uczestniczyć.
Jak się można domyśleć żyję jutrzejszym wykładem i wszystkie aktywności spowolniły się na rzecz tej jednej - wykładu. Zastanawiam się jak w ciągu 45 minut przedstawię temat tak rozległy w sposób wystarczający, aby wzbudzić kilku uczestników do dyskusji. Chyba zacznę od jakiegoś szalonego stwierdzenia, które rozpali kilka głów. Tylko, co miałoby to być?!

Bardzo powiązane z jutrzejszą prezentacją jest pewna broszurka, która powstała na podstawie moich relacji z lektury specyfikacji EJB3 i JPA. Niedawno otrzymałem ciekawą wiadomość, od osoby, która zainteresowana była scaleniem moich relacji w coś książkowego. Padło pytanie o moje chęci napisania książki, ale skoro na razie się na to nie zanosi (a już na pewno nie prędko), to padło pytanie o możliwość stworzenia broszury na bazie dotychczasowych materiałów. Nie mógłbym się nie zgodzić na taką propozycję i powstała broszurka o EJB3, która liczy aż 71 stron (!) Nie mogłem w to uwierzyć - 71 stron! Była już taka inicjatywa, ale było to na tyle dawno, że liczba stron nie była tak oszałamiająca - teraz byłem mile zaskoczony. Nie rozpisuję się już więcej na jej temat tylko zapraszam do jej pobrania. Broszura z relacjami z lektury specyfikacji EJB3 oraz JPA znajduje się pod adresem http://www.podrb.pl/pmj.pdf. Zapraszam wszystkich do jej pobrania i ostrej krytyki. Komentarze, szczególnie te krytyczne, są mi bardzo potrzebne. W końcu tylko tak mogę podnieść swój warsztat literacko-techniczny.

Skoro mowa o warsztacie literackim przejdę do ostatniej ciekawostki...pisanie książki. Pisałem już, że otrzymałem kilka propozycji napisania książki, ale ta ostatnia wydaje się być najbardziej prawdopodobna. Spotkałem się z wydawcą i pozostaje wybór tematu. W zasadzie nie jest to pytanie kiedy, tylko o czym. Jest tyle ciekawych projektów i specyfikacji, że aż boję się pomyśleć, że mógłbym ich wszystkich nie opisać. Jednak pewne muszą zostać pominięte, albo potraktowane mniej dokładnie niż inne. Tutaj jest nie lada problem - które? Wydaje mi się, że najlepiej czułbym się mogąc napisać o JSF 1.2 i projektach kontrolek JSF, np. IceFaces, RichFaces, RichClientFaces, MyFaces, OurFaces, JWL, ADF, Tobago, Tomahawk, Trinidad, ajax4jsf i wiele innych i chciałbym dołożyć do tego kilka, a może więcej niż kilka, słów o JBoss Seam. Wydaje się, że jest to wystarczająco obszerna tematyka, aby napisać więcej niż jedną książkę. Tylko, czy tego potrzebuje polski rynek techniczny? Czy byłoby zainteresowanie? Może jednak napisać coś w stylu Notatnik Projektanta Java EE, w którym zebrałbym wszystkie przemyślenia, które trafiają na grupy dyskusyjne, fora, do mojego Notatnika i Wiki, co mogłoby stanowić ciekawe uzupełnienie do codziennych naszych lektur? A może porzucić JSF na rzecz EJB3 i analizie jej możliwości udekorowując przykładami? Mnogość tematów, które mógłbym i chciałbym opisać jest przygniatająca, że trudno wybrać ten jeden temat.

Jutro wykład. Pora odłożyć zastanawianie się nad tematem książki na później.

18 marca 2007

VII spotkanie Warszawskiej Grupy Użytkowników Technologii Java (Warszawa-JUG)

0 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa-JUG) zaprasza na VII spotkanie, które odbędzie się w nadchodzący wtorek 20.03.2007 o godzinie 18:00 w sali 4420 na Wydziale MiMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Miesięczna ewaluacja Google Web Toolkit (GWT) - i co, fajne?
Prowadzący: Michał Margiel

Google Web Toolkit (GWT), jest świeżutkim (wersja 1.0 wypuszczona 16 maja 2006 roku) szkieletem programistycznym do tworzenia widoków w aplikacjach internetowych z wykorzystaniem mocy AJAX'a. Cały szkielet najlepiej opisuje tytuł książki Ed Burnette'a Google Web Toolkit: Taking the pain out of Ajax. A co w nim takiego fajnego i czemu dzięki niemu pozbywamy się tytułowego bólu - o tym wszyskim już 20 marca!

Prezentacja prowadzona będzie przez Michała Margiela, który jest studentem 5. roku Politechniki Warszawskiej, Wydziału Elektrycznego. Programista Java w firmie Javart. Niezmordowany uczestnik spotkań Warszawa-JUG, wcześniej jej protoplasty PLBUG, gdzie na pierwszym spotkaniu pojawiło się bagatela 8 osób (!) Michał interesuje się nowymi technologiami zwłaszcza internetowymi. I możnaby tak jeszcze długo, ale po co, skoro zobaczycie Michała w akcji już w nadchodzący wtorek!

Planowany czas prezentacji to 1,5 godziny z 10-30 minutową dyskusją.

Zapraszam w imieniu Warszawa-JUG!

17 marca 2007

Java Persistence - Rozdział 4.4 Klauzula FROM i deklaracje nawigacyjne

5 komentarzy
...co w oryginale przedstawia się jako 4.4 The FROM Clause and Navigational Declarations.

Klauzula FROM definiuje dziedzinę (obszar działania) zapytania przez deklaracje zmiennych identyfikujących. Zmienna identyfikująca jest identyfikatorem zadeklarowanym w klauzuli FROM zapytania. Dziedzina zapytania może być ograniczona przez wyrażenia ścieżkowe (oraz przez samą ich deklarację bez wykorzystania w klauzuli WHERE - wyjaśnienie znajduje się poniżej w sekcji 4.4.7 Klauzula FROM a SQL).

Zmienne identyfikujące wyznaczają egzemplarze encji.

Przykład: Odszukaj osoby, którzy uczestniczą w projekcie Apache Geronimo.

SELECT o FROM Osoba o JOIN o.projekty p WHERE p.nazwa = 'Apache Geronimo'

Klauzula FROM może zawierać wiele definicji zmiennych identyfikujących oddzielonych przecinkiem.

Przykład: Odszukaj osoby, których numer jest większy niż numer Jacka Laskowskiego.

SELECT DISTINCT o1 FROM Osoba o1, Osoba o2 WHERE o1.numer > o2.numer AND o2.imie = 'Jacek' AND o2.nazwisko = 'Laskowski'

Specyfikacja przedstawia składnię FROM w notacji BNF. Nie zamierzam kopiować składni i zachęcam do zajrzenia do specyfikacji. Poszczególne jej części omówione są poniżej.

4.4.1 Identyfikatory

Identyfikator jest ciągiem znaków o nieograniczonej długości. Identyfikator musi spełniać wymagania identyfikatorów w języku Java (korzysta się z metod Character.isJavaIdentifierStart oraz Character.isJavaIdentifierPart). Znak zapytania jest zarezerwowany dla JPQL (ang. Java Persistence Query Language, co wcześniej nazywałem JPA-QL, więc od teraz będzie krócej).

Następujące słowa są zarezerowanymi identyfikatorami (niektóre jeszcze nie wykorzystywane - zagadka: o jakich mowa?): SELECT, FROM, WHERE, UPDATE, DELETE, JOIN, OUTER, INNER, LEFT, GROUP, BY, HAVING, FETCH, DISTINCT, OBJECT, NULL, TRUE, FALSE, NOT, AND, OR, BETWEEN, LIKE, IN, AS, UNKNOWN, EMPTY, MEMBER, OF, IS, AVG, MAX, MIN, SUM, COUNT, ORDER, BY, ASC, DESC, MOD, UPPER, LOWER, TRIM, POSITION, CHARACTER_LENGTH, CHAR_LENGTH, BIT_LENGTH, CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP, NEW, EXISTS, ALL, ANY, SOME.

Nieistotna jest wielkość liter w identyfikatorach. Zarezerwowane identyfikatory nie mogą być używane jako zmienne identyfikujące. Zaleca się również niekorzystanie ze słów kluczowych języka SQL, które mogą zostać użyte w przyszłych wersjach specyfikacji.

4.4.2 Zmienne identyfikujące

Zmienna identyfikująca jest poprawnym identyfikatorem zadeklarowanym w klauzuli FROM.

Wszystkie zmienne identyfikujące muszą być zadeklarowane w klauzuli FROM. Zmienne identyfikujące nie mogą być zadeklarowane w innych klauzulach.

Zmienna identyfikująca nie może być zarezerwowanym identyfikatorem lub posiadać nazwę identyczną jak dowolna encja w tej samej PU.

Nieistotna jest wielkość liter w zmiennych identyfikujących.

Przykład: Zastosowanie różnych wielkości liter w zmiennej identyfikującej os.

SELECT DISTINCT oS FROM Osoba os, Osoba o2 WHERE OS.numer > o2.numer AND o2.imie = 'Jacek' AND o2.nazwisko = 'Laskowski'

Zmienna identyfikująca zawsze wyznacza referencję do pojedyńczej wartości. Zmienna identyfikująca może być zdefiniowana w jeden z 3 sposobów:
  • deklarację AS
  • klauzulę JOIN
  • w deklaracji kolekcji
Deklaracje zmiennych identyfikujących są wyliczane od lewej do prawej i deklaracja może używać wyniku poprzednich deklaracji zmiennych w zapytaniu.

4.4.3 Deklaracja AS zmiennych identyfikujących

Składnia deklaracji AS zmiennych identyfikujących jest podobna do składni SQL. Słowo AS jest opcjonalne.

deklaracja_zmiennej_AS ::= nazwa_abstrakcyjnego_schematu [AS] zmienna_identyfikująca

Deklaracje AS pozwalają na określenie punktu początkowego dla obiektów niedostępnych poprzez relacje.

Jeśli chcielibyśmy skorzystać z możliwości porównywania egzemplarzy encji z samą sobą, konieczne jest zdefiniowanie więcej niż jednej zmiennej identyfikującej wskazującą na tę samą encję.

Przykład: Użycie dwóch różnych zmiennych identyfikujących - o1 i o2 - o AST tej samej encji - Osoba.

SELECT DISTINCT o1 FROM Osoba o1, Osoba o2 WHERE o1.numer > o2.numer AND o2.imie = 'Jacek' AND o2.nazwisko = 'Laskowski'

4.4.4. Wyrażenia ścieżkowe (ang. path expression)

Zmienna identyfikująca, po której następuje kropka '.' będącym symbolem nawigacyjnym, a po niej pole-stanu lub pole-relacji jest wyrażeniem ścieżkowym. Typ wyrażenia ścieżkowego jest typem wyznaczonym jako wynik przejścia po ścieżce, tj. typem pola-stanu lub pola-relacji.

Wyrażenia ścieżkowe mogą składać się z wielu kropek będącymi separatorami kolejnych pól-relacji, które pozwalały na dalsze przejścia w grafie powiązań encji, jeśli poszczególne części nie są kolekcjami encji.

Przejścia po wyrażeniach ścieżkowych określane są poprzez semantykę inner join, tj. jeśli wartość niekończącego pola-relacji w wyrażeniu ścieżkowym jest null, przyjmuje się, że ścieżka nie ma wartości i nie uczestniczy w wyznaczaniu wyniku. Innymi słowy, w wyrażeniu ścieżkowym order.customer.imie, jeśli customer jest null, wtedy wartość nie będzie brała udziału w wykonaniu zapytania.

Specyfikacja przedstawia składnię wyrażeń ścieżkowych pojedyńczej wartości (nie-kolekcji) oraz wartości kolekcyjnych.

Nawigacja (przejście) do związanej encji wyznacza wartość wyrażenia jako AST tej encji.

Typ wyrażenia ścieżkowego kończącego się na polu-stanu wyznacza wartość wyrażenia jako AST pola encji, wskazanego przez pole-stanu.

Niedozwolone jest konstruowanie wyrażenia ścieżkowego z części ścieżki, która jest kolekcją. Specyfikacja przychodzi z pomocą w wytłumaczeniu tej kwestii i tak, jeśli Order o i o.lineItems jest polem-relacji do kolekcji pozycji na zamówieniu (ang. line items), wtedy ścieżka postaci o.lineItems.product jest niedozwolona. W takim przypadku należy wprowadzić zmienną identyfikującą dla kolekcji tak, aby inne wyrażenia ścieżkowe mogły przejść po wszystkich elementach kolekcji.

Przykład wykorzystania zmiennej identyfikującej kolekcję w klauzuli FROM

SELECT DISTINCT o FROM Osoba AS o, IN(o.projekty) p WHERE p.nazwa = 'Apache Geronimo'

Słowo kluczowe AS w klauzuli FROM jest opcjonalne w powyższym zapytaniu.

4.4.5 Łączenia (ang. joins)

Zanim przedstawię informacje zawarte w tej części specyfikacji, wiele z wyjaśnianych tutaj pojęć jest opisanych na wazniak.mimuw.edu.pl (aka ważniak).

Wewnętrzne łączenie (ang. inner join) może być zdefiniowane wprost przez użycie iloczynu kartezjańskiego w klauzuli FROM oraz warunku łączenia w klauzuli WHERE. W przypadku braku warunku łączenia, sprowadza się to do produktu kartezjańskiego.

Główne użycie dla tego typu łączenia jest w przypadku braku wykorzystania relacji poprzez klucz obcy, który jest związany z relacją encji.

Przykład zapożyczony ze specyfikacji:

select c from Customer c, Employee e where c.hatsize = e.shoesize

Jak zaznaczono w specyfikacji, użycie tego typu wiązań wewnętrznych (nazywanych również theta-łączeniami, ang. theta-join) jest mniej wykorzystywana ze względu na częstsze stosowanie łączeń poprzez relacje encji wprost zdefiniowanych w zapytaniu.

Składania (wyrażonych dosłownie) operacji łączenia korzysta ze słów kluczowych FETCH, LEFT [OUTER] JOIN i INNER JOIN.

Kolejne sekcje omawiają wspierane typy operacji łączenia INNER i OUTER.

4.4.5.1 Wewnętrzne łączenia - INNER JOIN

Składnia operacji wewnętrznego łączenia jest następująca:

[ INNER ] JOIN wyrażenie_ścieżkowe_łączenia [AS] zmienna_identyfikująca


Przykład: Odszukaj osoby, które należą do przynajmniej jednego projektu i mieszkają w Polsce

SELECT o FROM Osoba o JOIN o.projekty p WHERE o.kraj = 'Polska'

, które jest równoważne z zapytaniem, które korzysta ze słowa kluczowego INNER:

SELECT o FROM Osoba o INNER JOIN o.projekty p WHERE o.kraj = 'Polska'

, a to z kolei jest równoznaczne z zapytaniem korzystającym z konstrukcji IN:

SELECT o FROM Osoba o, IN(o.projekty) p WHERE o.kraj = 'Polska'

4.4.5.2 Lewostronne łączenia zewnętrzne - LEFT OUTER JOIN

LEFT JOIN oraz LEFT OUTER JOIN są synonimami. Jak można wyczytać na ważniaku (przez co materiał specyfikacji JPA staje się znacznie prostszy) łączenia zewnętrzne pozwalają na pobranie encji, gdzie wartości spełniające warunek łączenia mogą nie istnieć. W takim przypadku odpowiadające wartości w encji są null.

Składnia operacji lewostronnego łączenia zewnętrznego jest następująca:

LEFT [OUTER] JOIN wyrażenie_ścieżkowe_łączenia [AS] zmienna_identyfikująca

Przykład: Pobranie wszystkich osób z Polski bez względu na ich przynależność do projektów, tj. jeśli osoba nie uczestniczy w projekcie (lista projektów wynosi null) to i tak zostanie zawarta w wyniku zapytania.

SELECT DISTINCT o FROM Osoba o LEFT JOIN o.projekty p WHERE o.kraj = 'Polska'

Jest to równoważne z zapytaniem korzystającym ze słowa kluczowego OUTER:

SELECT DISTINCT o FROM Osoba o LEFT OUTER JOIN o.projekty p WHERE o.kraj = 'Polska'

Może wydawać się, że wystarczyłoby wydać zapytanie

SELECT DISTINCT o FROM Osoba o WHERE o.kraj = 'Polska'

i uzyskać żadane dane. Zakładamy niewprost, że Osoba jest w relacji z encją Projekt, więc tam, gdzie potrzebne dane będą automatycznie pobrane przez zarządcę trwałości. Pamiętajmy jednak, że istnieje możliwość utworzenia takiego modelu, gdzie Osoba i Projekt nie będą w relacji, a w/w zapytanie będzie mapowane przy użyciu adnotacji @SqlResultSetMapping. W takim przypadku możemy zażyczyć sobie pobrania informacji dodatkowych poza tymi koniecznymi dla encji Osoba.

Istotnym przypadkiem użycia LEFT JOIN jest wczesne (ang. eager) pobranie wartości z prawej strony łączenia w zapytaniu (w powyższym przypadku danych o projektach) w połączeniu z pobierającego łączenia - JOIN FETCH.

4.4.5.3 Pobierające łączenia - JOIN FETCH

Pobierające łączenie (ang. fetch join) umożliwia wczesne pobranie wartości asocjacji jako rezultat poboczny wykonania zapytania. Łączenie pobierające jest zdefiniowane nad encją i jej powiązanych encjami.

Patrząc na powyższy przykład z encjami Osoba i Projekt, gdyby nie zastosować JOIN FETCH, domyślnie informacje o projektach są pobieranie z opóźnieniem, co nie zawsze jest wskazane, np. w przypadku aplikacji internetowych, gdzie pobieramy encje odłączając je (dla entuzjastów Hibernate będzie odłączenie od egzemplarza Session podczas, gdy w JPA będzie to zarządca trwałości, czyli egzemplarz EntityManager).

Składnia operacji pobierającego łączenia jest następująca:

łączenie_pobierające ::= [ LEFT [OUTER] | INNER ] JOIN FETCH wyrażenie_ścieżkowe_łączenia

Asocjacja, która wskazywana jest przez prawą stronę klauzuli JOIN FETCH musi być asocjacją, która należy do encji i jest zwracana jako wynik zapytania. Niedozwolone jest, aby skorzystać ze zmiennej identyfikującej encje wskazywane przez prawą stronę klauzuli FETCH JOIN, a zatem odwołania do wprost pobieranych encji nie mogą wystąpić gdziekolwiek w zapytaniu.

Przykład: Pobranie osób mieszkających w Polsce z tym, że informacje o projektach będą również pobrane (wczesne pobranie).

SELECT DISTINCT o FROM Osoba o LEFT JOIN FETCH o.projekty WHERE o.kraj = 'Polska'

Łączenie pobierające ma tę samą semantykę łączenia jak łączenia wewnętrzne i zewnętrzne, jednakże powiązane obiekty wskazane po prawej stronie operacji łączenia pobierającego nie są zwracane jako wynik zapytania lub w inny sposób uczestniczą w zapytaniu. Stąd, jeśli osoba należy do 3 projektów, to powyższe zapytanie zwróci 3 encje Osoba, które de facto będą identyczne (i to stwierdzenie pozostawia mnie w stanie błogiego zastanowienia i niezrozumienia tematu, po co w takim razie chciałbym wykonywać takie zapytanie?).

4.4.6 Deklaracje kolekcji składowych

Zmienna identyfikująca może operować na wartościach kolekcji, które są pobrane w wyniku wyrażenia ścieżkowego. Takie wyrażenie ścieżkowe jest ścieżką korzystającą z pól-relacji encji. Wyrażenie ścieżkowe może opierać się o inne wyrażenie ścieżkowe, które również korzysta z pól-relacji odpowiednich encji.

Zmienna identyfikująca deklaracji kolekcji składowej jest definiowana używając operatora IN (słowo kluczowe). Parametrem dla IN jest wyrażenie ścieżkowe będące kolekcją.

Składnia deklaracji zmiennej identyfikującej kolekcji składowej jest następująca:

deklaracja_kolekcji_składowej ::= IN (wyrażenie_ścieżkowe_będące_kolekcją) [AS] zmienna_identyfikująca

Korzystając z przykładu opartego o zamówienie (Order), pozycje na zamówieniu (LineItem) oraz produkty (Product) w specyfikacji, poniższe zapytanie:

SELECT DISTINCT o FROM Order o JOIN o.lineItems l JOIN l.product p WHERE p.productType = ‘office_supplies’

może zostać równoważnie zapisane korzystając z operatora IN:

SELECT DISTINCT o FROM Order o, IN(o.lineItems) l WHERE l.product.productType = ‘office_supplies’

Szczegółowe omówienie zapytania znajduje się w specyfikacji na stronie 87.

4.4.7 Klauzula FROM a SQL

JPQL traktuje klauzulę FROM podobnie jak SQL, tj. zadeklarowane zmienne identyfikujące wpływają na wynik zapytania, nawet jeśli zmienne nie są wykorzystane w klauzuli WHERE. Klauzula FROM ustanawia dziedzinę zapytania, więc twórca zapytania powinien uważnie definiować zmienne identyfikujące, gdyż dziedzina zapytania zależy od istnienia wartości zadeklarowanego typu.

Przykładowo w specyfikacji zaprezentowano zapytanie

SELECT o FROM Order AS o, IN(o.lineItems) l, Product p

, które definiuje 3 zmienne identyfikujące - o, l, p. Zapytanie zwraca wszystkie zamówienia, które posiadają przynajmniej jedną pozycję i istniejący produkt. Jeśli nie istnieje jakikolwiek produkt w bazie, dziedzina zapytania będzie pusta i żadne z zamówień nie zostanie zwrócone w wyniku zapytania (!)

4.4.8 Polimorfizm

Zapytania w JPQL są polimorficzne. Klauzula FROM zapytania wyznacza nie tylko egzemplarze wymienionej w zapytaniu klasy encji, ale również wszystkich jej klas pochodnych (podklas). Egzemplarze zwrócone w wyniku zapytania zawierają egzemplarze klas pochodnych, które spełniają warunek zapytania (co nie było możliwe w EJB 2.1, gdzie nie istniało pojęcie dziedziczenia encji).

Tym samym specyfikacja kończy omawianie klauzuli FROM i przechodzi do klauzuli WHERE (rozdział 4.5), o czym będę relacjonował w kolejnym wpisie. Za mną klauzula FROM i w końcu udało mi się doczytać o wszystkich łączeniach w JPQL. Zabieram się do dalszego modelowania mojej przykładowej aplikacji z osobami, projektami i rolami (będącymi typem wyliczeniowym - enum). Najwyższa pora na spanie, ale wcześniej zajrzę do książki Hibernate. Od nowicjusza do profesjonalisty, gdzie zamierzam doczytać szczegóły. Za każdym razem, kiedy potrzebuję dobrego przewodnika po Hibernate (i po części JPA) zaglądam do niej i nie ukrywam, że warto ją mieć na swojej półce. Mało mam książek informatycznych, ale ta wciąż leży pod ręką.

16 marca 2007

QualiPSo - platforma inspiracji i podnoszenia konkurencyjności OSS

2 komentarzy
No proszę, wreszcie się doczekałem! Właśnie ogłoszono ustanowienie QualiPSo - platforma inspiracji i podnoszenia konkurencyjności OSS. Ma być kwintesencją już istniejących kuźni projektów OSS - Apache Software Foundation, CodeHaus czy SourceForge i jak widać z mojej listy, ma rozszerzyć ją o przykład europejski. We Francji istnieje bardzo dobrze przyjmowany wśród programistów OSS - ObjectWeb, ale nie ma co ukrywać, że daleko mu do sławy ASF, czy CodeHaus. Coś jest na rzeczy, że jest wielu programistów starego kontynentu, ale brakuje miejsca w Europie, z którym możnaby utożsamiać się jak to dzieje się w przypadku ASF. Z dużym zainteresowaniem zamierzam śledzić poczynania QualiPSo szczególnie, że zanim ogłoszono powstanie platformy wypytywano mnie o moje bolączki w trakcie uczestniczenia w projektach OSS, które planowano zniwelować. Oby nie skończyło się na obietnicach.

p.s. Nie wiem dlaczego, ale nazwa QualiPSo przypomnia mi brzmienie Eclipse. Hmm, a może nie? Jakoś nie mogę się pozbyć tego skojarzenia.

Java Persistence - Rozdział 4.3 Typy abstrakcyjnego schematu i obszar działania zapytania

1 komentarzy
Tym razem bardzo teoretyczna sekcja z rozdziału 4 - 4.3 Typy abstrakcyjnego schematu oraz obszar działania zapytania (ang. Abstract Schema Types and Query Domains).

JPA-QL jest językiem z kontrolą typu i każde wyrażenie ma swój typ. Typ wyrażenia wyznaczany jest z jego struktury, typów schematu abstrakcyjnego (AST) deklaracji zmiennych identyfikujących, typów pól trwałych i relacji oraz typy samych literałów (stałych).

Typ abstrakcyjnego schematu (AST, ang. abstract schema type) encji jest wyznaczony przez klasę encji i metadanych dostarczanych w postaci adnotacji lub zapisów w deskryptorze XML (stąd też istnieje możliwość stworzenia narzędzi mapujących, które nie wymagają uruchomienia aplikacji. Hint! Hint! ;-)).

Nieformalnie, typ abstrakcyjnego schematu encji może być zdefiniowany następująco:
  • Dla każdego pola trwałego lub metody odczytującej pole trwałe w klasie encji, istnieje pole (pole-stanu, ang. state-field), którego AST odpowiada typowi pola lub wyniku metody, odpowiednio.
  • Dla każdego pola relacji lub metody odczytującej pole relacji w klasie encji, istnieje pole (pole-wiązania, ang. association-field), którego AST odpowiada typowi związanej encji (lub jeśli relacja jest jeden-do-wielu lub wiele-do-wielu, ich kolekcji).
AST jest pojęciem, którego dostawca trwałości nie jest zobowiązany dostarczać. Jest to byt koncepcyjny, którego relacji możemy nie doszukać się u danego dostawcy.

Jeśli ktokolwiek mógłby zastanawiać się po co tak dokładne wyjaśnianie mechanizmu typów w JPA-QL, to spieszę wyjaśnić, że znajomość AST pozwala na budowanie poprawnych zapytań JPA-QL. Wydaje się, że nie ma co wyjaśniać, ale znaczenie zrozumienia AST wzrasta ze wzrostem skomplikowania zapytania (postaram się coś wymyśleć jako przykład ;-)).

Obszar działania (domena) zapytania (ang. query domain) składa się z AST wszystkich encji, które są zdefiniowane w pojedyńczym PU.

Obszar działania zapytania może zostać zawężony przez zasięg (ang. navigability) relacji encji, na której jest oparte. Pole-wiązania w AST encji wyznacza zasięg. Korzystając z pól-wiązania i ich wartości, zapytanie może obejmować encje i używać ich AST w zapytaniu.

Próbując zrozumieć AST można pomyśleć o sieci powiązań między encjami. Mamy sieć (albo jak kto woli graf), w której powiązania wyznaczają drogę (ścieżki). Każdy AST wyznacza ścieżki od encji. Jeśli encja nie jest w relacji z innymi encjami, wtedy AST sprowadza się do typów pól/właściwości encji. Encje w relacji z innymi encjami mają bardziej rozbudowany AST, gdzie powiązanie encji tworzy AST złożone i podążając ścieżkami możemy dotrzeć do nowych AST związanych encji i je wykorzystywać w zapytaniu. Dalsze wyjaśnienia w przykładach za moment i w samej specyfikacji.

4.3.1 Nomenklatura (nazewnictwo)

Encje są określone w zapytaniu poprzez ich nazwy. Nazwa encji jest zdefiniowana przez element name adnotacji @Entity (lub element entity-name w deskryptorze XML). Wartość domyślna dla nazwy encji to niekwalifikowana nazwa klasy encji.

Dla przykładu klasa pl.jaceklaskowski.jpa.entity.Projekt reprezentuje encję, której nazwą jest Projekt (co jest równoznaczne z udekorowaniem klasy adnotacją @Entity(name = "Projekt")).

package pl.jaceklaskowski.jpa.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Projekt implements Serializable {

private static final long serialVersionUID = 1L;

@Id
private String nazwa;

public Projekt(String nazwa) {
this.nazwa = nazwa;
}

public String getNazwa() {
return nazwa;
}

public void setNazwa(String nazwa) {
this.nazwa = nazwa;
}
}

podczas, gdy ta sama klasa opatrzona adnotacją @Entity(name = "EncjaProjektu") reprezentowałaby encję o nazwie EncjaProjektu.

Nazwy encji muszą być unikatowe w ramach pojedyńczego PU. Wywnioskować można z tego, że ta sama encja może przyjmować inne nazwy w różnych PU oraz dodatkowo, że encja o nazwie X może wskazywać na różne klasy encji w różnych PU.

Sekcja 4.3.2 Example przedstawia przykład kilku encji (Order, Product, LineItem, ShippingAddress oraz BillingAddress) i sposób wyznaczania AST. Wydaje się być stosownym korzystanie z diagramów UML do zobrazowania zależności między encjami (czyżby pora powrócić do temu ewaluacji narzędzi UML?).

Znajdziemy tutaj ciekawe zapytanie korzystające z słowa kluczowego JOIN do wyszukania wszystkich zamówień (Order), które nie zostały jeszcze zrealizowane.

Pojawia się uwaga odnośnie słów kluczowych i ich notacji. Zazwyczaj słowa kluczowe w zapytaniu, np. SELECT, FROM, AS, JOIN pisane są z wielkich liter, ale nie jest to wymagane. Wielkość liter nie jest istotna i korzystanie z SeLeCt jest równie poprawne jak SELECT, czy select.

Poniższy przykład jest bardzo zbliżony do przykładu ze specyfikacji, tym razem jednak mamy encje Osoba i Projekt, które uczestniczą w relacji jeden-do-wielu (tym razem, ponieważ faktycznie powinno być wiele-do-wielu). Odszukanie wszystkich osób należących do projektu "Apache Geronimo" sprowadza się do następującego zapytania (i tu pojawi się moje pierwsze wykorzystanie JOIN!):

Projekt geronimo = new Projekt("Apache Geronimo");

Osoba jacekLaskowski = new Osoba("Jacek", "Laskowski");
jacekLaskowski.addProjekt(geronimo);

EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(geronimo);
em.persist(jacekLaskowski);
tx.commit();

Query query = em.createQuery("SELECT o FROM Osoba o JOIN o.projekty p WHERE p.nazwa = 'Apache Geronimo'");
List<Osoba> osoby = query.getResultList();
assert osoby.size() == 1;

14 marca 2007

Java Persistence - Rozdział 4 Język zapytań - sekcje 4.1 Przegląd oraz 4.2 Typy wyrażeń

0 komentarzy
Nie pamiętam, czy już o tym wspominałem, ale od ponad miesiąca jest dostępny egzamin Sun Certified Business Component Developer (SCBCD) for the Java Platform, Enterprise Edition 5 (CX-310-091). 4 z 11 sekcji dotyczą JPA, więc przy wymaganych 59% poprawnych odpowiedzi (36 z 61 pytań) wydaje się, że przestudiowanie specyfikacji daje solidne podstawy wierzyć, że egzamin jest do opanowania. Ciekaw jestem, czy jest już ktoś, kto przeszedł pomyślnie egzamin i może poszczycić się nim? Z wielkim zainteresowaniem wysłuchałbym rad jak tego dokonać.

Mając w głowie możliwość podejścia do egzaminu powracam do studiowania specyfikacji JPA. Tym razem pora na nowy rozdział 4 o języku zapytań - Chapter 4: Query Language. Rozdział przedstawia pełną specyfikację języka zapytań JPA.

Język zapytań JPA (dalej JPA-QL - ang. JPA Query Language) zdefiniowano do tworzenia zapytań do odczytywania encji i ich trwałego stanu. Dzięki JPA-QL twórca aplikacji może uniezależnić zapytania od konkretnej bazy danych.

JPA-QL jest rozszerzeniem języka zapytań EJB (dalej EJB-QL - ang. Enterprise JavaBeans Query Language). Udostępnia operacje związane z wykonywaniem zbiorczych operacji UPDATE oraz DELETE, operacji JOIN, GROUP BY, HAVING, projekcje (ang. projection) oraz podzapytania. Jak można było przekonać się w poprzednim rozdziale 3.6 Query API JPA-QL wspiera dynamiczne i statyczne zapytania oraz nazwane parametry. Wszystkie elementy języka są dostępne w obu typach zapytań - dynamicznych i statycznych.

4.1 Przegląd

JPA-QL jest językiem zapytań dynamicznych i statycznych (zdefiniowanych z użyciem adnotacji). Język JPA-QL może być zamieniony na język docelowy repozytorium danych, np. SQL (tutaj po raz pierwszy wspomina się o innych bazach danych niż bazy relacyjne, czy wręcz repozytoria danych. Mimo to, wydaje się, że specyfikacja JPA jest celowana w rynek baz relacyjnych, bo to one potrzebują wsparcia przy zamianie reprezentacji relacyjnej na obiektową i na odrót). Zamian JPA-QL na docelowy język bazy pozwala na przesunięcie odpowiedzialności za wykonanie zapytania bezpośrednio do motoru bazy danych, zamiast wykonywać go na instancjach encji podczas uruchomienia (co znacząco obciążałoby pamięć aplikacji JPA). Dzięki temu dostawca JPA ma możliwość zoptymalizowania zapytań przy zagwarantowaniu przez specyfikację JPA ich niezależności od samego dostawcy JPA i docelowej bazy danych.

JPA-QL korzysta z abstrakcyjnego modelu trwałego encji, wliczając ich powiązania, dostarczając operacji i wyrażeń do operowania na nim. Składnia JPA-QL jest zbliżona do SQL. Specyfikacji jasno wskazuje możliwość sprawdzenia poprawności zapytań JPA-QL zanim encje zostaną zainstalowane w środowisku uruchomieniowym.

Pojawia się wyjaśnienie, w którym specyfikacja tłumaczy wyrażenie abstrakcyjny schemat trwały (ang. abstract persistence schema), który odnosi się do abstrakcji schematu trwałego, tj. encji, ich stanu i zależności, na którym wykonywane są zapytania JPA-QL.

Zapytania mogą być definiowane za pomocą adnotacji oraz w deskryptorze XML. Zapytanie może korzystać z abstrakcyjnego schematu zbioru encji, które zdefiniowane są w tym samym PU (jednostka trwałości, ang. persistence unit). Wyrażenia ścieżkowe pozwalają na nawigację przez relacje zdefiniowane w PU.

Specyfikacja przypomina definicję PU, którą warto odnotować (bardzo krótka acz przedstawiająca sedno sprawy):

PU definiuje zbiór wszystkich klas, które są powiązane i zgrupowane w aplikacji, i które muszą być związane z pojedyńczą bazą danych.

4.2 Typy wyrażeń

Wyrażeniem JPA-QL może być wyrażenie SELECT, UPDATE bądź DELETE. Dowolne wyrażenie może być utworzone dynamicznie bądź zdefiniowane statycznie przy użyciu adnotacji bądź w deskryptorze XML. Zapytania mogą posiadać parametry.

4.2.1 Wyrażenia SELECT

Wyrażenie SELECT składa się z:
  • klauzuli SELECT, która określa typ zwracanych obiektów lub wartości
  • klauzuli FROM, która dostarcza deklaracji, które wyznaczają obszar działania wyrażeń zdefiniowanych w innych klauzulach zapytania
  • opcjonalnej klauzuli WHERE, która może ograniczać wyniki zapytania
  • opcjonalnej klauzuli GROUP BY, która pozwala na grupowanie wyników zapytania
  • opcjonalnej klauzuli HAVING, która pozwala na filtrowanie według grup
  • opcjonalnej klauzuli ORDER BY, która może być używana do uporządkowania wyników zapytania
Notacja BNF przedstawia się następująco:

select_statement :: = select_clause from_clause [where_clause] [groupby_clause] [having_clause] [orderby_clause]

Wyrażenie SELECT musi zawierać klauzulę SELECT oraz FROM podczas, gdy pozostałe klauzule są opcjonalne.

Query query = em
.createQuery("SELECT p.imie, p.nazwisko, p.dzienUrodzin FROM Pracownik p "
+ "WHERE p.nazwisko LIKE 'L%' GROUP BY p.imie, p.nazwisko, p.dzienUrodzin HAVING p.dzienUrodzin > :dzisiaj ORDER BY p.dzienUrodzin");
query.setParameter("dzisiaj", dzisiaj, TemporalType.DATE);

4.2.2 Wyrażenia UPDATE i DELETE

Wyrażenie UPDATE i DELETE umożliwiają wykonanie zbiorczych operacji na zbiorze encji.

W notacji BNF przedstawiają się następująco:

update_statement :: = update_clause [where_clause]

delete_statement :: = delete_clause [where_clause]

Klauzule UPDATE (update_clause) oraz DELETE (delete_clause) określają typ encji, na których zostanie wykonana operacja, modyfikacji bądź skasowania, odpowiednio. Klauzula WHERE (podobnie jak w wyrażeniu SELECT) służy do ograniczenia zasięgu operacji.

Przejrzałem nadchodzące rozdziały i zauważyłem użycie zapytań typu JOIN oraz FETCH JOIN, które nie dawały mi ostatnio spokoju. W końcu nastąpi moment, w którym wierzę, że uda mi się zrozumieć ich działanie (i będę mógł mądrować się jakie to one są proste! ;-)).

11 marca 2007

Java Persistence - 3.7 Podsumowanie wyjątków

3 komentarzy
Rozdział 3.7 Summary of Exceptions jest przedstawieniem wyjątków zdefiniowanych przez specyfikację JPA. Wszystkie należą do pakietu javax.persistence.

PersistenceException - wyjątek rzucany przez dostawcę trwałości podczas wystąpienia sytuacji awaryjnej ogólnego typu. Może się pojawić w sytuacji wykonania operacji, która nie może się wykonać, np. z powodu braku połączenia do bazy danych.
PersistenceException jest klasą początkową hierarchii wyjątków JPA. Pojawienie się wyjątku PersistenceException i pochodnych powoduje wycofanie transakcji, poza NoResultException oraz NonUniqueResultException.
    try {
Persistence.createEntityManagerFactory("niezdefiniowanePU");
assert false : "Oczekiwano PersistenceException, ponieważ nezdefiniowanePU nie istnieje!";
} catch (PersistenceException oczekiwane) {
}
TransactionRequiredException - wyjątek rzucany przez dostawcę trwałości, kiedy transakcja jest wymagana, jednakże aktualnie niedostępna.

try {
Query query = em.createQuery("UPDATE Pracownik p SET p.imie = 'ZmianaImieniaWszystkim'");
query.executeUpdate();
assert false : "Oczekiwano TransactionRequiredException, ponieważ transakcja nieaktywna!";
} catch (TransactionRequiredException oczekiwane) {
}

OptimisticLockException - wyjątek rzucany przez dostawcę trwałości, kiedy wystąpi wyjątek związany z optymistycznym blokowaniem. Wyjatek może pojawić się podczas wywołania metody, synchronizacji (wywołanie metody flush) bądź ostatecznie podczas zatwierdzania transakcji.
Niestety nie wiem, jak przygotować test, aby zaprezentować wyjątek, więc go nie będzie.

RollbackException - wyjątek rzucany przez dostawcę trwałości, kiedy wywołanie EntityTransaction.commit() zakończy się niepowodzeniem.
Z braku pomysłów na test nie będzie go.

EntityExistsException - wyjątek rzucany przez dostawcę trwałości podczas wywołania metody persist z egzemplarzem encji, który znajduje się już w bazie danych (alternatywnie inny wyjątek PersistenceException może pojawić się zamiast EntityExistsException podczas zatwierdzania transakcji).

try {
Pracownik jacek = (Pracownik) em.createQuery(
"SELECT p FROM Pracownik p WHERE p.imie = 'Jacek' AND p.nazwisko = 'Laskowski'").getSingleResult();
Pracownik kopiaJacka = new Pracownik();
kopiaJacka.setNumer(jacek.getNumer()); // numer jest @Id
em.persist(kopiaJacka);
assert false : "Oczekiwano EntityExistsException, ponieważ encja już istnieje w bazie!";
} catch (EntityExistsException oczekiwane) {
}

EntityNotFoundException - wyjątek rzucany przez dostawcę trwałości, podczas wywołania operacji (np. dostęp do atrybutów) na referencji encji zwróconej przez metodę getReference, kiedy encja została w międzyczasie usunięta. Wyjątek EntityNotFoundException może się również pojawić, podczas wywołania metody refresh, kiedy encja nie istnieje już w bazie danych.

try {
assert em.getTransaction().isActive() == false;
Pracownik pracownikRef = em.getReference(Pracownik.class, 3);
pracownikRef.getImie();
assert false : "Oczekiwano EntityNotFoundException, ponieważ encja została skasowana a wykonano operację na jej referencji!";
} catch (EntityNotFoundException oczekiwane) {
}
EntityTransaction tx = null;
try {
assert em.getTransaction().isActive() == false;
tx = em.getTransaction();
tx.begin();
Pracownik jacek = (Pracownik) em.createQuery(
"SELECT p FROM Pracownik p WHERE p.imie = 'Jacek' AND p.nazwisko = 'Laskowski'").getSingleResult();
em.remove(jacek);
em.flush();
em.refresh(jacek);
jacek.getImie();
assert false : "Oczekiwano EntityNotFoundException, ponieważ encja została skasowana a wykonano operację na jej referencji!";
} catch (EntityNotFoundException oczekiwane) {
tx.rollback();
}

NoResultException - wyjątek rzucany przez dostawcę trwałości, podczas wywołania metody Query.getSingleResult, kiedy nie istnieje wynik zapytania.

try {
assert em.getTransaction().isActive() == false;
em.createQuery("SELECT p FROM Pracownik p WHERE p.nazwisko = 'NieIstnieje'").getSingleResult();
assert false : "Oczekiwano NoResultException, ponieważ nie istnieje pracownik o nazwisku NieIstnieje!";
} catch (NoResultException oczekiwane) {
}

NonUniqueResultException - wyjątek rzucany przez dostawcę trwałości, podczas wywołania metody Query.getSingleResult, kiedy istnieje więcej niż jedna encja w wyniku zapytania.

try {
assert em.getTransaction().isActive() == false;
utworzPracownika();
utworzPracownika();
Query query = em.createQuery("SELECT p FROM Pracownik p");
List pracownicy = query.getResultList();
assert pracownicy.size() > 1;
query.getSingleResult();
assert false : "Oczekiwano NonUniqueResultException, ponieważ zwracanych jest więcej encji niż jedna!";
} catch (NonUniqueResultException oczekiwane) {
}

Java Persistence - Rozdział 3.6 Query API

3 komentarzy
Ta relacja okazała się być inna niż inne. Rozpocząłem wdrażanie pomysłu, do którego przymierzałem się od dłuższego czasu - testowanie wiadomości jako testów TestNG. Po napisaniu artykułu na ten temat - Java Persistence API z OpenJPA i Derby oraz TestNG z Eclipse IDE w tle - byłoby nieroztropnie nie skorzystać z okazji i nie wdrożyć idei w życie. Mimo, że sama lektura zajęła chwilę, to już skonstruowanie przykładów już trwało dłużej. Ciekawym efektem konieczności stworzenia przykładu jest dokładniejsze zrozumienie działania danej funkcjonalności. Pojawiło się kilka pytań, kilka testów nie poszło za pierwszym razem i ostatecznie czuję, że zrozumienie Query API oceniam na dobre 3+. Ciekawe jak się przyjmie taka forma relacjonowania?

Interfejs publiczny klasy javax.persistence.Query (Query API) dostarcza dwa rodzaje zapytań - statyczne (nazwane - ang. static/named queries) oraz dynamiczne (ang. dynamic queries). Zapytaniem statycznym nazywamy zapytanie zdefiniowane w adnotacji za pomocą @NamedQuery, a zapytaniem dynamicznym jest zapytanie, które jest zdefinowane bezpośrednio w kodzie aplikacji. Query API dostarcza również możliwość wiązania parametrów (przekazywanie wartości do miejsc w zapytaniu oznaczonych jako zmienne) oraz stronicowanie (zawężanie wyników z bazy danych - dzielenie ich logicznie na strony).

Query API składa się z metod, które kontrolują wykonanie zapytań SQL (SELECT, UPDATE lub DELETE).

List getResultList() - wykonuje zapytanie SELECT i zwraca wynik jako java.util.List.

Query query = em.createQuery("SELECT p FROM Pracownik p");
List<Pracownik> pracownicy = query.getResultList();

Object getSingleResult() - wykonuje zapytanie SELECT, które zwraca pojedyńczy wynik.

Query query = em.createQuery("SELECT p FROM Pracownik p WHERE p.imie = :imie AND p.nazwisko = :nazwisko");
query.setParameter("imie", "Jacek");
query.setParameter("nazwisko", "Laskowski");
Pracownik pracownik = (Pracownik) query.getSingleResult();

int executeUpdate() - wykonuje zapytanie UPDATE lub DELETE zwracając ilość zmodyfikowanych lub skasowanych wierszy. Metoda pozwala na wykonanie większej ilości modyfikacji danych w bazie danych bez tworzenia encji.

EntityTransaction tx = em.getTransaction();
tx.begin();
Query query = em
.createQuery("UPDATE Pracownik p SET p.nazwisko = :noweNazwisko WHERE p.imie = :imie AND p.nazwisko = :nazwisko");
query.setParameter("imie", "Agata");
query.setParameter("nazwisko", "Bretes");
query.setParameter("noweNazwisko", "Laskowska");
int iloscUaktualnionychRekordow = query.executeUpdate();
tx.commit();

EntityTransaction tx = em.getTransaction();
tx.begin();
Query query = em.createQuery("DELETE FROM Pracownik p");
int iloscUsunietychPracownikow = query.executeUpdate();
tx.commit();

Query setMaxResults(int maxResult) - ustawia maksymalną ilość wyników przy wykonaniu zapytania

query = em.createQuery("SELECT p FROM Pracownik p");
iloscPracownikow = query.getResultList().size();
assert iloscPracownikow > 3;
query = em.createQuery("SELECT p FROM Pracownik p");
query.setMaxResults(2);
iloscPracownikow = query.getResultList().size();
assert iloscPracownikow == 2;

Query setFirstResult(int startPosition) - ustanawia numer pierwszego wyniku w zbiorze wszystkich wyników (rozpoczynamy od 0)

query = em.createQuery("SELECT p FROM Pracownik p");
iloscPracownikow = query.getResultList().size();
assert iloscPracownikow == 4;
query = em.createQuery("SELECT p FROM Pracownik p");
query.setFirstResult(3);
iloscPracownikow = query.getResultList().size();
assert iloscPracownikow == 1;

Query setHint(String hintName, Object value) - przypisuje wskazówkę do zapytania specyficzną dla wykorzystywanego dostawcy JPA. Nieznane dla dostawcy JPA wskazówki są ignorowane.

query = em.createQuery("SELECT p FROM Pracownik p");
query.setHint("openjpa.hint.OracleSelectHint", "/*+ first_rows(100) */");
query.setHint("org.hibernate.readOnly", "true");
query.setHint("toplink.pessimistic-lock", "Lock");
iloscPracownikow = query.getResultList().size();

Query setParameter(String name, Object value) - ustawia wartość nazwanego parametru w zapytaniu. Należy zwrócić uwagę na nazwę parametru oraz jego typ.

query = em.createQuery("SELECT p FROM Pracownik p WHERE p.imie = :imie");
query.setParameter("imie", "Jacek");
iloscPracownikow = query.getResultList().size();

Query setParameter(String name, Date value, TemporalType temporalType) - przypisuje egzemplarz java.util.Date do nazwanego parametru w zapytaniu. Należy zwrócić uwagę na nazwę parametru oraz jego typ.

Calendar kalendarz = Calendar.getInstance();
kalendarz.set(Calendar.HOUR_OF_DAY, 0);
kalendarz.set(Calendar.MINUTE, 0);
kalendarz.set(Calendar.SECOND, 0);
kalendarz.set(Calendar.MILLISECOND, 0);
Date dzisiaj = kalendarz.getTime();
query = em.createQuery("SELECT p FROM Pracownik p WHERE p.dzienUrodzin = :dzisiaj");
query.setParameter("dzisiaj", dzisiaj, TemporalType.DATE);
List<Pracownik> pracownicy = query.getResultList();

Query setParameter(String name, Calendar value, TemporalType temporalType) - przypisuje egzemplarz java.util.Calendar do nazwanego parametru w zapytaniu. Należy zwrócić uwagę na nazwę parametru oraz jego typ.

Calendar kalendarz = Calendar.getInstance();
kalendarz.set(Calendar.HOUR_OF_DAY, 0);
kalendarz.set(Calendar.MINUTE, 0);
kalendarz.set(Calendar.SECOND, 0);
kalendarz.set(Calendar.MILLISECOND, 0);
query = em.createQuery("SELECT p FROM Pracownik p WHERE p.dzienImienin = :dzisiaj");
query.setParameter("dzisiaj", kalendarz, TemporalType.DATE);
List<Pracownik> pracownicy = query.getResultList();

Query setParameter(int position, Object value) - ustawia wartość parametru pozycyjnego w zapytaniu. Należy zwrócić uwagę na nazwę i typ parametru.

query = em.createQuery("SELECT p FROM Pracownik p WHERE p.imie = :imie");
query.setParameter(1, "Jacek");
List<Pracownik> pracownicy = query.getResultList();

Query setParameter(int position, Date value, TemporalType temporalType) - przypisuje egzemplarz java.util.Date do parametru pozycyjnego w zapytaniu. Należy zwrócić uwagę na nazwę i typ parametru.

Calendar kalendarz = Calendar.getInstance();
kalendarz.set(Calendar.HOUR_OF_DAY, 0);
kalendarz.set(Calendar.MINUTE, 0);
kalendarz.set(Calendar.SECOND, 0);
kalendarz.set(Calendar.MILLISECOND, 0);
Date dzisiaj = kalendarz.getTime();

query = em.createQuery("SELECT p FROM Pracownik p WHERE p.dzienUrodzin = :dzienUrodzin");
query.setParameter(1, dzisiaj);
List<Pracownik> pracownicy = query.getResultList();

Query setParameter(int position, Calendar value, TemporalType temporalType) - przypisuje egzemplarz java.util.Calendar do parametru pozycyjnego w zapytaniu. Należy zwrócić uwagę na nazwę i typ parametru.

Calendar kalendarz = Calendar.getInstance();
kalendarz.set(Calendar.HOUR_OF_DAY, 0);
kalendarz.set(Calendar.MINUTE, 0);
kalendarz.set(Calendar.SECOND, 0);
kalendarz.set(Calendar.MILLISECOND, 0);

query = em.createQuery("SELECT p FROM Pracownik p WHERE p.dzienImienin = :dzisiaj");
query.setParameter(1, kalendarz);
List<Pracownik> pracownicy = query.getResultList();

Query setFlushMode(FlushModeType flushMode) - ustawia tryb synchronizacji używany podczas wykonania zapytania, który nadpisuje ustawienia zarządcy trwałości.

Wszystkie metody zwracające Query pozwalają ustanowić ciąg wywołań w postaci jednego wywołania, np.

em.createQuery("SELECT p FROM Pracownik p")
.setFirstResult(10)
.setMaxResults(10)
.setHint("org.hibernate.readOnly", "true")
.getResultList()

Elementy wyniku zapytania, którego klauzula SELECT składa się z wielu wyrażeń kolumnowych jest typu Object[].

List<Object[]> pracownicyJakoTablica = em.createQuery("SELECT p.numer, p.imie, p.nazwisko FROM Pracownik p")
.getResultList();
assert pracownicyJakoTablica.get(0).length == 3;

W szczególności, przy klauzuli SELECT z jednym wyrażeniem kolumnowym typem jest Object.

List<Object> pracownicyJakoTablica = em.createQuery("SELECT p.numer FROM Pracownik p").getResultList();
assert pracownicyJakoTablica.get(0) instanceof Long;

Użycie natywnych zapytań SQL (utworzenie Query poprzez metody createNativeQuery), mapowanie wyników (opisane poniżej), wyznacza ilość elementów w tablicy Object[].

List<Object> pracownicyJakoTablica = em.createNativeQuery("SELECT numer FROM Pracownik").getResultList();
assert pracownicyJakoTablica.get(0) instanceof Long;

lub

List<Object[]> pracownicyJakoTablica = em.createNativeQuery("SELECT numer, imie, nazwisko FROM Pracownik")
.getResultList();
assert pracownicyJakoTablica.get(0).length == 3;

Rezultat wykonania setMaxResults oraz setFirstResults dla zapytań korzystających z FETCH JOIN po kolekcjach jest niezdefiniowany (gdybym to ja wiedział, co to są FETCH JOIN!)

Metody Query, poza executeUpdate, nie wymagają aktywnego kontekstu transakcyjnego. W szczególności, metody getResultList i getSingleResult nie wymagają aktywnej transakcji. Jeśli zarządca encyjny (trwałości) jest związany z transakcją (ang. transaction-scoped), tj. rozpoczęcie (begin) oraz zatwierdzenie (commit) czy wycofanie (rollback) powodują synchronizację stanu encji, wynikowe encje będą odłączone (ang. detached). W przypadku zarządcy encyjnego o rozszerzonym kontekście transakcyjnym (ang. extended persistence context), zwrócone encje będą trwałe. Więcej dywagacji na ten temat znajduje się w rozdziale 5 (który mnie z niecierpliwością oczekuje. Tym samym przykładów nie będzie, nie dałbym rady - zresztą jak sprawdzić, czy encja jest odłączona?).

Wyjątki niekontrolowane/uruchomieniowe (ang. unchecked/runtime exceptions) rzucane przez metody interfejsu Query, poza NoResultException czy NonUniqueResultException (oba należą do pakietu javax.persistence), powodują wycofanie transakcji.

EntityTransaction tx = em.getTransaction();
tx.begin();
Query query = em.createQuery("SELECT p FROM Pracownik p WHERE p.imie = :imie");
query.setParameter("imie", "Jacek");
Pracownik p = (Pracownik) query.getSingleResult();
assert p != null;
query.setParameter("imie", "nieistnieje");
try {
query.getSingleResult();
assert 0 == 1 : "Nie powinno nas tu być! Oczekiwano NoResultException!";
} catch (NoResultException nre) {
// oczekiwano, sprawdźmy stan transakcji
assert tx.getRollbackOnly() == false : "Przechwycono wyjątek NoResultException, więc transakcja powinna być wciąż aktywna.";
tx.commit();
}
assert tx.isActive() == false;

ale

Query query = em.createQuery("SELECT p FROM Pracownik p WHERE p.imie = :imie");
query.setParameter("imie", "Jacek");
Pracownik p = (Pracownik) query.getSingleResult();
assert p != null;

// Do wyjaśnienia na grupie Apache OpenJPA
em.clear();

EntityTransaction tx = em.getTransaction();
tx.begin();
assert tx.isActive() == true;
try {
em.persist(p);
// tutaj niekoniecznie rzucony wyjątek - mamy dwa wyjścia - em.flush, albo tx.commit
// Obojętnie, kto jest dostawcą JPA tx.commit() na prewno zrobi swoje
tx.commit();
assert 0 == 1 : "Nie powinno nas tu być! Oczekiwano EntityExistsException!";
} catch (EntityExistsException eee) {
// oczekiwano, sprawdźmy stan transakcji
assert tx.getRollbackOnly() == true : "Przechwycono wyjątek EntityExistsException, więc transakcja wycofywana.";
tx.rollback();
}
assert tx.isActive() == false;

Kolejny podrozdział 3.6.2 opisuje tryby synchronizacji przypisane do zapytań (ang. flush mode), które jak pamiętam były już gdzieś w specyfikacji JPA opisane. Mimo to, dobrze sobie przypomnieć je i tym razem.

Z działaniem zapytania związany jest tryb synchronizacji. Specyfikacja JPA dostarcza 2 tryby synchronizacji (klasa wyliczeniowa javax.persistence.FlushModeType):
  • COMMIT
  • AUTO (domyślne ustawienie)
Ich efekt zależy od stanu transakcji, w ramach której działa zapytanie.

Jeśli zapytania wykonywane są w ramach aktywnej transakcji i tryb opróźniania AUTO jest w użyciu (albo na zarządcy trwałości bądź bezpośrednio na egzemplarzu Query), wtedy dostawca trwałości zobowiązany jest zapewnić, że wszystkie modyfikacje stanu encji w PU, które mogłyby wpłynąć na wykonanie zapytania są widoczne dla niego. Dostawca trwałości ma prawo spełnić to wymaganie różnymi sposobami, wliczając zapisanie stanu encji do bazy danych. Jeśli ustawiono tryb opróżniania COMMIT, wynik działania zapytania jest nieokreślony.

EntityTransaction tx = em.getTransaction();
tx.begin();
assert tx.isActive() == true;
Query query = em.createQuery("SELECT p FROM Pracownik p WHERE p.imie = :imie");
query.setParameter("imie", "Jacek");
Pracownik p = (Pracownik) query.getSingleResult();
assert p != null;

query = em.createQuery("UPDATE Pracownik p SET p.imie = :noweImie WHERE p.imie = :imie");
query.setParameter("imie", "Jacek");
query.setParameter("noweImie", "InnyJacek");
int uaktualniono = query.executeUpdate();

assert uaktualniono == 1;

assert em.getFlushMode() == FlushModeType.AUTO;

query = em.createQuery("SELECT p FROM Pracownik p WHERE p.imie = :imie");
query.setParameter("imie", "InnyJacek");
p = (Pracownik) query.getSingleResult();
assert p != null;

tx.commit();
assert tx.isActive() == false;

Jeśli zapytania wykonywane są poza transakcją, niedozwolone jest, aby dostawca trwałości synchronizował zawartość bazy ze stanem encji, tj. zapisywał bieżące zmiany stanu encji bez wyraźnego polecenia - wywołania metody flush.

3.6.3 Nazwane parametry

Nazwanym parametrem nazywamy identyfikator zapytania poprzedzony znakiem ":" (dwukropek). Parametry nazwane są wrażliwe na wielkość liter.

Nazwane parametry muszą przestrzegać reguł nazewnictwa opisanych w sekcji 4.4.1 (czyli nie wiem, bo wciąż ten rozdział przede mną). Użycie nazwanych parametrów jest możliwe wyłącznie dla zapytań korzystających z JPA-QL i nie jest określony dla zapytań natywnych SQL. Jedynie parametry pozycyjne gwarantowane są działać z zapytaniami natywnymi SQL (a to bardzo ciekawe, gdyż w Java EE Tutorial w sekcji Named Parameters in Queues rozdziału The EntityManager napisano, że parametry nazwane mogą być używane przez zapytania nazwane i statyczne - planuję to wyjaśnić na forach EJB/JPA).

Nazwy parametrów nazwanych przekazywanych do metod Query.setParameters nie zawierają przedrostka ':'.

Query query = em
.createQuery("UPDATE Pracownik p SET p.nazwisko = :noweNazwisko WHERE p.imie = :imie AND p.nazwisko = :nazwisko");
query.setParameter("imie", "Agata");
query.setParameter("nazwisko", "Bretes");
query.setParameter("noweNazwisko", "Laskowska");

3.6.4 Nazwane zapytania

Nazwane zapytania są statycznymi zapytaniami zdefiniowanymi poprzez metadane - adnotacje lub w deskryptorze XML. Nazwane zapytania mogą być wyrażone w JPA-QL bądź SQL. Nazwy zapytań nazwanych są związane z PU.

Definicja nazwanego zapytania korzystając z JPA-QL w klasie encji:

@NamedQuery(name = "znajdzPracownikowPoImieniu", query = "SELECT p FROM Pracownik p WHERE p.imie LIKE :imie")

i jego użycie:

Query query = em.createNamedQuery("znajdzPracownikowPoImieniu");
query.setParameter("imie", "Jacek");
Pracownik p = (Pracownik) query.getSingleResult();
assert p != null;

oraz dla przypadku zapytań natywnych w SQL:

@NamedNativeQuery(name = "znajdzPracownikowPoImieniuSQL", query = "SELECT * FROM Pracownik WHERE imie LIKE ?")

i jego użycie:

Query query = em.createNamedQuery("znajdzPracownikowPoImieniuSQL");
query.setParameter(1, "Jacek");
Object[] polaPracownika = (Object[]) query.getSingleResult();
assert polaPracownika.length > 0;

3.6.5 Zapytania polimorficzne

Domyślnie wszystkie zapytania są polimorficzne, tj. klauzula FROM zapytania wyznacza wyłącznie klasę nadrzędną, której dotyczy zapytanie, ale wszystkie klasy podrzędne (a w zasadzie encje reprezentowane przez te klasy) również w nim uczestniczą. Rozważa się umożliwienie zawężenia polimorfizmu zapytania w przyszłych wydaniach specyfikacji.

EntityTransaction tx = em.getTransaction();
tx.begin();
em.createQuery("DELETE FROM Pracownik p").executeUpdate();
Pracownik jacekLaskowski = new Pracownik("Jacek", "Laskowski");
em.persist(jacekLaskowski);

Query query = em.createNamedQuery("wszyscyPracownicy");
List<Pracownik> pracownicy = query.getResultList();
assert pracownicy.size() == 1;
assert pracownicy.get(0) instanceof Pracownik;

assert em.getFlushMode() == FlushModeType.AUTO;

Pracownik prezes = new PracownikSpecjalny("Jan", "Kowalski", "Prezes");
em.persist(prezes);
pracownicy = query.getResultList();
assert pracownicy.size() == 2;
tx.rollback();

3.6.6 Zapytania natywne SQL

Zapytania mogą być wyrażone w języku SQL wraz z elementami specyficznymi dla języka SQL w wykorzystywanej bazie danych. Wynik zapytania może składać się z wartości skalarnych, encji i ich kombinacji. Zwrócone z zapytania encje mogą być różnych typów.

UWAGA: Korzystanie z natywnych zapytań powinno być minimalne, gdzie JPA-QL nie jest wystarczająco ekspresyjny. Możliwość korzystania z elementów języka SQL specyficznego dla wykorzystywanej bazy danych powoduje, że zapytanie, a tym samym i aplikacja, jest nieprzenośna względem baz danych, tj. związana jest z konkretną bazą danych.

W przypadku zapytania natywnego SQL, które zwraca wiele encji, encje muszą być określone i związane z odpowiednimi kolumnami z zapytania SQL poprzez adnotację @SqlResultSetMapping. Mapowanie będzie wykorzystane przez dostawcę JPA do przypisania wyniku do odpowiednich klas encji.

Ciekawy przykład znajduje się w dokumentacji @SqlResultSetMapping.

Jeśli wyniki zapytania są ograniczone do encji pojedyńczej klasy trwałej, można skorzystać z prostszego sposobu niekorzystającego z @SqlResultSetMapping.

Query query = em.createNativeQuery("SELECT numer, imie, nazwisko FROM Pracownik", Pracownik.class);
List<Pracownik> pracownicy = query.getResultList();

W przypadku, kiedy encja jest zwracana, zapytanie SQL skorzysta ze wszystkich kolumn, które są przypisane (zmapowane) do encji, wliczając w to kolumny kluczy obcych do odpowiednich encji. Wynik otrzymany przy niewystarczających danych jest nieokreślony. Wynik zapytania SQL nie może być mapowany do pól nietrwałych encji.

Nazwy kolumn w adnotacjach mapujących wynik zapytania SQL wskazują na nazwy kolumn w klauzuli SELECT zapytania. Wymaga się skorzystania z aliasów w klauzuli SELECT zapytania, kiedy więcej niż jedna kolumna w wyniku zapytania nosi tę samą nazwę.

Typy skalarne mogą być zawarte w zapytaniu przez @ColumnResult.

W przypadku, kiedy encja jest właścicielem relacji jednowartościowej (ang. single-valued relationship) i klucz obcy jest złożony (składający się z kilku kolumn), element FieldResult powinien być użyty dla każdej kolumny klucza obcego. Element FieldResult musi korzystać z notacji z kropką '.' do wyznaczenia mapowania kolumn do właściwości/pola klucza głównego docelowej encji.

Nie wymaga się, aby notacja mapująca z kropką była wspierana dla innego mapowania niż dla złożonych kluczy obcych lub wbudowanych kluczy głównych.

Jeśli docelowa encja ma klucz główny typu @IdClass, określenie składa się z nazwy pola/właściwości relacji, po której następuje '.' (kropka), po której następuje pole/właściwość klucza głównego w docelowej encji (oznaczonej adnotacją @Id).

Elementy FieldResult dla złożonego klucza obcego tworzą klasę EmbeddedId klucza głównego dla docelowej encji. Może to być wykorzystane do pozyskania encji jeśli relacja jest ustawiona jako wcześnie materializowana.

Użycie nazwanych parametrów nie jest zdefiniowane dla zapytań natywnych SQL. Jedynie przypisywanie pozycyjnych parametrów jest wspierane.

Wsparcie dla JOIN jest ograniczone do relacji jednowartościowych.

Wiele ciekawych przykładów dotyczących zapytań natywnych SQL i ich mapowania jest zamieszczonych w specyfikacji, do lektury której gorąco zachęcam.