Skip to content

4. Beispielanwendung – 02: rdvmedecins-jsf2-spring

Wir wollen nun die vorherige Anwendung in eine Spring-/Tomcat-Umgebung portieren:

Es handelt sich hierbei tatsächlich um eine Portierung. Wir gehen von der vorherigen Anwendung aus und passen sie an die neue Umgebung an. Wir werden nur die Änderungen erläutern. Diese lassen sich in drei Kategorien einteilen:

  • Der Server ist nicht mehr GlassFish, sondern Tomcat, ein schlanker Server ohne Container EJB,
  • als Ersatz für EJB wird Spring verwendet, der Hauptkonkurrent von EJB und [http://www.springsource.com/],
  • die verwendete Implementierung JPA wird Hibernate anstelle von EclipseLink sein.

Da wir häufig per Kopieren/Einfügen zwischen dem alten und dem neuen Projekt arbeiten werden, lassen wir die bisherigen Projekte in NetBeans geöffnet:

  

Die Verwendung des Spring-Frameworks erfordert bestimmte Kenntnisse, die in [ref7] zu finden sind (siehe Seite 166).

4.1. Die Schichten [DAO] und [JPA]

4.1.1. Das NetBeans-Projekt

Wir erstellen ein Maven-Projekt vom Typ [Java Application]:

  • in [1], das erstellte Projekt,
  • in [2], dasselbe Projekt, jedoch ohne die Pakete [Source Packages] und [Test Packages] sowie die Abhängigkeit [junit-3.8.1].

Das Schwierigste bei Maven-Projekten ist es, die richtigen Abhängigkeiten zu finden. Für dieses Spring-/JPA-/Hibernate-Projekt sind dies die folgenden:


<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>
  • Zeilen 18–29: für Hibernate,
  • Zeilen 30–34: für den Treiber JDBC von MySQL,
  • Zeilen 35–41: für den Test JUnit,
  • Zeilen 42–51: für den Apache-Commons-Verbindungspool DBCP. Ein Verbindungspool ist ein Pool offener Verbindungen. Wenn die Anwendung eine Verbindung benötigt, fordert sie diese vom Pool an. Wenn sie diese nicht mehr benötigt, gibt sie sie zurück. Die Verbindungen werden beim Start der Anwendung geöffnet und bleiben während der gesamten Laufzeit der Anwendung bestehen. Dadurch werden die Kosten für wiederholtes Öffnen und Schließen von Verbindungen vermieden. Diese Art von Pool gab es bereits in Glassfish, aber dessen Verwendung war für uns transparent. Das wird auch hier der Fall sein, aber wir müssen ihn installieren und konfigurieren,
  • Zeilen 52–75: für Spring.

Fügen wir diese Abhängigkeiten hinzu und erstellen wir das Projekt:

  • in [1]. Beim Erstellen des Projekts wird Maven dazu veranlasst, die Abhängigkeiten herunterzuladen,
  • in [2] erscheinen diese dann im Zweig [Dependencies]. Es sind sehr viele, da die Frameworks Hibernate und Spring selbst sehr viele Abhängigkeiten haben. Auch hier müssen wir uns dank Maven nicht um diese kümmern. Sie werden automatisch heruntergeladen.

Nachdem wir nun die Abhängigkeiten haben, fügen wir den Code des Projekts EJB aus der Ebene [dao] in das Spring-Projekt der Ebene [dao] ein:

  • in [1], kopieren wir es in das Quellprojekt,
  • in [2], dort wird es in das Zielprojekt eingefügt,
  • in [3] das Ergebnis.

Nach dem Kopieren müssen die Fehler korrigiert werden.

4.1.2. Das Paket [exceptions]

Die Klasse [RdvMedecinsExceptions] [1] weist Fehler auf, da das Paket [javax] in Zeile 4 nicht mehr existiert. Es handelt sich um ein Paket, das spezifisch für EJB ist. Der Fehler in Zeile 6 ergibt sich aus dem Fehler in Zeile 4. Diese beiden Zeilen werden gelöscht. Dadurch werden die Fehler [2] beseitigt.

4.1.3. Das Paket [jpa]

  • in [1] ist die Klasse [Creneau] fehlerhaft, da das Validierungspaket der Zeile [5] fehlt. Man hätte dieses Paket zu den Abhängigkeiten des Projekts hinzufügen können. Bei den Tests löst Hibernate jedoch aufgrund dieses Pakets eine Ausnahme aus. Da es für unsere Anwendung nicht unverzichtbar ist, haben wir es entfernt. Um die Klasse zu korrigieren, müssen lediglich alle fehlerhaften Zeilen [2] gelöscht werden. Dies wird für alle fehlerhaften Klassen durchgeführt.

4.1.4. Das Paket [dao]

Wir sind nun an folgendem Punkt angelangt:

  • in [1], die beiden korrigierten Pakete,
  • in [2], das Paket [dao]. Da es kein EJB mehr gibt, gibt es auch den Begriff der entfernten und lokalen Schnittstelle von EJB nicht mehr. Wir löschen sie: [3].
  • In [1] haben die Fehler der Klasse [DaoJpa] zwei Ursachen:
  • dem Import eines Pakets, das mit EJB verknüpft ist (Zeilen 6–8);
  • die Verwendung der lokalen und der Remote-Schnittstelle, die wir gerade entfernt haben.

Wir entfernen die fehlerhaften Zeilen und verwenden anstelle der lokalen und der Remote-Schnittstelle [2] die Schnittstelle [IDao].

Im Projekt EJB war die Klasse [DaoJpa] ein Singleton, und ihre Methoden wurden innerhalb einer Transaktion ausgeführt. Wir werden sehen, dass die Klasse [DaoJpa] eine von Spring verwaltete Bean sein wird. Standardmäßig ist jede Spring-Bean ein Singleton. Soviel zur ersten Eigenschaft. Die zweite ergibt sich aus der Spring-Annotation @Transactional für [3]:

Nachdem dies erledigt ist, weist das Projekt keine Fehler mehr auf: [4].

4.1.5. Konfiguration der Schicht [JPA]

Im Projekt EJB hatten wir die Schicht [JPA] mit der Datei [persistence.xml] konfiguriert. Hier haben wir eine Schicht [JPA], daher müssen wir diese Datei erstellen. Im Projekt EJB hatten wir sie mit Glassfish generiert. Hier erstellen wir sie manuell. Der Hauptgrund dafür ist, dass ein Teil der Konfiguration der Datei [persistence.xml] in die Spring-Konfigurationsdatei selbst migriert wird.

Wir erstellen die Datei „[persistence.xml]“:

mit folgendem Inhalt:


<?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>
  • Zeile 3: Wir vergeben einen Namen für die Persistenzeinheit,
  • Zeile 3: Der Transaktionstyp lautet RESOURCE_LOCAL. Im Projekt EJB war dies JTA, um anzugeben, dass die Transaktionen vom Container EJB verwaltet wurden. Der Wert RESOURCE_LOCAL gibt an, dass die Anwendung ihre Transaktionen selbst verwaltet. Dies geschieht hier über Spring,
  • Zeilen 4–7: die vollständigen Namen der vier Entitäten JPA. Dies ist optional, da Hibernate sie automatisch im ClassPath des Projekts sucht.

Das war’s. Der Name des Providers JPA, seine Eigenschaften sowie die Merkmale JDBC der Datenquelle befinden sich nun in der Spring-Konfigurationsdatei.

4.1.6. Die Spring-Konfigurationsdatei

Wir haben erwähnt, dass die Klasse [DaoJpa] ein von Spring verwaltetes Bean ist. Dies erfolgt über eine Konfigurationsdatei. Diese Datei enthält auch die Konfiguration für den Datenbankzugriff sowie die Transaktionsverwaltung. Sie muss sich im ClassPath des Projekts befinden. Wir legen sie im Zweig [Other sources] ab:

Die Datei [spring-config-dao.xml] sieht wie folgt aus:


<?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">

  <!-- Anwendungsschichten -->
  <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>

  <!-- die Datenquelle 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>

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

  <!-- Ausnahmeübersetzung -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

  <!-- Persistenz -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

</beans>

Es handelt sich um eine Spring 2.x-kompatible Datei. Wir haben nicht versucht, die neuen Funktionen der 3.x-Versionen zu nutzen.

  • Zeilen 2–4: Das Stamm-Tag <beans> der Konfigurationsdatei. Wir gehen nicht näher auf die verschiedenen Attribute dieses Tags ein. Achten Sie darauf, den Text zu kopieren und einzufügen, da ein Fehler bei einem dieser Attribute zu Fehlern führen kann, die manchmal schwer zu verstehen sind.
  • Zeile 7: Das „dao“-Bean ist eine Referenz auf eine Instanz der Klasse [rdvmedecins.dao.DaoJpa]. Es wird eine einzige Instanz erstellt (Singleton), die die Schicht [dao] der Anwendung implementiert,
  • Zeilen 24–29: Es wird eine Datenquelle definiert. Sie stellt den bereits erwähnten „Verbindungspool“-Dienst bereit. Hier wird [DBCP] aus dem Apache-Commons-Projekt DBCP [http://jakarta.apache.org/commons/dbcp/] verwendet,
  • Zeilen 25–28: Um Verbindungen zur Zieldatenbank herzustellen, benötigt die Datenquelle den verwendeten Treiber JDBC (Zeile 25), den Datenbanknamen URL (Zeile 26), den Benutzer der Verbindung und dessen Passwort (Zeilen 27–28),
  • Zeilen 10–21: Konfigurieren die JPA-Schicht,
  • Zeile 10: definiert eine Bean vom Typ [EntityManagerFactory], die Objekte vom Typ [EntityManager] erstellen kann, um Persistenzkontexte zu verwalten. Die instanziierte Klasse [LocalContainerEntityManagerFactoryBean] wird von Spring bereitgestellt. Sie benötigt für die Instanziierung eine Reihe von Parametern, die in den Zeilen 11–20 definiert sind,
  • Zeile 11: die Datenquelle, die verwendet werden soll, um Verbindungen zu SGBD herzustellen. Dabei handelt es sich um die in den Zeilen 24–29 definierte Quelle [DBCP],
  • Zeilen 12–20: die zu verwendende Implementierung JPA,
  • Zeile 13: Legt Hibernate als zu verwendende Implementierung „JPA“ fest,
  • Zeile 14: Der Dialekt SQL, den Hibernate mit dem Ziel SGBD verwenden soll, hier MySQL5,
  • Zeile 16 (auskommentiert): Legt fest, dass die von Hibernate ausgeführten SQL-Befehle in der Konsole protokolliert werden sollen,
  • Zeile 17 (auskommentiert): Legt fest, dass beim Start der Anwendung die Datenbank generiert wird (Drop und Create),
  • Zeile 32: gibt an, dass Transaktionen mit Java-Annotationen verwaltet werden (sie hätten auch in spring-config.xml deklariert werden können). Dabei handelt es sich insbesondere um die Annotation @Transactional, die in der Klasse [DaoJpa] vorkommt,
  • Zeilen 33–35: definieren den zu verwendenden Transaktionsmanager,
  • Zeile 33: Der Transaktionsmanager ist eine von Spring bereitgestellte Klasse,
  • Zeile 34: Der Spring-Transaktionsmanager muss die Klasse EntityManagerFactory kennen, die die Schicht JPA verwaltet. Diese ist in den Zeilen 10–21 definiert,
  • Zeile 41: definiert die Klasse, die die Spring-Persistenz-Annotationen verwaltet,
  • Zeile 38: Definiert die Spring-Klasse, die insbesondere die Annotation @Repository verwaltet, wodurch eine so annotierte Klasse für die Übersetzung der nativen Ausnahmen des Treibers JDBC von SGBD in generische Spring-Ausnahmen vom Typ [DataAccessException] in Frage kommt. Diese Umwandlung kapseln die native Ausnahme JDBC in einen Typ [DataAccessException] mit verschiedenen Unterklassen:

Image

Diese Übersetzung ermöglicht es dem Client-Programm, Ausnahmen generisch zu behandeln, unabhängig vom Ziel-SGBD. Wir haben die Annotation @Repository in unserem Java-Code nicht verwendet. Daher ist Zeile 38 überflüssig. Wir haben sie lediglich zu Informationszwecken beibehalten.

Damit sind wir mit der Spring-Konfigurationsdatei fertig. Sie wurde der Spring-Dokumentation entnommen. Ihre Anpassung an verschiedene Situationen beschränkt sich oft auf zwei Änderungen:

  • die Anpassung an die Zieldatenbank: Zeilen 24–29,
  • die Anpassung der Implementierung JPA: Zeilen 12–20.

Bei der Ausführung des Codes werden alle Beans der Konfigurationsdatei instanziiert. Wir werden sehen, wie das funktioniert.

4.1.7. Die Testklasse JUnit

Wir hatten die Schicht [DAO] des Projekts EJB mit einem Test JUnit getestet. Dasselbe tun wir nun für die Schicht [DAO] des Spring-Projekts:

  • In den Projekten [1] und [2], bei denen der Test JUnit per Kopieren und Einfügen zwischen den beiden Projekten übertragen wurde,
  • in [3] weist der importierte Test in seiner neuen Umgebung Fehler auf.

Der gemeldete Fehler [1] bezieht sich auf die Remote-Schnittstelle von EJB, die nicht mehr existiert. Außerdem war der Initialisierungscode für das Feld [dao] in Zeile 19 ein Aufruf von JNDI, der spezifisch für EJB (Zeilen 25–28) war. Um das Feld [dao] in Zeile 19 zu instanziieren, müssen wir die Spring-Konfigurationsdatei nutzen. Dies geschieht wie folgt:

  • Zeile 21: Der Typ der Schnittstelle lautet nun [IDao],
  • Zeile 28: Instanziiert alle in der Datei [spring-config-dao.xml] deklarierten Beans, insbesondere diese:

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • Zeile 29 fordert vom Spring-Kontext aus Zeile 28 eine Referenz auf das Bean mit der ID „dao“ an. Man erhält dann eine Referenz auf das Singleton [DaoJpa] (oben: class), das Spring instanziiert hat.

Die Zeilen 28–29 bilden die folgenden Blöcke (rosa gepunktet):

Wenn die Tests des Clients JUnit ausgeführt werden, wurde die Schicht [DAO] instanziiert. Daher können ihre Methoden getestet werden. Beachten Sie, dass für diesen Test kein Server erforderlich ist – im Gegensatz zum Test von EJB und [DAO], für den der Glassfish-Server benötigt wurde. Hier läuft alles innerhalb derselben Instanz JVM ab.

Nun kann der Test JUnit ausgeführt werden. Dazu muss der Server MySQL gestartet sein. Die Ergebnisse lauten wie folgt:

Der Test JUnit war erfolgreich. Sehen wir uns die Testprotokolle an, wie es bereits beim Test von EJB geschehen ist:

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]; Wurzel der Werkshierarchie
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]]
  • Zeilen 1–4: Spring-Protokolle,
  • Zeilen 5–10: Hibernate-Protokolle,
  • Zeile 11: Spring meldet alle Beans, die es instanziiert hat. An erster Stelle steht dabei die Bean [dao],
  • Zeilen 12 ff.: die Logs des Tests JUnit,
  • Zeilen 60–65: Hier ist deutlich die Ausnahme zu sehen, die durch das Hinzufügen eines bereits in der Datenbank vorhandenen Termins ausgelöst wurde. Zur Erinnerung: Bei EJB war diese Ausnahme aufgrund eines Serialisierungsfehlers nicht aufgetreten.

