05 marca 2007

Java Persistence - Rozdział 6. Dystrybuowanie encji

Do przygotowania jednego z przykładów potrzebowałem rozpoznać mechanizmy nadpisywania konfiguracji encji (a właściwie jednostki utrwalania - ang. persistence unit) w zależności od ich wykorzystania, tj. jeśli encje były wykorzystane w samodzielnej aplikacji (ang. standalone application) to ich jednostka utrwalania będzie inna niż ta używana przez aplikację internetową. Poszukiwania odpowiedzi na to pytanie rozpocząłem od specyfikacji Java Persistence API w rozdziale 6 o sposobie opakowywania encji do dystrybucji (Chapter 6 - Entity Packaging).

Jednostka utrwalania, dalej zwana PU, jest logicznym bytem składającym się z:
  • Definicji fabryki zarządców encyjnych (ang. entity manager factory) i jej zarządców wraz z ich konfiguracją (sekcja provider).
  • Zbioru zarządzanych klas trwałych (encji) zawartych w danym PU (i przez to zarządzanych przez zarządców encyjnych tworzonych przez ustaloną fabrykę zarządcy encyjnego).
  • Metadane mapowania (adnotacje lub deskryptor XML), które definiują mapowanie encji do bazy danych.
6.2 Format dystrybucji (opakowywanie, ang. packaging) PU

W ramach środowiska Java EE, plik EJB-JAR, WAR, EAR oraz klienta aplikacyjnego może definiować dowolną ilość PU.

PU może znajdować się w ramach plików jar, które są częścią pliku WAR lub EAR, jako zbiór klas wewnątrz pliku ejb-jar lub wewnątrz katalogu classes w ramach aplikacji internetowej (war) bądź jako kombinacja poniższych.

PU jest określana (definiowana) w pliku persistence.xml. Plik lub katalog w formacie jar, których (pod)katalog META-INF zawiera plik persistence.xml jest nazywany punktem początkowym (korzeniem) PU (ang. PU root). Środowisko Java EE akceptuje następujące miejsca jako punkty początkowe PU:
  • plik EJB-JAR
  • katalog WEB-INF/classes wewnątrz pliku WAR (tj. plik persistence.xml znajduje się w katalogu WEB-INF/classes/META-INF)
  • dowolny plik jar w katalogu WEB-INF/lib wewnątrz pliku WAR
  • dowolny plik jar w katalogu głównym pliku EAR
  • dowolny plik jar z bibliotek pomocniczych (współdzielonych) w pliku EAR
  • plik jar klienta aplikacyjnego
Nie wymaga się, aby plik EJB-JAR lub WAR zawierający PU zawarty był w EAR chyba, że PU odnosi się do klas spoza EJB-JAR czy WAR.

Każdy PU posiada nazwę. Nazwa musi być unikatowa w ramach pojedyńczego EJB-JAR, pojedyńczego WAR, w pliku jar klienta aplikacyjnego lub wewnątrz EAR (czy to w katalogu głównym EAR czy bibliotekach pomocniczych).

Pojedyńczy plik persistence.xml może definiować wiele PU wyznaczając tym samym zasięg widoczności/działania PU (i wynikające z tego ograniczenia, np. niepowtarzalność nazwy PU).

Specyfikacja gwarantuje, że klasy trwałe zdefiniowane na poziomie pliku EAR muszą być dostępne dla wszystkich innych modułów składowych aplikacji jako te same klasy bez rozróżnienia na PU, których mogą być częścią. Wydaje mi się jednak, że jest to wymaganie do spełnienia przez dostawców serwerów aplikacyjnych Java EE, a nie programistę korzystającego z JPA i w zasadzie nie ma się czym przejmować i próbować to zrozumieć (jeśli wciąż niejasne).

Gwarantuje się przenośność aplikacji korzystających z JPA w środowisku Java SE, jeśli aplikacje deklarują klasy trwałe (klasy encji) w PU explicite (w sekcji class dla danego PU).

