Skip to content

4. Przykładowa aplikacja – 02: rdvmedecins-jsf2-spring

Teraz zamierzamy przenieść poprzednią aplikację do środowiska Spring / Tomcat:

Jest to faktycznie przeniesienie aplikacji. Wychodzimy od poprzedniej aplikacji i dostosowujemy ją do nowego środowiska. Omówimy jedynie zmiany. Są one trzech rodzajów:

  • serwerem nie jest już Glassfish, lecz Tomcat – lekki serwer, który nie posiada kontenera EJB,
  • w miejsce EJB zastosujemy Spring, głównego konkurenta EJB i [http://www.springsource.com/],
  • zastosowaną implementacją JPA będzie Hibernate zamiast EclipseLink.

Ponieważ będziemy często korzystać z funkcji kopiowania i wklejania między starym a nowym projektem, pozostawiamy poprzednie projekty otwarte w NetBeans:

  

Korzystanie z frameworka Spring wymaga pewnej wiedzy, którą można znaleźć w [ref7] (patrz strona 166).

4.1. Warstwy [DAO] i [JPA]

4.1.1. Projekt NetBeans

Tworzymy projekt Maven typu [Java Application]:

  • w [1], utworzony projekt,
  • w [2], ten sam projekt pozbawiony pakietów z [Source Packages] i [Test Packages] oraz zależności [junit-3.8.1].

Najtrudniejszą rzeczą w projektach Maven jest znalezienie odpowiednich zależności. W przypadku tego projektu Spring / JPA / Hibernate są to następujące zależności:


<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>istia.st</groupId>
  <artifactId>mv-rdvmedecins-spring-dao-jpa</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-rdvmedecins-spring-dao-jpa</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

    <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.hibernate.java-persistence</groupId>
      <artifactId>jpa-api</artifactId>
      <version>2.0.Beta-20090815</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
  </dependencies>

</project>
  • wiersze 18–29: dla Hibernate,
  • wiersze 30–34: dla sterownika JDBC z MySQL,
  • wiersze 35–41: dla testu JUnit,
  • wiersze 42–51: dla puli połączeń Apache Commons o nazwie DBCP. Pula połączeń to zbiór otwartych połączeń. Gdy aplikacja potrzebuje połączenia, zwraca się o nie do puli. Gdy nie jest już potrzebne, zwraca je. Połączenia są otwierane podczas uruchamiania aplikacji i pozostają otwarte przez cały czas jej działania. Pozwala to uniknąć kosztów związanych z wielokrotnym otwieraniem i zamykaniem połączeń. Ten typ puli istniał w Glassfish, ale jego wykorzystanie było dla nas przezroczyste. Tak samo będzie w tym przypadku, ale musimy go zainstalować i skonfigurować,
  • wiersze 52–75: dla Springa.

Dodajmy te zależności i skompilujmy projekt:

  • w [1], kompilujemy projekt, co zmusi Maven do pobrania zależności,
  • w [2], a następnie pojawią się one w gałęzi [Dependencies]. Jest ich bardzo wiele, ponieważ same frameworki Hibernate i Spring mają bardzo liczne zależności. Również w tym przypadku, dzięki Mavenowi, nie musimy się nimi martwić. Są one pobierane automatycznie.

Teraz, gdy mamy już zależności, wklejamy kod projektu EJB z warstwy [dao] do projektu Spring w warstwie [dao]:

  • do [1], kopiujemy do projektu źródłowego,
  • w pliku [2] wklejamy do projektu docelowego,
  • w pliku [3] – wynik.

Po zakończeniu kopiowania należy poprawić błędy.

4.1.2. Pakiet [exceptions]

Klasa [RdvMedecinsExceptions] [1] zawiera błędy spowodowane pakietem [javax] w wierszu 4, który już nie istnieje. Jest to pakiet właściwy dla EJB. Błąd w wierszu 6 wynika z błędu w wierszu 4. Usuwamy te dwa wiersze. Spowoduje to usunięcie błędów dotyczących pakietu [2].

4.1.3. Pakiet [jpa]

  • w [1] klasa [Creneau] zawiera błąd z powodu braku pakietu walidacyjnego w wierszu [5]. Można było dodać ten pakiet do zależności projektu. Jednak podczas testów Hibernate zgłasza z jego powodu wyjątek. Ponieważ nie jest on niezbędny dla naszej aplikacji, usunęliśmy go. Aby poprawić klasę, wystarczy usunąć wszystkie błędne wiersze [2]. Czynność tę wykonujemy dla wszystkich błędnych klas.

4.1.4. Pakiet [dao]

Jesteśmy w następnym punkcie:

  • w [1], obie poprawione pakiety,
  • w [2], pakiet [dao]. Ponieważ nie ma już pliku EJB, nie ma już również pojęcia interfejsu zdalnego i lokalnego w pliku EJB. Usuwamy je z pliku [3].
  • W przypadku [1] błędy klasy [DaoJpa] mają dwa źródła:
  • import pakietu powiązanego z EJB (wiersze 6–8);
  • użycie interfejsów lokalnego i zdalnego, które właśnie usunęliśmy.

Usuwamy błędne wiersze i zamiast lokalnego i zdalnego interfejsu [2] używamy interfejsu [IDao].

W projekcie EJB klasa [DaoJpa] była singletonem, a jej metody były wykonywane w ramach transakcji. Zobaczymy, że klasa [DaoJpa] będzie beanem zarządzanym przez Spring. Domyślnie każdy bean Springa jest singletonem. To tyle, jeśli chodzi o pierwszą właściwość. Drugą uzyskuje się dzięki adnotacji @Transactional w Springu [3]:

Po wykonaniu tych czynności projekt nie zawiera już żadnych błędów [4].

4.1.5. Konfiguracja warstwy [JPA]

W projekcie EJB skonfigurowaliśmy warstwę [JPA] za pomocą pliku [persistence.xml]. Tutaj mamy warstwę [JPA], więc musimy utworzyć ten plik. W projekcie EJB wygenerowaliśmy go za pomocą Glassfish. Tutaj tworzymy go ręcznie. Głównym powodem jest to, że część konfiguracji pliku [persistence.xml] zostaje przeniesiona do samego pliku konfiguracyjnego Springa.

Tworzymy plik [persistence.xml]:

o następującej treści:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
  <persistence-unit name="spring-dao-jpa-hibernate-mysqlPU" transaction-type="RESOURCE_LOCAL">
    <class>rdvmedecins.jpa.Client</class>
    <class>rdvmedecins.jpa.Creneau</class>
    <class>rdvmedecins.jpa.Medecin</class>
    <class>rdvmedecins.jpa.Rv</class>
  </persistence-unit>
</persistence>
  • wiersz 3: nadajemy nazwę jednostce trwałości,
  • wiersz 3: typ transakcji to RESOURCE_LOCAL. W projekcie EJB był to JTA, co oznaczało, że transakcje były zarządzane przez kontener EJB. Wartość RESOURCE_LOCAL oznacza, że aplikacja sama zarządza swoimi transakcjami. W tym przypadku będzie to realizowane za pośrednictwem Springa,
  • wiersze 4–7: pełne nazwy czterech encji JPA. Jest to opcjonalne, ponieważ Hibernate automatycznie wyszukuje je w pliku ClassPath projektu.

To wszystko. Nazwa dostawcy JPA, jego właściwości oraz cechy JDBC źródła danych znajdują się teraz w pliku konfiguracyjnym Springa.

4.1.6. Plik konfiguracyjny Springa

Wspomnieliśmy, że klasa [DaoJpa] jest beanem zarządzanym przez Spring. Odbywa się to za pomocą pliku konfiguracyjnego. Plik ten będzie zawierał również konfigurację dostępu do bazy danych oraz zarządzanie transakcjami. Musi on znajdować się w katalogu ClassPath projektu. Umieszczamy go w gałęzi [Other sources]:

Plik [spring-config-dao.xml] ma następującą treść:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

  <!-- warstwy aplikacyjne -->
  <bean id="dao" class="    " />
  
  <!-- EntityManagerFactory -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
        <!--
        <property name="showSql" value="true" />
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
  </bean>

  <!-- źródło danych DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2" />
    <property name="username" value="root" />
    <property name="password" value="" />
  </bean>

  <!-- menedżer transakcji -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <!-- przetwarzanie wyjątków -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

  <!-- trwałość -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

</beans>

Jest to plik zgodny ze Spring 2.x. Nie staraliśmy się wykorzystać nowych funkcji wersji 3.x.

  • wiersze 2–4: tag nadrzędny <beans> w pliku konfiguracyjnym. Nie będziemy omawiać poszczególnych atrybutów tego tagu. Należy zachować ostrożność podczas kopiowania i wklejania, ponieważ błąd w jednym z tych atrybutów powoduje błędy, które czasami są trudne do zrozumienia,
  • wiersz 7: bean „dao” jest odwołaniem do instancji klasy [rdvmedecins.dao.DaoJpa]. Utworzona zostanie pojedyncza instancja (singleton), która będzie implementować warstwę [dao] aplikacji,
  • wiersze 24–29: zdefiniowano źródło danych. Zapewnia ono wspomnianą wcześniej usługę „puli połączeń”. Wykorzystano tutaj klasę [DBCP] z projektu Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/],
  • wiersze 25–28: aby nawiązać połączenia z docelową bazą danych, źródło danych musi znać używany sterownik JDBC (wiersz 25), nazwę bazy danych URL (wiersz 26), użytkownika połączenia i jego hasło (wiersze 27–28),
  • wiersze 10–21: konfigurują warstwę JPA,
  • wiersz 10: definiuje bean typu [EntityManagerFactory] zdolny do tworzenia obiektów typu [EntityManager] w celu zarządzania kontekstami trwałości. Instancja klasy [LocalContainerEntityManagerFactoryBean] jest dostarczana przez Spring. Do instancjonowania potrzebuje ona pewnej liczby parametrów, zdefiniowanych w wierszach 11–20,
  • wiersz 11: źródło danych, które ma być wykorzystane do uzyskania połączeń z SGBD. Jest to źródło [DBCP] zdefiniowane w wierszach 24–29,
  • wiersze 12–20: implementacja JPA, która ma być używana,
  • wiersz 13: określa Hibernate jako implementację JPA, której należy użyć,
  • wiersz 14: dialekt SQL, którego Hibernate ma używać z docelowym SGBD, w tym przypadku MySQL5,
  • wiersz 16 (w komentarzach): wymaga, aby polecenia SQL wykonywane przez Hibernate były rejestrowane w konsoli,
  • wiersz 17 (w komentarzach): nakazuje, aby przy uruchomieniu aplikacji wygenerowano bazę danych (drop i create),
  • wiersz 32: wskazuje, że transakcje są zarządzane za pomocą adnotacji Java (mogłyby one również zostać zadeklarowane w pliku spring-config.xml). Chodzi w szczególności o adnotację @Transactional występującą w klasie [DaoJpa],
  • wiersze 33–35: definiują menedżera transakcji, który ma być używany,
  • wiersz 33: menedżer transakcji to klasa dostarczana przez Spring,
  • w wierszu 34: menedżer transakcji Springa musi znać klasę EntityManagerFactory, która zarządza warstwą JPA. Jest to klasa zdefiniowana w wierszach 10–21,
  • wiersz 41: definiuje klasę obsługującą adnotacje trwałości Springa,
  • wiersz 38: definiuje klasę Spring, która obsługuje w szczególności adnotację @Repository, dzięki której klasa opatrzona tą adnotacją kwalifikuje się do przekształcania natywnych wyjątków sterownika JDBC z SGBD na generyczne wyjątki Spring typu [DataAccessException]. Konwersja ta hermetyzuje natywny wyjątek typu JDBC w typie [DataAccessException], który posiada różne podklasy:

Image

To przekształcenie pozwala programowi klienckiemu na generyczne obsługiwanie wyjątków niezależnie od docelowego typu SGBD. W naszym kodzie Java nie użyliśmy adnotacji @Repository. Dlatego wiersz 38 jest zbędny. Pozostawiliśmy go wyłącznie w celach informacyjnych.

To już wszystko, jeśli chodzi o plik konfiguracyjny Springa. Został on zaczerpnięty z dokumentacji Springa. Jego dostosowanie do różnych sytuacji często sprowadza się do dwóch zmian:

  • zmianę docelowej bazy danych: wiersze 24–29,
  • zmianę implementacji JPA: wiersze 12–20.

Podczas wykonywania kodu wszystkie bean z pliku konfiguracyjnego zostaną zainicjowane. Zobaczymy, jak to się dzieje.

4.1.7. Klasa testowa JUnit

Wcześniej przetestowaliśmy warstwę [DAO] projektu EJB za pomocą testu JUnit. To samo robimy dla warstwy [DAO] projektu Spring:

  • w [1] i [2], po skopiowaniu i wklejeniu testu JUnit między tymi dwoma projektami,
  • w [3] zaimportowany test wykazuje błędy w nowym środowisku.

Zgłoszony błąd [1] dotyczy zdalnego interfejsu EJB, który już nie istnieje. Ponadto kod inicjalizacji pola [dao] w wierszu 19 stanowił wywołanie JNDI specyficzne dla EJB (wiersze 25–28). Aby utworzyć instancję pola [dao] w wierszu 19, musimy wykorzystać plik konfiguracyjny Springa. Odbywa się to w następujący sposób:

  • wiersz 21: typ interfejsu zmienił się na [IDao],
  • wiersz 28: instancjonuje wszystkie bean’y zadeklarowane w pliku [spring-config-dao.xml], w szczególności ten:

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • w wierszu 29 zwraca się do kontekstu Spring z wiersza 28 o odwołanie do bean o id="dao". Otrzymujemy wówczas odwołanie do singletonu [DaoJpa] (class powyżej), który został zainicjowany przez Spring.

Wiersze 28–29 tworzą następujące bloki (różne kropkowane linie):

Podczas wykonywania testów klienta JUnit instancja warstwy [DAO] została utworzona. Można zatem testować jej metody. Warto zauważyć, że do przeprowadzenia tego testu nie jest potrzebny serwer, w przeciwieństwie do testu EJB [DAO], który wymagał serwera Glassfish. Tutaj wszystko odbywa się w ramach tej samej instancji JVM.

Teraz można uruchomić test JUnit. Serwer MySQL musi być uruchomiony. Wyniki są następujące:

Test JUnit zakończył się powodzeniem. Przyjrzyjmy się logom testu, tak jak to zrobiono podczas testu EJB:

mai 24, 2012 5:10:29 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@67291453: startup date [Thu May 24 17:10:29 CEST 2012]; root of context hierarchy
mai 24, 2012 5:10:29 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
...
mai 24, 2012 5:10:30 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
mai 24, 2012 5:10:30 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2}
mai 24, 2012 5:10:30 PM org.hibernate.cfg.Environment <clinit>
...
Infos: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6affe94b: defining beans [dao,entityManagerFactory,dataSource,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,txManager,org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0]; korzeń hierarchii fabrycznej
Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Ajout d'un Rv le [Thu May 24 17:10:30 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
après persist : Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Rv ajouté
mai 24, 2012 5:10:31 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
WARN: SQL Error: 1062, SQLState: 23000
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
mai 24, 2012 5:10:31 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
Ajout d'un Rv le [Thu May 24 17:10:30 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
ERROR: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
  • wiersze 1–4: logi Springa,
  • wiersze 5–10: logi Hibernate,
  • wiersz 11: Spring zgłasza wszystkie instancje beanów, które utworzył. Na pierwszym miejscu znajduje się bean [dao],
  • wiersze 12 i kolejne: logi testu JUnit,
  • wiersze 60–65: wyraźnie widać wyjątek spowodowany dodaniem terminu, który już istnieje w bazie danych. Przypominamy, że w przypadku EJB nie wystąpił ten wyjątek z powodu problemu z serializacją.

Warstwa [dao] działa poprawnie. Teraz tworzymy warstwę [métier].

4.2. Warstwa [métier]

Postępujemy tak samo jak w przypadku warstwy [DAO], kopiując i wklejając projekt EJB do projektu Spring.

4.2.1. Projekt NetBeans

Tworzymy nowy projekt Maven typu [Java Application], oczyszczony ze wszystkiego, czego nie chcemy zachować z projektu [1]:

4.2.2. Zależności projektu

W architekturze:

warstwa [métier] opiera się na warstwie [dao]. Dodajemy zatem zależność od poprzedniego projektu:

  • w warstwach [1] i [2] dodajemy zależność od projektu warstwy [dao],
  • w przypadku [3] zależność ta spowodowała pojawienie się kolejnych zależności, dotyczących projektu warstwy [dao].
  • w [1] i [2] skopiowano kody źródłowe Java z projektu EJB do projektu Spring,
  • do [3], a zaimportowane pliki źródłowe zawierają błędy w nowym środowisku.

Najpierw usuwamy interfejsy zdalne i lokalne z warstwy [métier], które już nie istnieją w [4]:

  • w [5] błędy klasy [Metier] mają kilka przyczyn:
    • użycie pakietu [javax.ejb], który już nie istnieje;
    • użycie interfejsu [IDaoLocal], który już nie istnieje;
    • użycie interfejsów [IMetierRemote] i [IMetierLocal], które już nie istnieją.

Należy

  • usuwamy wszystkie błędne wiersze związane z pakietem [javax.ejb],
  • zastępujemy interfejs [IDaoLocal] interfejsem [IDao],
  • zastępujemy interfejsy [IMetierRemote] i [IMetierLocal] interfejsem [IMetier].
  • na [6], co spowoduje poprawienie klasy,
  • W pliku [7] nie ma już błędów.

Usunęliśmy odniesienia do plików EJB, ale teraz musimy odzyskać ich właściwości:

 
  • wiersz 22: mieliśmy singleton. Tę właściwość uzyskamy, czyniąc tę klasę beanem zarządzanym przez Spring,
  • wiersz 23: każda metoda była wykonywana w ramach transakcji. Osiągniemy to za pomocą adnotacji Spring @Transactional,
  • wiersze 27–28: odwołanie do warstwy [DAO] uzyskiwano poprzez wstrzyknięcie z kontenera EJB. Wykorzystamy wstrzyknięcie Springa.

Kod klasy [Metier] w projekcie Spring zmienia się zatem w następujący sposób:

To wszystko, jeśli chodzi o kod Java. Reszta dzieje się w pliku konfiguracyjnym Springa.

4.2.3. Plik konfiguracyjny Springa

Kopiujemy plik konfiguracyjny Springa z projektu warstwy [DAO] do projektu warstwy [métier]. Najpierw tworzymy gałąź [Other Resources] w projekcie warstwy [métier], jeśli jeszcze nie istnieje:

  • w [1], w zakładce [Files], tworzymy podfolder w folderze [main],
  • w [2] musi nosić nazwę [resources],
  • w [3], w zakładce [Projects] utworzono gałąź [Other Sources].

Możemy teraz przejść do kopiowania i wklejania pliku konfiguracyjnego Springa:

  • w [1] kopiujemy plik z projektu [DAO] do projektu [métier] [2],
  • do [3], skopiowany plik.

Skopiowany plik konfiguracyjny konfiguruje warstwę [DAO]. Dodajemy do niego bean w celu skonfigurowania warstwy [métier]:

1
2
3
4
5
   <!-- warstwy aplikacyjne -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • wiersz 2: bean warstwy [DAO],
  • wiersze 3–5: bean warstwy [métier],
  • wiersz 3: bean nosi nazwę métier (atrybut id) i jest instancją klasy [rdvmedecins.metier.service.Metier] (atrybut class). Bean ten zostanie zainicjowany, podobnie jak pozostałe, podczas uruchamiania aplikacji.

Przypomnijmy kod beana [rdvmedecins.metier.service.Metier]:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // warstwa DAO
  private IDao dao;

  public Metier() {
}
  • wiersz 8: pole [dao] zostanie zainicjowane przez Springa jednocześnie z beanem métier. Wróćmy do definicji tego beana w pliku konfiguracyjnym Springa:
1
2
3
4
5
   <!-- warstwy aplikacyjne -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • wiersz 4: tag <property> służy do inicjalizacji pól instancjonowanego beana. Nazwa pola jest podana przez atrybut name. Zatem instancjonowane zostanie pole dao klasy [rdvmedecins.metier.service.Metier]. Odbędzie się to za pomocą metody setDao, która musi istnieć. Wartość, która zostanie mu przypisana, to wartość atrybutu ref. W tym przypadku jest to odniesienie do bean dao z wiersza 2.

Mówiąc prościej, w kodzie:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // warstwa DAO
  private IDao dao;

  public Metier() {
}

Pole „dao” w wierszu 19 zostanie zainicjowane przez Springa za pomocą odwołania do warstwy [dao]. Właśnie tego chcieliśmy. Pole „dao” zostanie zainicjowane przez Springa za pomocą obiektu setter, który musimy dodać:


  // metoda setter

  public void setDao(IDao dao) {
    this.dao = dao;
}

Zmieniamy nazwę pliku konfiguracyjnego Springa, aby uwzględnić te zmiany:

Jesteśmy teraz gotowi do przeprowadzenia testu. Wykorzystujemy test konsolowy użyty wcześniej do przetestowania EJB i [Metier].

4.2.4. Test warstwy [métier]

Test zostanie przeprowadzony w oparciu o następującą architekturę:

Kopiujemy test konsolowy z projektu EJB do projektu Spring:

  • do [1] i [2], a po skopiowaniu i wklejeniu między tymi dwoma projektami
  • w projekcie [3] zaimportowany kod zawiera błędy.
 

W zaimportowanym kodzie występują dwa rodzaje błędów:

  • wiersz 13: interfejs [IMetierRemote] został zastąpiony interfejsem [IMetier],
  • wiersze 24–27: instancja warstwy [métier] nie jest już tworzona poprzez wywołanie JNDI, lecz poprzez instancjonowanie beanów z pliku konfiguracyjnego Springa.

Poprawiamy te dwa punkty:

  • wiersz 22: wykorzystywany jest plik [spring-config-metier-dao.xml]. Wszystkie komponenty z tego pliku są wówczas instancjonowane. Wśród nich znajdują się następujące:

  <!-- warstwy aplikacyjne -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>

Te dwa bean'y reprezentują warstwy [DAO] i [métier] architektury testu:

Po wykonaniu tych czynności test może się rozpocząć:

  

Logi testu wyglądają następująco:

mai 25, 2012 9:45:07 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@22a92801: startup date [Fri May 25 09:45:07 CEST 2012]; root of context hierarchy
mai 25, 2012 9:45:07 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
....
Infos: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@38a0a058: defining beans [dao,metier,entityManagerFactory,dataSource,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,txManager,org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0]; korzeń hierarchii fabrycznej
Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des rendez-vous du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Ajout d'un Rv le [Fri May 25 09:45:07 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
après persist : Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Rv ajouté
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
  • wiersze 1–4: logi Springa i Hibernate’a,
  • wiersz 5: bean’y instancjonowane przez Spring. Warto zwrócić uwagę na bean’y typu DAO i biznesowe,
  • wiersze 6–53: logi testu. Są one zgodne z wynikami uzyskanymi w teście projektu EJB. Odsyłamy czytelnika do komentarzy dotyczących tego testu (punkt 3.5.3).

Stworzyliśmy warstwę [métier]. Przechodzimy do ostatniej warstwy, warstwy [web].

4.3. Warstwa [web]

Aby utworzyć warstwę [web], postąpimy tak samo jak w przypadku dwóch pozostałych warstw, kopiując i wklejając z warstwy [web] projektu EJB.

4.3.1. Projekt NetBeans

Najpierw tworzymy projekt internetowy:

  • w [1] tworzymy nowy projekt,
  • w [2] tworzymy projekt Maven typu [Web Application],
  • w [3] nadajemy mu nazwę,
  • w [4], tym razem wybieramy serwer Tomcat, a nie Glassfish, który był używany w projekcie EJB,
  • w projekcie [5], uzyskany projekt,
  • w [6], projekt po usunięciu [index.jsp] oraz pakietu z [Source Packages].

4.3.2. Zależności projektu

Przyjrzyjmy się architekturze projektu:

Warstwa [web] wymaga warstw [métier], [DAO] oraz [JPA]. Warstwy te są częścią dwóch projektów, które właśnie skompilowaliśmy. Stąd wynika zależność od każdego z tych projektów:

  • w projekcie [1] dodajemy zależność od projektu Spring / biznesowego,
  • w projekcie [2] dodano projekt Spring / biznesowy. Ponieważ sam miał zależność od projektu Spring / DAO / JPA, został on automatycznie dodany do zależności [3].

Wróćmy do struktury naszej aplikacji:

Warstwa internetowa to warstwa JSF. Potrzebujemy więc bibliotek Java Server Faces. Serwer Tomcat ich nie posiada. Zależność ta nie będzie więc miała zakresu (scope) [provided], jak miało to miejsce w przypadku serwera Glassfish, ale zakresu [compile], który jest zakresem domyślnym, gdy nie określa się zakresu.

Dodajemy te zależności bezpośrednio w kodzie [pom.xml]:


<dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>mv-rdvmedecins-spring-metier</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.7</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.1.7</version>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  • W pliku [pom.xml] dodano wiersze 7–16. Są to zależności względem JSF. Są to te same zależności, które są używane w projekcie EJB / Glassfish. Należy zauważyć, że nie zawierają one tagu <scope>. Domyślnie mają więc zakres [compile]. Biblioteka JSF zostanie zatem dołączona do archiwum [war] projektu internetowego.

Po dodaniu tych zależności do pliku [pom.xml] kompilujemy projekt, aby zostały one pobrane.

4.3.3. Przeniesienie projektu JSF / Glassfish do projektu JSF / Tomcat

Kopiujemy cały kod z projektu JSF / Glassfish do projektu JSF / Tomcat:

  • [1, 2, 3]: kopiowanie stron internetowych ze starego projektu do nowego,
  • [1, 2, 3]: kopiowanie kodu Java ze starego projektu do nowego. Występują błędy. To normalne. Poprawimy je,
  • w [1], w zakładce [Files] programu NetBeans tworzymy podfolder [resources] w folderze [main],
  • co powoduje utworzenie w zakładce [Projects] gałęzi [Other Sources] [3],
  • [1, 2, 3]: kopiuje się pliki wiadomości ze starego projektu do nowego projektu.

4.3.4. Zmiany w zaimportowanym projekcie

Zauważyliśmy, że zaimportowany kod Java zawiera błędy. Przyjrzyjmy się im:

  • w pliku [1] błąd dotyczy wyłącznie komponentu [Application],
  • w przypadku [2] błąd wynika wyłącznie z tego, że interfejs [IMetierLocal] już nie istnieje. W tym miejscu może budzić zdziwienie fakt, że wiersz 20 nie został oznaczony jako błędny. Adnotacja @EJB odnosi się wyraźnie do EJB i jest tutaj rozpoznawana. Wynika to z obecności zależności [javaee-web-api-6.0] [3]. Java EE 6 wprowadziła architekturę, która umożliwia wdrażanie aplikacji internetowej opartej na EJB bez interfejsu zdalnego, na serwerach nieposiadających kontenera EJB. Wystarczy, aby serwer zapewniał zależność [javaee-web-api-6.0]. Widać bowiem, że ma ona zakres [provided] [3].

W tym przypadku nie będziemy korzystać z zależności [javaee-web-api-6.0]. Usuwamy ją: [1]:

Powoduje to pojawienie się nowych błędów [2]. Zaczniemy od tych dotyczących komponentu [Form]:

  • W przypadku [1] błędne wiersze wynikają z braku pakietu [javax]. Usuwamy je wszystkie z pliku [2]. Błędne wiersze sprawiały, że klasa [Form] była beanem o zasięgu sesji (wiersze 18–20 w pliku [1]). Ponadto bean [Application] był wstrzykiwany w wierszu 25. Informacje te zostaną przeniesione do pliku konfiguracyjnego JSF i [faces-config.xml].

Przejdźmy do komponentu [Application]:

Usuwamy wszystkie błędne wiersze z pliku [1] i zmieniamy interfejs [IMetierLocal] w wierszach 13 i 21 na [IMetier]. W pliku [2] nie ma już błędów. W pliku [1] usunęliśmy wiersze 15–16, które definiowały klasę [Application] jako bean o zasięgu application.. Informacja ta zostanie przeniesiona do pliku konfiguracyjnego JSF [faces-config.xml]. Usunęliśmy również wiersz 20, który wstrzykiwał do bean'a odwołanie do warstwy [métier]. Teraz zostanie ono zainicjowane przez Spring. Mamy już niezbędny plik konfiguracyjny – jest to plik z projektu Spring / Métier. Kopiujemy go:

  • do [1, 2], kopiujemy plik konfiguracyjny Springa z projektu Spring / Métier do projektu Spring / JSF,

W [3] – wynik.

W beanie [Application] należy wykorzystać ten plik konfiguracyjny, aby uzyskać odwołanie do warstwy [métier]. Odbywa się to w jego metodzie [init]:


package beans;

...
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {

  // warstwa biznesowa
  private IMetier metier;
...

  public Application() {
  }

  @PostConstruct
  public void init() {
    try {
      // instancja warstwy [métier]
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
      metier = (IMetier) ctx.getBean("metier");
      // lekarze i klienci są zapisywani w pamięci podręcznej
...
    } catch (Throwable th) {
...
    }
...
  }
  • wiersz 20: tworzone są instancje beanów z pliku konfiguracyjnego Springa,
  • wiersz 21: pobierane jest odwołanie do fasoli biznesowej, a więc do warstwy [métier].

Ogólnie rzecz biorąc, instancjonowanie beanów Springa powinno odbywać się w metodzie init beana o zasięgu aplikacji. Istnieje również inna metoda, w której instancjonowanie beanów odbywa się za pośrednictwem serwletu Springa. Wymaga to zmiany pliku [web.xml] oraz dodania zależności od artefaktu [spring-web]. Nie zrobiliśmy tego w tym przypadku, aby zachować spójność z tym, co zostało zastosowane w poprzednich kodach.

Usunęliśmy adnotacje z klas [Application] i [Form], które definiowały je jako fasolki JSF. Klasy te muszą pozostać fasolkami JSF. Zamiast adnotacji używamy pliku konfiguracyjnego JSF [WEB-INF / faces.config.xml] do deklarowania beanów.

Plik ten ma teraz następującą postać:


<?xml version='1.0' encoding='UTF-8'?>

<!-- =========== FULL CONFIGURATION FILE ================================== -->

<faces-config version="2.0"
              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_2_0.xsd">

  <application>
    <!-- plik komunikatów -->
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
    <!-- bean applicationBean -->
    <managed-bean>
      <managed-bean-name>applicationBean</managed-bean-name>
      <managed-bean-class>beans.Application</managed-bean-class>
      <managed-bean-scope>application</managed-bean-scope>
    </managed-bean>
    <!-- bean formularza -->
    <managed-bean>
      <managed-bean-name>form</managed-bean-name>
      <managed-bean-class>beans.Form</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
        <property-name>application</property-name>
        <value>#{applicationBean}</value>
      </managed-property>
    </managed-bean>
</faces-config>
  • wiersze 10–19 konfigurują plik komunikatów. Była to jedyna konfiguracja, jaką mieliśmy w projekcie JSF / EJB,
  • wiersze 21–35 deklarują komponenty aplikacji JSF. Była to standardowa metoda stosowana w JSF 1.x. W wersji JSF 2 wprowadzono adnotacje, ale metoda stosowana w JSF i 1.x jest nadal obsługiwana,
  • wiersze 21–25: deklarują bean applicationBean,
  • wiersz 22: nazwa bean. Można by pokusić się o nazwę „application”. Należy tego unikać, ponieważ jest to nazwa predefiniowanego bean w JSF,
  • wiersz 23: pełna nazwa klasy bean,
  • wiersz 24: jego zakres,
  • wiersze 27–35: definiują bean form,
  • wiersz 28: nazwa bean,
  • wiersz 29: pełna nazwa klasy bean,
  • wiersz 30: jego zakres,
  • wiersze 31–34: definiują właściwość klasy [beans.Form],
  • wiersz 32: nazwa właściwości. Klasa [beans.Form] musi posiadać pole o tej nazwie oraz odpowiadający mu setter,
  • wiersz 33: wartość pola. W tym przypadku jest to odwołanie do bean’a applicationBean zdefiniowanego w wierszu 21. W ten sposób dokonujemy wstrzyknięcia bean o zasięgu application do bean o zasięgu session, aby ten ostatni miał dostęp do danych o zasięgu application.

Jak wspomniano powyżej, pole [application] w beanie [beans.Form] ma zostać zainicjowane za pomocą setter. Należy zatem dodać je do klasy [beans.Form], o ile jeszcze nie istnieje:


public void setApplication(Application application) {
    this.application = application;
  }

4.3.5. Testowanie aplikacji

Nasza aplikacja jest teraz wolna od błędów i gotowa do testów:

  • w [1], poprawiony projekt,
  • w [2] kompilujemy go,
  • w [3] uruchamiamy ją. Konieczne jest uruchomienie SGBD i MySQL. Serwer Tomcat zostanie wówczas uruchomiony ([4]), jeśli jeszcze nie był uruchomiony, a następnie wyświetlona zostanie strona główna aplikacji ([5]):

W tym momencie pojawia się omawiana aplikacja. Pozostawiamy czytelnikowi sprawdzenie, czy działa. Teraz zatrzymajmy aplikację:

  • w [1], aplikacja zostaje wyładowana,
  • w [2] aplikacji już nie ma.

Przyjrzyjmy się teraz logom serwera Tomcat:

1
2
3
4
5
6
mai 25, 2012 2:15:57 PM org.apache.catalina.loader.WebappClassLoader clearReferencesJdbc
Grave: The web application [/mv-rdvmedecins-spring-jsf2] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
mai 25, 2012 2:15:57 PM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
Grave: The web application [/mv-rdvmedecins-spring-jsf2] appears to have started a thread named [MySQL Statement Cancellation Timer] but has failed to stop it. This is very likely to create a memory leak.
mai 25, 2012 2:15:59 PM org.apache.catalina.startup.HostConfig checkResources
Infos: Repli (undeploy) de l'application web ayant pour chemin de contexte /mv-rdvmedecins-spring-jsf2

Wiersze 2 i 4 sygnalizują nieprawidłowe działanie podczas zamykania aplikacji. Wiersz 4 wskazuje na prawdopodobne ryzyko wycieku pamięci. Rzeczywiście, wyciek ten ma miejsce i po pewnym czasie NetBeans przestaje działać. Problem ten jest szczególnie irytujący, ponieważ wymaga ponownego uruchamiania NetBeans przy każdym nowym uruchomieniu projektu. Problem ten został już omówiony w dokumencie „Wprowadzenie do Struts 2 na przykładach” [http://tahe.developpez.com/java/struts2].

W Internecie można znaleźć wiele informacji na temat tego błędu. Pojawia się on podczas wielokrotnego ładowania i wyładowywania aplikacji z serwera Tomcat. Po pewnym czasie pojawia się błąd java.lang.OutOfMemoryError: PermGen space. Wydaje się, że nie ma rozwiązania pozwalającego uniknąć tego błędu, gdy wynika on z plików archiwów stron trzecich (jar), jak ma to miejsce w tym przypadku. Aby go usunąć, należy zatem ponownie uruchomić serwer Tomcat.

Można jednak opóźnić wystąpienie tego błędu. Po pierwsze, należy zwiększyć pojemność pamięci, w której doszło do przepełnienia.

  • w [1] przechodzimy do właściwości serwera Tomcat,
  • w [2], w zakładce [Platform], ustalamy wartość pamięci, która ulega przepełnieniu. W tym przypadku ustawiono 1 GB, ponieważ całkowita pamięć wynosiła 8 GB. W przypadku mniejszej pamięci można ustawić 512M (512 megabajtów).

Następnie umieszczamy sterownik JDBC z MySQL w <tomcat>/lib, gdzie <tomcat> to katalog instalacyjny Tomcata.

  • w pliku [1], w właściwościach serwera Tomcat należy odnotować jego katalog instalacyjny <tomcat>,
  • w <tomcat>/lib [2] umieszczamy najnowszy sterownik JDBC, zastępując MySQL i [3].

Następnie usuwamy zależność projektu od sterownika JDBC, zastępując go wersjami MySQL i [4].

Po wykonaniu tych czynności testujemy aplikację. Stwierdzamy, że można wielokrotnie ładować i wyładowywać aplikację. Problemy z wyciekami pamięci nie zostały jednak rozwiązane. Po prostu pojawiają się one później.

4.4. Conclusion

Przenieśliśmy aplikację JSF / EJB / Glassfish do środowiska JSF / Spring / Tomcat. Odbyło się to głównie poprzez kopiowanie i wklejanie między tymi dwoma projektami. Było to możliwe, ponieważ technologie Spring i EJB3 wykazują duże podobieństwa. EJB3 powstało bowiem po tym, jak Spring okazał się wydajniejszy od EJB2. EJB3 przejął wówczas sprawdzone rozwiązania z Springa.

4.5. Testy z wykorzystaniem Eclipse

  • w [1] importujemy trzy projekty Spring,
  • w [2] wybieramy test JUnit z warstwy [DAO] i uruchamiamy go w [3],
  • w warstwie [4] test zakończył się powodzeniem,
  • w [5] – logi konsoli.
  • w [6A] [6B] uruchamia się klienta konsoli warstwy [métier],
  • w [7] – uzyskany obraz konsoli,
  • w [8] [9] uruchamia się projekt internetowy na serwerze Tomcat 7 [10],
  • i [11], strona główna aplikacji zostanie wyświetlona w wewnętrznej przeglądarce środowiska Eclipse.