poniedziałek, 26 maja 2008

EJB3 - interceptory

Jedną z nowości w specyfikacji EJB w wersji trzeciej są interceptory, poprzez które w prosty sposób można zastosować w budowanym systemie paradygmat programowania zorientowanego aspektowo.
Interceptory w EJB3 mogą odnosić się zarówno do cyklu życia ziaren jak i ich metod biznesowych. Implementuje się je w postaci metod. Metody-interceptory mogą znajdować się w klasach ziaren jak i w klasach bazowych tych ziaren, ale także mogą się znajdować w odrębnych klasach (klasy interceptorów). Metody-interceptory mogą mieć dowolny poziom dostępu (public, package, protected, private), ale nie mogą być za to ani static, ani final.
Warto zaznaczyć, że implementacja interceptorów nie jest niczym ograniczona, gdyż można z nich wywoływać JNDI, JMS, inne ziarna, JDBC, oraz EntityManager'a.

1. InvocationContext

Metoda-interceptor do skutecznego działania musi mieć przede wszystkim kontekst wywołania, tj. instancję ziarna, informację o wywoływanej metodzie biznesowej oraz jej parametrów, a także rezultatu wywołania. Te dane można uzyskać poprzez obiekt InvocationContext, który jest przekazywany jako parametr do metody-interceptora.
Na szczególną uwagę zasługuje metoda InvocationContext.proceed(). Jej wywołanie powoduje przejście sterowania do następnego interceptora lub (jeśli jest wywołana w ostatnim interceptorze w łańcuchu) do metody biznesowej ziarna (jeśli jest to interceptor metod biznesowych).

2. Klasa interceptorów

Klasa interceptorów jest zwykłą klasą(nie musi być nawet oznaczona specjalną adnotacją), która po prostu posiada w sobie zdefiniowane metody-interceptory. Klasy te muszą posiadać publiczny bezargumentowy konstruktor.
Cykl życia interceptorów jest ściśle związany z cyklem życia ziarna do którego jest przypisany.
Klasy interceptorów można przypisywać do całego ziarna lub do metod biznesowych ziaren z osobna poprzez adnotację @Interceptors, który jako argument przyjmuje listę klas interceptorów.
W praktyce dołączanie klasy interceptorów wygląda mniej więcej następująco:
@Stateless
@Interceptors(pl.dwalczak.Interceptor1.class)
public class Ziarno {
...
  @Interceptors(pl.dwalczak.Interceptor2.class)
  public void metodaBiznesowa() {
  }
}

3. Interceptory cyklu życia ziaren

Aby dana metoda była interceptorem cyklu życia ziarna należy ją oznaczyć jedną z następujących adnotacji, odpowiednio do pożądanej fazy życia:   - @PostConstruct,   - @PostActivate,   - @PreDestroy,   - @PrePassivate Jeżeli metoda jest zdefiniowana w klasie ziarna, to powinna mieć następującą sygnaturę:
void <METHOD>()
Natomiast, jeżeli znajduje się w klasie interceptorów to powinna ona wyglądać tat:
void <METHOD>(InvocationContext)

4. Interceptory metod biznesowych

Aby dana metoda była interceptorem dla metod biznesowych, należy ją oznaczyć adnotacją @AroundInvoke.
W jednej klasie interceptora, ziarna, bądź klasie bazowej ziarna można tą adnotacją oznaczyć tylko jedną metodę, niemniej nie prowadzi to do ograniczenia, żeby metoda biznesowa ziarna miała tylko jeden interceptor. Wszystkie interceptory zdefiniowane w klasach bazowych, interceptorach podłączonych poprzez adnotację @Interceptors oraz ten zdefiniowany w klasie ziarna będą obowiązywać, a kolejność ich wykonania będzie zgodna z kolejnością ich deklarowania oraz hierarchią dziedziczenia klasy ziarna.
Metody oznaczone adnotacją @AroundInvoke są wykonywane na tym samycm stosie wywołania co metoda biznesowa. Taka metoda powinna zwracać wartość zwróconą przez metodę biznesową, oraz być zdolną do wyrzucenia wyjątku wyrzucanego przez metodę biznesową. Z tego powodu powinna mieć następującą sygnaturę:
Object <METHOD>(InvocationContext) throws Exception
Interceptor aby przekazać sterowanie dalej, tj. do następnego interceptora, bądź metody biznesowej, powinien wywołać metodę proceed na obiekcie InvocationContext.

5. Zasoby pomocnicze

Specyfikacja EJB3
The Interceptor Pattern

sobota, 24 maja 2008

JPA - relacje jeden-do-jednego

W niniejszym artykule przyjrzę się bliżej relacji jeden-do-jednego oraz mapowaniu jej w JPA.
Można powiedzieć, że relacja jeden-do-jednego jest szczególnym przypadkiem relacji jeden-do-wielu/wiele-do-jednego omówionej przeze mnie w artykule JPA - relacje jeden-do-wielu i wiele-do-jednego. Osoby nie zaznajomione z mapowaniem tejże relacji, zachęcałbym aby w pierwszej kolejności przeczytały wspomniany artykuł.
Relacja jeden-do-jednego jest również w pewien sposób związana z pojęciem obiektów wbudowanych omawianych przeze mnie w artykule JPA - obiekty wbudowane (komponenty). Otóż wspomniane obiekty wbudowane stosuje się zamiennie z relacją jeden-do-jednego w celu poprawienia wydajności.

1. Przykład - dziedzina problemu

