29 lipca 2007

Wakacje, wakacje, wakacje. I ja mam je również!

0 komentarzy
W końcu nadeszły moje upragnione wakacje (czytaj: urlop), podczas których zdecydowałem się być całkowicie poza siecią. Mam kilka książek do poczytania i niektóre nawet techniczne, ale to wyłącznie dla złagodzenia skutków szoku braku połączenia sieciowego. Bawcie się dobrze, przynajmniej tak dobrze jak ja! Przechodzę w stan hibernacji...(zbieżność przypadkowa).

26 lipca 2007

Java Persistence - Rozdział 8. Adnotacje metadanych (podstawowe) i jej sekcje 8.1 Entity oraz 8.2 Adnotacje zwrotne

0 komentarzy
Rozdział 8. Adnotacje metadanych (adnotacje podstawowe, ang. metadata annotations) przedstawia adnotacje opisujące klasy POJO jako klasy encji. Wszystkie adnotacje należą do pakietu javax.persistence.

Adnotacja @Entity definiuje klasę encji i może jedynie wystąpić na poziomie klasy. Adnotacja składa się z pojedyńczego elementu name, który przypisuje nazwę encji do późniejszego odwołania się do niej w zapytaniach JPQL. Domyślna wartość elementu odpowiada niekwalifikowanej (skróconej) nazwie klasy encji.

W poniższym przykładzie element name jest nadmiarowy, ponieważ jej domyślna wartość odpowiada dokładnie podanej wartości.

import javax.persistence.Entity;

@Entity(name = "Osoba")
public class Osoba implements Serializable {

private static final long serialVersionUID = 1L;

@Id
private String imie;

}

Adnotacja @EntityListeners definiuje klasy nasłuchujące zdarzeń rozwojowych encji lub mapowanej nadklasy (ang. mapped superclass), która może dekorować klasę encji bądź mapowaną nadklasę.
Adnotacja @EntityListeners posiada pojedyńczy element o typie Class[].

import javax.persistence.Entity;
import javax.persistence.EntityListeners;

@Entity(name = "Osoba")
@EntityListeners(OsobaListener.class)
public class Osoba implements Serializable {

private static final long serialVersionUID = 1L;

@Id
private long numer;

}

Adnotacja @ExcludeSuperclassListeners wyłącza wykonanie klas nasłuchujących deklarowanych przez nadklasy encji dla udekorowanej encji lub mapowanej nadklasy oraz ich podklas. Innymi słowy wyłącza się wykonanie klas nasłuchujących zdefiniowanych wyżej w hierarchii dziedziczenia dla wszystkich klas poczynając od klasy, w której występuje adnotacja oraz jej potomków. Adnotacja może dekorować klasę i nie posiada elementów.

import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.ExcludeSuperclassListeners;

@Entity
@ExcludeSuperclassListeners
@EntityListeners(PracownikSpecjalnyListener.class)
public class PracownikSpecjalny extends Osoba {

private static final long serialVersionUID = 1L;

}

Adnotacja @ExcludeDefaultListeners wyłącza wykonanie domyślnych klas nasłuchujących dla encji lub mapowanej nadklasy oraz ich potomków. Adnotacja może dekorować klasę i nie posiada elementów.

import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.ExcludeDefaultListeners;
import javax.persistence.ExcludeSuperclassListeners;

@Entity
@ExcludeSuperclassListeners
@EntityListeners(PracownikSpecjalnyListener.class)
@ExcludeDefaultListeners
public class PracownikSpecjalny extends Osoba {

private static final long serialVersionUID = 1L;

}

Adnotacje
  • @PrePersist
  • @PostPersist
  • @PreRemove
  • @PostRemove
  • @PreUpdate
  • @PostUpdate
  • @PostLoad
wskazują metody zwrotne wywoływane dla odpowiednich zdarzeń rozwojowych związanych z encją. Adnotacje mogą dekorować metody klasy encji, mapowanej nadklasy lub klasy nasłuchującej encji.

import javax.persistence.Entity;
import javax.persistence.EntityListeners;

@Entity(name = "Osoba")
@EntityListeners(OsobaListener.class)
public class Osoba implements Serializable {

private static final long serialVersionUID = 1L;

@Id
private long numer;

@PostPersist
protected void nowaOsobaUtworzona() {
logger.info("Utworzono osobę o identyfikatorze " + numer);
}

}

lub dla klasy nasłuchującej:

package pl.jaceklaskowski.jpa.entity.listeners;

import java.util.logging.Logger;

import javax.persistence.PostPersist;

import pl.jaceklaskowski.jpa.entity.Osoba;

public class OsobaListener {

private static final Logger logger = Logger.getLogger(OsobaListener.class.getName());

@PostPersist
protected void nowaOsobaUtworzona(Osoba osoba) {
logger.info("Utworzono osobę o identyfikatorze " + osoba.getNumer());
}
}

XIV spotkanie Warszawskiej Grupy Użytkowników Technologii Java (Warszawa JUG)

0 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na XIV spotkanie, które odbędzie się w nadchodzący wtorek 31.07.2007 o godzinie 18:00 w sali 4420 na Wydziale MIMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Testy jednostkowe - techniki i wyzwania
Prowadzący: Tomasz Kaczanowski

Testy jednostkowe są jednym z elementów zapewniania jakości oprogramowania. Za ich tworzenie odpowiada programista piszący kod. W czasie prezentacji Tomek zaprezentuje rozwiązania typowych problemów związanych z pisaniem testów jednostkowych, przedstawi podejście test-first oraz zaprezentuje pomocne techniki i narzędzia (mock-objects, code coverage). Do pełnego zrozumienia poruszanych zagadnień zaleca się zapoznanie z minimum wiedzy na temat testów jednostkowych, która została zaprezentowana w wątku Prezentacja 31 lipca 2007 - testy jednostkowe - techniki i wyzwania

Prezentację poprowadzi Tomasz Kaczanowski, który jestem programistą Javy (certyfikaty SCJP i SCWCD) w firmie eo Networks. Tomasz służbowo zajmuje się tworzeniem aplikacji internetowych, chociaż bliżej mu do rozwiązań korzystających z Java SE niż Java EE. Od dłuższego czasu śledzi (czyta i wypróbowuje na własnym kodzie) wszystko co związane z testami - zwłaszcza jednostkowymi. Uczuciowo związany z nurtem XP/Agile i...rodzinnym Krakowem.

Planowany czas prezentacji to 2 godziny z 15 minutową dyskusją.

Zapraszam w imieniu Warszawa JUG!

16 lipca 2007

Java Persistence - Rozdział 5.9 Zasady współpracy kontenera i dostawcy JPA

0 komentarzy
Rozdział 5.9 Zasady współpracy kontenera i dostawcy JPA (ang. Runtime Contracts between the Container and Persistence Provider) specyfikacji Java Persistence 1.0 opisuje zasady współpracy kontenera z dostawcą JPA w celu udostępnienia ujednoliconego interfejsu programistycznego do integracji dostawców JPA firm trzecich (innych niż dostawca domyślny JPA w wybranym kontenerze/serwerze). Specyfikacja podkreśla, że jeśli kontener nie wspiera integracji dostawców JPA innych niż domyślny dostawca JPA, kontener może korzystać z innego zestawu metod.

5.9.1 Wymagania stawiane kontenerowi

Do zarządzania kontekstów trwałych pojedyńczej transakcji, jeśli nie istnieje zarządca encji związany z aktywną transakcją JTA, wtedy:
  • Kontener tworzy nowego zarządcę encji wywołując metodę EntityManagerFactory.createEntityManager() podczas pierwszego wywołania zarządcy typu PersistenceContextType.TRANSACTION w ramach metody biznesowej wykonywanej w transakcji JTA.
  • Po zakończeniu transakcji JTA (zatwierdzenie/wycofanie), kontener zamyka zarządcę encji wywołując metodę EntityManager.close() (specyfikacja zauważa, że kontener ma prawo zarządzać zarządcami encji przez pulę zarządców, więc zamiast faktycznego wywołania metody close() może jedynie nastąpić wyczyszczenie go przez wywołanie EntityManager.clear()).
Kontener musi zgłosić wyjątek TransactionRequiredException, jeśli korzystając z kontekstu trwałego pojedyńczej transakcji metody typu EntityManager: persist, remove, merge oraz refresh są wywołane bez aktywnej transakcji.

Przykładowe ziarno bezstanowe EJB - EJB3TestServiceBean:

@Stateless
public class EJB3TestServiceBean implements EJB3TestService {

@PersistenceContext(unitName = "TestyEJB3EncjePU")
private EntityManager em;

@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void uruchom() {
Prelegent jacek = new Prelegent("Jacek", "Laskowski");
try {
em.persist(jacek);
} catch (TransactionRequiredException oczekiwano) {
System.out.println(">>> Oczekiwano wyjątku i faktycznie jest " + oczekiwano);
oczekiwano.printStackTrace();
}
}
}

uruchomione na Glassfish v2 b54 kończy się wspomnianym wyjątkiem - TransactionRequiredException.

