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)

niedziela, 27 kwietnia 2008

JPA - relacje jeden-do-wielu i wiele-do-jednego

W niniejszym artykule chciałbym zaprezentować mapowanie relacji jeden-do-wielu i bliźniaczą do niej relację wiele-do-jednego. W tego typie relacji możemy rozróżnić byty na jeden byt nadrzędny - po jednej stronie relacji i dowolną ilość bytów podrzędnych - po drugiej stronie relacji.
Relacja ta jest o tyle szczególna, że w świecie relacyjnym jej implementacja jest punktem wyjściowym do implementowania relacji jeden-do-jednego i wiele-do-wielu.
Rozróżniamy następujące warianty relacji tego typu:
  - dwukierunkową relacje jeden-do-wielu/wiele-do-jednego: obiekt nadrzędny posiada kolekcję referencji do obiektów podrzędnych, które posiadają referencję do obiektu nadrzędnego;
  - jednokierunkowa relacja jeden-do-wielu: obiekt nadrzędny posiada kolekcję referencji do obiektów podrzędnych;
  - jednokierunkowa relacja wiele-do-jednego: obiekt podrzędny posiada referencję do obiektu nadrzędnego;

1. Przykład - dziedzina problemu

Tradycyjnie w celach pomocniczych posłużę się mało skomplikowanym przykładem. A oto jego model obiektowy:

Jest to układ dwóch klas Osoba oraz Kontakt. Ta ostatnia reprezentuje dane kontaktowe osoby takie jak nr telefonu, czy też adres email. Osoba agreguje całkowicie Kontatk'y (wiązanie typu kompozycja), co dodatkowo uwidocznia nadrzędność jednego bytu do drugiego. Oczywiście omawiana relacja dotyczy się również wiązań typu asocjacja jak i zwykłej agregacji, jednak wybrałem kompozycję, gdyż w modelu relacyjnym będzie się to wiązało z dodatkowym ograniczeniem not null.
W relacyjnym świecie przykładowy model będzie prezentował się następująco:

2. Dwukierunkowa relacja wiele-do-jednego/jeden-do-wielu

Po modelu obiektowym, w którym występują połączeniach między obiektami, bardzo często oczekujemy możliwości dwukierunkowej "nawigacji" między powiązanymi obiektami. W naszym przykładzie oznacza to, że oczekujemy możliwości przejścia od obiektu Osoba do powiązanych z nią obiektów Kontakt i odwrotnie.
Aby sprostać tym wymaganiom musimy odpowiednio mapować obie te klasy.
2.1. Mapowanie klasy Kontakt
Encja Kontakt jest właścicielem powiązania, gdyż posiada klucz obcy do encji Osoba.
@Entity
@Table(name="kontakt")
public class Kontakt {
 ... 
 private Osoba osoba;

 @ManyToOne(optional=false)
 @JoinColumn(name="oso_id") // domyslnie kolumna by sie nazywala osoba_oso_id
 public Osoba getOsoba() {
  return osoba;
 }
}
JPA wymaga jedynie, żeby w obiekcie podrzędnym oznaczyć referencje do obiektu nadrzędnego za pomocą adnotacji @ManyToOne. Ja jednak dodatkowo nadpisałem pewne domyślne ustawiania. I tak:
  - ustawiłem atrybut optional=false tej adnotacji, co oznacza, że referencja do Osoba musi być ustawiona, co zostanie odpowiednio wyrażane podczas automatycznego generowania schematu bazy danych przez dodanie ograniczenia not null;
   - poprzez adnotację @JoinColumn ustawiłem nazwę kolumny, która jest kluczem obcym do tabeli Osoba. Domyślnie by została użyta kolumna o nazwie [nazwa_tabeli_nadrzędnej]_[nazwa_klucza_głównego_tabeli_nadrzędnej];
2.2. Mapowanie klasy Osoba
@Entity
@Table(name="osoba")
public class Osoba {
 ...
 private List<Kontakt> kontakty;

