Monthly Archives: March 2014

O TDD okiem praktyka

tddszt TDD jest bardzo fajnym podejściem do tworzenia oprogramowania. Bardzo często jednakże początkujący mają problem z tym jak w ogóle się za testy zabrać. Proste tutoriale konkretnych frameworków testujących skupiają się na technicznym aspekcie testów, zaś totalnie pomijają odpowiedzi na fundamentalne pytania – jak testować, co testować no i jak to połączyć w kupę by pisanie testów przeplatało się płynnie z tworzeniem kodu. Na te odpowiedzi w końcu mamy odpowiedź – jest nią książka Kenta BeckaTDD. Sztuka tworzenia dobrego kodu.

Książka składa się z trzech części. W pierwszej z nich otrzymujemy od razu konkretną dawkę mięsa – na przykładzie klas przeliczających jedną walutę na drugą, krok po kroku, mamy okazję zaobserwować TDD w praktyce. Tego nie da się opisać, to trzeba zobaczyć. Jeśli po lekturze pierwszej części książki wciąż nie będziesz wiedział Czytelniku jak zabrać się za testowanie – to już chyba nikt i nic Ci nie pomoże. Przykłady są czytelne, a do tego są dość wymowne.

Druga część książki to przykład jak stworzyć własny framework testujący. Przyznam szczerze, iż po przeczytaniu pierwszego rozdziału stwierdziłem, że nie jest to wiedza, która byłaby mi do czegokolwiek potrzebna. Z czystym sumieniem zatem odpuściłem lekturę. Druga część jest dość krótka, najlepiej potraktować ją jak ciekawostkę i tyle.

W ostatniej części mamy okazję poznać najważniejsze dobre praktyki w TDD. Tutaj już nie poruszamy się wśród pytań – co testować czy jak, ale jak robić to najbardziej optymalnie, czytelnie i z sensem. Część tę zatytułowano – Wzorce dla programowania sterowanego testami – myślę, że jest to bardzo adekwatny tytuł. Mamy wzorce projektowe to i możemy mieć wzorce dla testów.

Czy książka warta jest swej ceny (w sumie niemałej jak na objętość)? Zdecydowanie tak! W sumie może i dlatego utrzymuje się w top sprzedaży Helionu od momentu jej publikacji po polsku. Pozycja ta powinna stać się obowiązkową lekturą dla zarówno początkujących programistów, jak i starych wyjadaczy. Dla jednych i drugich nie będzie to na pewno czas stracony.

Metamodel i typowane zapytania criteria API

W poprzednim wpisie długo rozpisywałem się na temat Criteria API. Dość ciekawą konstrukcją w API było określanie typu pobieranych danych w przypadku wyciągania pojedynczych kolumn. Wyglądało to tak:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<Hero> hh = query.from(Hero.class);
query.select(hh.<String>get("name"))
     .where(cb.equal(hh.get("id"), 1L));

Jest to jednak podejście potencjalnie błędogenne. Możemy zawsze pomylić się w podawaniu typu kolumny, a dwa – przy zmianie encji i typu danych trzeba uaktualniać wszystkie zapytania oparte o kryteria. Nie do końca to fajne. Dlatego też w JPA istnieje coś fajnego – metamodel.

API Metamodelu