6.2.1 Plik PU - persistence.xml

Plik persistence.xml definiuje PU. Służy do definicji klas trwałych w danym PU, metadanych mapowania dla tych klas oraz inne dane konfiguracyjne dla PU, zarządców utrwalania i ich fabryki. Plik persistence.xml znajduje się w katalogu META-INF korzenia (punktu początkowego) PU.

Informacje o mapowaniu mogą być zdefiniowane w PU przez adnotacje w klasach encji, pliki XML zawarte w korzeniu PU, w plikach XML poza korzeniem PU, jednakże dostępnych na ścieżce klas (ang. classpath) i do których następują odwołania z persistence.xml lub ich kombinacje.

Klasy trwałe mogą być zawarte w ramach korzenia PU lub mogą być wskazane przez PU przez wyszczególnienie trwałych klas, archiwów jar trwałych klas czy plików XML z metadanymi mapowania, które na nie wskazują (ale które są wciąż dostępne na ścieżce klas) lub ich kombinacje.

Element persistence w deskryptorze XML składa się z jednego lub wielu elementów elementów persistence-unit.

Element persistence-unit posiada następujące atrybuty:
  • name - nazwa PU, która musi być unikatowa w ramach korzenia PU. Można odwołać się do niej poprzez adnotacje @PersistenceContext lub @PersistenceUnit oraz poprzez API fabryki zarządcy utrwalania.
  • transaction-type - definiuje rodzaj zarządców utrwalania tworzonych przez fabrykę zarządców dla danego PU: zarządcy JTA - wartość JTA - lub zarządcy lokalnego dla zasobu (kontrolowanego przez zasób, ang. resource-local entity/persistence manager) - wartość RESOURCE_LOCAL. Wartość JTA wymaga, aby źródło danych było zarządzane przez kontener - albo poprzez element jta-data-source lub dostarczane przez kontener. W środowisku Java EE, wartość RESOURCE_LOCAL implikuje użycie źródła danych niezarządzanego (w sensie transakcji) przez kontener. Domyślna wartość w środowisku Java EE to JTA podczas, gdy w Java SE to RESOURCE_LOCAL.