Die Schicht [dao] ist betriebsbereit. Wir erstellen nun die Schicht [métier].

4.2. Die Schicht [métier]

Wir gehen genauso vor wie bei der Schicht [DAO], indem wir Inhalte aus dem Projekt EJB in das Spring-Projekt kopieren und einfügen.

4.2.1. Das NetBeans-Projekt

Wir erstellen ein neues Maven-Projekt vom Typ [Java Application], das von allem bereinigt ist, was wir nicht behalten möchten ([1]):

4.2.2. Die Abhängigkeiten des Projekts

In der Architektur:

Die Schicht [métier] stützt sich auf die Schicht [dao]. Wir fügen daher eine Abhängigkeit zum vorherigen Projekt hinzu:

  • In [1] und [2] fügen wir eine Abhängigkeit zum Projekt der Schicht [dao] hinzu,
  • in [3] hat diese Abhängigkeit weitere Abhängigkeiten nach sich gezogen, nämlich die des Projekts der Ebene [dao].
  • In den Projekten [1] und [2] werden die Java-Quelldateien aus dem Projekt EJB in das Spring-Projekt kopiert,
  • in [3] weisen die importierten Quelldateien in ihrer neuen Umgebung Fehler auf.

Zunächst werden die Remote- und lokalen Schnittstellen der Schicht [métier] entfernt, die in [4] nicht mehr vorhanden sind:

  • In [5] haben die Fehler der Klasse [Metier] mehrere Ursachen:
    • die Verwendung des Pakets [javax.ejb], das nicht mehr existiert;
    • die Verwendung der Schnittstelle [IDaoLocal], die nicht mehr existiert;
    • die Verwendung der Schnittstellen [IMetierRemote] und [IMetierLocal], die nicht mehr existieren.