 @OneToMany(cascade={CascadeType.PERSIST, CascadeType.REMOVE}, mappedBy="osoba")
 @Cascade( {org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
 public List<Kontakt> getKontakty() {
  return kontakty;
 }
 
 public void dodajKontakt(Kontakt kontakt) {
  if (null == kontakty) {
   kontakty = new ArrayList<Kontakt>();
  }
  kontakt.setOsoba(this);
  kontakty.add(kontakt);
 }
}
Dla obiektu nadrzędnego jest wymagane, aby oznaczyć kolekcje do referencji obiektów podrzędnych za pomocą adnotacji @OneToMany. Ponieważ jest to wiązanie dwukierunkowe i mapowanie do modelu relacyjnego zostało już określone w klasie Kontakt, to należy wyrazić to poprzez atrybut mappedBy tej adnotacji, któremu należy przypisać wartość będącą nazwą referencji (w obiekcie podrzędnym) do obietku nadrzędnego. Z atrybutu mappedBy należy skorzystać nie tylko, żeby nie musieć podwójnie określać fizycznych parametrów modelu relacyjnego, ale przede wszystkim dlatego, żeby uniknąć zdublowanych zapisów do bazy danych. Atrybut mappedBy jest niedostępny dla adnotacji @ManyToOne.
Poza tym mapowanie jest wyposażone w parametry określające działania kaskadowe, ale o nich napiszę trochę więcej przy omawianiu działań utrwalania i usuwania obiektów.
2.3. Utrwalanie obiektów w bazie danych
Utrwalania obiektów może być wykonywane w sposób rozdzielny lub kaskadowy.
Schemat rozdzielnego utrwalania obiektów w naszym przykładzie wyglądałby następująco. Najpierw utworzenie i zapis obiektu Osoba:
Osoba o = new Osoba();
em.persist(o);
Następnie utworzenie, powiązanie z osobą i zapis obiektu Kontakt:
Kontakt k = new Kontakt("nr tel.", "+48123124234");
o.dodajKontakt(k);
em.persist(k);
Tu zwrócę uwagę, że metoda dodajKontakt dodaje kontakt do kolekcji kontakty w obiekcie o jak i ustawia referencję osoba w obiekcie k.
Jeżeli chcemy utrwalić obiekt Kontakt w następnej transakcji, to trzeba mu ustawić odpowiednio referencje do obiektu Osoba. Przy założeniu, że będziemy dysponować tylko id osoby, to można wykonać następujące działania:
Kontakt k = new Kontakt("GG", "124234");
Osoba o = em.getReference(Osoba.class, osobaId);
k.setOsoba(o);
em.persist(k);
Dodam tylko, że manualne stworzenie instancji Osoba i ustawienie pola id nie zadziała.

Utrwalanie kaskadowe obiektów. Często istnieje potrzeba utrwalania całego drzewa obiektów, tj. obiektu nadrzędnego - korzenia drzewa (Osoba) i powiązanych z nim obiektów podrzędnych (Kontakt). JPA umożliwia zrobienie tego poprzez wykonanie operacji utrwalania na obiekcie nadrzędnym. Przy czym należy pamiętać o ustawieniu atrybut cascade na CascadeType.PERSIST w mapowaniu - tak jak zostało to zrobione w mapowaniu klasy Osoba dla property kontakty.
Działanie kaskadowe utrwalania obiektów można ustawić również po stronie obiektu podrzędnego, jednak wydaje się to być mało praktyczne.
2.4. Usuwanie obiektów z DB
Podobnie jak utrwalanie również i usuwanie obiektów może być rozdzielne lub kaskadowe.
Kaskadowe usuwanie obiektów jest analogiczne do kaskadowego utrwalania. Tzn. wykonując operację usunięcia obiektu głównego - korzenia drzewa obiektów, automatycznie zostaną usunięte powiązane z nim obiekty podrzędne. Tu należy pamiętać o ustawieniu atrybut cascade na CascadeType.REMOVE w mapowaniu - tak jak zostało to zrobione w mapowaniu klasy Osoba dla property kontakty. Implementacja JPA powinna usunąć obiekty podrzędne przed usunięciem obiektu nadrzędnego, żeby uniknąć naruszenia bazodanowych ograniczeń. Jednak usuwanie te nie musi być optymalne. Np hibernate'owa implementacja usuwa każdy obiekt z osobna po jego id, podczas gdy lepszym rozwiązaniem byłoby usunięcie wszystkich obiektów z jednej grupy jednym zapytaniem odwołując się do klucza obcego do bytu nadrzędnego.
Działania kaskadowego usuwania obiektów nie można ustawić po stronie obiektu podrzędnego.
W związku z usuwaniem obiektów podrzędnych w relacji jeden-do-wielu/wiele-do-jednego można wykorzystać rozszerzenie hibernate'owe - kaskadowe działanie delete-orphan. Otóż jeżeli w mapowaniu klasy nadrzędnej, kolekcję referencji do obiektów podrzędnych oznaczymy adnotacją @Cascade( {org.hibernate.annotations.CascadeType.DELETE_ORPHAN}), to po usunięciu jakiegoś obiektu z tej kolekcji i utrwaleniu obiektu nadrzędnego, usunięty z kolekcji obiekt zostanie również usunięty z bazy danych.
o.getKontakty().remove(k);
em.persist(o);
Przy stosowaniu tego rozszerzenia należy pamiętać, że dla obiektu nadrzędnego choć raz utrwalonego, nie można podmieniać kolekcji obiektów podrzędnych, powiedzmy na nową instancje ArrayList, a próba utrwalenia takiego obiektu zwyczajnie się nie powiedzie. Jest tak, gdyż hibernate stosuje własne implementacje kolekcji - persistence collections, które pomagają w wykonaniu działania delete-orphan.

Usuwanie rozdzielne. Nie ma, żadnego problemu żeby usuwać obiekty podrzędne w sposób rozdzielny. Ponadto, bez użycia hibernate'owego rozszerzenia kaskadowego działania delete-orphan, jest to jedyny sposób aby z bazy danych pozbyć się pojedynczego obiektu podrzędnego.
Jednak zrezygnowanie z kaskadowego usuwania dla obiektów nadrzędnych jest mało atrakcyjne. Jeżeli jest założone ograniczenie bazodanowe, że klucz obcy do encji nadrzędnej nie może być pusty, to taka operacja w ogóle się nie powiedzie. Generalnie co się z tym stanie dalej, zależy od ustawień działań kaskadowych w bazie danych.

3. Jednokierunkowa relacja wiele-do-jednego

W porównaniu do dwukierunkowej relacji wiele-do-jednego/jeden-do-wielu, klasa Kontakt i jej mapowanie pozostaje niezmienione, za to z klasy Osoba zostanie usunięte powiązanie do klasy Kontakt.
Takie podejście uniemożliwia konfigurację działań kaskadowych (poza tymi określonymi w schemacie bazy danych) wyzwalanymi przez operacje na obiekcie Osoba. Ewentualnie można określić działania kaskadowe wyzwalane przez operacje na obiekcie Kontakt.
W omawianym przeze mnie przykładzie jednokierunkowa relacja wiele-do-jednego nie ma raczej praktycznego zastosowania.

4. Jednokierunkowa relacja jeden-do-wielu

W porównaniu do dwukierunkowej relacji wiele-do-jednego/jeden-do-wielu, to z klasy Kontakt zniknie powiązanie do klasy Osoba, natomiast klasa Osoba i jej mapowanie zmieni się następująco:
@Entity
@Table(name="osoba")
public class Osoba {
 ...
 private List<Kontakt> kontakty;

 @OneToMany(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
 @Cascade( {org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
 @JoinColumn(name="oso_id", nullable=false)
 public List<Kontakt> getKontakty() {
  return kontakty;
 }
 
 public void dodajKontakt(Kontakt kontakt) {
  if (null == kontakty) {
   kontakty = new ArrayList<Kontakt>();
  }
  kontakty.add(kontakt);
 }
}
Jak widać zmieniła się metoda dodajKontakt, która nie zawiera już instrukcji wiązania obiektu Kontakt z obiektem Osoba. Ale co najważniejsze zmieniło się nieco mapowanie. Ponieważ klasa Kontakt nie zawiera już mapowania tej relacji, to:
  - zniknęła definicja atrybutu mappedBy w adnotacji @OneToMany;
  - do mapowania property kontakty dodana została adnotacja @JoinColumn, która podobnie jak w klasie Kontakt służy do określenia kolumny w encji Kontakt, będącego kluczem obcym do encji Osoba. W przeciwnym wypadku domyślnie zostałaby użyta tabela pośrednicząca, jak w relacji wiele-do-wielu, o nazwie [nazwa_tabeli_nadrzędnej]_[nazwa_tabeli_podrzędnej], która zawierałaby dwa klucze obce do tych tabel o następującym schemacie nazw [nazwa_tabeli]_[nazwa_klucza_głównego].

Dla prezentowanego przeze mnie przykładu użycie jednokierunkowej relacji jeden-do-wielu jest całkiem rozsądnym podejściem. Jedyne co zostało utracone, to możliwość zapisu obiektów Kontakt niezależnie od obiektu Osoba. Utracona została również możliwość definiowania działań kaskadowych od strony obiektu Kontakt, ale do skorzystania z tej możliwości w tym przypadku i tak nie ma sensownej przesłanki.

5. Zasoby pomocnicze

Pliki źródłowe przykładu
JPA - pierwsze kroki
Hibernate Annotations - 2.2.5. Mapping entity bean associations/relationships
2.4. Hibernate Annotation Extensions
Specyfikacja JPA

poniedziałek, 14 kwietnia 2008

JPA - modelowanie dziedziczenia

Relacja dziedziczenia jest właściwa tylko dla modelu obiektowego i jako tako nie występuje w modelu relacyjnym. Z tego względu opracowane zostały podejścia do reprezentacji dziedziczenia w modelach relacyjnych:
  - jedna tabela na klasę konkretną (Single table per Concrete Class),
  - jedna tabela na hierarchię klas (Single table per Class Hierarchy),
  - złączenie podklasy (Joined Subclass).
Specyfikacja JPA, z użyciem adnotacji @Inheritance, umożliwia zastosowanie każdej z tych trzech strategii reprezentacji dziedziczenia.

Przykład - dziedzina problemu

Dla celów niniejszego artykułu zostanie wykorzystany poniższy model danych: Jest to prosta hierarchia klas, z których abstrakcyjna klasa Osoba jest klasą nadrzędną, a klasy OsobaPrawna i OsobaFizyczna z niej dziedziczą.

Jedna tabela na klasę konkretną (Single table per Concrete Class)

Wg. tej strategii model relacyjny prezentowałby się następująco: Te podejście przewiduje, iż na każdą klasę konkretną będzie przypadać jedna tabela w modelu relacyjnym. W naszym przypadku zostaną utworzone tabele dla klas OsobaPrawna i OsobaFizyczna. Przy czym tabele te będą zawierać również kolumny reprezentujące dziedziczone atrybuty. Tu trzeba zaznaczyć, że nie ma możliwości nadpisania mapowania dziedziczonych atrybutów - nie zadziała adnotacja @AttributeOverride(s).

Klasa Osoba nie będzie jawnie odwzorowana w modelu relacyjnym. Ta cecha sprawia, że polimorficzne asocjacje (tj. ogólne odwoływanie się do bytu Osoba, a nie do konkretnego rodzaju osoby) stają się nie możliwe, a polimorficzne zapytania bardzo trudne (w zapytaniach używa się unii, bądź preparuje się oddzielne zapytanie dla każdej podklasy).

Poniżej zamieszczam wycinki kodów definicji klas z mapowaniem:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Osoba {
 ...
}
@Entity(name="osoba_prawna")
public class OsobaPrawna extends Osoba {
 ...
}
@Entity(name="osoba_fizyczna")
public class OsobaFizyczna extends Osoba {
 ...
}
Pełne źródła w postaci projektu maven'owego można pobrać tutaj.

Jedna tabela na hierarchię klas (Single table per Class Hierarchy)

Wg. tej strategii model relacyjny prezentowałby się następująco: W tym podejściu jednej hierarchii klas przypada tylko jedna tabela. W naszym przypadku oznacza to, że wszystkie klasy (Osoba, OsobaPrawna, OsobaFizyczna) będą reprezentowane tylko przez jedną tabelę. Tabela ta oprócz kolumn reprezentujących atrybuty tych klas, będzie posiadać kolumnę flagę (discriminator) - os_typ, która będzie określać jakiej klasy konkretnej jest dany rekord. Nie jest koniecznie definiowanie w klasie atrybutu reprezentującego discriminator'a, ale jak już jest to robione, to w mapowaniu tego atrybutu musi być wyłączona opcja zapisu i uaktualniania tej wartości, gdyż nie można jej zmieniać manualnie.

Te rozwiązanie nie posiada wad związanych z polimorfizmem, które posiadało poprzednie podejście. Jednak posiada inną wadę związaną z możliwościami definiowania ograniczeń kolumn (przedewszystkim not null) specyficznych dla klas konkretnych. Np. nie można wymusić żeby kolumna mapująca OsobaPrawna.regon nie mogła przyjmować wartości pustych, gdyż tego warunku nie sprosta żaden rekord reprezentujący obiekt OsobaFizyczna.

Poniżej zamieszczam wycinki kodów definicji klas z mapowaniem:
@Entity(name="osoba")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="os_typ")
public abstract class Osoba {
 ...
}
@Entity
@DiscriminatorValue(value="OP")
public class OsobaPrawna extends Osoba {
 ...
}
@Entity
@DiscriminatorValue(value="OF")
public class OsobaFizyczna extends Osoba {
 ...
}
Pełne źródła w postaci projektu maven'owego można pobrać tutaj.

Złączenie podklasy (Joined Subclass)

Wg. tej strategii model relacyjny prezentowałby się następująco: W tym podejściu każda klasa (nawet abstrakcyjna) ma w relacyjnym modelu swoją reprezentacje w postaci tabeli. Oznacza to, że dane konkretnego obiektu będą zapisane po części w tabeli klasy abstrakcyjnej oraz po części w tabeli klasy konkretnej. U nas np. atrybuty obiektu OsobaPrawna będą przechowywane w tabeli OsobaPrawna jak oraz w zakresie dziedziczonych atrybutów w tabeli Osoba.
Byt dziedziczący musi mieć odwołanie do bytu z którego dziedziczy. W roli tego odwołania może być wspólny klucz główny. I tak wiersz w tabeli klast OsobaPrawna będzie miał klucz główny równy co do wartości do klucza wiersza w tabeli klasy Osoba.

Te podejście nie ma wad poprzednich rozwiązań. Ponadto taki model relacyjny charakteryzuje się wyższym stopniem normalizacji w stosunku do konkurencyjnych rozwiązań. Co też może również być wadą tej strategii, gdyż wyższy stopień normalizacji oznacza, że dane są rozproszone w większej ilości tabelach, co zwiększa skomplikowanie zapytań i w pewnym stopniu czas ich wykonania.

Poniżej zamieszczam wycinki kodów definicji klas z mapowaniem:
@Entity(name="osoba")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Osoba {
 ...
}
@Entity(name="osoba_prawna")
@PrimaryKeyJoinColumn(name="op_id")
public class OsobaPrawna extends Osoba {
 ...
}
@Entity(name="osoba_fizyczna")
@PrimaryKeyJoinColumn(name="of_id")
public class OsobaFizyczna extends Osoba {
 ...
}
Pełne źródła w postaci projektu maven'owego można pobrać tutaj.

Dziedziczenie z klasy nie będącą encją

Specyfikacja JPA przewiduje dwa warianty zachowania w przypadku dziedziczenia z klasy nie będącej encją.
Wariant pierwszy - klasa bazowa nie jest oznaczona adnotacją @MappedSuperclass. W tym przypadku atrybuty odziedziczone są ulotne i nie będą zapisane w bazie danych po operacji utrwalenia obiektu.
Wariant drugi - klasa bazowa jest oznaczona adnotacją @MappedSuperclass. Wówczas dziedziczone atrybuty są trwałe wg. mapowania zdefiniowanego w tej bazowej klasie. Przy czym istnieje możliwość nadpisania tych mapowań poprzez użycie adnotacji @AttributeOverride(s).
Moim zdaniem mapowane klasy nadrzędne są ciekawym rozwiązaniem i można go użyć chociażby do definicji klasy bazowej wszystkich encji, która będzie zawierać atrybuty zawierające informacje o dokonywanych na encji zmianach.
Poniżej zamieszczam przykład użycia mapowanej klasy:
@MappedSuperclass
public class EncjaAudytowalna {
 private String uzytkownikModyfikujacy;
 private Date dataModyfikacji;
 
 @Column(name="audyt_uzytkownik")
 public String getUzytkownikModyfikujacy() {
  return uzytkownikModyfikujacy;
 }
 public void setUzytkownikModyfikujacy(String uzytkownikModyfikujacy) {
  this.uzytkownikModyfikujacy = uzytkownikModyfikujacy;
 }
 
 @Column(name="audyt_data")
 @Temporal(value=TemporalType.TIMESTAMP)
 public Date getDataModyfikacji() {
  return dataModyfikacji;
 }
 public void setDataModyfikacji(Date dataModyfikacji) {
  this.dataModyfikacji = dataModyfikacji;
 }
}
@Entity
@AttributeOverride(name="uzytkownikModyfikujacy", column=@Column(name="uz_au_um"))
public class Uzytkownik extends EncjaAudytowalna {
 private Long id;
 private String name;

 @Id
 @GeneratedValue(strategy=GenerationType.AUTO)
 public Long getId() {
  return id;
 }
 public void setId(Long id) {
  this.id = id;
 }

 @Column(name="nazwa")
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
}

Zasoby

Hibernate Annotations - 2.2.4. Mapping inheritance
Specyfikacja JPA

niedziela, 13 kwietnia 2008

Serializacja XML - zapis java.util.Date oraz wartości enumeracji

Niniejszy artykuł jest kontynuacją wątku z artykułu TestNG - dane testowe z plików XML o wykorzystaniu standardowej (J2SE) schemy serializacji XML objektów do preparowania danych testowych. Ten artykuł będzie traktował o zapisie danych typu java.util.Date oraz typu wyliczeniowego - enumeracji.
Gwoli przypomnienia w tym przypadku nie chodzi o serializację obiektów z poziomu javy, tylko o ręczne preparowania plików XML zawierających zserializowane obiekty, które zostaną zdeserializowane z użyciem standardowego mechanizmu - java.beans.XMLDecoder, a następnie zostaną użyte jako dane wejściowe dla testów jednostkowych.

Niemniej przy serializacji obiektu z poziomy javy, który ma jedno pole o typie java.util.Date a drugie o typie wyliczeniowym, uzyskamy efekt dalece niezadowalający, którego na pewno nie będziemy chcieli naśladować.
Pole o typie wyliczeniowym w ogóle nie zostanie zapisane. Z kolei pole z datom zostanie zapisane, ale w formie liczby milisekund od 1 stycznia 1970r. Na pewno nie jest to wygodna postać dla człowieka, anie do odczytu, a tym bardziej do zapisu. W każdym razie java.beans.XMLEncoder zserializuje java.util.Date w następujący sposób:
<void property="birthDate"> 
 <object class="java.util.Date"> 
  <long>439254000000</long> 
 </object> 
</void>
Not tak... to przecież całkiem logiczne, bo jest to jedyny nie "potępiony" sposób ustawiania daty jaki oferuje interfejs java.util.Date.
Normalnie (w javie) jakbym miał uzyskać datę ze stringa, to bym skorzystał z java.text.SimpleDateFormat. No ale jak go użyć w tym XML'u... już podaję:
<void id="sdf" class="java.text.SimpleDateFormat">
 <string>yyyy-MM-dd</string>
 <void id="date0" method="parse">
  <string>1983-12-03</string>
 </void>
</void>
...
<object class="pl.dwalczak.Osoba">
 ...
 <void property="dataUrodzin">
  <object idref="date0"/>
 </void>
 ...
</object>
Pierwsza część konstrukcji (znacznik void z id="sdf"), to deklaracja, której w javie odpowiada:
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
java.util.Date date0 = sdf.parse("1983-12-03");
Nie definiuje ona żadnego zserializowanego obiektu, a jedynie zmienne, które mogą być wykorzystane przy definiowaniu zserializowanych obietków. Może być umieszczona w elemencie głównym, lub też bezpośrednio w zserializowanym obiekcie.
Druga część konstrukcji (znacznik object) definiuje obiekt typu pl.dwalczak.Osoba, w którym ustawia pole dataUrodzin wcześniej zdefiniowaną zmienną date0.

Wracając do typu wyliczeniowego.
Załóżmy, że klasa pl.dwalczak.Osoba posiada pole typOsoby o typie pl.dwalczak.TypOsoby, który jest enumeracjom:
package pl.dwalczak;
public enum TypOsoby {
 OsobaFizyczna,
 OsobaPrawna;
}
Wówczas ustawienie pola typOsoby dla obiektu pl.dwalczak.Osoba może wyglądać następująco:
<void property="type"> 
 <object class="com.dwalczak.TypOsoby" field="OsobaFizyczna"/> 
</void>
Lub tak:
<void property="type"> 
 <object class="com.dwalczak.TypOsoby" method="valueOf">
  <string>OsobaPrawna</string>
 </object> 
</void>

Podsumowanie

O ile problem z serializowaniem wartości typu wyliczeniowego, nie specjalnie zmniejsza atrakcyjność formatu standardowej serializacji obiektów XML w J2SE, jako ogólnego formatu zapisywania danych testowych, gdyż manualnie można to zapisać w całkiem przyzwoitej formie. To problem z datą faktycznie obniża tą atrakcyjność, gdyż formuła zapisu jej w "ludzkiej" postaci jest trochę za długa i skomplikowana. Mimo to, myślę że stosowanie tego formatu może być mniej pracochłonne niż projektowanie własnego i pisanie kodu deserializującego z niego obiekty.

Zasoby

Long Term Persistence of JavaBeans Components: XML Schema
Sergey Malenkov's Blog - How to encode Type-Safe Enums?

poniedziałek, 7 kwietnia 2008

Testowanie ziaren EJB3 z użyciem TestNG

Po pierwszych potyczkach z JPA nadszedł czas na zdobycie pierwszych doświadczeń w pisaniu ziaren EJB3. Najpierw chciałem jednak wybadać sprawy związane z testowaniem tychże ziaren. W poprzednich wersjach EJB z testowaniem było dość ciężko, gdyż jak wiadomo ziarna, żeby działały musiały być osadzone w kontenerze EJB, które były dostępne tylko w serwerach aplikacji. Jak dla mnie, jest to zdecydowanie za skomplikowany i zbyt czasochłonny proces testowania. Niestety ziarna EJB3 również wymagają, żeby uruchamiać je w kontenerze EJB.

Wbudowywalny kontener EJB3

Od pewnego czasu JBoss udostępnia wbudowywalny kontener EJB3. Jest to kontener, który można uruchomić poza serwerem aplikacji. W chwili obecnej kontener ten ma co prawda pewne ograniczenia, ale póki udostępnia wszystkie wymagane przeze mnie usługi nie będę się tym zbytnio przejmował ;)
Niemniej koncepcja wbudowywalnego kontenera EJB3 umożliwia stworzenie, szybkiego i łatwego w użyciu środowiska testów jednostkowych dla ziaren EJB3.

Środowisko testowe EJB3

Przy tworzeniu środowiska testów jednostkowych dla ziaren wykorzystam wspomniany wbudowywalny kontener EJB3.
Do jego uruchomienia wymagane jest, aby w zasięgu classpath'a znajdowały się (oprócz implementacji tego kontenera i bibliotek zależnych) następujące pliki konfiguracyjne: embedded-jboss-beans.xml, ejb3-interceptors-aop.xml, jndi.properties, default.persistence.properties.
W celu uniknięcia umieszczenia tych plików we wszystkich podprojektach modułów EJB tworzących korporacyjną aplikację, jak i określania w każdym z nich zależności od jar'ów wbudowanego kontenera EJB3, postanowiłem stworzyć dedykowany projekt maven'a. Projekt środowiska testów jednostkowych EJB będzie zawierał wspomniane pliki konfiguracyjne, zdefiniowane zależności do jar'ów implementacji kontenera oraz bazową klasę testową, dla testów jednostkowych ziaren.
A oto implementacja bazowej klasy testów jednostkowych:
package pl.dwalczak.ejb3testenv;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.log4j.Logger;
import org.jboss.ejb3.embedded.EJB3StandaloneBootstrap;
import org.jboss.ejb3.embedded.EJB3StandaloneDeployer;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;

abstract public class Ejb3Test {

 private static final Logger LOG = Logger.getLogger(Ejb3Test.class);
 private static final String LOCAL_POSTFIX = "/local";
 private InitialContext initialContext;
 private EJB3StandaloneDeployer deployer;

 @BeforeSuite
 public void startupEjb3() {
  EJB3StandaloneBootstrap.boot(null);
  EJB3StandaloneBootstrap.scanClasspath();

  deployer = EJB3StandaloneBootstrap.createDeployer();
  try {
   deployer.setKernel(EJB3StandaloneBootstrap.getKernel());
   deployer.getArchivesByResource().add("META-INF/ejb-jar.xml");
   deployer.create();
   deployer.start();
   initialContext = new InitialContext();
  } catch (NamingException e) {
   LOG.error("Can't initialize context.", e);
   throw new RuntimeException(e);
  } catch (Exception e) {
   LOG.error("Deployer error.", e);
   throw new RuntimeException(e);
  }
 }

 @AfterSuite
 public void shutdownEjb3() {
  if (null != initialContext) {
   try {
    initialContext.close();
   } catch (NamingException e) {
    LOG.error("Can't close context.", e);
   }
  }
  if (null != deployer) {
   try {
    deployer.stop();
    deployer.destroy();
   } catch (Exception e) {
    LOG.error("Can't close deployer.", e);
   }
  }
  EJB3StandaloneBootstrap.shutdown();
 }

 @SuppressWarnings(value = "unchecked")
 public  T lookup(String beanName, Class businessInterface) {
  T result = null;
  try {
   result = (T) initialContext.lookup(beanName + LOCAL_POSTFIX);
  } catch (NamingException e) {
   LOG.error("Can't lookup bean: " + beanName, e);
   throw new RuntimeException(e);
  }
  return result;
 }
}
Dzięki temu przystosowanie nowego modułu EJB w maven'ie będzie polegało na dodaniu zależności do tego projektu (przy scope ustawionym na test).

Trzeba tu dodać, że bardziej złożona aplikacja korporacyjna będzie raczej wymagała specjalnie dostosowanego projektu środowiska testowego. Niemniej myślę, że przygotowany przeze mnie projekt tegoż środowiska jest dobrym punktem startowym.

Testowanie ziaren EJB3 - przykład

W moim przykładzie mamy do czynienia z bardzo prostym ziarnem:
package pl.dwalczak.ejb3pg1;
import javax.ejb.Stateless;
import org.apache.log4j.Logger;

@Stateless
public class CalculatorBean implements Calculator {
 
 private static Logger LOG = Logger.getLogger(CalculatorBean.class);

 public int add(int x, int y) {
  LOG.debug("add(" + x + ", " + y + ")");
  return x + y;
 }

}
Interfejs biznesowy:
package pl.dwalczak.ejb3pg1;

public interface Calculator {

 int add(int x, int y);
}
Po dodaniu zależności do projektu środowiska testowego można napisać test jednostkowy do tego ziarna, który może wyglądać w sposób następujący:
package pl.dwalczak.ejb3pg1;
import org.testng.Assert;
import org.testng.annotations.Test;
import pl.dwalczak.ejb3testenv.Ejb3Test;

public class CalculatorTest extends Ejb3Test {
 
 @Test
 public void testAdd() {
  Calculator calculator = lookup("CalculatorBean", Calculator.class);
  int sum = calculator.add(2, 2);
  Assert.assertEquals(sum, 4);
 }

}
Jest jeszcze tylko jedna kwestia. Do modułu EJB trzeba dodać plik konfiguracyjny META-INF/ejb-jar.xml, nawet jeśli nie trzeba w nim nic konfigurować. Jest to po prostu niezbędne, aby wykorzystywany kontener zaczytał klasy ziaren EJB3, pomimo że te są skonfigurowane z użyciem adnotacji.

Zasoby

Pliki źródłowe przykładu
Pliki źródłowe ejb3-test-env
Embeddable EJB 3.0
JSR-000220 Enterprise JavaBeans 3.0
JavaTM Platform Enterprise Edition, v 5.0 API Specifications

wtorek, 1 kwietnia 2008

TestNG - dane testowe z plików XML

Pisząc artykuł TestNG - dostawca danych przypomniałem sobie o pewnym mini framework'u do wykonywania testów jednostkowych, który przyszło mi kiedyś napisać. Musiałem go napisać nie dlatego, że w projekcie nie znano jUnita ;) ale dlatego, że jUnit w pewnych przypadkach nie wiele pomagał. Konkretnie chodzi o sytuację gdy wiele testów ma ten sam algorytm, a różnią się między sobą danymi testowymi (czasami są to dość duże zestawy danych).
Moim zdaniem w takim przypadku warto oddzielnie zarządzać danymi i algorytmami testowymi i umiejscowić je w różnych plikach. Wówczas istnieje możliwość, aby dane testowe przygotowywała inna osoba niż autor algorytmów testowych, nie trzeba ponownie kompilować testów gdy się zmieniły dane, no i łatwiej można uniknąć problemów z kodowaniem danych wejściowych.

DataProvider w TestNG nie wspiera bezpośrednio zaczytywania danych testowych z plików XML. Ale jak się okazało, robienie tego samemu nie jest trudne i co najważniejsze nie wymaga znaczących nakładów kodowania. Wystarczy zastosować dostarczaną przez specyfikacje J2SE serializację XML JavaBean'ów.
Schema serializacji jest relatywnie zrozumiała dla ludzi, a co najważniejsze jest bardzo elastyczna. Schema ta dopuszcza zapisywanie dowolnej liczby dowolnych obiektów. Ponadto można łatwo wygenerować przykładowy plik wejściowy do testów, poprzez zserializowanie spreparowanych w kodzie przykładowych danych.

Poniżej zamieszczam przykładową zawartość pliku XML z danymi testowymi:
<?xml version="1.0" encoding="UTF-8" ?>
<java version="1.5.0" class="java.beans.XMLDecoder">
 <array>
  <string>test1</string>
  <object class="pl.dwalczak.testngdp1.User">
   <void property="nickName">
    <string>heniek</string>
   </void>
   <void property="address">
    <object class="pl.dwalczak.testngdp1.Address"
     id="addr1">
     <void property="city">
      <string>Poznań</string>
     </void>
     <void property="postcode">
      <string>11-111</string>
     </void>
     <void property="street">
      <string>Jadwigi</string>
     </void>
     <void property="number">
      <string>11a/3</string>
     </void>
    </object>
   </void>
   <void property="mailingAddress">
    <object idref="addr1" />
   </void>
  </object>
 </array>
 <array>
  <string>test2</string>
  <object class="pl.dwalczak.testngdp1.User">
   <void property="nickName">
    <string>maniek</string>
   </void>
   <void property="address">
    <object idref="addr1" />
   </void>
   <void property="mailingAddress">
    <object idref="addr1" />
   </void>
  </object>
 </array>
</java>
Natomiast implementacja TestNG'owego data providera, korzystającego z tego pliku XML, może wyglądać następująco:
package pl.dwalczak.testngdp1;
import java.beans.XMLDecoder;
import java.io.FileInputStream;
import java.util.ArrayList;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

@Test
public class SimpleTest {
 
 @DataProvider(name="dp")
 public Object[][] createData() {
  ArrayList<Object[]> result = new ArrayList<Object[]>();
  XMLDecoder dec = null;
  try {
   dec = new XMLDecoder(new FileInputStream("testData.xml"));
   while (true) {
    Object o = dec.readObject();
    result.add((Object[]) o);
   }
  } catch (ArrayIndexOutOfBoundsException e) {
   // no more objects in stream
  } catch (Exception e) {
   throw new RuntimeException(e);
  } finally {
   if (dec != null) {
    dec.close();
   }
  }
  
  return result.toArray(new Object[result.size()][]);
 }

 @Test(dataProvider="dp")
 public void test(String arg0, User user) {
  System.out.println("arg0: " + arg0);
  System.out.println("user: " + user);
 }
}

Zasoby

Pliki źródłowe przykładu
Long Term Persistence of JavaBeans Components: XML Schema
Dokumentacja TestNG - 5.5.2 - From DataProviders

piątek, 28 marca 2008

TestNG - dostawca danych

Jak wspomniałem w artykule TestNG - pierwsze kroki. Framework TestNG umożliwia definiowanie parametrów w pliku konfiguracyjnym, które później można wykorzystać jako argumenty metod testujących.
Jednak w TestNG dostępny jest także znacznie potężniejszy mechanizm parametryzacji testów danymi. Jest nim tzw. dostawca danych - data provider.
Dostawcę danych definiuje się jako metodę oznaczaną adnotacją DataProvider. Metoda ta powinna zwracać dwuwymiarową tablicę obiektów (Object[][]) lub iterator po jednowymiarowych tablicach objektów (Iterator<Object[]>). Przy czym kolejne tablice obiektów zwracane przez iterator (lub te w dwuwymiarowej tablicy) zawierają argumenty do kolejnego wywołania (tyle razy ile jest tych tablic) danej metody testującej.
Metoda dostawcy danych może być bezargumentowa lub posiadać jeden argument o typie java.lang.reflect.Method, który określa metodę testującą, dla której będą dostarczane dane.
W celu przypisania dostawcy danych do metody testującej, trzeba odpowiednio ustawić parametr dataProvider dla adnotacji Test, którą jest oznaczona dana metoda testująca. Opcjonalnie, jeśli metoda testująca jest w innej klasie niż metoda dostarczająca dane, należy ustawić jeszcze parametr dataProviderClass adnotacji Test. Poza tym typy argumentów metody testującej muszą być zgodne z typami obiektów w tablicach zwracanych przez dostawcę danych.
Niestety nie można łącznie wykorzystywać dostawcy danych i parametrów definiowanych w pliku konfiguracyjnym. Nie można również przekazywać tych parametrów do metod dostarczających dane.
Poniżej zamieszam przykładowe użycie dostawcy danych TestNG:
package pl.dwalczak.jpapg2;
import org.apache.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import pl.dwalczak.jpapg2.model.Address;
import pl.dwalczak.jpapg2.model.User;

@Test
public class UserTest extends JpaTestCase{
 
 private static final Logger LOG = Logger.getLogger(UserTest.class);
 
 @DataProvider(name="address")
 public Object[][] createTestData() {
  LOG.debug("createTestData");
  return new Object[][] {
   {"Heniek", new Address("Poznan", "11-111", "Jadwigi", "12c/23")},
   {"Maniek", new Address("Wroclaw", "22-222", "Kosciuszki", "14b")}};
 }

 @Test(dataProvider="address")
 public void createUser(String user, Address address) {
  LOG.debug("create user: " + user);
  em.persist(new User(user, address, address));
 }
 
 @Test(dependsOnMethods={"createUser"}, dataProvider="address")
 public void findUser(String user, Address address) {
  LOG.debug("find user: " + user);
  User u = (User) em.createNamedQuery("user.findByNickName").setParameter("nickName", user).getSingleResult();
  LOG.debug("found user: " + u);
  Assert.assertNotNull(u);
  Assert.assertEquals(address, u.getAddress());
 }
}

Zasoby

Pliki źródłowe przykładu
Dokumentacja TestNG - 5.5.2 - From DataProviders

czwartek, 27 marca 2008

JPA - obiekty wbudowane (komponenty)

Wstęp

Obiekty wbudowane stosuje się w przypadku rozbieżności modelu obiektowego i fizycznego modelu bazodanowego. Przykładowo z bytem Użytkownik jest związany byt Adres. W modelu obiektowym byty te będą implementowane przez odrębne obiekty, jednak w relacyjnej bazie danych w celu osiągnięcia lepszej wydajności mogą być zaimplementowane przy użyciu jednej tabelki, zawierającej dane zarówno Użytkownika jak i jego Adres. W tym przykładzie Adres będzie obiektem wbudowanym i będzie przynależeć do trwałego obiektu Użytkownik. Ważną cechą obiektów wbudowanych jest to, że nie mają swojej tożsamości trwałej (swojego identyfikatora) i nie mogą być współdzielone przez różne obiekty trwałe.

Klasy wbudowywane

Klasa obiektu wbudowanego musi być oznaczona adnotacją Embeddable. Nie oznacza się jej jako encji, tym bardziej nie określa się dla niej tabeli w bazie danych ani identyfikatora. Poza tym dla klasy wbudowywanej specyfikuje się mapowanie pól jak przypadku zwykłych encji. Poniżej znajduje się przykład definicji klasy wbudowywanej - Adres:
package pl.dwalczak.jpapg2.model;
import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class Address {
 private String city;
 private String postcode;
 private String street;
 private String number;

 @Column(length=50, nullable=false)
 public String getCity() {
  return city;
 }
 public void setCity(String city) {
  this.city = city;
 }

 @Column(length=6, nullable=false)
 public String getPostcode() {
  return postcode;
 }
 public void setPostcode(String postcode) {
  this.postcode = postcode;
 }

 @Column(length=50, nullable=false)
 public String getStreet() {
  return street;
 }
 public void setStreet(String street) {
  this.street = street;
 }

 @Column(length=10, nullable=false)
 public String getNumber() {
  return number;
 }
 public void setNumber(String number) {
  this.number = number;
 }
}

Umieszczanie obiektów wbudowywanych w obiektach trwałych

Pola klasy o typie oznaczonym jako wbudowywany opatruje się adnotacją Embedded. Nie jest to jednak niezbędne, gdyż specyfikacja JPA nakłada obowiązek na EntityManager aby sam wybadał sprawę na podstawie adnotacji Embeddable, którym jest opatrzona klasa danego pola. Pola obiektu wbudowanego są mapowane na kolumny tabeli encji, do której przynależy.
Nadpisywanie atrybutów mapowania
JPA umożliwia nadpisywanie atrybutów mapowania zdefiniowanych w klasie wbudowywanej. W tym celu używa się adnotacji AttributeOverrides, która zawiera listę adnotacji AttributeOverride. I te dopiero służą do nadpisywania mapowania poszczególnych pól klasy. Mechanizm nadpisywania atrybutów mapowania daje duże możliwości w kontekście wielokrotnego wykorzystania definicji klas wbudowywancyh. Poniżej znajduje się przykład encji, w skład której wchodzą dwa obiekty wbudowane o typie Adres (stały adres zamieszkania i adres korespondencyjny).
package pl.dwalczak.jpapg2.model;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

@NamedQuery(
 name="user.findByNickName",
 query="select u from User u where u.nickName = :nickName"
)

// Definicja sekwencji "users_seq" służącej do generowania klucza głównego tabeli "users".
@SequenceGenerator(name="users_seq")

// Mapowanie tabeli "users".
@Entity
@Table(name="users")
public class User {
 private Long id;
 private String nickName;
 private Address address;
 private Address mailingAddress;

 @Id
 @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="users_seq")
 @Column(name="usr_id")
 public Long getId() {
  return id;
 }
 protected void setId(Long id) {
  this.id = id;
 }

 @Column(name="usr_nickname", unique=true, nullable=false, length=24)
 public String getNickName() {
  return nickName;
 }
 public void setNickName(String name) {
  this.nickName = name;
 }
 
 @Embedded
 public Address getAddress() {
  return address;
 }
 public void setAddress(Address address) {
  this.address = address;
 }
 
 @Embedded
 @AttributeOverrides({
  @AttributeOverride(name="city", column=@Column(name="ma_city")),
  @AttributeOverride(name="postcode", column=@Column(name="ma_postcode")),
  @AttributeOverride(name="street", column=@Column(name="ma_street")),
  @AttributeOverride(name="number", column=@Column(name="ma_number"))
 })
 public Address getMailingAddress() {
  return mailingAddress;
 }
 public void setMailingAddress(Address mailingAddress) {
  this.mailingAddress = mailingAddress;
 }
}

Zasoby

Pliki źródłowe przykładu
Hibernate Annotations - 2.2.2.3. Embedded objects (aka components)
Specyfikacja JPA

wtorek, 18 marca 2008

TestNG - pierwsze kroki

Przy okazji pisania przykładu do artykułu "JPA - pierwsze kroki" postanowiłem, że zrobię też pierwsze kroki do poznania framework'u wspomagającego wykonywanie testów jednostkowych - TestNG. Moje pierwsze wrażenie jest bardzo pozytywne. W porównaniu do jUnit'a ma prostrzy i bardziej przystępny interfejs, przy czym ma większe możliwości i jest też dużo lepiej udokumentowany.

Hierarchia testów

W TestNG mamy doczycznienia z następującą hierarchią testów: Suite (zestaw), Test, Class (klasa zawierająca metody testujące) oraz Method (metoda testująca). O ile Suite i Test są jednostkami czysto logicznymi (definiowane na poziomie konfiguracji), to Class i Method są jednostkami zawierającymi fizyczną implementację testów. Dodatkowo TestNG umożliwia znakowania Class i Method znacznikiem grupy. Przy czym istnieje możliwość przypisania ich do wielu grup. Dla wszystkich wspomnianych jednostek hierarchii oraz dla grup można określać metody, które będą wywoływane przed lub po wykonaniu testów.

Zależności między testami

TestNG umożliwia definiowanie zależności między testami. Można ustalić, że wykonanie danej metoda testująca lub klasy z testami będzie zależne od pozytywnego wykonania innych metod testujących bądź grup testów.

Konfiguracja testów

Testy w TestNG konfiguruje się w pliku xml zgodnym z DTD: http://testng.org/testng-1.0.dtd oraz poprzez adnotacje. W jednym pliku konfiguracyjnym można zdefiniować jeden Suite (znacznik <suite>), w obrębie którego definiowana jest dowolna liczba Test'ów (znaczniki <test>), lub można zaimportować pliki konfiguracyjne innych zestawów testów. Na Test z kolei może się składać dowolna liczba grup (znacznik <groups>), klas (znacznik <classes>), a także można określać pakiety Javy (znacznik <pakages>), w obrębie których będą wyszukiwane klasy do testowanie. Dla klasy można dodatkowo określić, które metody mają być wykonane, a które nie. Zarówno dla jednostek Suite jak i Test można definiować parametry, które mogą być przekazane metodom testującym (znacznik <parameter>). Z użyciem adnotacje można m.in. określić takie rzeczy jak: - czy klasa zawiera testy (adnotacja Test), - czy dana metoda jest metodą testową (również adnotacja Test), - określenie grupy do której przynależy klasa bądź metoda (parametr groups do adnotacji Test), - czy metoda testująca bądź klasa zawierająca testy jest zależna od innych testów (parametr dependsOnMethods oraz dependsOnGroups adnotacji Test), - określenie metod do wywołania przed lub po wykonaniu testów (adnotacje Before* oraz After*), - określenie parametrów do przekazania metodzie testującej (adnotacja Parameters)

Przykład

Poniższy przykład to testy jednostkowe przygotowane na potrzeby artykułu "JPA - pierwsze kroki". Składa się na niego plik konfiguracyjny, abstrakcyjna klasa bazowa oraz dwie klasy konkretne zawierające metody testujące.
Plik konfiguracyjny
Plik konfiguracyjny zawiera oczywiście definicję testu (implementowany w dwóch klasach), a także wykorzystywanych przez większość metod testujących parametry.
<suite name="Suite1">
 <parameter name="user1" value="heniek" />
 <parameter name="user2" value="franek" />
 
 <test name="Test">
  <classes>
   <class name="pl.dwalczak.jpapg1.UserTest" />
   <class name="pl.dwalczak.jpapg1.MessageTest" />
  </classes>
 </test>
</suite>
Abstrakcyjna klasa bazowa
Ta klasa definiuje podstawowe usługi potrzebne przy testowaniu mapowania modelu JPA. Przede wszystkim dostarcza EntityManager, którego inicjuje i rozpoczyna transakcję przed wykonaniem każdej metody testującej. Natomiast po wykonaniu metody testującej zatwierdza transakcję oraz zamyka EntityManager'a. Po wykonaniu całego zestawu testów (Suite) zamykany jest EntityManagerFactory.
package pl.dwalczak.jpapg1;
import javax.persistence.EntityManager;
import org.apache.log4j.Logger;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeMethod;
import pl.dwalczak.jpapg1.util.JpaUtil;

abstract public class JpaTestCase {
 
 private static final Logger LOG = Logger.getLogger(JpaTestCase.class);
 protected EntityManager em;

 @BeforeMethod
 public void beginWorkUnitAndTransaction() {
  try {
   em = JpaUtil.getEntityManager();
   em.getTransaction().begin();
  } catch (RuntimeException e) {
   LOG.error("Can't beginWorkUnitAndTransaction", e);
  }
 }
 
 @AfterMethod
 public void closeWotkUnitAndCommitTransaction() {
  try {
   if (false == em.getTransaction().getRollbackOnly()) {
    em.getTransaction().commit();
   } else {
    em.getTransaction().rollback();
   }
  } catch (RuntimeException e) {
   LOG.error("Can't closeWotkUnitAndCommitTransaction", e);
  } finally {
   if (null != em) {
    try {
     if (em.getTransaction().isActive()) {
      em.getTransaction().rollback();
     }
     em.close();
    } catch (RuntimeException e) {
     LOG.error("Can't close entity manager!", e);
    }
   }
   em = null;
  }
 }
 
 @AfterSuite
 public void closeEntityManagerFactory() {
  JpaUtil.closeEntityManagerFactory();
 }
}
Klasa UserTest
Klasa ta zawiera wszystkie testy dotyczące encji "Użytkownik" i jest opatrzona w odpowiedni znacznik grupy ("user"). Znajdują się w niej przykłady przekazywania parametrów do metod testujących oraz określania zależności między metodami testującymi.
package pl.dwalczak.jpapg1;
import java.util.List;
import org.apache.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import pl.dwalczak.jpapg1.model.User;

@Test(groups="user")
public class UserTest extends JpaTestCase{
 
 private static final Logger LOG = Logger.getLogger(UserTest.class);

 @Test
 @Parameters({ "user1" })
 public void createUser1(String user) {
  LOG.debug("create user: " + user);
  em.persist(new User(user));
 }
 
 @Test
 @Parameters({ "user2" })
 public void createUser2(String user) {
  LOG.debug("create user: " + user);
  em.persist(new User(user));
 }
 
 @Test(dependsOnMethods={"createUser1", "createUser2"})
 @SuppressWarnings(value="unchecked")
 public void list() {
  LOG.debug("get user list");
  em.getTransaction().setRollbackOnly();
  List list = em.createNamedQuery("user.findAll").getResultList();
  LOG.debug("user list: " + list);
  Assert.assertEquals(list.size(), 2);
 }
 
 @Test(dependsOnMethods={"createUser2"})
 @Parameters({ "user2" })
 public void findUser(String user) {
  LOG.debug("find user: " + user);
  User u = (User) em.createNamedQuery("user.findByNickName").setParameter("nickName", user).getSingleResult();
  LOG.debug("found user: " + u);
  Assert.assertNotNull(u);
  
 }
}
Klasa MessageTest
Klasa ta zawiera wszystkie testy dotyczące encji "Wiadomość". Wykonanie wszystkich metod testujących tej klasy jest uzależnione od pozytywnego wyniku testów z grupy "user".
package pl.dwalczak.jpapg1;
import java.util.List;
import org.apache.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import pl.dwalczak.jpapg1.model.Message;
import pl.dwalczak.jpapg1.model.User;

@Test(dependsOnGroups="user")
public class MessageTest extends JpaTestCase {
 
 private static final Logger LOG = Logger.getLogger(MessageTest.class);
 
 @Test
 @Parameters({ "user1", "user2" })
 public void user1SendToUser2(String user1, String user2) {
  LOG.debug("user1SendToUser2");
  sendMessage("title1", "content1", user1, user2);
 }
 
 @Test
 @Parameters({ "user2", "user1" })
 public void user2SendToUser1(String user1, String user2) {
  LOG.debug("user2SendToUser1");
  sendMessage("title2", "content2", user1, user2);
 }
 
 private void sendMessage(String title, String content, String userFrom, String userTo) {
  User uFrom = (User) em.createNamedQuery("user.findByNickName").setParameter("nickName", userFrom).getSingleResult();
  User uTo = (User) em.createNamedQuery("user.findByNickName").setParameter("nickName", userTo).getSingleResult();
  Message message = new Message(title, content, uFrom, uTo);
  LOG.debug("send a message: " + message);
  em.persist(message);
 }
 
 @Test(dependsOnMethods={"user1SendToUser2", "user2SendToUser1"})
 @Parameters({"user1"})
 @SuppressWarnings(value="unchecked")
 public void viewUser1Messages(String user) {
  LOG.debug("view messages of: " + user);
  User u = (User) em.createNamedQuery("user.findByNickName").setParameter("nickName", user).getSingleResult();
  Assert.assertNotNull(u);
  List received = em.createNamedQuery("message.findReceived").setParameter("user", u).getResultList();
  LOG.debug("received messages: " + received);
  Assert.assertEquals(received.size(), 1);
  List send = em.createNamedQuery("message.findSend").setParameter("user", u).getResultList();
  LOG.debug("send messages: " + send);
  Assert.assertEquals(send.size(), 1);
 }
}

Zasoby

Pliki źródłowe
Dokumentacja TestNG

poniedziałek, 17 marca 2008

JPA - Pierwsze kroki

Przydługawy wstęp

Ponieważ od opublikowania ostatecznej wersji specyfikacji EJB3 upłynęło już sporo czasu, i pojawia się coraz więcej implementacji tej technologii - mniej lub bardziej kompletnych. To prawdopodobnie oznacza to, iż EJB3 będzie coraz częściej wybierana jako technologia bazowa w nowych projektach. Dlatego uznałem, że czas najwyższy zapoznać się z tą technologią nieco bliżej. Chciałbym zacząć od części specyfikacji określającej najniższą warstwę przeciętnej webaplikacji – modelu trwałych danych i styku z bazą danych (czy może raczej z JDBC), czyli Java Persistence API – JPA. W zasadzie JPA nie jest niczym rewolucyjnym. Sięgnięto raczej po sprawdzone koncepcje w najpopularniejszym chyba frameworku ORM – Hibernate. Cieszy mnie to tym bardziej, że przez ostatnie dwa lata z Hibernate'em miałem sporo do czynienia. Co więcej obecnie Hibernate stanowi jedną z implementacji JPA – i ja mam zamiar z niej skorzystać. Niewątpliwym plusem JPA jest to, że w zwarty i w dość prosty sposób standaryzuje podstawowe mechanizmy ORM. Interfejs JPA nie jest zbyt rozbudowany i w zaawansowanej aplikacji może się okazać jednak niewystarczający. W JPA nie znajdziemy wielu zaawansowanych elementów istniejących framework'ów ORM. Nie ma w nim API do dynamicznego tworzenia kryterium wyszukiwania, czy też nie ma możliwości definiowania pola klasy jako wyliczanej formuły SQL. Dlatego dostępne implementacje JPA dostarczają rozszerzenia do podstawowej specyfikacji. Oznacza to jednak, że transparentność implementacji specyfikacji JPA może okazać się tylko iluzoryczna. Z perspektywy użytkownika Hibernate'a poznanie JPA jest i tak nieuniknione jeżeli do mapowania chce stosować anotacji. Niemniej stosowanie konfiguracji w stylu JPA oraz EntityManager'a nie jest konieczne – przynajmniej na razie. Inną zaletą JPA jest możliwość jego użycia nie tylko w kontenerze EJB3, ale również w zwykłej JSE. No ale starczy już tych dywagacji, przejdę więc do opisu konkretnego przykładu użycia JPA.

Dziedzina problemu

Do zrobienia jest model, w którego centrum znajduje się byt Wiadomość - Message, który zawiera w sobie: temat - title, treść - content, oraz powiązania do bytu Użytkownik - User w roli nadawcy i odbiorcy.

Konfiguracja JPA

Specyfikacja JPA określa, że plik konfiguracyjny powinien znajdować się w pliku META-INF/persistence.xml. Listing pliku konfiguracyjnego mojej przykładowej aplikacji:
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
 <persistence-unit name="jpapg1">
  <!-- Określenie dostawcy implementującego JPA. -->
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <!-- 
  <class></class>
   Tu można podać listę klas mapujących model bazodanowy.
   W kontenerach EJB3 jest to zupełnie zbędne, gdyż specyfikacja JPA
   narzuca na implementację obowiązek automatycznego wyszukiwania klas.
   Ta cecha nie jest natomiast określona w przypadku uruchomienia w
   zwykłym środowisku JSE. Hibernate'a można jednak sparametryzować,
   żeby wyszukiwał te klasy również w JSE.
   -->
  <properties>
   <!-- Parametry konfiguracyjne dostawce JPA - Hibernate -->
   <property name="hibernate.archive.autodetection" value="class" />
   <property name="hibernate.show_sql" value="true" />
   <property name="hibernate.format_sql" value="true" />
   <!-- Parametry dostępu do bazy danych. -->
   <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver" />
   <property name="hibernate.connection.url" value="jdbc:hsqldb:file:jpapg1db" />
   <property name="hibernate.connection.username" value="sa" />
   <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
   <!--
    Parametr określający generowanie schematu bazy danych
    na podstawie mapingów.
    -->
   <property name="hibernate.hbm2ddl.auto" value="create" />
  </properties>
 </persistence-unit>
</persistence>

Model danych i mapowanie

Ograniczę się tylko na przedstawieniu listingu kodu źródłowego przykładu, który - mam przynajmniej taką nadzieję - jest wystarczająco okroszony komentarzami.
Użytkownik - User
package pl.dwalczak.jpapg1.model;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

@NamedQueries(value={
@NamedQuery(
        name="user.findAll",
        query="select u from User u order by u.nickName asc"
),
@NamedQuery(
  name="user.findByNickName",
  query="select u from User u where u.nickName = :nickName"
)})

// Definicja sekwencji "users_seq" służącej do generowania klucza głównego tabeli "users".
@SequenceGenerator(name="users_seq")

// Mapowanie tabeli "users".
@Entity
@Table(name="users")
public class User {

 private Long id;
 private String nickName;
 private List receivedMessages;
 private List sendMessages;
 
 // dostępność domyślnego konstruktora jest wymagana przez JPA.
 public User() {
 }
 
 public User(String nickName) {
  this.nickName = nickName;
 }

 // Klucz główny tabeli (kolumna "usr_id"),
 // którego wartość jest wyznaczana przez sekwencje "users_seq".
 @Id
 @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="users_seq")
 @Column(name="usr_id")
 public Long getId() {
  return id;
 }

 protected void setId(Long id) {
  this.id = id;
 }

 // Mapowanie kolumny "usr_nickname", która musi posiadać unikalne wartośc
 // i mieć maksymalną długość 24 znaków.
 @Column(name="usr_nickname", unique=true, nullable=false, length=24)
 public String getNickName() {
  return nickName;
 }

 public void setNickName(String name) {
  this.nickName = name;
 }

 // Mapowanie połączenia do tabeli "messages" w roli odbiorcy wiadomości.
 // Określenie kolumny w tabeli "messages" po której odbywa się złączenie ("msg_to").
 // Zaznaczenie, że w przypadku zapisu bytu "User" nie ma nic robić z "Message"
 // skojarzonymi przez te powiązanie.
 @OneToMany
 @JoinColumn(name="msg_to", insertable=false, updatable=false)
 public List getReceivedMessages() {
  return receivedMessages;
 }

 public void setReceivedMessages(List receivedMessages) {
  this.receivedMessages = receivedMessages;
 }

 // Definicja połączenia do tabeli "messages" w roli nadawcy wiadomości.
 @OneToMany
 @JoinColumn(name="msg_from", insertable=false, updatable=false)
 public List getSendMessages() {
  return sendMessages;
 }

 public void setSendMessages(List sendMessages) {
  this.sendMessages = sendMessages;
 }
}
Wiadomość - Message
package pl.dwalczak.jpapg1.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

@NamedQueries(value={
@NamedQuery(
        name="message.findReceived",
        query="select m from Message m where m.to = :user"
),
@NamedQuery(
        name="message.findSend",
        query="select m from Message m where m.from = :user"
)})

// Definicja sekwencji "messages_seq" służącej do generowania klucza głównego tabeli "messages".
@SequenceGenerator(name="messages_seq")

// Mapowanie tabeli "messages".
@Entity
@Table(name="messages")
public class Message {