Element persistence-unit może zawierać następujące elementy składowe:
  • description - opis PU
  • provider - definiuje klasę dostawcy JPA implementującą javax.persistence.spi.PersistenceProvider. Brak elementu implikuje wykorzystanie domyślnego dostawcy JPA (np. w GlassFish jest to TopLink Essentials, w Apache Geronimo - Apache OpenJPA, a w JBoss AS - Hibernate)
  • jta-data-source, non-jta-data-source - w środowisku Java EE specyfikuje nazwę w JNDI źródła danych JTA lub nietransakcyjnego. Brak deklaracji wymusza, aby podczas instalacji aplikacji podano źródło danych JTA lub źródło danych JTA musi zostać dostarczony przez kontener, z którym zostanie związana fabryka zarządców utrwalania JTA.
    Nazwa źródła danych jest lokalna dla środowiska uruchomieniowego i specyficzna dla niego.
    W środowisku Java SE użycie elementu (bądź innych sposobów konfiguracji) jest określane przez dostawcę JPA.
  • mapping-file, jar-file, class, exclude-unlisted-classes - służą do deklaracji klas związanych z encjami (klasy trwałe, zanurzane - ang. embeddable - czy mapowane) włączonych explicite do danego PU.
    Zbiór klas trwałych zarządzanych przez dany PU wyznaczany jest przez użycie następujących mechanizmów:
    • pliki mapowania XML
    • pliki jar na ścieżce klas aplikacji
    • podana dosłownie lista klas trwałych
    • klasy udekorowane adnotacjami trwałymi, które zawarte są w punkcie początkowym (korzeniu) PU (jeśli nie korzysta się z elementu exclude-unlisted-classes).

    Plik mapowania XML zawiera informacje o mapowaniu dla klas zawartych w nim. Nazwa pliku to orm.xml i może być umieszczony w katalogu META-INF punktu początkowego (korzenia) PU lub dowolnego pliku jar wskazanego przez persistence.xml. Dodatkowo można wskazać pliki mapowania XML przez elementy mapping-file, które mogą znajdować się w dowolnym miejscy na ścieżce klas. Plik orm.xml i inne związane pliki mapowania XML są ładowane przez dostawcę JPA jako zasoby. Można definiować wiele plików mapowania XML i ich ostateczny wynik dla danego PU będzie ich sumą. Nakładające się konfiguracje mapowania dla tych samych encji z różnych plików w ramach pojedyńczego PU powoduje, że ostateczny wynik wyznaczania konfiguracji utrwalania jest nieokreślony, tj. specyficzny dla dostawcy JPA i może nawet zakończyć się zgłoszeniem błędu.

    Element jar-file wskazuje pliki jar przeszukiwane w celu znalezienia klas trwałych oraz plików mapowania XML. Oprócz jar-file można wskazać klasy trwałe przez element mapping-file. Oba elementy nie wykluczają się, a uzupełniają. Ścieżka plików wskazanych przez jar-file jest względna do punktu początkowego (korzenia) PU. Element jar-file nie ma zastosowania dla PU w środowisku Java SE.

    Lista nazwanych wprost klas trwałych specyfikowana jest przez element class. Klasa wskazana przez element class musi być udekorowana przez adnotację @Entity, @Embeddable lub @MappedSuperclass. W środowisku Java SE zakłada się, że wszystkie klasy trwałe są wskazane przez element(y) class, aby zagwarantować przenośność aplikacji (nie zaleca się polegania na innych mechanizmach odszukiwania trwałych klas w PU). Może się zdarzyć, że dostawca JPA w środowisku Java SE może wymagać, aby wszystkie klasy trwałe były wprost podane w persistence.xml za pomocą elementu class.

    Wszystkie klasy znajdujące się w korzeniu PU są przeszukiwane pod kątem klas trwałych i każda adnotacja będzie brała udział w budowaniu finalnej postaci konfiguracji mapowania (brak adnotacji wskazuje na wykorzystanie wartości domyślnych i również jest konfiguracją). Wykluczenie klas znajdujących się w korzeniu PU z włączenia do PU odbywa się za pomocą elementu exclude-unlisted-classes, co powoduje, że wyłącznie klasy wskazane explicite w persistence.xml będą brane pod uwagę. Nie przewiduje się stosowania elementu exclude-unlisted-classes w środowisku Java SE. Domyślna wartość elementu exclude-unlisted-classes to false, tj. przeszukuj zawierający plik jar lub katalog (innymi słowy cały punkt początkowy PU).

    Lista zarządzanych klas przez PU jest sumą wszystkich wymienionych źródeł. Informacje o mapowaniu pozyskane z adnotacji (bądź ich braku, wtedy polega się na wartościach domyślnych) są nadpisywane przez informacje z pliku mapowania XML (jeśli taki plik istnieje dla danej klasy). Gwarantuje się nadpisywanie wartości przez pliki mapowania XML na poziomie pola lub właściwości trwałej (jest to poziom minimalny, gwarantowany przez specyfikację).

    Klasy i pliki jar wskazane przez konfigurację PU muszą być dostępne na ścieżce klas. Wskazanie ich w pliku persistence.xml nie powoduje ich umieszczenia na ścieżce klas.

    Wszystkie klasy zarządzane (przez dany PU) muszą być umieszczone na ścieżce klas, aby zagwarantować, że zarządcy utrwalania z różnych PU zarządzający klasą trwałą faktycznie będą korzystać z tej samej reprezentacji klasy.
  • properties - służy do zdefiniowania specyficznych dla dostawcy parametrów konfiguracyjnych, dotyczących konfiguracji fabryki zarządcy utrwalania oraz samego PU.

    Jeśli zdefiniowano parametr nieakceptowany przez danego dostawcę JPA (inny niż zdefinowane przez specyfikację) musi być on zignorowany.

    Zaleca się, aby parametry konfiguracyjne specyficzne dla dostawcy były nazwami rozpoczynającymi się od specyficznego dla dostawcy przedrostka, np. openjpa dla Apache OpenJPA, hibernate dla Hibernate, czy toplink dla Toplink Essentials. Parametry rozpocznające się javax.persistence są zarezerwowane dla ustandaryzowanych parametrów konfiguracyjnych JPA.