Wir

  • löschen alle fehlerhaften Zeilen, die sich auf das Paket [javax.ejb] beziehen,
  • ersetzen die Schnittstelle [IDaoLocal] durch die Schnittstelle [IDao],
  • ersetzen die Schnittstellen [IMetierRemote] und [IMetierLocal] durch die Schnittstelle [IMetier].
  • in [6], die so korrigierte Klasse,
  • in [7], es gibt keine Fehler mehr.

Wir haben die Verweise auf EJB entfernt, müssen nun aber deren Eigenschaften wiederherstellen:

 
  • Zeile 22: Wir hatten ein Singleton. Diese Eigenschaft wird erreicht, indem die Klasse zu einem von Spring verwalteten Bean gemacht wird,
  • Zeile 23: Jede Methode wurde in einer Transaktion ausgeführt. Dies wird mit der Spring-Annotation @Transactional erreicht,
  • Zeilen 27–28: Die Referenz auf die Schicht [DAO] wurde durch Injektion des Containers EJB bereitgestellt. Wir werden eine Spring-Injektion verwenden.

Der Code der Klasse [Metier] des Spring-Projekts sieht nun wie folgt aus:

Das war’s schon mit dem Java-Code. Der Rest erfolgt in der Spring-Konfigurationsdatei.