[#|2007-07-16T14:13:29.437+0200|INFO|sun-appserver9.1|javax.enterprise.system.stream.out|_ThreadID=22;_ThreadName=p: thread-pool-1; w: 8;|
--- Oczekiwano wyjątku i faktycznie jest javax.persistence.TransactionRequiredException|#]

[#|2007-07-16T14:13:29.437+0200|WARNING|sun-appserver9.1|javax.enterprise.system.stream.err|_ThreadID=22;_ThreadName=p: thread-pool-1; w: 8;_RequestID=09ef8733-9d34-4675-a15c-c6e546238a91;|
javax.persistence.TransactionRequiredException
at com.sun.enterprise.util.EntityManagerWrapper.doTxRequiredCheck(EntityManagerWrapper.java:236)
at com.sun.enterprise.util.EntityManagerWrapper.doTransactionScopedTxCheck(EntityManagerWrapper.java:200)
at com.sun.enterprise.util.EntityManagerWrapper.persist(EntityManagerWrapper.java:426)
at pl.jaceklaskowski.ejb3.testy.transactions.EJB3TestServiceBean.uruchom(EJB3TestServiceBean.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at com.sun.enterprise.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1067)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:176)
at com.sun.ejb.containers.BaseContainer.invokeTargetBeanMethod(BaseContainer.java:2884)
at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:3975)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:203)
at com.sun.ejb.containers.EJBObjectInvocationHandlerDelegate.invoke(EJBObjectInvocationHandlerDelegate.java:117)
at $Proxy53.uruchom(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at com.sun.corba.ee.impl.presentation.rmi.ReflectiveTie._invoke(ReflectiveTie.java:125)
at com.sun.corba.ee.impl.protocol.CorbaServerRequestDispatcherImpl.dispatchToServant(CorbaServerRequestDispatcherImpl.java:658)
at com.sun.corba.ee.impl.protocol.CorbaServerRequestDispatcherImpl.dispatch(CorbaServerRequestDispatcherImpl.java:198)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequestRequest(CorbaMessageMediatorImpl.java:1820)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequest(CorbaMessageMediatorImpl.java:1680)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleInput(CorbaMessageMediatorImpl.java:1062)
at com.sun.corba.ee.impl.protocol.giopmsgheaders.RequestMessage_1_2.callback(RequestMessage_1_2.java:194)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequest(CorbaMessageMediatorImpl.java:780)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.dispatch(CorbaMessageMediatorImpl.java:537)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.doWork(CorbaMessageMediatorImpl.java:2541)
at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:524)
|#]

dla następujacego persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<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="TestyEJB3EncjePU" transaction-type="JTA">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<jta-data-source>jdbc/TestyEjb3Postgres</jta-data-source>
<class>pl.jaceklaskowski.ejb3.testy.encje.Prelegent</class>
<properties>
<property name="toplink.ddl-generation" value="drop-and-create-tables" />
<property name="toplink.logging.level" value="INFO" />
</properties>
</persistence-unit>
</persistence>

W przypadku stanowych ziaren sesyjnych EJB z rozszerzonym kontekstem trwałym wymagane jest od kontenera, aby:
  • Kontener tworzy zarządcę encji przez EntityManagerFactory.createEntityManager() podczas tworzenia stanowego ziarna EJB, które zadeklarowało zależność od zarządcy encji typu PersistenceContextType.EXTENDED.
  • Kontener zamyka zarządcę wywołując EntityManager.close() po usunięciu stanowego ziarna EJB oraz wszystkich innych stanowych ziaren EJB, które odziedziczyły (=którym przekazano) ten sam kontekst trwały.
  • Podczas wywołania metody biznesowej stanowego ziarna sesyjnego EJB, jeśli stanowe ziarno EJB korzysta z określania zasięgu transakcji przez kontener (ang. CMTD - container managed transaction demarcation), a zarządca encji nie jest związany z bieżącą transakcją JTA, kontener związuje zarządcę z bieżącą transakcją JTA wywołując metodę EntityManager.joinTransaction(). Jeśli istnieje inny kontekst trwały już związany z bieżącą transakcją JTA, kontener zgłasza wyjątek EJBException.

    Niestety nie jestem w stanie przygotować testu, który obrazowałby tą sytuację. Jeśli są chętni pomóc mi zamodelować ten warunek i uzyskać EJBException zapraszam do kontaktu. Popytam w międzyczasie na grupach. Może w ogóle nie da się tego odtworzyć po stronie klienckiej?

  • Podczas wywołania metody biznesowej stanowego ziarna sesyjnego EJB, jeśli stanowe ziarno EJB korzysta z określania zasięgu transakcji przez komponent (ang. BMTD - bean managed transaction demarcation) i UserTransaction jest aktywny, kontener związuje kontekst trwały z transakcją JTA i wywołuje EntityManager.joinTransaction().

Kontener musi zgłasić wyjątek IllegalStateException, jeśli aplikacja wywołuje EntityManager.close() na zarządcy encji zarządzanym przez kontener.

Caused by: java.lang.IllegalStateException
at com.sun.enterprise.util.EntityManagerWrapper.close(EntityManagerWrapper.java:1056)
at pl.jaceklaskowski.ejb3.testy.przekazywaniekontekstu.ZamykajacyBean.wykonaj(ZamykajacyBean.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at com.sun.enterprise.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1067)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:176)
at com.sun.ejb.containers.BaseContainer.invokeTargetBeanMethod(BaseContainer.java:2884)
at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:3975)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:203)
... 17 more

dla wywołania EntityManager.close() w następującym bezstanowym ziarnie EJB:

@Stateless
public class ZamykajacyBean implements OperatorKontekstuTrwalego {

@PersistenceContext
EntityManager em;

public void wykonaj() {
em.close(); // niedozwolone
}
}

Kiedy kontener tworzy zarządcę encji, przy pomocy EntityManagerFactory.createEntityManager(Map map) może przekazać mapę właściwości dla dostawcy trwałego. Jeśli właściwości zostały zdefiniowane poprzez @PersistenceContext lub element persistence-context-ref w deskryptorze XML poprzez wywołanie metody EntityManagerFactory.createEntityManager(Map map) zostaną one przekazane dostawcy.

5.9.2 Wymagania stawiane dostawcy JPA

Dostawca JPA nie rozróżnia podziału na konteksty trwałe pojedyńczej transakcji i rozszerzone. Rolą dostawcy JPA jest dostarczanie zarządców encji na żądanie kontenera i zarejestrowanie siebie do otrzymywania zdarzeń związanych z transakcjami (w ten sposób to rolą kontenera jest odpowiednie wywoływanie metod rozpoczynających i kończących transakcję, co przekłada się na wysyłanie zmian stanu transakcji do dostawcy JPA. Rozróżnienie pojedyńczej i rozszerzonej transakcji widoczne jest na poziomie kontenera).

Wywołanie EntityManagerFactory.createEntityManager() przez kontener powoduje utworzenie i zwrócenie nowego zarządcy encji przez dostawcę. Jeśli istnieje aktywna transakcja JTA, dostawca musi zarejestrować się w celu otrzymywania zmian stanu bieżącej transakcji JTA.

Podczas wywołania EntityManager.joinTransaction() dostawca rejestruje się w celu otrzymywania zgłoszeń związanych z bieżącą transakcją JTA jeśli wcześniejsze wywołanie EntityManager.joinTransaction() nie zostało jeszcze zrealizowane (np. w przypadku braku aktywnej transakcji).

Podczas zatwierdzania transakcji JTA, dostawca musi wysłać wszystkie zmiany encji do bazy danych.

Podczas wycofywania transakcji JTA, dostawca musi odłączyć wszystkie zarządzane encje.

Kiedy dostawca zgłasza wyjątek, który zdefiniowano jako wycofujący transakcję, dostawca zaznacza bieżącą transakcję do wycofania.

Podczas wywołania EntityManager.close() dostawca powinien zwolnić wszystkie zasoby, które zostały w międzyczasie utworzone. Pełne zwolnienie zasobów nastąpi dopiero podczas zakończenia wszystkich aktywnych transakcji, w których zarządca encji i zarządzane przez niego zasoby brały udział. Jeśli zarządca jest już zamknięty, dostawca zgłasza wyjątek IllegalStateException.

Podczas wywołania EntityManager.clear(), dostawca musi wyczyścić kontekst trwały, a tym samym odłączyć wszystkie zarządzane encje (encje przejdą w stan odłączony).

public void testEntityManagerAPI() {
Osoba encja = new Osoba("Jacek", "Laskowski");
EntityTransaction et = em.getTransaction();
et.begin();
em.persist(encja);
et.commit();
assert em.contains(encja) : "Encja musi być w kontekście trwałym (musi być zarządzana)";
em.clear();
assert !em.contains(encja) : "Encja nie może być w kontekście trwałym (musi być odłączona)";
assert encja.getNumer() > 0 : "Encja musi mieć przypisany identyfikator trwały chociaż nie jest zarządzana";
}

Tym samym lekturę rozdziału 5. o zarządcach encji i kontekstach trwałych uważam za zakończoną. Poza kilkoma miejscami, które wymagają doprecyzowania przez utworzenie testów, które demonstrowałyby temat w akcji, wszystko zostało powiedziane...ekhm...napisane. Jutro się wszystko okaże podczas prezentacji JPA na spotkaniu Warszawa JUG. Zapraszam na 18:00 na MIMUW na Banacha 2.

15 lipca 2007

Java Persistence - Rozdział 5.8 Wymagania stawiane kontenerowi/serwerowi aplikacyjnemu

0 komentarzy
Rozdział 5.8 Wymagania stawiane kontenerowi/serwerowi aplikacyjnemu (ang. Requirements on the Container) jest bardzo krótki, gdyż treść podsumowuje już opisane wymagania jakie stawiane są kontenerowi, który odpowiedzialny jest za utrzymywanie stanu obiektów JPA. Przede wszystkim skrótowo przedstawiony jest sposób tworzenia kontekstów trwałych dostarczanych przez dowolnych dostawców JPA (integracja z dostawcami JPA) oraz sposób ich przekazywania między składowymi (komponentami) aplikacji.

5.8 Wymagania stawiane kontenerowi

Rolą serwera aplikacyjnego Java EE jest dostarczenie zestawu usług, które upraszczają konstruowanie aplikacji. Jedną z usług jest usługa utrwalania danych (usługa trwałości). W tej roli serwer występuje jako klient JPA korzystając bezpośrednio z fabryki zarządców encji oraz zarządzając zarządcami. Można wyróżnić "poziomy" integracji aplikacji z usługą utrwalania serwera aplikacyjnego:

1. Poziom zerowej integracji aplikacji z serwerem, w którym aplikacja tworzy zarządców poprzez Persistence (lub opcjonalnie przez metody interfejsu PersistenceProvider).
 @Stateless
public class KoordynatorTestowBean implements KoordynatorTestow {

public void uruchomTesty() {
uruchomAplikacyjnyKontekstTrwaly();
}

private void uruchomAplikacyjnyKontekstTrwaly() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestyEJB3EncjePU");
EntityManager em = emf.createEntityManager();
Prelegent p = new Prelegent("Jacek", "Laskowski");
em.persist(p);
em.close();
emf.close();
}

}
2. Integracja połowiczna aplikacji z serwerem poprzez korzystanie z utworzonej przez serwer fabryki zarządców, która pozyskiwana jest przez aplikację dzięki DI albo JNDI.
 @Stateless