 private Long id;
 private String title;
 private String content;
 private User from;
 private User to;
 
  // dostępność domyślnego konstruktora jest wymagana przez JPA
 public Message() {
 }
 
 public Message(String title, String content, User from, User to) {
  this.title = title;
  this.content = content;
  this.from = from;
  this.to = to;
 }

 // Klucz główny tabeli (kolumna "msg_id"),
 // którego wartość jest wyznaczana przez sekwencje "messages_seq".
 @Id
 @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="messages_seq")
 @Column(name="msg_id")
 public Long getId() {
  return id;
 }

 protected void setId(Long id) {
  this.id = id;
 }

 // Mapowanie kolumny "msg_title", której wartość może mieć maksymalnie 64 znaków.
 @Column(name="msg_title", length=64)
 public String getTitle() {
  return title;
 }

 public void setTitle(String title) {
  this.title = title;
 }

 // Mapowanie kolumny "msg_content", której wartość może mieć maksymalnie 1024 znaków.
 @Column(name="msg_content", length=1024)
 public String getContent() {
  return content;
 }

 public void setContent(String content) {
  this.content = content;
 }

 // Mapowanie do tabeli "users" - nadawcy wiadomości.
 // Powiązanie to jest obowiązkowe - musi być ustawione - i jest realizowane przez
 // klucz obcy do tabeli "users", którego wartość przechowuje kolumna "msg_from".
 // Jest to odpowiednik do powiązania zdefiniowanego na User.sendMessages.
 @ManyToOne(optional=false)
 @JoinColumn(name="msg_from", nullable=false)
 public User getFrom() {
  return from;
 }

