Cykl życia encji i wywołania zwrotne

Każdy programista mający styczność z Javą natknął się z całą pewnością z koncepcją ‘cyklu życia’ konkretnego obiektu/komponentu czy ziarna. Nie inaczej jest z encjami JPA. W zależności od dokonywanych na encji operacji mogą zostać wywołane fragmenty kody przypisane danym zdarzeniom (ang. lifecycle callbacks). Dziś o nich napiszę kilka słów.

Cykl życia encji

Cykl życia encji można dość łatwo zapamiętać korzystając ze znanego akronimu – CRUD (Create, Read, Update, Delete). Każda z tych operacji jest tłumaczona na konkretne instrukcje SQL, do nichzaś przypisane są zdarzenia mogące zajść przed oraz po operacji. Jedynym wyjątkiem są tutaj operacje odczytu. Czyli mamy takie zdarzenia:

  • PrePersist i PostPersist – wykonywane w momencie kiedy metoda persist() została wywołana na encji. Zdarzenie PrePersist może też być wywołane kiedy wywołano metodę merge() (na nowej encji), a także w przypadku nowych encji będących w relacji do obiektu, który ma operację kaskadową ustawioną na PERSIST. Z kolei zdarzenie PostPersist zachodzi nawet wtedy kiedy transakcja nie powiodła się (została cofnięta).
  • PreRemove i PostRemove – w momencie wywołania metody remove() odpalamy zdarzenie PreRemove (również w przypadku obiektów będących w relacji oznaczonych relacją kaskadową REMOVE). Kiedy ostatecznie jest wykonywana instrukcja SQL – DELETE wywoływane jest zdarzenie PostRemove. Podobnie jak w poprzednim punkcie wywołanie tego zdarzenia nie oznacza, ze operacja się udała.
  • PreUpdate i PostUpdate – w związku z tym, że nie jesteśmy pewni kiedy zostanie wykonana instrukcja aktualizująca stan encji w bazie, mamy tyle pewności, że PreUpdate zostanie wykonane przed wysłaniem instrukcji SQL do bazy. Sprawa z PostUpdate jest taka sama jak w poprzednich punktach.
  • PostLoad – zdarzenie wywoływane w momencie gdy encja została pobrana z bazy danych i fizycznie utworzona przez JPA. Obejmuje to również wywołanie metody refresh().

Skoro wiemy jakie mamy dostępne zdarzenia wypadałoby dowiedzieć się w jaki sposób możemy je wykorzystać. Wymaga to dwóch rzeczy – stworzenia metody wywoływanej przy okazji zdarzenia (w encji) oraz oznaczenia jej za pomocą adnotacji. Sygnatura tej metody może mieć dowolną nazwę – nie może jednak przyjmować jakichkolwiek parametrów, a także musi zwracać typ void. Metody final oraz statyczne nie wchodzą w grę, wyrzucanie checked exceptions też nie wchodzi w grę.

Metody musimy oznaczyć za pomocą adnotacji, które wskazują w przypadku którego zdarzenia ma być ona wywołana. Dostępne adnotacje znajdują się w pakiecie javax.persistence i odpowiadają nazwom zdarzeń, np: PostRemove. Tylko jedna adnotacja danego zdarzenia może wystąpić w ramach encji! Książka o metodach podpinanych do zdarzeń informuje w ten sposób:

Certain types of operations may not be portably performed inside callback methods. For example, invoking methods on an entity manager or executing queries obtained from an entity manager are not supported, as well as accessing entities other than the one to which the lifecycle event applies. Looking up resources in JNDI or using JDBC and JMS resources are allowed, so looking up and invoking EJB
session beans would be allowed.

 

 

Entity Listeners

 

Do tej pory pisałem o metodach, które znajdują się w konkretnej encji. Co jeśli chcielibyśmy logikę wykonywaną przy okazji zdarzeń uwspólnić i tym samym przenieść ją do innej klasy? Da się to oczywiście zrobić – służą do tego entity listeners. Podobnie jak w przypadku encji możliwe jest użycie w nich tylko raz jednej adnotacji danego zdarzenia. Jednakże każda encja może mieć ‘podpięte’ kilkanaście entity listeners, co pozwala na implementację dość rozbudowanej logiki. Oczywiście w związku z tym, iż metoda zdarzenia znajduje się poza docelową encją, sygnatura metod jest inna – musimy przecież w jakiś sposób dostarczyć do metody encję, na której będziemy operować. Encja ta jest parametrem metody, jej typ zaś musi być typem Object, typem encji lub interfejsem, który encja implementuje. Entity listeners muszą być bezstanowe, gdyż pojedyncze ich instancje mogą być współdzielone przez szereg encji.

Dodanie entity listeners do encji jest proste – dodajemy adnotację @EntityListeners do encji, a jako jej wartość podajemy listę klas, które zawierają interesujące nas metody. W przypadku zajścia zdarzenia dostawca JPA wpierw iteruje po listenerach wymienionych w powyższej adnotacji (w kolejności ich zapisu), a potem dopiero na końcu używa metody zapisanej w encji (o ile istnieje).

 

Dziedziczenie entity listeners

 

Na blogu do tej pory nie poruszyłem tematów związanych z dziedziczeniem encji, a także z mapowaniem wielu encji na jedną tabelę. Jednakże wiedza w tym temacie nie jest aż tak istotna by przedstawić zasady dziedziczenia entity listenerów i metod zdarzeń w encji – wystarczy wiedza o dziedziczeniu w ogóle.

Generalnie zasada jest prosta – deklaracje z klas wyżej w hierarchii są wywoływane przed tymi niżej w hierarchii. Dotyczy to zarówno deklaracji z użyciem adnotacji @EntityListeners jak i metod implementowanych w encjach. Jeżeli jednakże klasa nadrzędna posiada zaimplementowaną metodę obsługującą zdarzenie, wówczas istnieje możliwość jej nadpisania (ang. overriding) w klasie potomnej. Można wyłączyć wykonywanie odziedziczonych listenerów dodając do encji adnotację @ExcludeSuperclassListeners

Advertisements

4 thoughts on “Cykl życia encji i wywołania zwrotne

  1. Mirek Sz

    Z tymi listenerami nie jest tak pięknie.
    Listenery odpalają się w fazie flush. Teraz moja sytuacja: w zdarzeniu PostUpdate uruchamiam HQL jednak hibernate dochodzi do wniosku, musi wykonać flusha i teraz mamy flush in flush to co jest konsekwencją tego to masakra powielenia PK, złe wartości cuda…

  2. chlebik Post author

    A przy jakim sposobie zarządzania transakcją? Container czy application?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s