JPA 2.0
Coraz mniej czasu pozostaje do publikacji finalnej specyfikacji korporacyjnej wersji Javy oznaczonej numerkiem 6. Z dużą dozą prawdopodobieństwa można stwierdzić, że na tym etapie nie zostaną już wprowadzone do niej ogromne zmiany. Przyjrzyjmy się zatem nowej EE zaczynając od JPA 2.0, które będzie jednym z głównych składników tej platformy.
W przeciągu ostatnich lat JPA dzięki włączeniu go do specyfikacji JEE5 stało się technologią dobrze znaną i jedną z najczęściej w świecie Javy wykorzystywanych. Niestety JPA w pierwszej swojej finalnej wersji (z konieczności ujednolicenia interfejsu dostępu do warstwy utrwalania) jest w swojej naturze dosyć ograniczone. Ubogość JPA staje się wyraźnie widoczna dla programistów, którzy wcześniej wykorzystywali takie rozwiązania jak Hibernate i z różnych przyczyn na pewnym etapie postanowili, czy też zostali zmuszeni do przesiadki na natywne JPA. Okazuje się, że wiele czynności prostych do wykonania w świecie konkretnego dostawcy ORM przy wykorzystaniu adaptera jakim jest JPA niemiłosiernie się komplikuje. W takiej sytuacji mamy dwa wyjścia:
- Możemy użyć rozwiązań specyficznych dla konkretnego providera warstwy utrwalania w celu stworzenia obejścia dla słabości jakie niesie ze sobą JPA. Wiążemy się niestety w takim przypadku nieodwołalnie z konkretnym providerem zatracając główną ideę przyświecającą JPA.
- Możemy zagwarantować pełną zgodność z JPA, ale wymagać będzie to od nas sporej gimnastyki w celu utworzenia sztucznych bytów domenowych i zmapowania ich w sposób zapewniający nam oczekiwaną funkcjonalność.
Tak było z JPA 1.0 – zobaczmy zatem czy JPA 2.0 przychodzi nam z odsieczą.
Obiektowy język zapytań – Criteria API
Najbardziej zauważalną zmianą w JPA w stosunku do wersji 1.0 wydaje się być włączenie obiektowego języka zapytań stanowiącego alternatywę dla JPQL. Criteria API, bo o nim mowa ma stanowić bardziej przemyślaną implementację idei wprowadzonej pierwotnie przez kryteria Hibernate‘a. Ważną charakterystyką Criteria API ma być 100% wyrażalność w stosunku do zapytań w formie JPQL. Innymi słowy jeżeli uda nam się skonstruować zapytanie w JPQL, to będzie możliwe również stworzenie adekwatnego zapytania w Criteria API. Dla mnie osobiście brak kryteriów po przejściu z Hibernate‘a na JPA był bardzo dotkliwy. Tym bardziej zadowolony jestem z faktu, że w JPA 2.0 niedociągnięcie to zostanie poprawione.
// JPQL
SELECT DISTINCT p.billedTo
FROM Employee e JOIN e.contactInfo c JOIN c.phones p
WHERE e.contactInfo.address.zipcode = '95054'
AND p.phonetype = PhoneType.OFFICE
// Criteria API
DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject p = e.join("contactInfo").join("phones");
e.where(e.get("contactInfo").get("address").get("zipcode").equal("95054")
.and(p.get("phonetype").equal(PhoneType.OFFICE)))
.selectDistinct(p.get("billedTo"));
Ze względu na obszerność tematu zainteresowane osoby odsyłam do wyczerpującego wpisu dotyczącego wyłącznie nowego API (Java Persistence 2.0 Public Draft: Criteria API).
Rozszerzone możliwości kolekcji
Jedną ze sporych słabości JPA 1.0 była możliwość definiowania kolekcji opartych jedynie na klasach encji (@Entity). W wersji 2.0 rozszerzono mapowane kolekcje poprzez dodanie możliwości tworzenia kolekcji zawierających typy proste (@Basic) oraz zagnieżdzone (@Embeddable). Dzięki adnotacji @ElementCollection przedstawione poniżej deklaracje zostaną poprawnie zmapowane przez JPA 2.0. Kolekcje tego typu będą przechowywane w bazie danych przy wykorzystaniu dodatkowej tabeli. Wykorzystując adnotację @CollectionTable mamy możliwość wpłynięcia na nazwę tej tabeli.
@Entity
public class Book {
@ElementCollection
private Set<String> tags;
@ElementCollection
@CollectionTable(name = "AUTHORS")
private List<Author> authors;
@ElementCollection(fetch = FetchType.LAZY)
private List<Long> chapterStarts;
}
@Embeddable
public class Author {
private String firstName;
private String lastName;
}
Zarządzanie kolejnością elementów w kolekcji
Jedynym sposobem zarządzania kolejnością elementów w kolekcji wspieranym w JPA 1.0 była dobrze znana adnotacja @OrderBy. Do JPA 2.0 dodana została nowa adnotacja @OrderColumn umożliwiająca określenie kolumny tabeli, która będzie odpowiedzialna za zarządzanie kolejnością encji w kolekcji. Adnotacja o której mowa prezentuje się w następujący sposób.
@Target( { METHOD, FIELD })
@Retention(RUNTIME)
public @interface OrderColumn {
String name() default "";
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
boolean contiguous() default true;
int base() default 0;
String table() default "";
}
Adnotacja @OrderColumn pozwala na określenie kolumny odpowiedzialnej za przechowywanie indeksu elementu w kolekcji. Od tego momentu silnik JPA wykona za nas całą pracę dbając o to, aby we wskazanej kolumnie znajdowały się odpowiednie wartości. Należy zauważyć, że ze względu na śledzenie zmian kolejności w kolekcji wykorzystanie @OrderColumn niesie ze sobą pewne negatywne konsekwencje wydajnościowe. Poniżej przykład zastosowania opisanego rozwiązania.
@Entity
public class Book {
@OneToMany
@OrderColumn("author_index")
private List<Author> author;
}
@Entity
public class Author {
@Id
@GeneratedValue
private long id;
private String firstName;
private String lastName;
}
Warto zauważyć, że nie ma możliwości wykorzystywania w stosunku do jednej kolekcji zarówno adnotacji @OrderBy oraz @OrderColumn. Wydaje się to jednak dość naturalne.
Prawdziwe mapy
JPA 1.0 wymaga by klucz mapy był jedną z własności encji, której instancje stanowić mają wartości tejże mapy. Poniżej przykład poprawnego mapowania mapy w rozumieniu JPA 1.0.
@Entity
public class Customer {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy = "owner")
@MapKey(name = "type")
private Map<String, Address> addresses;
}
@Entity
public class Address {
@Id
@GeneratedValue
private long id;
private String type;
@ManyToOne
private Customer owner;
}
W JPA 2.0 klucz mapy może być również:
- wartością prostą (@Basic)
- obiektem zagnieżdzonym (@Embedded)
- encją (@Entity)
Należy zauważyć, że w stosunku do map obok @OneToMany oraz @ManyToMany można również użyć adnotacji @ElementCollection. Oznacza to, że również wartości w mapie mogą być nie tylko jak do tej pory encjami, ale również elementami prostymi oraz typami zagnieżdzonymi.
Poniżej przykład przekształcony w taki sposób, aby typ adresu nie musiał być przechowywany jako własność encji.
@Entity
public class Customer {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy = "owner")
@MapKeyColumn(name = "ADDRESS_TYPE")
private Map<String, Address> addresses;
}
@Entity
public class Address {
@Id
@GeneratedValue
private long id;
@ManyToOne
private Customer owner;
}
Ponieważ klucz był typem prostym mogliśmy użyć adnotacji @MapKeyColumn. W przypadku w którym klucz byłby encją należałoby zastosować adnotację @MapKeyJoinColumn.
@Entity
public class Customer {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy = "owner")
@MapKeyJoinColumn(name = "ADDRESS_TYPE_ID")
private Map<AddressType, Address> addresses;
}
@Entity
public class Address {
@Id
@GeneratedValue
private long id;
@ManyToOne
private Customer owner;
}
@Entity
public class AddressType {
@Id
@GeneratedValue
private long id;
private String type;
}
W przypadku klucza będącego typem zagnieżdzonym wykorzystamy adnotację @MapKeyClass.
@Entity
public class Customer {
@Id
@GeneratedValue
private long id;
@OneToMany(mappedBy = "owner")
@MapKeyClass(AddressType.class)
private Map<AddressType, Address> addresses;
}
@Entity
public class Address {
@Id
@GeneratedValue
private long id;
@ManyToOne
private Customer owner;
}
@Embeddable
public class AddressType {
private String type;
}
Jak widać na podstawie powyższych przykładów w JPA 2.0 udało się osiągnąć dużą swobodę jeśli chodzi o definiowanie relacji opartych na mapach.
Automatyczne usuwanie sierot
Wraz z JPA 2.0 adnotacje @OneToOne oraz @OneToMany zostały wzbogacone o nową własność o nazwie orphanRemoval. Aktywacja tej opcji spowoduje, że usunięcie encji z kolekcji spowoduje jej rzeczywiste usunięcie z bazy danych.
@Entity
public class Book {
@OneToMany(orphanRemoval = true)
private Set<Chapter> chapters;
}
//usunięcie powiązania pomiędzy książką, a rozdziałem spowoduje także usunięcie encji rozdziału
book.getChapters().remove(chapter);
Rozszerzone możliwości tworzenia klas zagnieżdzonych
W JPA 2.0 znacząco została poszerzona stosowalność klas zagnieżdzonych (@Embeddable). Będzie między innymi możliwe zagnieżdzanie klas zagnieżdzonych w innych klasach zagnieżdzonych. Jeszcze bardziej znaczącym udogodnieniem z mojego punktu widzenia wydaje się być możliwość definiowania relacji z wnętrza klas zagnieżdzonych do zwykłych klas encji. Obydwie możliwości zaprezentowano poniżej.
@Entity
public class Customer {
@Id
@GeneratedValue
private long id;
private ContactInformation contactInformation;
}
@Embeddable
public class ContactInformation {
private ContactPolicy contactPolicy;
@OneToMany
@JoinColumn
//klasa Address jest zwykłą encją adnotowaną przez @Entity
private Set<Address> addresses;
}
@Embeddable
public class ContactPolicy {
private boolean weeklyNews;
private boolean specialOffers;
}
Należy podejść z pewnym dystansem do powyższych możliwości, gdyż stosowanie ich w sposób nieprzemyślany może przekształcić model danych naszego projektu w programistyczny koszmar.
Zmiany w JPQL
Poniżej lista dobrze udokumentowanych zmian w języku zapytań:
- dodanie konstrukcji CASE (funkcjonalność podobna do znanego z Javy elementu języka switch)
- możliwość nawigowania wśród obiektów zagnieżdzonych (SELECT e FROM Entity e WHERE e.embedded.value = :value)
- dodatkowe funkcje KEY, VALUE, ENTRY służące do obsługi map (SELECT KEY(m) FROM Entity e JOIN e.map m WHERE VALUE(m) = :value)
- słowo kluczowe AS służące do tworzenia aliasów (SELECT e AS x FROM Entity e)
- warunek IN może przyjmować jako argument kolekcję (SELECT e FROM Entity e WHERE e.value IN :value, gdzie List<String> value;)
- warunek na klasę encji (SELECT e FROM EntitySuperClass e WHERE TYPE(e) = EntitySubclass.class)
Inne nowości
Poza opisanymi powyżej nowościami do JPA 2.0 zostało również włączone blokowanie pesymistyczne oraz API służące do cache‘owania.
Nowe możliwości w praktyce
Osoby chcące w praktyce przetestować nowe możliwości JEE6, a w tym JPA 2.0, zapraszam do zainstalowania Glassfish Server v3 Preview. Jest to serwer aplikacji, który w aktualnej chwili w największym stopniu jest zgodny z wciąż rozwijaną specyfikacją JEE6.








August 23rd, 2009 at 18:45
trzy dni temu na java-tv udostępniono nagranie z prezentacji o JPA 2.0 http://www.java-tv.com/2009/08/20/whats-new-and-exciting-in-jpa-20/
August 24th, 2009 at 20:19
hm, wiekszosc dostawcow JPA posiada jakies swoje nieprzenosne criteria api, wiec nie jest tak zle. ;-)
August 29th, 2009 at 22:05
Wszystko świetnie, ale wciąż brak wsparcia dla hintów (wskazówek dla optymalizatora kosztowego, podawanych w komentarzu zaraz po selekcie). Bez tego ten cały JPA o kant D rozbić, bo ubijamy wielki arsenał możliwości optymalizacji zapytań.