Przykład do tego artykułu w dużym stopniu został zaczerpnięty z JPA - obiekty wbudowane (komponenty).
A oto jego model obiektowy:
Jest to układ dwóch klas User oraz Address, w którym User agreguje całkowicie Address. Warto dodać, że tego typu wiązanie, w którym istnienie jednego bytu jest całkowicie zależne od istnienia drugiego bytu, jest bardzo powszechne w relacjach jeden-do-jednego. Podobnie jak w relacji jeden-do-wielu/wiele-do-jednego możemy wyróżnić byt nadrzędny(User) oraz byt podrzędny(Address). Ponieważ dla jednego bytu nadrzędnego przypada tylko jeden byt podrzędny, to w celu uzyskania lepszej wydajności (kosztem normalizacji) często stosuje się obiekty wbudowane zamiast jawnej relacji jeden-do-jednego.
W relacyjnym świecie przykładowy model będzie prezentował się następująco:
Powyższy model ukazuje, że implementacja relacji jeden-do-jednego po stronie bazy danych jest bardzo podobna do implementacji relacji jeden-do-wielu/wiele-do-jednego, ale o tym nieco póżniej.

2. Dwukierunkowa relacja jeden-do-jednego

Dwukierunkowa relacja to taka, w której oba obiekty są wzajemnie świadome tego, że są w relacji i z jednego obiektu można nawigować/przejść do drugiego. Zanim jednak przejdę do omawiania mapowania tej relacji, to najpierw chciałbym napisać kilka zdań o bazodanowej realizacji.
Otóż w bazie danych implementuje się relację jeden-do-jednego przez dodanie klucza obcego do jednej z dwojga encji. Jest więc dość podobna do implementacji relacji jeden-do-wielu/wiele-do-jednego, w której to encja podrzędna posiada klucz obcy do encji nadrzędnej. Z tą różnicą, że w tym przypadku nie ma wymagania, żeby encja podrzędna była właścicielem wiązania i może nim być również encja nadrzędna. Przy czym aby faktycznie była to relacja jeden-do-jednego, i nie mogła się zdegenerować do relacji jeden-do-wielu/wiele-do-jednego, należy założyć uniqe constraint'a na kolumnie klucza obcego.
Przy mapowaniu relacji jeden-do-jednego fundamentalną rolę odgrywa adnotacja @OneToOne, którą należy oznaczyć pola-wiązania obiektów będących w tej relacji. Co ciekawe dostępne atrybuty dla tej adnotacji to suma atrybutów dostępnych dla adnotacji @OneToMany i @ManyToOne, co uwypukla podobieństwo relacji jeden-do-jednego do relacji jeden-do-wielu/wiele-do-jednego i jednocześnie uwydatnia wysoką symetrię wiązań w tej relacji.
Opcjonalnie można użyć adnotacji @JoinColumn, która posłuży do specyfikacji klucza obcego - nazwy kolumny oraz określenie unique constraintu na tej kolumnie. Wg. specyfikacji JPA domyślnie kolumna klucza obcego nazywałaby się [nazwa_drugiej_tabeli]_[nazwa_klucza_głównego_drugiej_tabeli] oraz by nie został założony unique constraint przy generowaniu schematu bazy.
Fragment mapowania klasy User:
@Entity
@Table(name="user")
public class User {
 @OneToOne(cascade=CascadeType.ALL, optional=false)
 @JoinColumn(name="addr_id", unique=true)
 private Address address;
 ...
}
Fragment mapowania klasy Address:
@Entity
@Table(name="address")
public class Address {
 @OneToOne(mappedBy="address")
 private User user;
 ...
}
Semantyka atrybutów adnotacji @OneToOne jest taka sama jak dla wspomnianych adnotacjach @OneToMany i @ManyToOne. Powstrzymam się od ponownego ich omawiania gdyż to już zrobiłem w artykule JPA - relacje jeden-do-wielu i wiele-do-jednego.
Warto zauważyć, że powyższe mapowania są poprawne przy założeniu, że właścicielem powiązania jest encja User. Nieco inaczej by jednak wyglądało gdyby właścicielem powiązania była encja Address. Przede wszystkim adnotacja @JoinColumn znaczyłaby by pole Address.user a nie User.address. Analogiczna zamiana spotkałaby również atrybut @OneToOne.mappedBy.

3. Jednokierunkowa relacja jeden-do-jednego

Jednokierunkowa relacja to taka, w której tylko jeden obiekt jest świadomy bycia w relacji z innym obiektem i tylko od niego można nawigować/przejść do drugiego obiektu.
Bazodanowa realizacja jednokierunkowej relacji jeden-do-jednego oraz jej mapowanie jest po części takie same jak w przypadku dwukierunkowej relacji. Z tą różnicą, że mapowanie określamy tylko dla jednej z dwojga encji, która musi być właścicielem wiązania - posiadać klucz obcy do drugiej encji. Co ciekawe przy jednokierunkowej relacji, zamiennie do adnotacji @OneToOne można użyć @ManyToOne - jak ktoś tak bardzo lubi sobie zaciemniać model ;) Co prawda @ManyToOne nie ma możliwości określania atrybutu mappedBy, ale w przypadku jednokierunkowej relacji użycie tego atrybutu i tak byłoby nie możliwe.

4. Zasoby pomocnicze

Specyfikacja JPA
JPA - relacje jeden-do-wielu i wiele-do-jednego
JPA - obiekty wbudowane (komponenty)