4.2.3. Die Spring-Konfigurationsdatei

Wir kopieren die Spring-Konfigurationsdatei aus dem Projekt der Schicht [DAO] in das Projekt der Schicht [métier]. Zunächst erstellen wir den Zweig [Other Resources] im Projekt der Schicht [métier], falls dieser noch nicht existiert:

  • In [1], auf der Registerkarte [Files], erstellen wir einen Unterordner zum Ordner [main],
  • in [2] muss er [resources] heißen,
  • in [3] wurde auf der Registerkarte [Projects] der Zweig [Other Sources] erstellt.

Nun können wir die Spring-Konfigurationsdatei kopieren und einfügen:

  • von [1] kopieren wir die Datei aus dem Projekt [DAO] in das Projekt [métier] [2],
  • in [3], die kopierte Datei.

Die kopierte Konfigurationsdatei konfiguriert die Schicht [DAO]. Wir fügen ihr eine Bean hinzu, um die Schicht [métier] zu konfigurieren:

1
2
3
4
5
   <!-- Anwendungsschichten -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • Zeile 2: die Bean der Schicht [DAO],
  • Zeilen 3–5: die Bean der Schicht [métier],
  • Zeile 3: Die Bean heißt métier (Attribut id) und ist eine Instanz der Klasse [rdvmedecins.metier.service.Metier] (Attribut class). Diese Bean wird wie die anderen beim Start der Anwendung instanziiert.