Zacznijmy od odpowiedzi na pytanie – co to jest ów metamodel? W skrócie są to informacje o wszystkich encjach w danym persistence-unit – o ich własnościach, typach czy relacjach. Zapisane w formacie obiektowym, dzięki czemu można na owym metamodelu działać cuda w trakcie działania aplikacji. Punktem wejścia do poznawania metamodelu jest nasza instancja EntityManagera, która posiada szereg metod, które zwracają odpowiednie informacje o encji, klasie wbudowanej czy typach (po kolei metody entity(), embeddable() i managedType().

Metamodel mm = entityManager.getMetamodel();
EntityType<Hero> hero_ = mm.entity(Hero.class);

Jak zawsze zachęcam do zapoznania się z dokumentacją dotyczącą Metamodel,EntityType, ManagedType oraz EmbeddableType. Cała hierarchia metamodelu jest dość rozbudowana i mocno ‘zgeneryzowana’. Dzięki temu jednakże możemy w trakcie działania aplikacji przyglądać się naszym encjom czy typom operując na mocnym typowaniu. By zaś konkretnie pokazać do czego przydaje się metamodel pierwszy kawałek kodu:

Metamodel mm = entityManager.getMetamodel();
EntityType<Hero> hero_ = mm.entity(Hero.class);
// Suffix na koncu sluzy informacji, ze nie jest to instancja encji per se - jest to stala konwencja w JPA
                
for( Attribute<? super Hero, ?> attr : hero_.getAttributes() ) {
      System.out.println( attr.getName() + " " + 
                          attr.getJavaType().getName() + " " + 
                          attr.getPersistentAttributeType() );
}

Rezultat w konsoli wygląda tak:

weapons java.util.List ONE_TO_MANY
id java.lang.Long BASIC
dragon com.wordpress.chlebik.jpa.domain.Dragon ONE_TO_ONE
deities java.util.List MANY_TO_MANY
finance com.wordpress.chlebik.jpa.domain.FinanceState EMBEDDED
level java.lang.Integer BASIC
name java.lang.String BASIC
creationDate java.util.Date BASIC
nicknames java.util.List ELEMENT_COLLECTION

Na tym przykładzie widać już do czego może się przydać metamodel w przypadku kryteriów – bez najmniejszych problemów możemy sprawdzić typ konkretnej własności. Nie obchodzi nas jaki on naprawdę jest – wystarczy, że mamy możliwość dostarczenia tej informacji przy konstruowaniu zapytania. Jednakże zanim uda się nam skompilować kod zawierający zapytanie z użyciem kryteriów (i silnego typowania) musimy zrobić jeszcze jedną rzecz – wygenerować ów metamodel.

Generowanie metamodelu

Domyślnie (przynajmniej w moim projekcie) metamodel nie jest generowany automatycznie. Istnieje możliwość wygenerowania go ręcznie – po prostu tworząc odpowiednią klasę z tym samym pakiecie co encja/typ/klasa wbudowana, której metamodel jest nam potrzebny. Czyli nasza klasa Hero miałaby metamodel, który wyglądałby tak:

package com.wordpress.chlebik.jpa.domain;

import java.util.Date;
import javax.annotation.Generated;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@StaticMetamodel(Hero.class)
public abstract class Hero_ {

	public static volatile SingularAttribute<Hero, Long> id;
	public static volatile SingularAttribute<Hero, Date> creationDate;
	public static volatile ListAttribute<Hero, Deity> deities;
	public static volatile ListAttribute<Hero, Nickname> nicknames;
	public static volatile SingularAttribute<Hero, Integer> level;
	public static volatile SingularAttribute<Hero, String> name;
	public static volatile SingularAttribute<Hero, Dragon> dragon;
	public static volatile ListAttribute<Hero, Weapon> weapons;
	public static volatile SingularAttribute<Hero, FinanceState> finance;

}

Tak jak pisałem – klasa powinna znajdować się w tym samym pakiecie co klasa docelowa, a także być oznaczona adnotacją @StaticMetamodel. Oczywiście mądrzy (i leniwi, zwłaszcza leniwi) programiści wymyślili automaty do generowania tego typu rzeczy. Najsensowniej rozpocząć od tego wpisu na Stackoverflow, a następnie zapoznać się z linkiem tam zamieszczonym, który jasno pokaże jak się uporać z tym problemem w zależności od używanego dostawcy JPA lub mechanizmu budowania projektu. W moim projekcie (generowany z użyciem Netbeans) wystarczyło dodać plik JAR z generatorem metamodelu Hibernate do classpath projektu i magicznie zaczęło działać. Podczas budowania projektu klasy metamodelu generowane są dynamicznie. Oczywiście należy wygenerowane klasy przerzucić do docelowego pakietu, aby IDE nie krzyczało i by kompilacja w ogóle się powiodła. Można robić to ręcznie, albo dopisać w narzędziu budowania projektu odpowiednią regułkę (wtedy jakiekolwiek zmiany w encjach czy typach są na 100% od razu odwzorowywane w metamodelu).

Dzięki powyższemu możemy ostatecznie przepisać nasze zapytanie do postaci unikającej konieczności umieszczania informacji o pobieranym typie. Taka forma użycia metamodelu nazywana jest ‘kanoniczną’.

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<Hero> hh = query.from(Hero.class);                
query.select( hh.get( Hero_.name ) ) 
     .where(cb.equal(hh.get("id"), 1L));
TypedQuery<String> tq = entityManager.createQuery(query);

System.out.println("Imie bohatera: " + tq.getSingleResult()  );  

W sumie to tyle. Temat króciutki, ale dość ułatwiający życie programistom używającym kryteriów. Następnym razem zajmiemy się czymś jeszcze fajniejszym – cachowaniem i blokadami.

Dajcie kawałek SQLa – zapytania criteria API

W poprzednim wpisie zajmowaliśmy się JPQL. Możliwość tworzenia zapytań podobnych składniowo do SQL, ale z użyciem notacji obiektowej z całą pewnością upraszcza programowanie. Dzisiaj omówię rozwiązanie programistyczne – czyli tworzenie zapytań za pomocą notacji stricte obiektowej z użyciem odpowiednich metod – criteria queries.

Podstawy

Standardowo rozpoczniemy najprostszym możliwym przykładem. Oto wyjściowe zapytanie JPQL:


String jpql = "SELECT h.name FROM Hero h WHERE h.id = :id";
Query query = entityManager.createQuery(jpql, String.class)
                           .setParameter("id",1L);

System.out.println("Imie bohatera: " + query.getSingleResult()  );

Oczywiście jest to dość proste zapytanie, które jednakże posiada dość istotną wadę. Otóż o ile możemy użyć w nim parametrów, o tyle (np. w przypadku NamedQuery) musimy je określić przed uruchomieniem programu. Co w sytuacji kiedy wykonywane zapytanie zależy od danych dostarczonych przez użytkownika aplikacji? Oczywiście możemy sklejać zapytania JPQL bazując na przekazanych danych, ale ani to czytelne, a i o błąd dość łatwo. Właśnie dla takich sytuacji jak najbardziej przydają się Criteria Queries.

By rozpocząć od kodu – oto powyższe zapytanie przepisane z użyciem kryteriów:


CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Hero> query = cb.createQuery(Hero.class);

Root<Hero> hh = query.from(Hero.class);
query.select(hh)
     .where(cb.equal(hh.get("id"), 1L));

TypedQuery<Hero> tq = entityManager.createQuery(query);
System.out.println("Imie bohatera: " + tq.getSingleResult().getName()  );

Kodu jakby więcej, ale jest on o wiele bardziej elastyczny. Podstawowym elementem wykorzystywania kryteriów jest interfejs CriteriaBuilder pobierany z instancji Entity Managera. Jest to punkt wejścia dla używania kryteriów. Dzięki metodzie tego interfejsu o nazwie createQuery tworzymy obiekt typu CriteriaQuery. To ten obiekt będzie reprezentował wszystkie warunki, które zamierzamy użyć w zapytaniu. Troszeczkę nie do końca oczywiste jest istnienie obiektu typu Root. Interfejs Root jest odpowiednikiem zmiennej wskaźnikowej w zapytaniach JPQL. Czyli jeśli w zapytaniu JPQL zastosujemy taką konstrukcję:


SELECT h FROM Hero h

To dokładnym odpowiednikiem tej konstrukcji w kryteriach jest właśnie:


Root<Hero> hh = query.from(Hero.class);

Na chwilę obecną wystarczy takie opisanie tego interfejsu. Dalej w kodzie odwołujemy się do obiektu reprezentującego nasze criteria query używając metod tego interfejsu jak i obiektu CriteriaBuilder. Nazwy metod są dość czytelne i póki co nie ma potrzeby ich wyjaśniać. Następnie wykorzystując nasz obiekt kryteriów tworzymy znaną już nam instancję TypedQuery i wywołujemy metody pobierające wyniki. Dlaczego w ten sposób? Przypomnijmy sobie, że w przypadku JPQL schemat działania jest taki sam, z tym tylko, że jako parametr przy tworzeniu obiektu zapytania służy łańcuch tekstowy z zapytaniem, a nie obiekt. Zmniejsza to powiązania między obiektami – obiekt kryteriów niesie informację co ma być pobrane z bazy, zaś obiekt TypedQuery wie w jaki sposób wykonać owo zapytanie. W tym miejscu warto jeszcze dodać, iż powyższy kod może zostać zapisany bez używania generyków, jednakże wówczas nie mamy możliwości sprawdzenia poprawności niektórych wywołań na etapie kompilacji. Ja preferuję używanie generyków gdzie to tylko możliwe.

Podstawy tworzenia zapytań

Jak widzieliśmy w powyższym przykładzie cała zabawa z kryteriami rozpoczyna się od interfejsu CriteriaBuilder. Posiada on trzy metody, których możemy użyć do stworzenia instancji reprezentującej interfejs CriteriaQuery:

  • createQuery(Class) – tworzy instancję generyczną, najczęściej używana, co zresztą zademonstrowałem na poprzednim listingu
  • createQuery() – to samo co powyżej, ale w wersji zwracającej Object
  • createTupleQuery() – metoda będąca tak naprawdę wywołaniem metody createQuery(Tuple.class). O tym czym jest Tuple (krotka) napiszę później

Kiedy posiadamy już obiekt CriteriaQuery możemy przystąpić do definiowania naszego zapytania. Podstawowe elementy języka SQL oraz tym samym JPQL jak słowa kluczowe SELECT, WHERE czy FROM mają swoje odpowiedniki w postaci metod obiektu CriteriaQuery. Jednakże zanim do nich przejdziemy zajmiemy się dość istotnym elementem – rdzeniem (root) zapytania. W przypadku JPQL mieliśmy do czynienia ze zmienną aliasu. Dzięki niej mogliśmy odwoływać się do kolejnych wartości encji, na której pracowaliśmy. W przypadku kryteriów również musimy stworzyć uchwyt, za pomocą którego będziemy mogli ‘dostać się’ do danych. W poprzednim przykładzie służyła do tego następująca konstrukcja:


Root<Hero> hh = query.from(Hero.class);

 

Posługujemy się tutaj interfejsem Root – zachęcam do zapoznania się z rodzicami tego interfejsu. Dzięki temu dość łatwo zrozumieć, co jest wykonalne za pomocą tegoż interfejsu (wrócę do tego). Na razie trzeba zaznaczyć tylko, iż w zapytaniu możemy użyć kilkunastu tego typu obiektów (co dotyczy zwłaszcza złączeń). Drugim elementem, który można wyróżnić na początku poznawania kryteriów są path expressions. W skrócie – jest to ‘ścieżka’, za pomocą której dochodzimy do interesujących nas własności obiektu. W poniższym zapytaniu:


SELECT h FROM Hero WHERE h.id = :id

 

Tego typu wyrażeniem jest fragment h.id. Przekładając powyższe zapytanie na kryteria otrzymamy kod, który przedstawiłem na samym początku:


Root<Hero> hh = query.from(Hero.class);
query.select(hh)
     .where(cb.equal(hh.get("id"), 1L));

 

Fragment hh.get(“id”) to odpowiednik kropki w zapytaniach JPQL.

 

Klauzula SELECT

 

Standardowo do pobierania wyniku zapytania służy metoda select. Przyjmuje ona jako argument obiekt implementujący interfejs Selection. Do tej pory przekazywaliśmy do tej metody instancję o typie Root – w ten sposób informowaliśmy JPA, że interesuje nas encja jako wynik zapytania. Jednakże jeśli interesuje nas np. samo imię maga (czyli wartość łańcuchowa), wówczas musimy zastosować następującą konstrukcję:


CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<Hero> hh = query.from(Hero.class);
query.select(hh.<String>get("name"))
     .where(cb.equal(hh.get("id"), 1L));
TypedQuery<String> tq = entityManager.createQuery(query);

System.out.println("Imie bohatera: " + tq.getSingleResult()  );

Argument przekazywany do metody select musi być kompatybilny z typem zwracanym przez definicję zapytania (CriteriaQuery). Dostawca JPA nie jest w stanie po samej nazwie wyciąganego parametru rozpoznać jego typu, dlatego też musimy użyć parametryzacji.

Nadszedł czas by opisać wspomniane już krotki. Co to są krotki (tuple)? Krotka to w skrócie obiekt, który zawiera dane otrzymane z zapytania (reprezentuje wiersz danych). Dostęp do nich jest możliwy zarówno w notacji indeksowej (czyli podajemy po prostu indeks zwróconego wyniku jak w czystym JDBC), albo za pomocą nazwy kolumn (jeśli zaliasujemy je). Dzięki temu jeżeli potrzebujemy tylko wyciągnąć z bazy konkretne dane, bez narzutu tworzenia encji wówczas krotki są rozwiązaniem – łatwo się po nich iteruje, a do tego są wydajniejsze niż encje. Krótki kawałek kodu:


CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<Hero> hh = query.from(Hero.class);
query.select(cb.tuple( hh.get("id").alias("id"), hh.get("name").alias("name")  ) )
     .where(cb.equal(hh.get("id"), 1L));

TypedQuery<Tuple> tq = entityManager.createQuery(query);

for (Tuple t : tq.getResultList()) {
   System.out.println(t.get("id") + " " + t.get(1)  );
}

Skąd krotki? Ano bo musimy znać je by w pełni omówić metodę multiselect interfejsu CriteriaQuery. Jak widać z ostatniego kawałka kodu stworzenie odpowiednich wyrażeń dla metody select jest trochę ‘długaśne’. Co więcej – jeżeli chcemy wyciągnąć kilkanaście różnych danych (bardzo często z różnych tabel), zapytanie potrafi się z lekka rozrosnąć. Istotne jest również, że przy wyciąganiu szeregu kolumn rezultat może mieć kilka postaci – obiektu anonimowej klasy tworzonej w miejscu użycia, krotki lub tablicy obiektów (w zależności od typu używamy w metodzie select przy tworzeniu argumentu jedną z metod – tuple, construct lub array). By oszczędzić pracy programiście mamy dostęp do metody multiselect, która po prostu przyjmuje listę wartości, które chcemy wyciągnąć.
Tym samym nasz poprzedni przykład z krotkami dałoby się zapisać następująco:


CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<Hero> hh = query.from(Hero.class);
query.multiselect( hh.get("id").alias("id"), hh.get("name").alias("name") )
     .where(cb.equal(hh.get("id"), 1L))

//Reszta kodu po staremu

 

Klauzula FROM

 

O metodzie from już wspomniałem, pokazałem także przykład kodu. Teraz zajmiemy się czymś bardziej skomplikowanym, a konkretniej – złączeniami. Do ich obsługi używamy metodę o nazwie (a jakże) – join. Jak zawsze sugeruję zajrzeć do dokumentacji interfejsu Join – wtedy też dość łatwo zrozumiemy dlaczego nie ma problemu z łączeniem wywołań tej metody. Dowiemy się również wówczas, że metoda ta zwraca obiekt sparametryzowany – gdzie musimy zamieścić informację o typie encji, która jest podstawą złączenia, a także encji docelowej. Zobaczmy na przykładzie:


CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Hero> query = cb.createQuery(Hero.class);
Root<Hero> hh = query.from(Hero.class);
Join<Hero,String> nicknamesJoin = hh.join("nicknames");
query.select(hh.alias("hero"))
     .distinct(true)
     .where(cb.equal(hh.get("id"), 1L));

TypedQuery<Hero> tq = entityManager.createQuery(query);

for (Hero hero : tq.getResultList()) {
     System.out.println("Id bohatera: " + hero.getId()  );
     for( Nickname ne : hero.getNicknames() ) {
            System.out.println("Nickname: " + ne.getNick() );
     }
}

Pisząc o złączeniach nie sposób też poruszyć dość fajnej możliwości jaką są fetch join, czyli zapytanie, które przy okazji wykonania (jako efekt uboczny) pobiera do persistence context encje nie będące bezpośrednio używane podczas dostępu do zbioru wynikowego. Oto przykład:


CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Hero> query = cb.createQuery(Hero.class);
Root<Hero> hh = query.from(Hero.class);
hh.fetch("nicknames");
query.select(hh.alias("hero"))
     .distinct(true)
     .where(cb.equal(hh.get("id"), 1L));

TypedQuery<Hero> tq = entityManager.createQuery(query);
Hero backupHero = null;

for (Hero hero : tq.getResultList()) {
     System.out.println("Id bohatera: " + hero.getId()  );
     backupHero = hero;
}                               

for( Nickname n : backupHero.getNicknames() ) {
    System.out.println(n.getNick());
}   

Druga pętla for jest tylko dla celów demonstracyjnych – prawda jest taka, że bez niej oryginalne zapytanie też wyciąga już informacje o przydomkach i zapisuje je w persistence context.

 

Klauzula WHERE

 

Klauzula WHERE jest w kryteriach dostępna poprzez metodę where(). Pochodzi ona z interfejsu AbstractQuery i jak zawsze zachęcam do zapoznania się z dokumentacją. Można wywoływać tę metodę bezargumentowo, albo też z parametrami w postaci obiektów Predicate lub pojedynczego argumentu Expression. Należy pamiętać, że każde kolejne wywołanie metody where() powoduje nadpisanie uprzednio dodanych warunków!

By odpowiednio uformować klauzulę WHERE dostępnych jest wiele metod – generalnie nie ma sensu ich tu listować skoro zrobiło to już za mnie Oracle. W dalszej części wpisu zajmę się tylko zagadnieniami wymagającymi trochę więcej uwagi – zaczniemy od predykatów.

W przypadku używania metody where mamy możliwość użycia kilku argumentów typu Predicate (np. w formie tablicy stworzonej z listy, gdyż metoda przyjmuje jako parametr varargs ). Co jednak mamy logikę warunkową i wolelibyśmy mieć jeden obiekt do przekazania? Da się to załatwić za pomocą metody conjuction() interfejsu CriteriaBuilder


CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Hero> query = cb.createQuery(Hero.class);
Root<Hero> hh = query.from(Hero.class);
Predicate criteria = cb.conjunction();
//
// Warunki oczywiscie sa przykladowe i sluza tylko ilustracji
//
if( true ) {
     ParameterExpression<Long> id = cb.parameter(Long.class, "id");
     criteria = cb.and(criteria, cb.equal(hh.get("id"), id ) );
}
if( true ) {
     ParameterExpression<String> imieMaga = cb.parameter(String.class, "name");
     criteria = cb.and(criteria, cb.like( hh.<String>get("name"), imieMaga ) );
}                                   

query.select(hh)
     .where(criteria);

TypedQuery<Hero> tq = entityManager.createQuery(query);
tq.setParameter("id", 1L);
tq.setParameter("name", "Chlebik%");                             

for (Hero hero : tq.getResultList()) {
    System.out.println("Id bohatera: " + hero.getId()  );
}

W powyższym przykładzie mieliśmy też okazję skorzystać z parametrów. W przypadku criteria API istnieje też możliwość używania literałów (większość metod przyjmuje albo Expression albo literały). Gdzie jednakże nie jest to możliwe, wówczas można literał po prostu zawrapować za pomocą metody literal() – literał dla wartości NULL tworzymy za pomocą metody nullLiteral( Class clazz ). Wróćmy jednak do parametrów – w powyższym przykładzie zastosowaliśmy trochę dłuższą drogę. Stworzyliśmy obiekt ParameterExpression by dodać go potem do zapytania. Jeśli jednakże ta konkretna definicja parametru nie będzie już w kodzie używana, wówczas możemy zrobić to trochę bardziej elegancko:


query.select(hh)
     .where(cb.like(hh.<String>get("name"), cb.parameter(String.class, "name") ) );

TypedQuery<Hero> tq = entityManager.createQuery(query);
tq.setParameter("name", "Chlebik%");                             

Oczywiście mamy też możliwość budowania podzapytań. Da się to osiągnąć poprzez metodę subquery() interfejsu AbstractQuery, zwracana przez tę metodę wartość to instancja Subquery, która zasadniczo niewiele różni się od CriteriaQuery. Dość gadania, napiszmy jakiś kod (tak, wiem, że przykład jest przekombinowany, ale taki mamy model danych 😉 )

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Hero> query = cb.createQuery(Hero.class);
Root<Hero> hh = query.from(Hero.class);

Subquery<Hero> heroesWithWeapons = query.subquery(Hero.class);
Root<Hero> heroesWithWeaponsRoot = heroesWithWeapons.from(Hero.class);
Join<Hero,Weapon> heroesWithWeaponsJoin = heroesWithWeaponsRoot.join("weapons");
heroesWithWeapons.select( heroesWithWeaponsRoot ).where( cb.equal(
                                                            heroesWithWeaponsJoin.get("name"),
                                                            cb.parameter( String.class, "weaponName" ) )
);

// Radze zwrocic uwage na konstrukcje uzyta w metodzie IN()
query.select(hh)
     .where( cb.in( hh ).value(heroesWithWeapons) );

TypedQuery<Hero> tq = entityManager.createQuery(query);
tq.setParameter("weaponName", "Super-Duper-Miecz");                             

Klauzule sortujące i grupujące

Do tej pory skupialiśmy się na pobieraniu danych i warunkach tegoż pobierania. Teraz przyszła pora na to,co zazwyczaj znajduje się po klauzuli WHERE – czyli na instrukcje grupujące i sortujące. Sortowanie jako prostsze omówię na początek – służy do tego obiekt typu Order, który można otrzymać używając metod asc() lub desc() interfejsu CriteriaBuilder.

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Hero> query = cb.createQuery(Hero.class);
Root<Hero> hh = query.from(Hero.class);

query.select(hh).orderBy( cb.desc(hh.get("level")) ) ;

Metody agregujące i grupujące są równie proste w użyciu – na tym przykładzie wybieramy bohaterów, którzy mają przynajmniej jedno bóstwo:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Hero> query = cb.createQuery(Hero.class);
Root<Hero> hh = query.from(Hero.class);
Join<Hero,Deity> deities = hh.join("deities");

query.select(hh)
     .groupBy( hh )
     .having( cb.ge( cb.count(deities), 1 ) );

TypedQuery<Hero> tq = entityManager.createQuery(query);

To tyle na dziś. W następnej notce napiszę coś więcej na temat silnie typowanych zapytań oraz metadanych.