09 lipca 2007

Java Persistence - Rozdziały 5.5 Zarządzanie transakcjami oraz 5.6 Konteksty trwałe zarządzane przez serwer

Kolejne rozdziały JPA tym razem dotyczące transakcji, więc nie jest lekko. Wydaje mi się, że temat transakcji zazwyczaj traktowany jest po macoszemu i wielu (wliczając mnie) odetchnęło z ulgą dowiedziawszy się, że w Java EE istnieje możliwość deklaratywnego oznaczania miejsc ich rozpoczęcia. Podobnie ma się sprawa z usługą bezpieczeństwa. Bezpieczeństwo i transakcyjność to dwie z wielu usług, które dostarcza serwer aplikacyjny Java EE i właśnie ich dostępność i prostota konfiguracji i użycia jest argumentem za jego stosowaniem (ponowie uwaga do użytkowników Spring Framework - można zestawić podobny zestaw usług, ale czy warto się tym parać?). Pora rozpoznać temat integracji usług w JPA.

Rozdział 5.5 Zarządzanie transakcjami

W zależności od typu transakcyjnego zarządcy trwałego, transakcje związane z operacjami zarządcy mogą być zarządzane przez JTA albo przez egzemplarz (resource-local) EntityTransaction. Użycie terminu angielskiego - resource-local - lokalny względem zasobu, który uczestniczy w transakcji, idealnie oddaje późniejszą konfigurację jednostki trwałej (PU). O tym później.

Egzemplarz EntityTransaction odpowiada transakcji związanej z zasobem, który z kolei związany jest z encjami zarządzanymi przez zarządcę trwałego.

Zarządca trwały, którego transakcje zarządzane są przez JTA nazywany jest zarządcą trwałym JTA (ang. JTA entity manager).

Zarządca trwały, którego transakcje zarządzane są przez aplikację przez interfejs EntityTransaction nazywany jest lokalnym zarządcą trwałym (ang. resource-local entity manager).

Zarządca trwały zarządzany przez kontener (serwer aplikacyjny Java EE) musi być zarządcą trwałym JTA. Zarządcy trwali JTA są określeni jedynie dla wykorzystania w środowisku serwera aplikacyjnego Java EE.

Zarządca trwały zarządzany przez aplikację może być JTA lub lokalny.

Określenie typu transakcyjnego zarządcy trwałego odbywa się podczas konstruowania fabryki zarządcy trwałego.

W środowisku kontenerów aplikacji internetowych oraz EJB wymagane jest wsparcie dla zarządcy encji JTA oraz lokalnego. W ramach kontenera EJB zazwyczaj wykorzystywany jest zarządca encji JTA. W środowisku Java SE wymagane jest jedynie wsparcie dla lokalnych zarządców encji.

Zarządca encji JTA uczestniczy w transakcji JTA, która rozpoczyna się i jest zatwierdzana zewnętrznie w stosunku do zarządcy encji i jest przekazana do zarządcy zasobów związanego z encjami uczestniczącymi w transakcji.

Lokalny zarządca encji może korzystać z zasobów serwera bądź lokalnych w celu nawiązania połączenia do bazy danych i jest nieświadomy istnienia transakcji JTA, które mogą być aktywne w danej chwili.

Interfejs EntityTransaction dostarcza metody do zarządzania transakcjami w trybie lokalnym. Metoda EntityManager.getTransaction() zwraca interfejs EntityTransaction. W momencie wystąpienia wyjątku, który oznaczony jest jako wycofujący transakcję, lokalny zarządca encji musi oznaczyć transakcję jako wyłącznie do wycofania.

Jeśli wywołanie EntityTransaction.commit() zakończy się wyjątkiem, dostawca JPA musi wycofać transakcję.

Specyfikacja przedstawia opis interfejsu javax.persistence.EntityTransaction.