Jedynie atrybut name elementu persistence-unit jest obowiązkowy. Pozostałe elementy i atrybut transaction-type są opcjonalne.

Rozdział 6.2.1.8 zawiera wiele (aż 5!) przykładowych konfiguracji - plików persistence.xml z wyjaśnionymi konfiguracjami końcowymi.

Ciekawą konfiguracją jest plik persistence.xml zawierający jedynie pojedyńczy element persistence-unit.

<persistence-unit name="OrderManagement"/>

Bardzo skromna definicja PU (literalnie), która jednak może być niezwykle bogata w zasoby. Dowolna udekorowana klasa znaleziona w punkcie początkowym PU będzie dodana do listy klas trwałych. Jeśli istnieje również plik META-INF/orm.xml, wszystkie klasy, które są wymienione w pliku oraz informacje o mapowaniu są brane pod uwagę z uwzględnieniem reguł wymienionych wcześniej. Brak wskazania dostawcy JPA wskazuje na wykorzystanie domyślnego dostawcy w środowisku oraz że konfiguracja nie jest zależna od konkretnego dostawcy. Typ transakcyjny (transaction-type) nie jest zdefiniowany, więc w Java EE zakłada się JTA podczas, gdy w Java SE będzie to RESOURCE_LOCAL. Kontener (w środowisku Java EE) musi dostarczyć źródło danych, np. podczas instalacji PU. W środowisku Java SE źródło danych będzie dostarczone w inny, specyficzny dla środowiska, sposób.

6.2.2 Zasięg PU

Dowolny EJB-JAR, WAR, plik jar klienta aplikacyjnego czy EAR mogą definiować PU.

Odwołując się do PU poprzez element unitName adnotacji @PersistenceContext czy @PersistenceUnit, lub element persistence-unit-name w deskryptorze XML, widoczność PU jest wyznaczona przez jej miejsce definicji (przez umiejscowienie pliku persistence.xml). PU nie wykracza poza ramy definiującego ją pliku jar, tj. EJB-JAR, WAR czy klienta aplikacyjnego i jest widoczna wyłącznie dla komponentów zawartych w nich. W przypadku pliku EAR, zasięg widoczności PU obejmuje wszystkie komponenty wchodzące w skład EARa.

Jeśli PU w EAR istnieje o tej samej nazwie w module WAR, EJB-JAR czy klienta aplikacyjnego, PU z EAR nie jest domyślnie widoczny w module definiującym własną PU z tą samą nazwą chyba, że odwołuje się do nazwy PU poprzez wykorzystanie '#', która pozwala na odwołanie się do innego PU w ramach tego samego EARa. Wskazanie do PU o tej samej nazwie w innym module zawiera ścieżkę względną do modułu odwołującego się, np. ../lib/persistenceUnitRoot.jar#myPU, gdzie myPU jest nazwą PU zadeklarowaną w persistence.xml w punkcie początkowym ../lib/persistenceUnitRoot.jar (a to oznacza, że persistence.xml znajduje się w katalogu ../lib/persistenceUnitRoot.jar!/META-INF). Składnia '#' może być wykorzystana w adnotacjach oraz elemencie persistence-unit-name w deskryptorze XML do odwołania się do PU na poziomie EARa.

Na zakończenie rozdziału 6. przedstawiono definicję (XML Schema) pliku persistence.xml.