 public void setFrom(User from) {
  this.from = from;
 }

 // Mapowanie do tabeli "users" - adresat wiadomości.
 // Jest to odpowiednik do powiązania zdefiniowanego na User.receivedMessages.
 @ManyToOne(optional=false)
 @JoinColumn(name="msg_to", nullable=false)
 public User getTo() {
  return to;
 }

 public void setTo(User to) {
  this.to = to;
 }
}

Entity Manager

Entity Manager jest tym w JPA, czym jest Session w Hibernate. W JSE można go pozyskać w następujący sposób:
javax.persistence.Persistence.createEntityManagerFactory($PERSISTANCE_UNIT_NAME).createEntityManager();
W EJB3 można to zrobić przez wstrzyknięcie (przez kontener) do pola w beanie EJB:
import javax.ejb.Stateless;
import javax.persistence.PersistenceContext;
@Stateless
public class ExampleBean implements Example {
 @PersistenceContext
 EntityManager em;
 ...
}
Entity Manager jest punktem wyjścia do wykonania takich operacji jak: - utrwalenie stanu objektu (metoda persist) - wykonanie zapytań (metoda find oraz grupa metod z rodziny createQuery) - manulnego określania tranzakcji - rozpoczęcie, akceptacji oraz wycofania (metoda getTransaction())

Język zapytań

Język zapytań w JPA to JPA-QL (rozszerzenie EJB-QL) i jest bardzo podobny do Hibernate'owego HQL'a. Myślę, że wykorzystane w tym przykładzie zapytania są na tyle proste, że nie wymagają komentarza.

Zasoby

Pliki źródłowe przykładu
Java Persistence with Hibernate - Sample Chapter 2
JPA - javadoc
Hibernate Annotations
Hibernate EntityManager