@Test
public void testEntityTransactionAPI() {
Osoba encja = new Osoba("Jacek", "Laskowski");
try {
em.persist(encja);
// Podczas flush nastąpi przesłanie zmian stanu encji do bazy danych
em.flush();
assert false : "Oczekiwano wyjątku javax.persistence.TransactionRequiredException, gdyż nie rozpoczęto transakcji";
} catch (TransactionRequiredException oczekiwany) {
}
EntityTransaction et = em.getTransaction();
assert et.isActive() == false : "Transakcja nie powinna już być aktywna - nie wywołano begin()";
et.begin();
assert et.isActive() : "Transakcja musi już być aktywna - wywołano begin()";
assert et.getRollbackOnly() == false : "Transakcja musi być w stanie umożliwiającym jej zatwierdzenie";
et.setRollbackOnly();
assert et.getRollbackOnly() : "Transakcja musi być w stanie wykluczającym jej zatwierdzenie";
em.persist(encja);
try {
// Podczas zatwierdzania transakcji nastąpi przesłanie zmian stanu encji do bazy danych
et.commit();
assert false : "Oczekiwano wyjątku javax.persistence.RollbackException, gdyż transakcja oznaczona jedynie do wycofania";
} catch (RollbackException oczekiwany) {
}
}

W sekcji 5.5.3 Example (strona 120) przedstawiono kompletny przykład aplikacji korzystającej z zarządcy encji zarządzanego przez aplikację w trybie lokalnym.

5.6 Kontekst trwały zarządzany przez kontener (serwer)

Użycie zarządcy encji zarządzanego przez kontener nakłada na serwer konieczność zarządzania stadiami rozwojowymi kontekstu trwałego w sposób niezauważalny dla aplikacji. Serwer zarządza transakcjami związanymi z zarządcą za pomocą JTA.

Kontekst trwały zarządzany przez kontener może trwać tak długo jak aktywna jest pojedyńcza transakcja bądź być związany z wieloma transakcjami. Związek między długością życia kontekstu trwałego a transakcjami określany jest przez typ wyliczeniowy javax.persistence.PersistenceContextType, który definiowany jest podczas tworzenia zarządcy trwałego. Specyfikacja odnosi się do tych typów zarządców trwałych jako kontekst trwały pojedyńczej transakcji (ang. transaction-scoped persistence context) lub kontekst trwały rozszerzony (ang. extended persistence context), odpowiednio.

Długość życia kontekstu trwałego jest zadeklarowana używając adnotacji @PersistenceContext lub poprzez element persistence-context-ref w deskryptorze aplikacji. Domyślnie zakłada się użycie PersistenceContextType.TRANSACTION.

@javax.persistence.PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager em;

Wykorzystanie elementu persistence-context-ref w deskryptorze aplikacji - WEB-INF/web.xml lub META-INF/ejb-jar.xml (przykład za specyfikacją Java EE sekcja EE.5.12 Persistence Unit References strona 100).

<persistence-context-ref>
<description>Persistence context for the inventory management application.</description>
<persistence-context-ref-name>persistence/InventoryAppDB</persistence-context-ref-name>
<persistence-unit-name>InventoryManagement</persistence-unit-name>
<persistence-context-type>Extended</persistence-context-type>
</persistence-context-ref>

Wartości elementu persistence-context-type to wartości Transaction lub Extended. Jedynym źródłem informacji o elemencie to dokument dostępny pod adresem http://java.sun.com/xml/ns/javaee/javaee_5.xsd.

5.6.1 Kontekst trwały zarządzany przez kontener pojedyńczej transakcji

Kontekst trwały zawsze związany jest z fabryką zarządców encji.

Aplikacja może uzyskać dostęp do zarządcy encji zarządzanego przez kontener, pojedyńczej transakcji, w trybie JTA (ciekawie prezentuje się opis w języku angielskim - a container-managed entity manager with transaction-scoped persistence context bound to the JTA transaction) poprzez mechanizm DI lub odszukanie w JNDI.

Zarządca encji pojedyńczej transakcji jest założony domyślne (w przypadku braku elementu type adnotacji @PersistenceContext) bądź zdefiniowany przez PersistenceContextType.TRANSACTION.

Nowy kontekst trwały rozpoczyna się, podczas pierwszego wywołania operacji na zarządcy encji (interfejs EntityManager) zarządzanym przez kontener w zasięgu aktywnej transakcji JTA i tylko wtedy, kiedy nie istnieje zarządca trwały już związany z aktywną transakcją. Kontekst trwały jest tworzony i związywany natychmiast z aktywną transakcją JTA.

Kontekst trwały kończy się, podczas zakończenia związanej transakcji JTA (zatwierdzenie bądź wycofanie) i kiedy wszystkie encje związane z danym zarządcą trwałym, z którym związany jest kontekst, przejdą w stan odłączony (ang. detached).

