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