JDD 09 – agenda
Aug 23

JPA 2.0

EJB | autor: Michał Mally Add comments

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:

  1. 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.
  2. 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.

Linki

  1. JSR-000317 JavaTM Persistence 2.0 (Proposed Final Draft)
  2. Beginning Java™ EE 6 Platform with GlassFish™ 3: From Novice to Professional
  3. EclipseLink JPA 2.0 Implementation Status
  4. The Java EE 6 Tutorial, Volume I: Part V Persistence
  5. Java Persistence 2.0 Public Draft: Criteria API
Podziel się z innymi:
  • Wykop
  • Digg
  • del.icio.us
  • StumbleUpon
  • Slashdot

3 Responses to “JPA 2.0”

  1. Paweł Ryznar Says:

    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/

  2. Jakub Klama Says:

    hm, wiekszosc dostawcow JPA posiada jakies swoje nieprzenosne criteria api, wiec nie jest tak zle. ;-)

  3. OracleDBA Says:

    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ń.

Leave a Reply

Security Code: