16 marca 2007

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

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;