W tym artykule chciałbym podzielić się swoim doświadczeniem w ramach konfiguracji projektu typu Web wykorzystującego Spring, SmartGWT, Hiberante. Całość konfiguracji w zamierzeniu ma być budowana przez Maven-a. Zakładam, że do developmentu używane będzie środowisko Eclipse Helios SR 2. Dlatego zacznijmy od konfiguracji środowiska projektowego.
Moduł warstwy web komunikuje się z modułem serwisów w celu wykonywania akcji po stronie serwera. Nie ma on dostępu do modułu domenowego, gdyż moduł serwisów izoluje dostęp do warstwy domenowej.
Zajmijmy się teraz konfiguracją modułów. W tym celu utworzyć należy strukturę plików tak jak zostało to przedstawione na rysunku obok.
Najważniejsze w tym momencie są pliki pom.xml, czyli pliki konfiguracyjne Mavena. Teraz zajmę się ustawieniami w tych plikach. Dobrze, główny plik konfiguracyjny (root) znajdujący się na poziomie katalogów m2-domain, m2-service, m2-web wygląda następująco:
Co widać w tym pliku to ustawienia dotyczące wersji bibliotek używanych w ramach aplikacji, definicję modułów z których składa się aplikacja oraz definicja repozytoriów dla bibliotek.
Następnie konfigurujemy plik pom.xml dla modułu domenowego:
Kontekst persystencji encji JPA jest zasobem, który jest skojarzony z transakcją. Tym sposobem, kontekst persystencji jest propagowany w ramach wywołania metod tak, że encje w jednostce persystencji (persistence unit) widzą swoje stany pośrednie, w ramach ich wspólnego kontekstu persystencji, gdy są one skojarzone z tym samym kontekstem transakcji. Również, ograniczenie polegające na tym, że jedynie jeden kontekst persystencji dla danej jednostki persystencji musi być skojarzony z określonym kontekstem transakcji zapewnia, że dla jakiejkolwiek encji typu T z identyfikatorem I, jej stan będzie reprezentowany przez jedynie jeden kontekst persystencji wewnątrz danego kontekstu transakcji. Wewnątrz pojedynczego wątku aplikacji w danym momencie jest dostępny jedynie jeden kontekst transakcji. Serwer EJB jest uprawniony do tego by odłączyć pojedynczy kontekst persystencji od tego wątku i skojarzyć nowy kontekst persystencji z tą samą jednostką persystencji by zapewnić ograniczenia izolacji transakcji. Gdy serwer EJB wykona to, nowo utworzony kontekst persystencji nie jest w stanie zobaczyć zmian pośrednich wykonanych na encjach skojarzonych z uśpionym (suspended) kontekstem perystencji.
W ramach specyfikacji EJB3 możliwe są trzy strategie mapowania dziedziczenia: 1. SINGLE_TABLE - jedna tabela, która zawiera wszystkie pola z wszystkich klas w hierarchii 2. JOINED - tabela na każdą klasę w hierarchii. Tabele dla podklas zawierają jedynie te pola, które są specyficzne dla danej klasy. 3. TABLE_PER_CLASS - tabela na każdą konkretną klasę (nie abstrakcyjną), gdzie każda tabela zawiera wszystkie pola zdefiniowane w super-klasie. Strategia ta jest opcjonalna w specyfikacji EJB3.0.
Stany charakteryzujące instancję encji: • NEW - instancja encji nie ma identyfikatora perystencji i nie jest skojarzona z kontekstem persystencji • MANAGED - instancja encji ma identyfikator persystencji i jest skojarzona z bieżącym kontekstem persystencji • DETACHED - instancja encji ma identyfikator persystencji i nie jest skojarzona z kontekstem persystencji • REMOVED - instancja encji ma identyfikator persystencji i jest skojarzona z kontekstem persystencji i dodatkowo jest zaplanowana do usunięcia z bazy danych.
Efekty operacji związanych z cyklem życia encji opisano poniżej. 1. Perystencja instancji encji - nowa instancja encji staje się zarządzana (managed) oraz persystentna (peristent) poprzez wywołanie metody persist na niej lub w wyniku kaskadowej operacji persystencji.
Znaczenie operacji persist: • gdy encja jest w stanie NEW staje się zarządzana (MANAGED). Encja zostanie zapisana w bazie w trakcie komita transakcji lub jako rezultat wykonania flush. • gdy encja jest w stanie MANAGED jest ignorowana w trakcie operacji. • gdy encja jest w stanie REMOVED staje się MANAGED. • gdy encja jest obiektem odłączonym może być wyzwolony wyjątek EntityExistsException w przypadku wywołania operacji lub EntityExistsException lub PersistenceException w trakcie flush lub commit • dla wszystkich encji (nazwijmy je Y) do których odwołuje się poprzez relacje dana encja, jeżeli relacja do tych encji jest oznaczona jako kaskadowa z cascade=PERSIST lub cascade=ALL operacja jest stosowana do encji skojarzonych (Y).
2. Usunięcie - zarządzana (managed) instancja encji staje się usuniętą (removed) poprzez wywołanie metody remove lub w wyniku wykonania operacji kaskadowej.
Znaczenie operacji remove: • gdy encja jest w stanie NEW operacja jest ignorowana, ale jest propagowana do encji w relacji • gdy encja jest w stanie MANAGED operacja powoduje przejście do stanu REMOVED • gdy encja jest odłączona operacja powoduje wyrzucenie wyjątku IllegalArgumentException • gdy encja jest w stanie REMOVED operacja jest ignorowana • faktyczne usunięcie encji z bazy danych następuje po wykonaniu comit lub po wykonaniu flush-a
3. Synchronizacja z bazą danych - stan encji persystentnych jest synchronizowany (synchronized) z bazą danych po zakomitowaniu transakcji. Ta synchronizacja polega na zapisie do bazy danych jakichkolwiek aktualizacji na encji i jej relacjach. Aktualizacja stanu encji zawiera przypisanie nowych wartości do właściwości lub pól persystentnych encji oraz modyfikację zmienialnych wartości dla właściwości i pól persystentnych. Synchonizacja z bazą danych nie zawiera odświeżenia jakichkolwiek zarządzanych encji dopóki operacja odświeżenia nie jest eksplicite wywołana na tych encjach.
Jest ważnym by zapewnić, że zmiany dokonane w części odwrotnej relacji powodowały odpowiednie zmiany w części właściwej, tak by zapewnić by zmiany nie zostały utracone po synchronizacji z bazą danych.
Silnik dostawcy persystencji jest uprawniony do wykonywania synchronizacji z bazą danych w trakcie, gdy transakcja jest aktywna. Można użyć metody flush w celu wymuszenia synchronizacji z bazą danych przez aplikację. Synchronizacja stosuje się do encji skojarzonych z kontekstem persystencji.
4. Encje odłączone (Detached entities) - odłączenie encji może nastąpić w wyniku zakomitowania transakcji gdy używany jest zarządca encji (entity manager) typu transaction-scoped container-managed, zrolbekowania transakcji, wyczyszczenia kontekstu persystencji, zamknięcia zarządcy encji oraz serializacji encji lub przekazania encji przez wartość do separowanej warstwy aplikacji przy użyciu interfejsu zdanego.
Merging Detached Entity State: operacja łączenia (merge) dopuszcza propagację stanu z encji odłączonych do encji persystentnych zarządzanych przez EntityManager-a.
5. Instancje zarządzane - odpowiedzialność aplikacji za zapewnienie, że instancja jest zarządzana w dokładnie jednym kontekście persystencji. Zachowanie nie jest zdefiniowane jeżeli ta sama instancja jest przypisywana do zarządzania w więcej niż pojedynczym kontekście persystencji.
W celu stwierdzenia czy dana instancja encji jest zarządzana w bieżącym kontekście persystencji można użyć metody contains.
contains zwraca true: • gdy encja została pobrana z bazy danych i nie została usunięta i odłączona, • gdy instancja encji jest nową i została na rzecz encji wykonana metoda persist contains zwraca false: • gdy instancja jest odłączona • gdy metoda remove została wywołana na rzecz encji lub wystapiło usunięcie kaskadowe • gdy instancja jest nową i nie została jeszcze wywołana metoda persist na rzecz encji.
Adnotacja Version specyfikuje pole lub właściwość typu wersja dla encji i służy jako wartość optymistycznego lokowania. Wersja jest używana w celu zapewnienia integralności w przypadku wykonywania operacji łączenia (merge) i kontroli współbieżności typu optymistycznego.
Reguły dotyczące pól / właściwości typu Version: 1. Jedynie jedna właściwość lub pole dla klasy może być oznaczona adnotacją Version. 2. Właściwość Vesion musi być mapowane do głównej tabeli dla klasy encji. 3. Właściwości typu Version nie powinny być aktualizowane bezpośrednio przez aplikację. 4. Pola następującego typu mogą być oznaczone przez Version: int, Integer, short, Short, long, Long, Timestamp 5. Operacje grupowe (bulk operations) nie respektują atrybutu Version. Tego typu operacje aktualizują poprostu wiersze w bazie danych, niezależnie od wartości wersji. Ponadto, zmiany wprowadzone przez aktualizacje nie są uwzględniane w encjach istniejących w kontekście persystencji.
W przypadku pola lub właściwości innej niż właściwość relacyjna nie oznaczonej adnotacją mapującą zdefiniowaną w rozdziale 9 specyfikacji ejb-3_0-fr-spec-persistance (lub w przypadku braku odpowiadającej specyfikacji informacji mapującej w pliku deskryptora XML) stosowane są następujące reguły domyślnego mapowania: • Jeżeli typ jest klasą, która jest oznaczona adnotacją Embeddable, następuje mapowanie w ten sam sposób jak w przypadku pola lub właściwości oznaczonej jako Embedded • Jeżeli typ pola lub właściwości jest jednym z podanych, następuje mapowanie w ten sam sposób jak gdyby pole oznaczone było adnotacją Basic: prymitywne typy Java, wrappery na typy prymitywne, java.lang.String, java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, byte[], Byte[], char[], Character[], enums, lub jakakolwiek klasa oznaczona jako Serializable.
W przypadku, gdy żadna z powyższych reguł nie zachodzi i pole lub właściwość nie zostało oznaczone żadną adnotacją zgłoszony zostanie błąd.
Encja może używać innych drobnoziarnistych klas do reprezentowania jej stanu. Instancje tych klas, w przeciwieństwie do samych instancji encji nie posiadają identyfikatora persystencji. Zamiast tego, istnieją wyłącznie jako obiekty osadzone w encji, do której należą. Takie osadzone obiekty należą wyłącznie do encji, w których zostały osadzone i nie są współdzielone z innymi encjami. Próba współdzielenia obiektów osadzonych w ramach encji ma niezdefiniowaną semantykę. Ponieważ takie obiekty nie mają persystentnej identyfikacji, są zwykle mapowane wspólnie z instancją encji, do której należą.
Klasy osadzone muszą być zgodne z wymaganiami dla encji określonymi w pkt 2.1 specyfikacji (ejb-3_0-fr-spec-persistance) z wyjątkiem takim że, klasy osadzone nie są oznaczone adnotacją Entity. Klasy osadzone muszą być oznaczone adnotacją Embeddable lub oznaczone jako takie w deskryptorze XML. Typ dostępu do osadzonego obiektu określa typ dostępu dla encji, w której obiekt jest osadzony. Specyfikacja wymaga obsługi tylko jednego poziomu osadzania.
Adnotacja Embeddable jest używana w celu specyfikacji klasy, której instancje są przechowywane jako nieodłączna część encji i współdzielą identyfikację z tą encją. Każda z perystentnych właściwości lub pól obiektu osadzonego jest mapowana do tabeli bazy danych odpowiadającej encji. Mapowane adnotacje Basic, Column, Lob, Temporal i Enumerated mogą być jedynie przenośnie użyte do mapowania pól lub właściwości persystentnych klasy oznaczonej jako Embeddable.
W celu specyfikacji pola lub właściwości encji, której wartość jest instancją klasy osadzonej należy użyć adnotacji Embedded. Adnotacje AttributeOverride i / lub AttributeOverrides mogą być użyte w celu przykrycia mapowania kolumn zadeklarowanych wewnątrz klasy osadzonej, które są mapowane do tabeli skojarzonej z encją. Nie jest wymagane od implementacji wsparcie dla obiektów osadzonych mapowanych na więcej niż jedną tabelę. Użycie adnotacji Embedded dla pola lub właściwości jest tożsame z użyciem pola lub właściwości klasy oznaczonej jako Embeddable.
Kontekst persystencji (Persistence context) istnieje od momentu utworzenia Entity Manager-a przy użyciu metody EntityManagerFactory.createEntityManager do czasu zamknięcia Entity Manager-a, czyli do czasu wywołania metody EntityManager.close. Rozszerzony kontekst persystencji pobrany z Entity Manager-a zarządzanego przez aplikację jest niezależnym kontekstem persystencji to znaczy, że nie jest on propagowany wraz z transakcją.
Kontekst persystencji istnieje niezależnie od transakcji, jednakże zazwyczaj w tym samym czasie co transakcja. Entity Manager jest zwykle tworzony zaraz po rozpoczęciu transakcji, a kończy swoje działanie po powrocie z metody (lub w przypadku manager-a zarządzanego przez aplikację) po wywołaniu EntityManager.close.
Mapped Superclass oznacza klasę, dla której informacja o mapowaniu jest wprowadzana do encji, które z niej dziedziczą. Dla klasy tego typu nie ma zdefiniowanej oddzielnej tabeli. Klasa oznaczona adnotacją MappedSuperclass może zostać zmapowana w ten sam sposób jak encja z wyjątkiem tego, że wprowadzone mapowania będą miały znaczenie jedynie w jej podklasach. Takie zachowanie jest uwarunkowane tym, że dla klasy Mapped Superclass nie istnieje skojarzona tabela. W podklasach, odziedziczone z Mapped Superclass mapowania zostają wprowadzone w kontekście tabel zdefiniowanych dla tych podklas. Odziedziczone informacje o mapowaniu mogą zostać przykryte w takich podklasach przy użyciu adnotacji AttributeOverride i AssociationOverride lub odpowiadających im elementów z XML-a.
EJB3 dostarcza wbudowane usługi do zarządzania transakcjami, które są domyślnie dostępne w bean-ach sesyjnych oraz bean-ach typu MDB (Message Driven Bean). Kontener wytycza granice transakcji oraz automatycznie rozpoczyna i zatwierdza (commit) transakcje w oparciu o deklaratywne metadane dostarczone przez twórcę bean-a.
W przypadku, gdy jakiś EJB deklaruje w metadanych swoje zachowanie jako transakcyjne, kontener ingeruje w wywołania metod bean-a i wprowadza zachowanie transakcyjne w obrębie metod bean-a sesyjnego. Może zostać wyspecyfikowany dla każdej metody jeden stały zestaw opcji. Domyślnym zachowaniem dostarczanym przez kontener jest weryfikacja, bezpośrednio przed wywołaniem metody, czy kontekst transakcyjny jest skojarzony z bieżącym wątkiem. Jeżeli wyżej wymieniony kontekst nie jest dostępny, kontener rozpoczyna nową transakcję bezpośrednio przed wywołaniem metody. Jeżeli natomiast, kontekst transakcyjny jest dostępny, kontener propaguje bieżącą transakcję w wywołaniu metody a także dopuszcza jej dostępność w kodzie metody. Następnie, po powrocie z wywołania metody, kontener wykonuje następną weryfikację. W wyniku ponownej weryfikacji, jeżeli kontener był odpowiedzialny za utworzenie nowego kontekstu transakcyjnego, automatycznie zatwierdza transakcję po wyjściu z metody (lub gdy wyrzucony został wyjątek przez metodę, odwraca (rollbak) transakcję którą rozpoczął). W przypadku, gdy kontener nie był twórcą transakcji, pozwala na kontynuowanie transakcji bez jej naruszania. Poprzez ingerencję w wywołania metod bean-a, kontener EJB ma możliwość wprowadzenia zachowania transakcyjnego w runtime-mie, na bazie tego co zostało wyspecyfikowane deklaratywnie w trakcie tworzenia (developmentu).
Domyślne zachowanie opisane wyżej jest określane poprzez specyfikację atrybutu transakcji na REQUIRED. Developer ma możliwość określenia atrybutu transakcji oprócz wyżej wymienionego, na sześć różnych sposobów:
MANDATORY – transakcja jest konieczna; błąd, gdy nie ma bieżącej transakcji podczas wywołania metody
REQUIRED – transakcja jest wymagana; gdy nie ma bieżącej transakcji tworzona jest automatycznie nowa
REQUIRES_NEW – wymagana nowa transakcja; nawet gdy podczas wywołania jest bieżąca transakcja jest ona usypiana i tworzona jest zupełnie nowa na rzecz wywołania metody
SUPPORTS – transakcja może być ale nie musi
NOT_SUPPORTED – transakcja nie jest wspierana; gdy istnieje bieżąca transakcja jest usypiana, a wywołanie odbywa się bez kontekstu transakcyjnego
NEVER – transakcja jest niedopuszczalna; gdy istnieje bieżąca transakcja zostaje zwrócony błąd
Wszystkie sześć atrybutów jest typowo dostępne dla metod bean-a sesyjnego, tym niemniej pewne atrybuty nie są dostępne w metodzie będącej timeout callback-iem, lub gdy bean sesyjny implementuje interfejs javax.ejb.SessionSynchronization. W bean-ach typu MDB dostępne są jedynie atrybuty REQUIRED oraz NOT_SUPPORTED.
JPA (Java Persistence Architecture), która jest częścią specyfikacji EJB 3.0, służy uproszczeniu tworzenia aplikacji EJB wykorzystujących persystencję danych. Podstawowym uproszczeniem wprowadzonym przez EJB 3.0 było wprowadzenie interfejsu EntityManager w celu dostępu do elementów bazy danych oraz tworzenia, usuwania lub aktualizacji encji w transakcji. Encje są obiektami, które reprezentują dane w bazie. Są one persystentne w takim sensie, że pozostają nawet po zakończeniu działania aplikacji, która je utworzyła. W celu wsparcia persystencji encji zarządca encji (entity manager) współdziała z kontekstem persystencji (persistence context), który definiowany jest przez Java Persistence Architecture jako:
Zestaw instancji encji zarządzanych (managed), w którym dla jakiegokolwiek identyfikatora encji występuje unikalna instancja encji. Wewnątrz kontekstu persystencji, instancje encji oraz ich cykl życia są zarządzane przez zarządce encji (entity manager).
Java Persistence Architecture określa również, że container-managed persistence context, który jest kontekstem persystencji, którego cykl życia jest zarządzany automatycznie przez kontener, może być zdefiniowany tak by miał albo cykl życia który jest ograniczony to pojedynczej transakcji albo rozszerzony cykl życia który rozpięty jest na wiele transakcji. Zakres cyklu życia zależy od typu określonego w klasie PersistenceContextType w trakcie tworzenia zarządcy encji (entity manager).
W przypadku, gdy container-managed persistence context jest ograniczony do pojedynczej transakcji, kontekst persystencji kończy się wraz z zatwierdzeniem lub wycofywaniem (commit & rollback) skojarzonej transakcji przez Java Transaction API. Dla porównania container-managed persistence context, który ma rozszerzony cykl życia zaczyna się, gdy jest tworzony stanowy bean sesyjny i kończy się wyłącznie, gdy w/w bean jest usuwany z kontenera. Przyjmuje się, że rozszerzony kontekst persystencji (extended persistence context) skojarzony jest ze stanowym beanem sesyjnym. Należy zwrócić uwagę, że jedynie stanowe beany sesyjne mogą posiadać rozszerzonego zarządcę persystencji zarządzanego przez kontener (container-managed, extended entity manager).
Jedną z istotnych różnic pomiędzy kontekstem persystencji ograniczonym do transakcji (transaction-scoped) i rozszerzonym kontekstem persystencji jest stan encji po zakończeniu transakcji. W przypadku transakcyjnego kontekstu persystencji encje zostają odłączone (detached), to jest, nie są dłużej zarządzane, natomiast w przypadku rozszerzonego kontekstu persystencji encje pozostają zarządzane (mamaged). Innymi słowy część operacji związanych z modyfikacją (persist(), merge(), remove()) może być wykonanych poza transakcją i są one kolejkowane i zatwierdzane (komitowane) gdy kontekst persystencji jest kojarzony (attached) z transakcją (i kiedy są zatwierdzane przez commit). Część operacji nie może być wykonana poza transakcją i są to operacje flush(), lock() oraz zapytania typu update/delete.
Wyżej wymienione zagadnienia powodują, że rozszerzony kontekst persystencji jest idealnym rozwiązaniem dla modelowania konwersacji z użytkownikiem, który rozciąga się na wiele interakcji. Podobne zachowanie można osiągnąć używając transakcji zarządzanej przez bean-a (bean-managed transaction), która rozciąga się na całą konwersację użytkownika. W tym przypadku transakcja rozpoczynana jest poprzez użycie metody UserTransaction.begin(). Jednakże to alternatywne podejście jest bardziej skomplikowane niż wykorzystanie rozszerzonego kontekstu persystencji.
Domyślnym typem kontekstu persystencji jest kontekst transakcyjny. W celu wstrzyknięcia EntityManager'a z wyspecyfikowanym rozszerzonym kontekstem persystencji, należy wyspecyfikować dyrektywę wstrzyknięcia w następujący sposób (lub zdefiniować element persistence-context-ref w deskryptorze XML):