Hier noch einmal der Code der Bean [rdvmedecins.metier.service.Metier]:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // DAO-Schicht
  private IDao dao;

  public Metier() {
}
  • Zeile 8: Das Feld [dao] wird von Spring gleichzeitig mit dem Bean métier instanziiert. Kehren wir zur Definition dieses Beans in der Spring-Konfigurationsdatei zurück:
1
2
3
4
5
   <!-- Anwendungsschichten -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • Zeile 4: Das <property>-Tag dient dazu, Felder der instanziierten Bean zu initialisieren. Der Name des Feldes wird durch das Attribut name angegeben. Es wird also das Feld „dao“ der Klasse [rdvmedecins.metier.service.Metier] instanziiert. Dies geschieht über eine Methode setDao, die vorhanden sein muss. Der Wert, der ihm zugewiesen wird, ist der Wert des Attributs „ref“. Dieser Wert ist hier die Referenz des „dao“-Beans aus Zeile 2.

Einfacher ausgedrückt, im Code:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // DAO-Schicht
  private IDao dao;

  public Metier() {
}

Das Feld „dao“ in Zeile 19 wird von Spring mit einer Referenz auf die Schicht [dao] initialisiert. Genau das wollten wir erreichen. Das Feld „dao“ wird von Spring über ein setter initialisiert, das wir hinzufügen müssen:


  // Setter

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

Wir benennen die Spring-Konfigurationsdatei um, um den Änderungen Rechnung zu tragen:

Wir sind nun bereit für einen Test. Wir greifen auf den Konsolentest zurück, der zum Testen von EJB und [Metier] verwendet wurde.

4.2.4. Test der [métier]-Schicht

Der Test wird mit der folgenden Architektur durchgeführt:

Wir kopieren den Konsolentest aus dem Projekt EJB in das Spring-Projekt:

  • in [1] und [2]; beim Kopieren und Einfügen zwischen den beiden Projekten
  • in [3] weist der importierte Code Fehler auf.
 

Der importierte Code weist zwei Arten von Fehlern auf:

  • Zeile 13: Die Schnittstelle [IMetierRemote] wurde durch die Schnittstelle [IMetier] ersetzt,
  • Zeilen 24–27: Die Instanziierung der Schicht [métier] erfolgt nicht mehr über einen Aufruf von JNDI, sondern durch Instanziierung der Beans aus der Spring-Konfigurationsdatei.

Wir korrigieren diese beiden Punkte:

  • Zeile 22: Die Datei [spring-config-metier-dao.xml] wird verwendet. Alle Beans dieser Datei werden daraufhin instanziiert. Darunter befinden sich folgende:

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

Diese beiden Beans stellen die Schichten [DAO] und [métier] der Testarchitektur dar:

Danach kann der Test ausgeführt werden:

  

Die Testprotokolle lauten dann wie folgt:

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]; Wurzel der Werkshierarchie
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]]
  • Zeilen 1–4: die Protokolle von Spring und Hibernate,
  • Zeile 5: die von Spring instanziierten Beans. Zu beachten sind die DAO- und Geschäftslogik-Beans,
  • Zeilen 6–53: die Testprotokolle. Sie stimmen mit den Ergebnissen des Tests des Projekts EJB überein. Wir verweisen den Leser auf die Kommentare zu diesem Test (Abschnitt 3.5.3).

Wir haben die Schicht [métier] erstellt. Wir fahren nun mit der letzten Schicht fort, der Schicht [web].

4.3. Die Schicht [web]

Um die Ebene [web] zu erstellen, gehen wir genauso vor wie bei den beiden anderen Ebenen, nämlich durch Kopieren und Einfügen aus der Ebene [web] des Projekts EJB.

4.3.1. Das NetBeans-Projekt