Jeśli zarządca trwały jest wywołany poza zasięgiem aktywnej transakcji, encja utworzona na podstawie danych z bazy danych przechodzi natychmiast w stan odłączony.

5.6.2 Rozszerzony kontekst trwały zarządzany przez kontener (serwer)

Rozszerzony kontekst trwały zarządzany przez kontener może być jedynie utworzony w ramach stanowego ziarna sesyjnego EJB. Kontekst istnieje od momentu, w którym utworzone zostanie ziarno, które deklaruje zależność od zarządcy encji typu PersistenceContextType.EXTENDED i określa się go mianem przypisany (ang. bound) do stanowego ziarna sesyjnego EJB. Zależność na rozszerzonym kontekście trwałym jest deklarowana poprzez adnotację @PersistenceContext lub element persistence-context-ref w deskryptorze.

Kontekst trwały jest zamykany po pomyślnym wywołaniu metody stanowego ziarna EJB udekorowanej przez adnotację @Remove lub jeśli egzemplarz ziarna zostanie zniszczony w inny sposób, np. niektóre metody zakończone wyjątkiem są sygnałem dla kontenera o bezużyteczności egzemplarza ziarna, które są następnie niszczone.

Jeśli stanowe ziarno tworzy (w dowolny sposób - DI lub JNDI) inne stanowe ziarno, które ma zadeklarowaną zależność od rozszerzonego kontekstu trwałego, rozszerzony kontekst trwały pierwszego ziarna jest dziedziczony przez (przekazane do) drugiego ziarna i zostaje przypisane do niego, i tak kolejno do następnych ziaren stanowych EJB bez względu na istnienie aktywnej transakcji w trakcie tworzenia ziaren (dla przypomnienia utworzone w ten sposób encje na bazie danych z bazy danych są natychmiast w stanie odłączony).

Jeśli kontekst trwały został odziedziczony przez ziarna stanowe EJB, kontener zamknie kontekst dopiero, kiedy wszystkie ziarna zostaną usunięte (z potencjalnym wywołaniem metody @Remove) lub w inny sposób zniszczone.

5.6.3 Przekazywanie kontekstu trwałego

Pojedyńczy kontekst trwały może odpowiadać jednemu lub wielu egzemplarzom zarządców encji JTA (związanym z tą samą fabryką zarządców encji - zarządcy encji pochodzący z różnych fabryk zarządców encji nigdy nie dzielą tego samego kontekstu trwałego).

Kontekst trwały jest przekazywany do egzemplarzy zarządców encji wraz z przekazywaniem transakcji JTA.

Przekazywanie kontekstów trwałych ma miejsce wyłącznie w ramach środowiska lokalnego. Konteksty trwałe nie są przekazywane do zewnętrznych (zdalnych) warstaw.

5.6.3.1 Wymagania związane z przekazywaniem kontekstu trwałego

Przy braku aktywnej transakcji JTA wywołanie komponentu nie wiąże się z przekazaniem kontekstu trwałego. Wywołanie zarządcy encji pojedyńczej transakcji (PersistenceContextType.TRANSACTION) z poziomu tego komponentu wiąże się ze stworzeniem nowego kontekstu trwałego. Wywołanie rozszerzonego zarządcy encji (PersistenceContextType.EXTENDED) wiąże się z użyciem istniejącego rozszerzonego zarządcy encji związanego z tym komponentem. Jeśli zarządca encji jest wywołany w ramach transakcji JTA, kontekst trwały zostanie z nią związany.

Przy wywołaniu komponentu, podczas przekazania transakcji JTA do niego, wyróżniamy 2 sytuacje. Jeśli komponent to stanowe ziarno sesyjne EJB, z którym związany jest rozszerzony kontekst trwały oraz istnieje inny kontekst trwały związany z transakcją JTA, zostanie rzucony wyjątek javax.ejb.EJBException. W przeciwnym przypadku, jeśli istnieje kontekst trwały związany z transakcją JTA, nastąpi przekazanie i korzystanie z tego kontekstu trwałego.

Dla mnie ostatnie sekcje specyfikacji, szczególnie ta związana z przekazywaniem kontekstu trwałego, pozostawia wiele pytań do odpowiedzi. Czas odszukać odpowiedzi w książkach o JPA. Wracam do Pro EJB 3 - Java Persistence API z Apress. Po 2 otwierających rozdziałach książka wydaje się być napisana lekko i przyjemnie.