public class KoordynatorTestowBean implements KoordynatorTestow {

@PersistenceUnit(unitName = "TestyEJB3EncjePU")
private EntityManagerFactory emf;

public void uruchomTesty() {
uruchomAplikacyjnyKontekstTrwalyPrzezZarzadzanaFabryke();
}

private void uruchomAplikacyjnyKontekstTrwalyPrzezZarzadzanaFabryke() {
EntityManager em = emf.createEntityManager();
Prelegent p = new Prelegent("Jacek", "Laskowski");
em.persist(p);
em.close();
}

}
3. Pełna integracja polegająca na przekazywaniu zarządców encji za pomocą DI albo JNDI.
 @Stateless
public class KoordynatorTestowBean implements KoordynatorTestow {

@PersistenceContext(unitName = "TestyEJB3EncjePU")
private EntityManager em;

public void uruchomTesty() {
uruchomSerwerowyKontekstTrwaly();
}

private void uruchomSerwerowyKontekstTrwaly() {
Prelegent p = new Prelegent("Jacek", "Laskowski");
em.persist(p);
}

}
Dowolna kombinacja integracji jest możliwa, nawet taka, w której wykorzystuje się wszystkie wymienione wyżej sposoby integracji.

Serwer aplikacyjny musi udostępniać fabrykę zarządców encji w drzewie JNDI, jeśli aplikacja korzysta z kontekstu trwałego zarządzanego przez serwer. Serwer ma możliwość korzystania z nieustandaryzowanego interfejsu integracji z dostawcą JPA. Wymaga się jednak, aby serwer wspierał również innych dostawców (niż domyślny) poprzez wykorzystanie metody PersistenceProvider.createContainerEntityManagerFactory(). Zamknięcie fabryki następuje porzez wywołanie metody EntityManagerFactory.close() przy zatrzymaniu serwera (jeśli wcześniej kontekst nie został zamknięty przez aplikację - tylko w sytuacji aplikacyjnych kontekstów trwałych).

Interfejs javax.persistence.spi.PersistenceProvider realizowany jest przez dostawcę JPA i pozwala na utworzenie fabryki zarządców. Realizacja interfejsu definiowana jest w pliku persistence.xml dla każdego zarządcy encji (=danego dostawcy JPA). Interfejs wykorzystywany jest bezpośrednio przez serwer aplikacyjny podczas tworzenia fabryki zarządców encji lub przez klasę Persistence dla samodzielnych aplikacji (aplikacji uruchomionych poza zasięgiem usług serwera aplikacyjnego).

Dobrym ćwiczeniem na rozpoznanie integracji dostawcy JPA z serwerem byłoby stworzenie własnego dostawcy JPA.

Dowolna aplikacja uruchomiona w ramach serwera aplikacyjnego może bezpośrednio korzystać z JPA, tj. samodzielnie zarządzać obiektami JPA dzięki aplikacyjnym kontekstom trwałym (kontekstom niezarządzanym przez serwer).

Serwer aplikacyjny jest zobowiązany do zarządzania cyklem rozwojowym kontekstów trwałych zarządzanych przez kontener i zarządzane obiekty typu EntityManager wstrzeliwuje (przekazuje) do komponentów aplikacji internetowej (m.in. servlety, filtry), ziarnom sesyjnym oraz sterowanym komunikatami EJB oraz umieszcza w drzewie JNDI.

Nie jest określone, czy nowy egzemplarz zarządcy encji jest tworzony każdorazowo dla kontekstu trwałego, czy zarządcy są czasami współdzieleni. Dokładne związywanie kontekstu trwałego z transakcją JTA przez kontener nie jest określone przez specyfikację.

Jeśli kontekst trwały jest związany z transakcją JTA, kontener korzysta z kontekstu trwałego dla kolejnych wywołań w ramach tej transakcji zgodnie z regułami przekazywania kontekstu trwałego opisanymi w rozdziale 5.6.3 (relacjonowałem tą część w notatce Java Persistence - Rozdziały 5.5 Zarządzanie transakcjami oraz 5.6 Konteksty trwałe zarządzane przez serwer).

14 lipca 2007

IBM WebSphere Application Server Version 6.1 Feature Pack for EJB 3 Beta

2 komentarzy
Wreszcie jest! Nie jest to wersja produkcyjna, ale zawsze to lepsze niż nic. Firma IBM wydała wersję BETA rozszerzenia o nazwie IBM WebSphere Application Server Version 6.1 Feature Pack for EJB 3. Dla niektórych nie stanowi to żadnej zagadki, dlaczego tak bardzo mi na tym zależy, a dla tych, którzy nie wiedzą napiszę jedynie, że część mojego dnia spędzam z produktami tejże firmy i z dużym zniecierpliwieniem oczekiwałem na pojawienie się nowej wersji dodatku do IBM WebSphere Application Server 6.1. I w końcu się doczekałem.

Wcześniejszą wersję ALPHA miałem uruchomioną z serwerem WAS 6.1.0.3, więc konieczne było najpierw odinstalowanie poprzedniej wersji dodatku, zainstalowanie poprawki do wersji 6.1.0.7 i dopiero wtedy zainstalowanie rozszeszenia.

jlaskowski@dev /cygdrive/c/was61/AppServer
$ ./bin/versionInfo.bat
WVER0010I: Copyright (c) IBM Corporation 2002, 2005; All rights reserved.
WVER0012I: VersionInfo reporter version 1.15.1.13, dated 3/29/06

--------------------------------------------------------------------------------
IBM WebSphere Application Server Product Installation Status Report
--------------------------------------------------------------------------------

Report at date and time 13 lipiec 2007 09:37:59 CEST

Installation
--------------------------------------------------------------------------------
Product Directory C:\was61\AppServer
Version Directory C:\was61\AppServer\properties\version
DTD Directory C:\was61\AppServer\properties\version\dtd
Log Directory C:\was61\AppServer\logs
Backup Directory C:\was61\AppServer\properties\version\nif\backup
TMP Directory C:\DOCUME~1\JLASKO~1\LOCALS~1\Temp

Product List
--------------------------------------------------------------------------------
BASE installed

Installed Product
--------------------------------------------------------------------------------
Name IBM WebSphere Application Server
Version 6.1.0.3
ID BASE
Build Level cf30646.29
Build Date 11/15/06

--------------------------------------------------------------------------------
End Installation Status Report
--------------------------------------------------------------------------------
Zaczynam od instalacji narzędzia instalacyjnego poprawki (ang. Update Installer) zgodnie z dokumentem Update Installer for WebSphere Application Server V6.1 releases, a dokładniej Installing the Update Installer for WebSphere Software.

Po 1-2 minutach nowa wersja narzędzia jest zainstalowana. Plik version.txt, w katalogu narzędzi wskazuje na wersję Build Date: 06/14/2007.

Następnie przechodzę do instalacji poprawki dla serwera WAS 6.1 i podniesienia wersji do 6.1.0.7 - Readme for IBM WebSphere Application Server version 6.1.0.7.

Kopiuję pliki 6.1.0-WS-WASSDK-WinX32-FP0000007.pak oraz 6.1.0-WS-WAS-WinX32-FP0000007.pak do katalogu C:\was61\UpdateInstaller\maintenance, który jest katalogiem poprawek i uruchamiam narzędzie aktualizujące WAS 6.1 przy pomocy polecenia update.

Chwila (około 5 minut) i poprawki zainstalowane.
C:\was61\AppServer>.\bin\installRegistryUtils.bat -listProducts

WNIF0100I: Copyright (c) IBM Corporation 2006; Wszelkie prawa zastrzeżone.
WNIF0102I: Narzędzie raportowania rejestru instalacji wersja 1.4

----------------------------------------------------------------------------
Raport rejestru instalacji produktu IBM WebSphere Application Server
----------------------------------------------------------------------------

Data i godzina raportu: 14 lipiec 2007 09:40:52 CEST

----------------------------------------------------------------------------

Produkt został zainstalowany
----------------------------------------------------------------------------
Identyfikator produktu UPDI
Położenie instalacji C:\was61\UpdateInstaller
Wersja 6.1.0.9

Produkt został zainstalowany
----------------------------------------------------------------------------
Identyfikator produktu BASE
Położenie instalacji C:\was61\AppServer
Wersja 6.1.0.7

----------------------------------------------------------------------------
Koniec raportu rejestru instalacji
----------------------------------------------------------------------------
Następnie pobieram fep.61.ejb3.windows.ia32.zip. Wewnątrz znajduje się katalog EJB3, w którym znajduję narzędzie instalacyjne - install.exe. Po rozpakowaniu do dowolnego katalogu, rozpoczynam instalację przez uruchomienie install.

Wybieram przycisk Next >.

System sprawdzony - można zaczynać. Next > ponownie.

Podsumowanie instalacji i znowu Next >.

Jest dobrze. Instalacja trwa. Powiedziałbym, że jest bardzo dobrze. Mija 10 minuta i...

IBM WAS 6.1 Feature Pack for EJB3 zainstalowany poprawnie. Zgodnie z zaleceniem tworzę profil dedykowany do ewaluacji dodatku (nie muszę, ale co mi szkodzi).

Fajny panel z portami i po Dalej > w końcu ostatnie zatwierdzenie.

Po chwili - może 5-10 minut - sprawdzam jak się ma nowy profil.
C:\was61\AppServer\bin>installRegistryUtils.bat -listProducts

WNIF0100I: Copyright (c) IBM Corporation 2006; Wszelkie prawa zastrzeżone.
WNIF0102I: Narzędzie raportowania rejestru instalacji wersja 1.4

----------------------------------------------------------------------------
Raport rejestru instalacji produktu IBM WebSphere Application Server
----------------------------------------------------------------------------

Data i godzina raportu: 14 lipiec 2007 16:31:00 CEST

----------------------------------------------------------------------------

Produkt został zainstalowany
----------------------------------------------------------------------------
Identyfikator produktu UPDI
Położenie instalacji C:\was61\UpdateInstaller
Wersja 6.1.0.9

Produkt został zainstalowany
----------------------------------------------------------------------------
Identyfikator produktu BASE
Położenie instalacji C:\was61\AppServer
Wersja 6.1.0.7

Produkt został zainstalowany
----------------------------------------------------------------------------
Identyfikator produktu EJB3
Położenie instalacji C:\was61\AppServer
Wersja 6.1.0.7

----------------------------------------------------------------------------
Koniec raportu rejestru instalacji
----------------------------------------------------------------------------
Sprawdzenie poprawności instalacji sprowadza się do uruchomienia przykładowej aplikacji EJB3Counter, która dostarczana jest wraz z dodatkiem (Feature Pack) EJB3.

Pojawił się niegroźny, ale mylący błąd w dokumentacji o miejscu, w którym dostępna jest aplikacja WAS_HOME/samples/EJB3SampleApplications/EJB3Counter zamiast WAS_HOME/samples/src/EJB3SampleApplications/EJB3Counter, ale poza tym wszystko się zgadza.
C:\was61\AppServer\bin>wsadmin -lang jython -f ..\samples\src\EJB3SampleApplications\EJB3Counter\installEJB3CounterSampl
e.py -conntype SOAP -port 8880
WASX7209I: Połączono z procesem server1 w węźle DEVNode01 przy użyciu konektora SOAP. Typ procesu: UnManagedProcess.
EJB3CounterSample was not previously installed
ADMA5016I: Instalowanie aplikacji EJB3CounterSample zostało rozpoczęte.
ADMA5058I: Poprawność wersji aplikacji i modułu została sprawdzona z wersjami docelowych obiektów wdrożenia.
ADMA5005I: Aplikacja EJB3CounterSample jest skonfigurowana w repozytorium serwera WebSphere Application Server.
ADMA5053I: Utworzono odwołania do biblioteki dla zainstalowanego pakietu opcjonalnego.
ADMA5005I: Aplikacja EJB3CounterSample jest skonfigurowana w repozytorium serwera WebSphere Application Server.
ADMA5001I: Pliki binarne aplikacji są zapisane w C:\was61\AppServer\profiles\AppSrv02\wstemp\Script113c5319e07\workspace
\cells\DEVNode01Cell\applications\EJB3CounterSample.ear\EJB3CounterSample.ear
ADMA5005I: Aplikacja EJB3CounterSample jest skonfigurowana w repozytorium serwera WebSphere Application Server.
SECJ0400I: Pomyślnie zaktualizowano aplikację EJB3CounterSample przy użyciu informacji z atrybutu appContextIDForSecurit
y.
ADMA5011I: Zakończono procedurę czyszczącą katalog tymczasowy dla aplikacji EJB3CounterSample.
ADMA5013I: Aplikacja EJB3CounterSample została pomyślnie zainstalowana.
Otwieram przeglądarkę i uruchamiam aplikację.

Cała zabawa z przykładem polega na utrwalaniu kolejnych wciśnięć przycisku Increment i zapamiętywaniu ich ilości. Kolejne uruchomienie aplikacji zapamiętuje poprzedni stan. Nie wiem jaki dostawca JPA jest w ramach dodatku, ale coś mi mówi, że jest to Apache OpenJPA. Czekam na odpowiedź na to pytanie, które zadałem na forum IBM WebSphere Application Server Version 6.1 Feature Pack for EJB 3 Beta Program. Zainteresowany odpowiedziami związanymi z rozszerzeniem? Koniecznie zajrzyj na forum. Pora powrócić do specyfikacji. A! Przecież wcześniej jestem umówiony z żonką na Death Proof. Idę zaczerpnąć trochę zabawy w wykonaniu Tarantino.

12 lipca 2007

XIII spotkanie Warszawskiej Grupy Użytkowników Technologii Java (Warszawa JUG)

0 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na XIII spotkanie, które odbędzie się w nadchodzący wtorek 17.07.2007 o godzinie 18:00 w sali 4420 na Wydziale MIMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Java Persistence 1.0 (JPA) i środowisko do praktycznego poznawania specyfikacji
Prowadzący: Jacek Laskowski

Tematem prezentacji będzie wprowadzenie do specyfikacji Java Persistence API (JPA) i przedstawienie środowiska do jej praktycznego poznawania opartego o Apache Maven 2.
JPA jest częścią specyfikacji Enterprise JavaBeans 3.0, która z kolei stanowi część specyfikacji Java Enterprise Edition 5 (Java EE 5). W języku Java świat przedstawiany jest w postaci obiektów podczas, gdy w świecie baz danych relacyjnych w postaci krotek. Specyfikacja JDBC wraz z językiem SQL pozwala na pobranie danych z bazy i jej obsługę, jednakże nakłada na programistę obowiązek zasilania danymi relacyjnymi obiektowego modelu danych. Za pomocą rozwiązań mapujących dane relacyjne na typy obiektowe i na odwrót (zwanych rozwiązaniami ORM) mamy możliwość pracy z danymi relacyjnymi obiektowo. W ten sposób możemy znacząco skrócić czas tworzenia aplikacji. Rolą JPA jest ujednolicenie możliwości Hibernate, TopLink oraz JDO i udostępnienie ustandaryzowanego interfejsu dostępu do relacyjnych danych.
Podstawową zaletą JPA jest możliwość uruchomienia dostawcy JPA w ramach serwera aplikacyjnego, ale również w trybie poza kontenerem oraz wykorzystanie podejścia programistycznego opartego o POJO. Wiele z funkcjonalności do tej pory znanych z Hibernate jest dostępna w JPA za pomocą darmowych dostawców JPA, którymi są Apache OpenJPA, TopLink Essentials oraz Hibernate EntityManager.

Prezentację poprowadzi Jacek Laskowski, który zajmuje się rozwiązaniami opartymi o język Java i platformę Korporacyjnej Javy (Java EE) od pierwszych dni ich opublikowania. Jacek jest członkiem zespołów Apache Geronimo, Apache OpenEJB, Apache ServiceMix, Apache XBean oraz Apache ActiveMQ. Jestem założycielem i liderem Warszawskiej Grupy Użytkowników Technologii Java (Warszawa JUG), która regularnie spotyka się na MIMUW w Warszawie. Jacek bierze aktywny udział w wielu forach dyskusyjnych i otwartych projektach. Własne doświadczenia z językiem Java, Java EE i otwartymi projektami opisuje w Notatniku Projektanta Java EE. Służbowo pracuje na stanowisku konsultanta oprogramowania w IBM Polska.

Planowany czas prezentacji to 1,5 godziny z 15 minutową dyskusją.

Zapraszam w imieniu swoim i Warszawa JUG!

11 lipca 2007

Java Persistence - Rozdział 5.7 Kontekst trwały zarządzany przez aplikację (aplikacyjny kontekst trwały)

1 komentarzy
Kolejna relacja z lektury specyfikacji Java Persistence API. W rozdziale 5. Zarządcy encji i konteksty trwałe pozostały (pod)rozdziały 5.7, 5.8 oraz 5.9. Tym razem zajmę się rozdziałem 5.7 o aplikacyjnych kontekstach trwałych głównie wykorzystywanych w aplikacjach desktopowych w środowisku Java SE.

5.7 Kontekst trwały zarządzany przez aplikację (aplikacyjny kontekst trwały)

Użycie zarządzcy trwałego zarządzanego przez aplikację (ang. application-managed entity manager) nakłada na aplikację obowiązek bezpośredniej komunikacji z fabryką zarządców trwałych w celu zarządzania cyklem rozwojowym zarządcy trwałego oraz tworzenia i niszczenia kontekstów trwałych z nim związanych.

Określenia zarządca trwały zarządzany przez aplikację oraz aplikacyjny zarządca trwały są w tym kontekście jednoznaczne. Będę używał ich naprzemiennie.

Kontekst trwały zarządzany przez aplikację przypomina rozszerzony kontekst trwały zarządzany przez kontener, tj. może istnieć dłużej niż pojedyńcza transakcja. Decyzję o utworzeniu i zamknięciu (zniszczeniu) kontekstu trwałego podejmuje aplikacja (aplikacją jest również sam serwer aplikacyjny Java EE i bez względu na jego rolę w specyfikacji Java EE interfejs publiczny JPA jest identyczny - istotna różnica to, kto odpowiada za zarządzanie kontekstami i całą infrastukturą JPA).

Metody EntityManager.close() oraz EntityManager.isClose() służą do zarządzania cyklem rozwojowym aplikacyjnego zarządcy trwałego i kontekstów trwałych z nim związanych.

Metoda EntityManager.close() służy do zamknięcia zarządcy trwałego i zwolnienia zajmowanych/zarządzanych przez niego zasobów (konteksty trwałe). Wywołanie jakiejkolwiek metody na zarządcy encji, na którym wywołano metodę close(), oraz egzemplarzy Query, które zostały przez niego stworzone, zakończy się wyjątkiem java.lang.IllegalStateException za wyjątkiem metod EntityManager.getTransaction() oraz EntityManager.isOpen() (która zwróci false). Jeśli metoda EntityManager.close() zostanie wywołana w trakcie aktywnej transakcji, konteksty trwałe z nim związane pozostają zarządzane aż do czasu zakończenia transakcji.

Metoda EntityManager.isOpen() określa, czy zarządca encji jest otwarty. Wartość true zwracana jest do momentu zamknięcia zarządcy (wywołanie metody EntityManager.close()).

Rozszerzony kontekst trwały jest powoływany do życia w momencie utworzenia zarządcy trwałego korzystając z metody EntityManagerFactory.createEntityManager(), aż do zamknięcia zarządcy przez EntityManager.close(). Aplikacyjny kontekst trwały jest samodzielnym kontekstem trwałym, tj. nie jest przekazywany do zasobów uczestniczących w tej samej transakcji.

Użycie zarządcy trwałego JTA nakłada na aplikację obowiązek utrzymania zarządcy w ramach aktywnej transakcji JTA. Jeśli zarządca trwały JTA utworzony został poza aktywną transakcją JTA to aplikacja dba o ich połączenie, kiedy konieczne, korzystając z metody EntityManager.joinTransaction().

Rozdział 5.7.1. Examples przedstawia kilka przykładów kontekstów trwałych ze względu na ich zarządzanie - aplikacja vs kontener. Pierwszy przykład 5.7.1.1 Application-managed Persistence Context used in Stateless Session Bean jest wyjątkowo ciekawy, ponieważ uwidacznia znaczenie wykorzystania usług serwera aplikacyjnego - usługi monitora transakcyjnego - do automatycznego zarządzania transakcjami. Mimo, że bezstanowe ziarno sesyjne EJB same zarządza zarządcą encji (przypadek aplikacyjnego zarządcy encji) to utrzymywanie aktywnej transakcji JTA spoczywa na barkach serwera aplikacyjnego. Metody getOrder() oraz getProduct() z biznesowego interfejsu ziarna korzystają z mechanizmu wyszukiwania JTA (metody EntityManager.find() oraz EntityManager.createQuery()), a to nie wymaga aktywnej transakcji, ale już wywołanie metody EntityManager.persist() musi być związane z aktywną transakcją, a jej utworzeniem/zamknięciem zajmuje się serwer (ang. container-managed transaction demarcation). Podczas tworzenia zarządcy trwałego następuje związanie zarządcy z aktywną transakcją JTA i stąd niekonieczne jest explicite wywołanie metod EntityManager.joinTransaction() lub EntityManager.getTransaction(), a później EntityTransaction.begin().

Kolejny przykład w sekcji 5.7.1.2 Application-managed Persistence Context used in Stateless Session Bean, szczególnie implementacja metody createLineItem() ukazuje konieczność związania aplikacyjnego zarządcy trwałego z aktywną transakcją, gdyż utworzenie zarządcy nastąpiło poza aktywną transakcją JTA. Każde wywołanie metody bezstanowego ziarna sesyjnego EJB wiąże się z wywołaniem metody EntityManager.clear(), co czyści kontekst trwały, więc jakiekolwiek wywołanie metody EntityManager.contains() na danej encji (nawet tej, która była utrwalona/odszukana wcześniej) zwróciłoby false.

W sekcji 5.7.1 znajdują się jeszcze 2 przykłady mniejszego kalibru merytorycznego w porównaniu z poprzednimi dwoma. Zainteresowanych zapraszam do specyfikacji na strony 127 i 128.

10 lipca 2007

Krótka przygoda z ICEFaces 1.6.0, Eclipse 3.3 i wtyczką Glassfish Eclipse Plugin

1 komentarzy
Do niedawna sądziłem, że wsparcie Eclipse IDE przy tworzeniu aplikacji Java EE jest...delikatnie mówiąc...słabsze niż w NetBeans IDE. Co rusz natrafiam jednakże na funkcjonalność Eclipse IDE, które zmienia moje zdanie nt. temat. Pojawiła się właśnie nowa wersja biblioteki ICEfaces 1.6.0 i postanowiłem sprawdzić, ile czasu zajmie mi uruchomienie prostej aplikacji internetowej z nią korzystając z Eclipse IDE 3.3 oraz wtyczki Glassfish.

Rozpoczynam od pobrania ICEfaces 1.6.0 ze strony ICEfaces Downloads.

Początkowo próbowałem namierzyć biblioteki w repozytorium m2, ale nie miałem szczęścia i odpuściłem. Później jeszcze raz próbowałem z wtyczką m2eclipse po zainstalowaniu ICEfaces w lokalnym repozytorium m2, ale w połączeniu z Eclipse i wtyczką Glassfish nie szło mi najlepiej i również odpuściłem.

Otwieram Eclipse IDE 3.3 z zainstalowaną wtyczką dla Glassfish (pracuję z Glassfish v2 b54).

Tworzę dynamiczny projekt aplikacji internetowej (New > Project > Web > Dynamic Web Project) o nazwie icefaces-witajswiecie. W międzyczasie definiuję Glassfish oraz wybieram JavaServer Faces Project Facet


i deklaruję korzystanie z biblioteki JSF przez zaznaczenie opcji Server Supplied JSF Implementation.

Kończę tworzenie projektu przyciskiem Finish.

Jako pierwszy krok dodaję biblioteki wymagane przez projekt ICEfaces opisane w dokumencie ICEfaces Library Dependencies.

Tworzenie strony JSF w Eclipse IDE to tworzenie strony JSP - New > JSP. Eclipse nie dostarcza dedykowanego pomocnika (wizard) dla stron JSF.

Pierwszą stronę nazwałem index.jsp, która będzie przekierowywała na stronę zawierającą elementy ICEfaces.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" >
<html>
<body>
<jsp:forward page="index.iface" />
</body>
</html>
A skąd to dziwne rozszerzenie - .iface? Jest to rozszerzenie, z którym domyślnie związuje się infrastrukturę JSF dostarczaną przez ICEfaces przez deklarację w deskryptorze aplikacji internetowej WEB-INF/web.xml.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>icefaces-witajswiecie</display-name>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value>
</context-param>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.iface</param-value>
</context-param>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.jspx</param-value>
</context-param>
<context-param>
<param-name>com.icesoft.faces.synchronousUpdate</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.icesoft.faces.concurrentDOMViews</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Persistent Faces Servlet</servlet-name>
<servlet-class>com.icesoft.faces.webapp.xmlhttp.PersistentFacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Blocking Servlet</servlet-name>
<servlet-class>com.icesoft.faces.webapp.xmlhttp.BlockingServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>*.iface</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>/xmlhttp/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Blocking Servlet</servlet-name>
<url-pattern>/block/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
</web-app>
W deskryptorze aplikacji internetowej zauważyć można definicję kilku dodatkowych servletów, które uaktywniają ICEfaces. ICEfaces jest rozszerzeniem dla JSF (siłą JSF jest właśnie mechanizm umożliwiający jego rozbudowę z czego skorzystał nie tylko ICEfaces, ale również JBoss Seam, RichFaces, MyFaces i wiele innych bibliotek komponentów czy środowisk JSF). Koniecznie należy zarejestrować PersistentFacesServlet (wersja ICEfaces standardowego servletu FacesServlet) oraz BlockingServlet do obsługi asynchronicznych wywołań.

Kolejna strona - index.jspx - jest główną stroną aplikacji.

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html" xmlns:ice="http://www.icesoft.com/icefaces/component" version="2.0">
<jsp:directive.page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" />
<f:view>
<html>
<head>
<title>Witaj ICEfaces</title>
</head>
<body>
<h3>Witaj ICEfaces</h3>
<p>Wciśnij przycisk poniżej i obserwuj ikonę stanu w przeglądarce oraz dziennik zdarzeń serwera aplikacyjnego.</p>
<ice:form partialSubmit="true">
<h:commandButton value="Wciśnij mnie" actionListener="#{ranking.zaglosuj}" />
</ice:form>
</body>
</html>
</f:view>
</jsp:root>
Baczne oko zauważy, że cały plik jest dobrze zbudowanym plikiem XHTML, stąd rozszerzenie jspx (domyślne dla dokumentów JSP, które są poprawnymi dokumentami XML). Jest to wymaganie ICEfaces, które nakłada na aplikację tworzenie stron JSP jako poprawnie zbudowanych dokumentów XML.

Strona index.jspx zawiera znaczniki ICEfaces rozpoczynające się nazwą ice.

Ciekawostką Eclipse IDE 3.3 z wtyczką Eclipse WTP 2.0.0 jest mechanizm podpowiedzi dla stron JSF.


Strona index.jspx wykorzystuje zarządzane ziarno JSF ranking. Definicja ziarna odbywa się w pliku faces-config.xml.

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
version="1.2">
<managed-bean>
<managed-bean-name>ranking</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.icefaces.ranking.beans.Ranking</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>

Podczas edycji deskryptora aplikacji JSF - faces-config.xml - ujawniła się kolejna ciekawa funkcjonalność Eclipse IDE (a właściwie wtyczki Eclipse WTP 2.0.0) - graficzny edytor faces-config.xml.

Edytor posiada wiele widoków podłączonych do zakładek (u dołu zrzutu ekranu powyżej) i jest ciekawym i darmowym dodatkiem podczas tworzenia aplikacji JSF. Jak na kilka chwil z nim przyznaję, że zrobił na mnie pozytywne wrażenie.

Na koniec pozostaje przedstawić zarządzane ziarno JSF - ranking - reprezentowane przez klasę pl.jaceklaskowski.icefaces.ranking.beans.Ranking.

package pl.jaceklaskowski.icefaces.ranking.beans;

import javax.faces.event.ActionEvent;

public class Ranking {
public void zaglosuj(ActionEvent event){
System.out.println("oddano glos");
}
}
Mając wszystko zdefiniowane i gotowe do uruchomienia uruchamiamy aplikację wybierając menu Run As > Run on Server.

Poprawne uruchomienie Glassfisha i samej aplikacji wiąże się z wypisaniem wersji ICEfaces w dzienniku zdarzeń Glassfisha.

[#|2007-07-10T22:09:43.409+0200|INFO|sun-appserver9.1|com.icesoft.faces.application.D2DViewHandler|
_ThreadID=17;_ThreadName=httpWorkerThread-4848-1;|
ICEsoft Technologies, Inc.
ICEfaces 1.6.0
Build number: 15
Revision: 14409
|#]

A otworzenie strony pod adresem http://localhost:8080/icefaces-witajswiecie wita nas w następujący sposób.

Wciskając przycisk Wciśnij mnie można zauważyć, że ikona stanu przeglądarki niedrgnie, natomiast w dzienniku zdarzeń Glassfisha pojawi się następujący wpis:

[#|2007-07-10T22:11:05.049+0200|INFO|sun-appserver9.1|javax.enterprise.system.stream.out|
_ThreadID=24;_ThreadName=httpSSLWorkerThread-8080-1;|oddano glos|#]

Nie jest to wyrafinowana aplikacja JSF z użyciem ICEfaces, ale pozwala na dobry start przy tworzeniu kolejnych bardziej złożonych aplikacji w Eclipse IDE i Glassfish. Warto rozważyć wykorzystanie ICEfaces we własnych projektach korzystających z JSF, gdyż ICEfaces 1.6.0 jest zintegrowane z JBoss Seam 2.0.0.BETA (dostępny po zbudowaniu ze źródeł) oraz wspiera inne biblioteki jak chociażby facelets. Dodatkowo, podobnie jak JBoss Seam, ICEfaces wspiera koncepcję równoległego przetwarzania DOM, a to pozwala na uruchomienie wielu okien i zakładek przeglądarki do pracy z tą samą aplikacją.

Cały projekt dostępny jest jako icefaces-witajswiecie.zip. Wystarczy rozpakować w wybranym miejscu na dysku, zaimportować w Eclipse IDE i uruchomić. Uwag nie zgłaszać autorowi ;-)

09 lipca 2007

Java Persistence - Rozdziały 5.5 Zarządzanie transakcjami oraz 5.6 Konteksty trwałe zarządzane przez serwer

4 komentarzy
Kolejne rozdziały JPA tym razem dotyczące transakcji, więc nie jest lekko. Wydaje mi się, że temat transakcji zazwyczaj traktowany jest po macoszemu i wielu (wliczając mnie) odetchnęło z ulgą dowiedziawszy się, że w Java EE istnieje możliwość deklaratywnego oznaczania miejsc ich rozpoczęcia. Podobnie ma się sprawa z usługą bezpieczeństwa. Bezpieczeństwo i transakcyjność to dwie z wielu usług, które dostarcza serwer aplikacyjny Java EE i właśnie ich dostępność i prostota konfiguracji i użycia jest argumentem za jego stosowaniem (ponowie uwaga do użytkowników Spring Framework - można zestawić podobny zestaw usług, ale czy warto się tym parać?). Pora rozpoznać temat integracji usług w JPA.

Rozdział 5.5 Zarządzanie transakcjami

W zależności od typu transakcyjnego zarządcy trwałego, transakcje związane z operacjami zarządcy mogą być zarządzane przez JTA albo przez egzemplarz (resource-local) EntityTransaction. Użycie terminu angielskiego - resource-local - lokalny względem zasobu, który uczestniczy w transakcji, idealnie oddaje późniejszą konfigurację jednostki trwałej (PU). O tym później.

Egzemplarz EntityTransaction odpowiada transakcji związanej z zasobem, który z kolei związany jest z encjami zarządzanymi przez zarządcę trwałego.

Zarządca trwały, którego transakcje zarządzane są przez JTA nazywany jest zarządcą trwałym JTA (ang. JTA entity manager).

Zarządca trwały, którego transakcje zarządzane są przez aplikację przez interfejs EntityTransaction nazywany jest lokalnym zarządcą trwałym (ang. resource-local entity manager).

Zarządca trwały zarządzany przez kontener (serwer aplikacyjny Java EE) musi być zarządcą trwałym JTA. Zarządcy trwali JTA są określeni jedynie dla wykorzystania w środowisku serwera aplikacyjnego Java EE.

Zarządca trwały zarządzany przez aplikację może być JTA lub lokalny.

Określenie typu transakcyjnego zarządcy trwałego odbywa się podczas konstruowania fabryki zarządcy trwałego.

W środowisku kontenerów aplikacji internetowych oraz EJB wymagane jest wsparcie dla zarządcy encji JTA oraz lokalnego. W ramach kontenera EJB zazwyczaj wykorzystywany jest zarządca encji JTA. W środowisku Java SE wymagane jest jedynie wsparcie dla lokalnych zarządców encji.

Zarządca encji JTA uczestniczy w transakcji JTA, która rozpoczyna się i jest zatwierdzana zewnętrznie w stosunku do zarządcy encji i jest przekazana do zarządcy zasobów związanego z encjami uczestniczącymi w transakcji.

Lokalny zarządca encji może korzystać z zasobów serwera bądź lokalnych w celu nawiązania połączenia do bazy danych i jest nieświadomy istnienia transakcji JTA, które mogą być aktywne w danej chwili.

Interfejs EntityTransaction dostarcza metody do zarządzania transakcjami w trybie lokalnym. Metoda EntityManager.getTransaction() zwraca interfejs EntityTransaction. W momencie wystąpienia wyjątku, który oznaczony jest jako wycofujący transakcję, lokalny zarządca encji musi oznaczyć transakcję jako wyłącznie do wycofania.

Jeśli wywołanie EntityTransaction.commit() zakończy się wyjątkiem, dostawca JPA musi wycofać transakcję.

Specyfikacja przedstawia opis interfejsu javax.persistence.EntityTransaction.

@Test
public void testEntityTransactionAPI() {
Osoba encja = new Osoba("Jacek", "Laskowski");
try {
em.persist(encja);
// Podczas flush nastąpi przesłanie zmian stanu encji do bazy danych
em.flush();
assert false : "Oczekiwano wyjątku javax.persistence.TransactionRequiredException, gdyż nie rozpoczęto transakcji";
} catch (TransactionRequiredException oczekiwany) {
}
EntityTransaction et = em.getTransaction();
assert et.isActive() == false : "Transakcja nie powinna już być aktywna - nie wywołano begin()";
et.begin();
assert et.isActive() : "Transakcja musi już być aktywna - wywołano begin()";
assert et.getRollbackOnly() == false : "Transakcja musi być w stanie umożliwiającym jej zatwierdzenie";
et.setRollbackOnly();
assert et.getRollbackOnly() : "Transakcja musi być w stanie wykluczającym jej zatwierdzenie";
em.persist(encja);
try {
// Podczas zatwierdzania transakcji nastąpi przesłanie zmian stanu encji do bazy danych
et.commit();
assert false : "Oczekiwano wyjątku javax.persistence.RollbackException, gdyż transakcja oznaczona jedynie do wycofania";
} catch (RollbackException oczekiwany) {
}
}

W sekcji 5.5.3 Example (strona 120) przedstawiono kompletny przykład aplikacji korzystającej z zarządcy encji zarządzanego przez aplikację w trybie lokalnym.

5.6 Kontekst trwały zarządzany przez kontener (serwer)

Użycie zarządcy encji zarządzanego przez kontener nakłada na serwer konieczność zarządzania stadiami rozwojowymi kontekstu trwałego w sposób niezauważalny dla aplikacji. Serwer zarządza transakcjami związanymi z zarządcą za pomocą JTA.

Kontekst trwały zarządzany przez kontener może trwać tak długo jak aktywna jest pojedyńcza transakcja bądź być związany z wieloma transakcjami. Związek między długością życia kontekstu trwałego a transakcjami określany jest przez typ wyliczeniowy javax.persistence.PersistenceContextType, który definiowany jest podczas tworzenia zarządcy trwałego. Specyfikacja odnosi się do tych typów zarządców trwałych jako kontekst trwały pojedyńczej transakcji (ang. transaction-scoped persistence context) lub kontekst trwały rozszerzony (ang. extended persistence context), odpowiednio.

Długość życia kontekstu trwałego jest zadeklarowana używając adnotacji @PersistenceContext lub poprzez element persistence-context-ref w deskryptorze aplikacji. Domyślnie zakłada się użycie PersistenceContextType.TRANSACTION.

@javax.persistence.PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager em;

Wykorzystanie elementu persistence-context-ref w deskryptorze aplikacji - WEB-INF/web.xml lub META-INF/ejb-jar.xml (przykład za specyfikacją Java EE sekcja EE.5.12 Persistence Unit References strona 100).

<persistence-context-ref>
<description>Persistence context for the inventory management application.</description>
<persistence-context-ref-name>persistence/InventoryAppDB</persistence-context-ref-name>
<persistence-unit-name>InventoryManagement</persistence-unit-name>
<persistence-context-type>Extended</persistence-context-type>
</persistence-context-ref>

Wartości elementu persistence-context-type to wartości Transaction lub Extended. Jedynym źródłem informacji o elemencie to dokument dostępny pod adresem http://java.sun.com/xml/ns/javaee/javaee_5.xsd.

5.6.1 Kontekst trwały zarządzany przez kontener pojedyńczej transakcji

Kontekst trwały zawsze związany jest z fabryką zarządców encji.

Aplikacja może uzyskać dostęp do zarządcy encji zarządzanego przez kontener, pojedyńczej transakcji, w trybie JTA (ciekawie prezentuje się opis w języku angielskim - a container-managed entity manager with transaction-scoped persistence context bound to the JTA transaction) poprzez mechanizm DI lub odszukanie w JNDI.

Zarządca encji pojedyńczej transakcji jest założony domyślne (w przypadku braku elementu type adnotacji @PersistenceContext) bądź zdefiniowany przez PersistenceContextType.TRANSACTION.

Nowy kontekst trwały rozpoczyna się, podczas pierwszego wywołania operacji na zarządcy encji (interfejs EntityManager) zarządzanym przez kontener w zasięgu aktywnej transakcji JTA i tylko wtedy, kiedy nie istnieje zarządca trwały już związany z aktywną transakcją. Kontekst trwały jest tworzony i związywany natychmiast z aktywną transakcją JTA.

Kontekst trwały kończy się, podczas zakończenia związanej transakcji JTA (zatwierdzenie bądź wycofanie) i kiedy wszystkie encje związane z danym zarządcą trwałym, z którym związany jest kontekst, przejdą w stan odłączony (ang. detached).

Jeśli zarządca trwały jest wywołany poza zasięgiem aktywnej transakcji, encja utworzona na podstawie danych z bazy danych przechodzi natychmiast w stan odłączony.

5.6.2 Rozszerzony kontekst trwały zarządzany przez kontener (serwer)

Rozszerzony kontekst trwały zarządzany przez kontener może być jedynie utworzony w ramach stanowego ziarna sesyjnego EJB. Kontekst istnieje od momentu, w którym utworzone zostanie ziarno, które deklaruje zależność od zarządcy encji typu PersistenceContextType.EXTENDED i określa się go mianem przypisany (ang. bound) do stanowego ziarna sesyjnego EJB. Zależność na rozszerzonym kontekście trwałym jest deklarowana poprzez adnotację @PersistenceContext lub element persistence-context-ref w deskryptorze.

Kontekst trwały jest zamykany po pomyślnym wywołaniu metody stanowego ziarna EJB udekorowanej przez adnotację @Remove lub jeśli egzemplarz ziarna zostanie zniszczony w inny sposób, np. niektóre metody zakończone wyjątkiem są sygnałem dla kontenera o bezużyteczności egzemplarza ziarna, które są następnie niszczone.

Jeśli stanowe ziarno tworzy (w dowolny sposób - DI lub JNDI) inne stanowe ziarno, które ma zadeklarowaną zależność od rozszerzonego kontekstu trwałego, rozszerzony kontekst trwały pierwszego ziarna jest dziedziczony przez (przekazane do) drugiego ziarna i zostaje przypisane do niego, i tak kolejno do następnych ziaren stanowych EJB bez względu na istnienie aktywnej transakcji w trakcie tworzenia ziaren (dla przypomnienia utworzone w ten sposób encje na bazie danych z bazy danych są natychmiast w stanie odłączony).

Jeśli kontekst trwały został odziedziczony przez ziarna stanowe EJB, kontener zamknie kontekst dopiero, kiedy wszystkie ziarna zostaną usunięte (z potencjalnym wywołaniem metody @Remove) lub w inny sposób zniszczone.

5.6.3 Przekazywanie kontekstu trwałego

Pojedyńczy kontekst trwały może odpowiadać jednemu lub wielu egzemplarzom zarządców encji JTA (związanym z tą samą fabryką zarządców encji - zarządcy encji pochodzący z różnych fabryk zarządców encji nigdy nie dzielą tego samego kontekstu trwałego).

Kontekst trwały jest przekazywany do egzemplarzy zarządców encji wraz z przekazywaniem transakcji JTA.

Przekazywanie kontekstów trwałych ma miejsce wyłącznie w ramach środowiska lokalnego. Konteksty trwałe nie są przekazywane do zewnętrznych (zdalnych) warstaw.

5.6.3.1 Wymagania związane z przekazywaniem kontekstu trwałego

Przy braku aktywnej transakcji JTA wywołanie komponentu nie wiąże się z przekazaniem kontekstu trwałego. Wywołanie zarządcy encji pojedyńczej transakcji (PersistenceContextType.TRANSACTION) z poziomu tego komponentu wiąże się ze stworzeniem nowego kontekstu trwałego. Wywołanie rozszerzonego zarządcy encji (PersistenceContextType.EXTENDED) wiąże się z użyciem istniejącego rozszerzonego zarządcy encji związanego z tym komponentem. Jeśli zarządca encji jest wywołany w ramach transakcji JTA, kontekst trwały zostanie z nią związany.

Przy wywołaniu komponentu, podczas przekazania transakcji JTA do niego, wyróżniamy 2 sytuacje. Jeśli komponent to stanowe ziarno sesyjne EJB, z którym związany jest rozszerzony kontekst trwały oraz istnieje inny kontekst trwały związany z transakcją JTA, zostanie rzucony wyjątek javax.ejb.EJBException. W przeciwnym przypadku, jeśli istnieje kontekst trwały związany z transakcją JTA, nastąpi przekazanie i korzystanie z tego kontekstu trwałego.

Dla mnie ostatnie sekcje specyfikacji, szczególnie ta związana z przekazywaniem kontekstu trwałego, pozostawia wiele pytań do odpowiedzi. Czas odszukać odpowiedzi w książkach o JPA. Wracam do Pro EJB 3 - Java Persistence API z Apress. Po 2 otwierających rozdziałach książka wydaje się być napisana lekko i przyjemnie.

08 lipca 2007

Java Persistence w Spring Framework wspomaganym przez Eclipse z Spring IDE oraz m2eclipse

2 komentarzy
We wtorek za 2 tygodnie - 17.07.2007 - prowadzę prezentację o Java Persistence API podczas spotkania grupy Warszawa JUG. Nie mam wiele czasu, ale tyle pomysłów, aby zaprezentować JPA, że zacząłem przygotowywania już teraz, aby powiedzieć wiele na temat i w zaplanowanym czasie.

Jedną z rzeczy, które zamierzam pokazać jest integracja JPA z popularnymi szkieletami programistycznymi jak Spring Framework czy JBoss Seam. 1-2 przykłady, kilka(naście) słów i przechodzę do kolejnego tematu. Wymaga to przygotowania przykładów zawczasu i przećwiczenia ich we wszystkich możliwych wariantach, bo kiedy wystąpi wyjątek dobrze jest mieć rozwiązanie w zapasie. Kiedy przygotowywałem poszczególne sekcje prezentacji i zatrzymałem się na Spring Framework natrafiłem na artykuł, który zaplanowałem przeczytać już jakiś czas temu - Introduction to Spring 2 and JPA. Nie mogłem wyobrazić sobie lepszego momentu na lekturę niż właśnie dzisiaj, teraz.

W tym samym czasie pojawiły się nowe wersje narzędzi Eclipse IDE 3.3 oraz Spring IDE 2.0, a poza tym, właśnie podczas rozpoznawania SCA zatrzymałem się na rozpoznawaniu implementation.spring i konstruowaniu aplikacji z jego wykorzystaniem. Najwyższa pora popróbować się z JPA wspartym przez Spring Framework i Spring IDE i utworzyć aplikację do dalszych eksperymentów technologicznych.

Korzystając z Spring IDE tworzę projekt - File > New > Other... > Spring > Spring Project.

, gdzie podaję wymagane informacje, m.in. nazwa projektu to spring-jpa.

Na bazie wspomnianego artykułu tworzę encję Prezenter (pakiet pl.jaceklaskowski.spring.ranking.entity)

package pl.jaceklaskowski.spring.ranking.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Prezenter implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private long id;

private String imie;
private String nazwisko;

public Prezenter() {
}

public Prezenter(String imie, String nazwisko) {
this.imie = imie;
this.nazwisko = nazwisko;
}

public long getId() {
return id;
}

public String getImie() {
return imie;
}

public void setImie(String imie) {
this.imie = imie;
}

public String getNazwisko() {
return nazwisko;
}

public void setNazwisko(String nazwisko) {
this.nazwisko = nazwisko;
}

}

oraz interfejs usługi zarządzania prezenterami PrezenterService.

package pl.jaceklaskowski.spring.ranking;

import java.util.List;

import pl.jaceklaskowski.spring.ranking.entity.Prezenter;

public interface PrezenterService {
public void save(Prezenter prezenter);

public void delete(Prezenter prezenter);

public List<Prezenter> findAll();

public Prezenter findByPrezenterId(long id);
}

Ideą jest utworzenie aplikacji, która mogłaby zamodelować sytuację, w której encja Prezenter jest w relacji 1-do-wielu z encją Prezentacja, która jest w relacji 1-do-wielu z encją Ocena. Rozbudowanie aplikacji pozostawię na później, a w tej notatce zajmę się jedynie sposobem uruchomienia JPA w Spring Framework i to jak najmniejszym wysiłkiem bazując na lekturze artykułu.

Podczas lektury artykułu natrafiłem na kilka elementów, które są dla mnie niezrozumiałe. Dlaczego modeluje się encję Employee z dwoma identyfikatorami - JPA (identyfikator technologiczny) oraz numer (identyfikator biznesowy). Dlaczego public List<Employee> findByEmployeeNumber(String empno) zwraca listę Employee, zamiast pojedyńczą encję Employee? Dlaczego public Employee save(Employee emp) zwraca Employee? I w końcu, dlaczego klasa encji Employee i Address nie implementują interfejsu java.io.Serializable, co jest zalecane (żeby nie napisać jest dobrą praktyką)? Pytania pozostaną zapewne bez odpowiedzi, ale nie ma to większego znaczenia w realizacji postawionego na dzisiaj celu.

Tworzę implementację PrezenterService - PrezenterDAO, która reprezentuje warstwę dostępu do danych (ang. Data Access Object).

package pl.jaceklaskowski.spring.ranking;

import java.util.List;

import org.springframework.orm.jpa.support.JpaDaoSupport;

import pl.jaceklaskowski.spring.ranking.entity.Prezenter;

public class PrezenterDAO extends JpaDaoSupport implements PrezenterService {

public void delete(Prezenter prezenter) {
getJpaTemplate().remove(prezenter);
}

public List<Prezenter> findAll() {
return getJpaTemplate().find("select p from Prezenter p");
}

public Prezenter findByPrezenterId(long id) {
return getJpaTemplate().find(Prezenter.class, id);

}

public void save(Prezenter prezenter) {
getJpaTemplate().persist(prezenter);
}

}

I w tym momencie, korzystając z klasy JpaDaoSupport wchodzę na tereny Spring Framework. To jest jeden z tych momentów, w którym znacząco dostrzegam zaletę wprowadzenia systemu kontrolującego zależności i pozwalającego mi na deklaratywne ich definiowanie - Apache Maven 2 (M2) czy Apache Ivy. Skoro tworzę aplikację w IDE, oczekuję wsparcia podświetlenia składni i takie tam. Jednakże jego użyteczność kończy się, w przypadku stosowania odwołań do typów, które nie są dla niego widoczne. Chcę skorzystać z typów dostarczanych przez Spring Framework, ale...nie mam go zainstalowanego na moim komputerze, więc nawet, gdybym chciał to jak miałbym zdefiniować zależność w Eclipse? Pozostaje jedynie zainstalować Springa, ale po co miałbym tracić na to czas? Potrzebna mi wyłącznie biblioteka, a nie cała wersja dystrybucyjna projektu, nieprawdaż? Korzystając z m2 wystarczyłoby przecież zadeklarować zależność w pliku konfiguracyjnym projektu - pom.xml - i m2 pobrałaby mi ją. Wyraźnie przyspieszenie mojej produktywności - zrzucić obowiązki na usługę, która się w danej kwestii specjalizuje. Bezcenne.

Nie, nie dam rady tak marudzić. Włączam Maven > Enable Dependency Managemenet na projekcie korzystając z zainstalowanej wtyczki m2eclipse (kompletnie nie wiem, co się zaraz stanie, ale coś mi mówi, że może być tylko lepiej). O! Tego się nie spodziewałem - tworzy się pom.xml dla projektu. Wspaniale!

Wciskam przycisk Next > i co widzę? Możliwość zdefiniowania zależności projektowych. Brawo!

Skrzętnie korzystam z okazji i deklaruję zależność od biblioteki specyfikacji JPA (opieram się o bibliotekę dostarczaną przez projekt Apache Geronimo)


oraz Spring Framework.

Wyszukiwanie zależności przegląda moje lokalne repozytorium, więc na razie zadowolony jestem z posiadania możliwości dodania wersji Spring Framework 2.0.2 chociaż istnieje już wersja 2.0.6. Zaraz się i tym zajmę. Ostatecznie kończę deklarowanie zależności z następującymi bibliotekami.


Wciskam przycisk Finish i zostaje stworzony plik pom.xml, który zaraz modyfikuję o zmianę związaną z aktualną wersją Spring Framework 2.0.6. Okazuje się, że wtyczka wykonuje jakieś skomplikowane czynności, bo Eclipse nie odpowiada. Zmroziła go moja pomysłowość?! ;-)

...po 15-20 sekundach...

Jest! Plik pom.xml jest automatycznie otwarty, wprowadzam zmianę i zapominam o przejściowych problemach. Wracamy do JpaDaoSupport i PrezenterService, i w ogóle Spring Framework i JPA.

Zalet integracji JPA w Spring Framework nie będę przedstawiał - artykuł robi to wyśmienicie. Jednego nie można nie wspomnieć - Spring Framework znacznie upraszcza tworzenie aplikacji, nie tylko korzystającej z JPA.

Pora na plik konfiguracyjny Spring Framework domyślnie nazywany beans.xml, chociaż bardziej znamienne nazwy są zawsze na miejscu. Może spring-jpa-beans.xml jako zlepek nazwy projektu (która sama w sobie nie jest czymś wyrafinowanym) i beans.xml jako dziedzictwo domyślnej nazwy?

Do tworzenia pliku XML skorzystamy z świeżo zainstalowanej wtyczki Spring IDE 2.0. Niech się przekonam, gdzie Spring IDE umiejscowi plik w strukturze projektu zarządzanego przez m2 (powinno być src/main/resources, ale skąd Spring IDE miałby to wiedzieć, że projekt jest kontrolowany przez m2).

Wybieram menu New > Other... > Spring > Spring Bean Definition


Ach, to jest rozwiązanie - panel pytający użytkownika o podanie miejsca położenia pliku. No tak - czego możnaby oczekiwać?! Sprytnie.

Ostatecznie przekopiowujemy zawartość spring.xml z artykułu z odpowiednimi modyfikacji. Ciekawostką wtyczki Spring IDE jest podpowiedź przy uzupełnianiu wartości atrybutów ziaren Spring.

Dostawcą JPA w przykładzie jest TopLink JPA. Możliwi dostawcy JPA w Spring Framework to Hibernate JPA, Apache OpenJPA oraz TopLink JPA (więcej w liście typów w pakiecie org.springframework.orm.jpa.vendor).

Dodaję nowe zależności w projekcie - HSQL oraz TopLink Essentials JPA - korzystając z pomocy mojego artykułu Nauka Java Persistence z Apache Maven 2 i dostawcami JPA: OpenJPA, Hibernate i TopLink, w którym opisałem sposób dodawania zależności od TopLink Essentials.

Tworzę JUnit test, który rozszerza AbstractJpaTests, więc konieczne dodaję kolejną zależność spring-mock-2.0.6.jar do projektu.

package pl.jaceklaskowski.spring.ranking;

import org.springframework.test.jpa.AbstractJpaTests;

import pl.jaceklaskowski.spring.ranking.entity.Prezenter;

public class PrezenterServiceIntegrationTest extends AbstractJpaTests {
private PrezenterService prezenterService;

private long janKowalskiId;

public void setPrezenterService(PrezenterService prezenterService) {
this.prezenterService = prezenterService;
}

protected String[] getConfigLocations() {
return new String[] { "classpath:/spring-jpa-beans.xml" };
}

protected void onSetUpInTransaction() throws Exception {
Prezenter prezenter = new Prezenter("Jan", "Kowalski");
prezenterService.save(prezenter);
janKowalskiId = prezenter.getId();
}

public void testPrezenterExists() {
Prezenter prezenter = prezenterService.findByPrezenterId(janKowalskiId);
assertEquals("Jan", prezenter.getImie());
assertEquals("Kowalski", prezenter.getNazwisko());
}
}

Uruchomienie testu potwierdza poprawne zestawienie środowiska (poza brakiem konfiguracji log4j, ale kto by się tym przejmował teraz).

log4j:WARN No appenders could be found for logger (org.springframework.util.ClassUtils).
log4j:WARN Please initialize the log4j system properly.
[TopLink Config]: 2007.07.08 10:12:51.312--ServerSession(9182681)--Thread(Thread[main,5,main])--The alias name for the entity class [class pl.jaceklaskowski.spring.ranking.entity.Prezenter] is being defaulted to: Prezenter.
[TopLink Config]: 2007.07.08 10:12:51.328--ServerSession(9182681)--Thread(Thread[main,5,main])--The table name for entity [class pl.jaceklaskowski.spring.ranking.entity.Prezenter] is being defaulted to: PREZENTER.
[TopLink Config]: 2007.07.08 10:12:51.343--ServerSession(9182681)--Thread(Thread[main,5,main])--The column name for element [private long pl.jaceklaskowski.spring.ranking.entity.Prezenter.id] is being defaulted to: ID.
[TopLink Config]: 2007.07.08 10:12:51.359--ServerSession(9182681)--Thread(Thread[main,5,main])--The column name for element [private java.lang.String pl.jaceklaskowski.spring.ranking.entity.Prezenter.imie] is being defaulted to: IMIE.
[TopLink Config]: 2007.07.08 10:12:51.359--ServerSession(9182681)--Thread(Thread[main,5,main])--The column name for element [private java.lang.String pl.jaceklaskowski.spring.ranking.entity.Prezenter.nazwisko] is being defaulted to: NAZWISKO.
[TopLink Info]: 2007.07.08 10:12:51.531--ServerSession(9182681)--Thread(Thread[main,5,main])--TopLink, version: Oracle TopLink Essentials - 2.0 (Build 40 (03/30/2007))
[TopLink Config]: 2007.07.08 10:12:51.531--ServerSession(9182681)--Connection(6131844)--Thread(Thread[main,5,main])--connecting(DatabaseLogin(
platform=>HSQLPlatform
user name=> ""
connector=>JNDIConnector datasource name=>null
))
[TopLink Config]: 2007.07.08 10:12:51.781--ServerSession(9182681)--Connection(5555373)--Thread(Thread[main,5,main])--Connected: jdbc:hsqldb:mem:spring-jpa
User: SA
Database: HSQL Database Engine Version: 1.8.0
Driver: HSQL Database Engine Driver Version: 1.8.0
[TopLink Config]: 2007.07.08 10:12:51.781--ServerSession(9182681)--Connection(20738936)--Thread(Thread[main,5,main])--connecting(DatabaseLogin(
platform=>HSQLPlatform
user name=> ""
connector=>JNDIConnector datasource name=>null
))
[TopLink Config]: 2007.07.08 10:12:51.781--ServerSession(9182681)--Connection(29422309)--Thread(Thread[main,5,main])--Connected: jdbc:hsqldb:mem:spring-jpa
User: SA
Database: HSQL Database Engine Version: 1.8.0
Driver: HSQL Database Engine Driver Version: 1.8.0
[TopLink Info]: 2007.07.08 10:12:51.859--ServerSession(9182681)--Thread(Thread[main,5,main])--file:/C:/.eclipse/sandbox/spring-jpa/target/-spring-jpaPU login successful
[TopLink Fine]: 2007.07.08 10:12:51.875--ServerSession(9182681)--Connection(30844270)--Thread(Thread[main,5,main])--CREATE TABLE PREZENTER (ID NUMERIC(19) NOT NULL, IMIE VARCHAR(255), NAZWISKO VARCHAR(255), PRIMARY KEY (ID))
[TopLink Fine]: 2007.07.08 10:12:51.890--ServerSession(9182681)--Connection(26750913)--Thread(Thread[main,5,main])--CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT NUMERIC(38), PRIMARY KEY (SEQ_NAME))
[TopLink Fine]: 2007.07.08 10:12:51.890--ServerSession(9182681)--Connection(6775863)--Thread(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN_TABLE'
[TopLink Fine]: 2007.07.08 10:12:51.890--ServerSession(9182681)--Connection(20092482)--Thread(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN_TABLE', 1)
[TopLink Fine]: 2007.07.08 10:12:51.984--ClientSession(31212095)--Connection(181086)--Thread(Thread[main,5,main])--UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
bind => [50, SEQ_GEN_TABLE]
[TopLink Fine]: 2007.07.08 10:12:51.984--ClientSession(31212095)--Connection(181086)--Thread(Thread[main,5,main])--SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?
bind => [SEQ_GEN_TABLE]

Zaprezentowany w artykule plik persistence.xml ukazuje działanie Spring Framework i jego modułu integrującego JPA, którzy symulują działanie serwera aplikacyjnego Java EE, w którym nie definiuje się klas (za pomocą elementu class), które podlegają zarządzaniu przez zarządcę trwałego (kolejny argument, że Spring Framework jest/staje się semi-serwerem aplikacyjnym Java EE).

Na zakończenie pełny plik konfiguracyjny projektu - pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>spring-jpa</groupId>
<artifactId>spring-jpa</artifactId>
<version>0.0.1</version>
<repositories>
<repository>
<id>java.net</id>
<name>java.net Maven Repository</name>
<url>https://maven-repository.dev.java.net/nonav/repository</url>
<layout>legacy</layout>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jpa_3.0_spec</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jpa</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-mock</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
</dependency>
<dependency>
<groupId>toplink.essentials</groupId>
<artifactId>toplink-essentials</artifactId>
<version>2.0-40</version>
</dependency>
</dependencies>
</project>

Na dzisiaj wystarczy - szkielet aplikacji ze Spring i JPA stworzony. Cały projekt dostępny jest jako spring-jpa.zip. Wystarczy rozpakować, zaimportować do wybranego IDE (w paczce zawarte są pliki konfiguracyjne dla Eclipse IDE) i uruchomić test jednostkowy PrezenterServiceIntegrationTest. Zakładając istnienie wtyczek i dostępności zależności powinno działać.

Kolejne odsłony tematu planuję rozszerzyć o przedstawienie sposobu uaktywnienia Apache OpenJPA w Spring Framework jako dostawcy JPA oraz wykorzystanie bazy danych PostgreSQL lub Derby (zamiast HSQL). Planuję również przejść ścieżkę integracji aplikacji opartej na Spring i JPA z SCA, a później rozpoznanie integracji Spring z iBatis (tyle się o nim pisze).