Zunächst erstellen wir ein Webprojekt:

  • In [1] erstellen wir ein neues Projekt,
  • in [2] ein Maven-Projekt vom Typ [Web Application],
  • in [3], geben wir ihm einen Namen,
  • in [4] wählen wir diesmal den Tomcat-Server und nicht Glassfish, der für das Projekt EJB verwendet wurde,
  • bei [5] ist das resultierende Projekt,
  • in [6], das Projekt nach dem Löschen von [index.jsp] und des Pakets von [Source Packages].

4.3.2. Die Abhängigkeiten des Projekts

Sehen wir uns die Architektur des Projekts an:

Die Schicht [web] benötigt die Schichten [métier], [DAO] und [JPA]. Diese sind Teil der beiden Projekte, die wir gerade erstellt haben. Daher besteht eine Abhängigkeit zu jedem dieser Projekte:

  • In [1] fügen wir die Abhängigkeit zum Spring-/Geschäftsprojekt hinzu,
  • in [2] wurde das Spring-/Geschäftsprojekt hinzugefügt. Da dieses Projekt selbst eine Abhängigkeit vom Projekt „Spring / DAO / JPA“ hatte, wurde dieses automatisch zu den Abhängigkeiten von [3] hinzugefügt.

Kommen wir nun zurück zur Struktur unserer Anwendung:

Die Webschicht ist eine JSF-Schicht. Wir benötigen daher die Java-Server-Faces-Bibliotheken. Der Tomcat-Server verfügt nicht über diese. Die Abhängigkeit hat daher nicht den Geltungsbereich (Scope) [provided], wie es beim Glassfish-Server der Fall war, sondern den Geltungsbereich [compile], der der Standardgeltungsbereich ist, wenn kein Geltungsbereich angegeben wird.

Wir fügen diese Abhängigkeiten direkt in den Code von [pom.xml] ein:


<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>
  • Die Zeilen 7–16 wurden der Datei [pom.xml] hinzugefügt. Dabei handelt es sich um die Abhängigkeiten zu JSF. Diese werden im Projekt EJB / Glassfish verwendet. Es ist zu beachten, dass sie kein <scope>-Tag enthalten. Sie haben daher standardmäßig den Geltungsbereich [compile]. Die Bibliothek JSF wird somit in das Archiv [war] des Webprojekts eingebunden.

Nachdem wir diese Abhängigkeiten zur Datei [pom.xml] hinzugefügt haben, kompilieren wir das Projekt, damit sie heruntergeladen werden.

4.3.3. Portierung des Projekts JSF / Glassfish auf das Projekt JSF / Tomcat

Wir kopieren den gesamten Code aus dem Projekt JSF / Glassfish in das Projekt JSF / Tomcat:

  • [1, 2, 3]: Kopieren der Webseiten vom alten Projekt in das neue,
  • [1, 2, 3]: Kopieren des Java-Codes vom alten Projekt in das neue. Es gibt Fehler. Das ist normal. Wir werden sie beheben,
  • In [1], auf der Registerkarte [Files] von NetBeans, wird ein Unterordner [resources] im Ordner [main] erstellt,
  • dadurch wird auf der Registerkarte „[Projects]“ der Zweig „[Other Sources] [3]“ erstellt,
  • [1, 2, 3]: Die Nachrichtendateien werden vom alten Projekt in das neue Projekt kopiert.

4.3.4. Änderungen am importierten Projekt

Wir haben darauf hingewiesen, dass der importierte Java-Code Fehler enthält. Sehen wir uns diese einmal an:

  • In [1] ist lediglich die Bean [Application] fehlerhaft,
  • bei [2] ist der Fehler ausschließlich auf die Schnittstelle [IMetierLocal] zurückzuführen, die nicht mehr existiert. Hier mag es verwundern, dass Zeile 20 nicht als Fehler markiert wird. Die Annotation @EJB verweist explizit auf EJB und wird hier erkannt. Dies liegt am Vorhandensein der Abhängigkeit [javaee-web-api-6.0] [3]. Java EE 6 hat eine Architektur eingeführt, die es ermöglicht, eine Webanwendung, die auf EJB ohne Remote-Schnittstelle basiert, auf Servern ohne EJB-Container bereitzustellen. Es reicht aus, wenn der Server die Abhängigkeit [javaee-web-api-6.0] bereitstellt. Man sieht nämlich, dass diese den Geltungsbereich [provided] [3] hat.

Die Abhängigkeit [javaee-web-api-6.0] werden wir hier nicht verwenden. Wir entfernen sie: [1]:

Dies führt zu neuen Fehlern [2]. Wir beginnen mit denen der Bean [Form]:

  • In [1] hängen die fehlerhaften Zeilen mit dem Fehlen des Pakets [javax] zusammen. Wir löschen sie alle: [2]. Die fehlerhaften Zeilen machten die Klasse [Form] zu einem Bean mit Sitzungsgültigkeit (Zeilen 18–20 von [1]). Außerdem wurde der Bean [Application] in Zeile 25 injiziert. Diese Informationen werden in die Konfigurationsdatei von JSF und [faces-config.xml] übernommen.

Kommen wir nun zur Bean [Application]:

Wir löschen alle fehlerhaften Zeilen aus [1] und ändern die Schnittstelle [IMetierLocal] in den Zeilen 13 und 21 in [IMetier]. In [2] gibt es keine Fehler mehr. In [1] haben wir die Zeilen 15–16 entfernt, die die Klasse [Application] zu einem Bean mit dem Geltungsbereich application. machten. Diese Information wird in die Konfigurationsdatei von JSF [faces-config.xml] übertragen. Außerdem haben wir Zeile 20 entfernt, die eine Referenz der Schicht [métier] in die Bean einfügte. Diese wird nun von Spring initialisiert. Die erforderliche Konfigurationsdatei liegt bereits vor; es handelt sich um die des Spring-/Métier-Projekts. Wir kopieren sie:

  • in [1, 2]. Wir kopieren die Spring-Konfigurationsdatei aus dem Projekt „Spring / Métier“ in das Projekt „Spring / JSF“,

In [3] ist das Ergebnis zu sehen.

Im Bean [Application] muss diese Konfigurationsdatei verwendet werden, um eine Referenz auf die Schicht [métier] zu erhalten. Dies geschieht in der Methode [init]:


package beans;

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

public class Application {

  // Geschäftsschicht
  private IMetier metier;
...

  public Application() {
  }

  @PostConstruct
  public void init() {
    try {
      // Instanzierung der Schicht [métier]
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
      metier = (IMetier) ctx.getBean("metier");
      // Ärzte und Kunden werden zwischengespeichert
...
    } catch (Throwable th) {
...
    }
...
  }
  • Zeile 20: Die Beans aus der Spring-Konfigurationsdatei werden instanziiert,
  • Zeile 21: Es wird eine Referenz auf das Geschäfts-Bean, also auf die Schicht [métier], angefordert.

Im Allgemeinen sollte die Instanziierung der Spring-Beans in der init-Methode des Beans mit Anwendungsbereich erfolgen. Es gibt eine weitere Methode, bei der die Instanziierung der Beans durch ein Spring-Servlet erfolgt. Dies erfordert eine Änderung der Datei [web.xml] und das Hinzufügen einer Abhängigkeit zum Artefakt [spring-web]. Wir haben dies hier nicht getan, um im Einklang mit den in den vorherigen Codebeispielen verwendeten Vorgehensweisen zu bleiben.

Wir haben die Annotationen in den Klassen [Application] und [Form] entfernt, die diese zu JSF-Beans machten. Diese Klassen müssen JSF-Beans bleiben. Anstelle der Annotationen verwenden wir nun die Konfigurationsdatei von JSF und [WEB-INF / faces.config.xml], um die Beans zu deklarieren.

Diese Datei sieht nun wie folgt aus:


<?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>
    <!-- die Nachrichtendatei -->
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
    <!-- das 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>
    <!-- das Formular-Bean -->
    <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>
  • Die Zeilen 10–19 konfigurieren die Meldungsdatei. Dies war die einzige Konfiguration, die wir im Projekt JSF / EJB hatten,
  • die Zeilen 21–35 deklarieren die Beans der Anwendung JSF. Dies war die Standardmethode bei JSF 1.x. JSF 2 führte Annotationen ein, aber die Methode von JSF und 1.x wird weiterhin unterstützt,
  • Zeilen 21–25: Deklarieren die Bean applicationBean,
  • Zeile 22: der Name der Bean. Man könnte versucht sein, den Namen „application“ zu verwenden. Dies sollte vermieden werden, da es sich um den Namen einer vordefinierten Bean von JSF handelt,
  • Zeile 23: der vollständige Name der Bean-Klasse,
  • Zeile 24: ihr Geltungsbereich,
  • Zeilen 27–35: definieren die Form-Bean,
  • Zeile 28: der Name der Bean,
  • Zeile 29: Der vollständige Name der Bean-Klasse,
  • Zeile 30: deren Gültigkeitsbereich,
  • Zeilen 31–34: definieren eine Eigenschaft der Klasse [beans.Form],
  • Zeile 32: Name der Eigenschaft. Die Klasse „[beans.Form]“ muss ein Feld mit diesem Namen und den dazugehörigen Setter haben,
  • Zeile 33: der Wert des Feldes. Hier handelt es sich um die Referenz auf die in Zeile 21 definierte Bean applicationBean. Hier erfolgt also die Injektion der Bean mit dem Geltungsbereich application in die Bean mit dem Geltungsbereich session, damit diese Zugriff auf die Daten des Geltungsbereichs application hat.

Wir haben oben erwähnt, dass das Feld [application] der Bean [beans.Form] über eine setter initialisiert werden soll. Es muss daher zur Klasse [beans.Form] hinzugefügt werden, falls es dort noch nicht vorhanden ist:


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

4.3.5. Testen der Anwendung

Unsere Anwendung ist nun fehlerfrei und bereit für Tests:

  • in [1], das korrigierte Projekt,
  • in [2] wird es erstellt,
  • in [3] wird sie ausgeführt. Die Dateien SGBD und MySQL müssen gestartet sein. Der Tomcat-Server wird dann gestartet ([4]), falls er noch nicht lief, und anschließend wird die Startseite der Anwendung angezeigt ([5]):

Ab diesem Punkt finden wir die untersuchte Anwendung wieder. Wir überlassen es dem Leser, zu überprüfen, ob sie funktioniert. Beenden wir nun die Anwendung:

  • in [1] wird die Anwendung entladen,
  • in [2] ist sie nicht mehr vorhanden.

Sehen wir uns nun die „ “-Protokolle von Tomcat an:

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

Die Zeilen 2 und 4 weisen auf eine Fehlfunktion beim Beenden der Anwendung hin. Zeile 4 deutet darauf hin, dass wahrscheinlich ein Speicherleck vorliegt. Tatsächlich tritt dieser Fehler auf, und nach einer Weile ist NetBeans nicht mehr nutzbar. Dieses Problem ist besonders ärgerlich, da NetBeans bei jeder neuen Ausführung des Projekts neu gestartet werden muss. Dieses Problem wurde bereits im Dokument „Einführung in Struts 2 anhand von Beispielen“ ([http://tahe.developpez.com/java/struts2]) beschrieben.

Im Internet finden sich zahlreiche Informationen zu diesem Fehler. Er tritt auf, wenn eine Anwendung wiederholt in Tomcat geladen und wieder entladen wird. Nach einer Weile erscheint der Fehler java.lang.OutOfMemoryError: PermGen space. Es scheint keine Lösung zu geben, um diesen Fehler zu vermeiden, wenn er, wie in diesem Fall, von JAR-Dateien von Drittanbietern verursacht wird. Man muss Tomcat dann neu starten, damit der Fehler verschwindet.

Man kann das Auftreten dieses Fehlers jedoch hinauszögern. Zunächst erhöht man den Speicherplatz, der übergelaufen ist.

  • in [1], man geht in die Eigenschaften des Tomcat-Servers,
  • in [2], auf der Registerkarte [Platform] legen wir den Wert für den Speicher fest, der überläuft. Hier haben wir 1 GB eingestellt, da wir über einen Gesamtspeicher von 8 GB verfügten. Bei einem kleineren Speicher kann man 512M (512 Megabyte) einstellen.

Anschließend wird der Treiber JDBC von MySQL in <tomcat>/lib kopiert, wobei <tomcat> das Installationsverzeichnis von Tomcat ist.

  • in [1]. In den Eigenschaften von Tomcat notieren wir das Installationsverzeichnis <tomcat>,
  • in <tomcat>/lib [2] wird ein aktueller Treiber JDBC anstelle von MySQL und [3] abgelegt.

Anschließend wird die Abhängigkeit des Projekts vom Treiber JDBC von MySQL und [4] entfernt.

Anschließend wird die Anwendung getestet. Es zeigt sich, dass die Anwendung wiederholt geladen und entladen werden kann. Die Probleme mit Speicherlecks sind jedoch nicht behoben. Sie treten lediglich später auf.

4.4. Fazit

Wir haben die Anwendung JSF / EJB / Glassfish auf eine Umgebung mit JSF / Spring / Tomcat portiert. Dies erfolgte im Wesentlichen durch Kopieren und Einfügen zwischen den beiden Projekten. Dies war möglich, da die Technologien Spring und EJB3 große Ähnlichkeiten aufweisen. EJB3 wurde nämlich entwickelt, nachdem sich Spring als leistungsfähiger als EJB2 erwiesen hatte. EJB3 hat dann die guten Ideen von Spring übernommen.

4.5. Tests mit Eclipse

  • In [1] werden die drei Spring-Projekte importiert,
  • in [2] wählt man den Test JUnit aus der Schicht [DAO] aus und führt ihn in [3] aus,
  • in [4]; der Test ist erfolgreich,
  • in [5] die Konsolenprotokolle.
  • in [6A] [6B] wird der Konsolen-Client der Schicht [métier] ausgeführt,
  • in [7] die resultierende Konsolenanzeige,
  • in [8] [9], das Webprojekt wird auf einem Tomcat-7-Server ausgeführt [10],
  • bis [11], wird die Startseite der Anwendung im internen Browser von Eclipse angezeigt.