Skip to content

4. Applicazione di esempio – 02: rdvmedecins-jsf2-spring

Ora porteremo l'applicazione precedente su un ambiente Spring/Tomcat:

Si tratta davvero di un porting. Partiremo dall'applicazione precedente e la adatteremo al nuovo ambiente. Ci limiteremo a commentare le modifiche. Le modifiche principali sono tre:

  • il server non è più GlassFish ma Tomcat, un server leggero che non dispone di un contenitore EJB;
  • per sostituire gli EJB, useremo Spring, il principale concorrente di EJB [http://www.springsource.com/],
  • l'implementazione JPA utilizzata sarà Hibernate invece di EclipseLink.

Dato che faremo molti copia-incolla tra i vecchi e i nuovi progetti, terremo aperti i progetti precedenti in NetBeans:

  

L'utilizzo del framework Spring richiede determinate conoscenze, che è possibile trovare in [rif. 7] (vedi pagina 166).

4.1. I livelli [DAO] e [JPA]

4.1.1. Il progetto NetBeans

Stiamo creando un progetto Maven di tipo [Applicazione Java]:

  • in [1], il progetto creato,
  • in [2], lo stesso progetto con i [Pacchetti sorgente] e i [Pacchetti di test] rimossi, insieme alla dipendenza [junit-3.8.1].

La parte più difficile dei progetti Maven è trovare le dipendenze giuste. Per questo progetto Spring / JPA / Hibernate, sono le seguenti:


<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>
  • righe 18–29: per Hibernate,
  • righe 30–34: per il driver JDBC di MySQL,
  • righe 35–41: per il test JUnit,
  • righe 42–51: per il pool di connessioni Apache Commons DBCP. Un pool di connessioni è un insieme di connessioni aperte. Quando l’applicazione ha bisogno di una connessione, ne richiede una dal pool. Quando non ne ha più bisogno, la restituisce. Le connessioni vengono aperte all'avvio dell'applicazione e rimangono aperte per tutta la durata di vita dell'applicazione. Ciò evita il sovraccarico derivante dall'apertura e dalla chiusura ripetute delle connessioni. Questo tipo di pool esisteva in GlassFish, ma il suo utilizzo era trasparente per noi. Sarà così anche in questo caso, ma dobbiamo installarlo e configurarlo,
  • righe 52–75: per Spring.

Aggiungiamo queste dipendenze e compiliamo il progetto:

  • in [1], compiliamo il progetto, il che costringerà Maven a scaricare le dipendenze;
  • in [2], queste compaiono quindi nel ramo [Dependencies]. Ce ne sono moltissime, poiché i framework Hibernate e Spring stessi hanno numerose dipendenze. Ancora una volta, grazie a Maven, non dobbiamo preoccuparcene. Vengono scaricate automaticamente.

Ora che abbiamo le dipendenze, incolliamo il codice del progetto EJB dal livello [dao] nel progetto Spring nel livello [dao]:

  • in [1], copiamo nel progetto di origine,
  • in [2], incolliamo nel progetto di destinazione,
  • in [3], il risultato.

Una volta completata la copia, è necessario correggere gli errori.

4.1.2. Il pacchetto [exceptions]

La classe [RdvMedecinsExceptions] [1] presenta degli errori perché il pacchetto [javax] alla riga 4 non esiste più. Si tratta di un pacchetto specifico per EJB. L'errore alla riga 6 deriva da quello alla riga 4. Eliminiamo queste due righe. In questo modo gli errori vengono risolti [2].

4.1.3. Il pacchetto [jpa]

  • In [1], la classe [Creneau] è errata a causa dell'assenza del pacchetto validation alla riga [5]. Avremmo potuto aggiungere questo pacchetto alle dipendenze del progetto. Tuttavia, durante i test, Hibernate genera un'eccezione a causa di ciò. Poiché non è essenziale per la nostra applicazione, lo abbiamo rimosso. Per correggere la classe, è sufficiente eliminare tutte le righe errate [2]. Procediamo in questo modo per tutte le classi errate.

4.1.4. Il pacchetto [dao]

Ora ci troviamo al punto seguente:

  • in [1], i due pacchetti corretti,
  • in [2], il pacchetto [dao]. Poiché non ci sono più EJB, i concetti di interfacce remote e locali EJB non sono più applicabili. Li rimuoviamo [3].
  • In [1], gli errori nella classe [DaoJpa] hanno due cause:
  • l'importazione di un pacchetto relativo agli EJB (righe 6–8);
  • l'uso delle interfacce locali e remote che abbiamo appena rimosso.

Rimuoviamo le righe errate e utilizziamo l'interfaccia [IDao] al posto delle interfacce locale e remota [2].

Nel progetto EJB, la classe [DaoJpa] era un singleton e i suoi metodi venivano eseguiti all'interno di una transazione. Vedremo che la classe [DaoJpa] sarà un bean gestito da Spring. Per impostazione predefinita, ogni bean Spring è un singleton. Questo soddisfa la prima proprietà. La seconda si ottiene utilizzando l'annotazione @Transactional di Spring [3]:

Una volta fatto questo, il progetto non presenta più alcun errore [4].

4.1.5. Configurazione del livello [JPA]

Nel progetto EJB, abbiamo configurato il livello [JPA] utilizzando il file [persistence.xml]. Qui abbiamo un livello [JPA], quindi dobbiamo creare questo file. Nel progetto EJB, lo abbiamo generato utilizzando GlassFish. Qui, lo stiamo creando manualmente. Il motivo principale è che parte della configurazione del file [persistence.xml] viene migrata nel file di configurazione Spring stesso.

Creiamo il file [persistence.xml]:

con il seguente contenuto:


<?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>
  • Riga 3: Assegniamo un nome all'unità di persistenza,
  • riga 3: il tipo di transazione è RESOURCE_LOCAL. Nel progetto EJB era JTA per indicare che le transazioni erano gestite dal contenitore EJB. Il valore RESOURCE_LOCAL indica che l'applicazione gestisce le proprie transazioni. In questo caso ciò avverrà tramite Spring,
  • righe 4–7: i nomi completi delle quattro entità JPA. Questo è facoltativo perché Hibernate le cerca automaticamente nel ClassPath del progetto.

Questo è tutto. Il nome del provider JPA, le sue proprietà e le caratteristiche JDBC della fonte dati sono ora nel file di configurazione di Spring.

4.1.6. Il file di configurazione di Spring

Abbiamo accennato al fatto che la classe [DaoJpa] è un bean gestito da Spring. Ciò avviene tramite un file di configurazione. Questo file conterrà anche la configurazione dell'accesso al database e la gestione delle transazioni. Deve trovarsi nel ClassPath del progetto. Lo collochiamo nella cartella [Other sources]:

Il file [spring-config-dao.xml] è il seguente:


<?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">
 
  <!-- application layers -->
  <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>
 
  <!-- data source 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>
 
  <!-- transaction manager -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <!-- translation of exceptions -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
 
  <!-- persistence -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
 
</beans>

Questo è un file compatibile con Spring 2.x. Non abbiamo cercato di utilizzare le nuove funzionalità delle versioni 3.x.

  • Righe 2–4: il tag radice <beans> del file di configurazione. Non commenteremo i vari attributi di questo tag. Assicuratevi di copiare e incollare con attenzione, poiché un errore in uno qualsiasi di questi attributi può causare errori a volte difficili da comprendere.
  • Riga 7: il bean "dao" è un riferimento a un'istanza della classe [rdvmedecins.dao.DaoJpa]. Verrà creata una singola istanza (singleton) che implementerà il livello [dao] dell'applicazione.
  • Righe 24–29: viene definita una fonte dati. Essa fornisce il servizio "connection pool" menzionato in precedenza. Qui viene utilizzato il [DBCP] del progetto Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/],
  • righe 25–28: per stabilire connessioni con il database di destinazione, l'origine dati deve conoscere il driver JDBC in uso (riga 25), l'URL del database (riga 26), il nome utente di connessione e la relativa password (righe 27–28),
  • righe 10–21: configurano il livello JPA,
  • riga 10: definisce un bean [EntityManagerFactory] in grado di creare oggetti [EntityManager] per gestire i contesti di persistenza. La classe istanziata [LocalContainerEntityManagerFactoryBean] è fornita da Spring. Richiede una serie di parametri per istanziarsi, definiti nelle righe 11–20,
  • riga 11: l'origine dati da utilizzare per ottenere connessioni al DBMS. Si tratta dell'origine [DBCP] definita nelle righe 24–29,
  • righe 12–20: l'implementazione JPA da utilizzare,
  • riga 13: definisce Hibernate come l'implementazione JPA da utilizzare,
  • riga 14: il dialetto SQL che Hibernate deve utilizzare con il DBMS di destinazione, in questo caso MySQL5,
  • riga 16 (commentata): richiede che le istruzioni SQL eseguite da Hibernate vengano registrate nella console,
  • riga 17 (commentata): richiede che il database venga generato (drop e create) all'avvio dell'applicazione,
  • riga 32: specifica che le transazioni sono gestite utilizzando le annotazioni Java (avrebbero potuto essere dichiarate anche in spring-config.xml). Nello specifico, si tratta dell'annotazione @Transactional presente nella classe [DaoJpa],
  • righe 33–35: definiscono il gestore delle transazioni da utilizzare,
  • riga 33: il gestore delle transazioni è una classe fornita da Spring,
  • riga 34: il gestore delle transazioni di Spring deve conoscere l'EntityManagerFactory che gestisce il livello JPA. Si tratta di quella definita alle righe 10–21,
  • riga 41: definisce la classe che gestisce le annotazioni di persistenza di Spring,
  • Riga 38: definisce la classe Spring che gestisce, tra le altre cose, l'annotazione @Repository, la quale rende una classe così annotata idonea alla conversione delle eccezioni native del driver JDBC provenienti dal DBMS in eccezioni generiche Spring di tipo [DataAccessException]. Tale conversione incapsula l'eccezione JDBC nativa in un tipo [DataAccessException] che presenta diverse sottoclassi:

Image

Questa traduzione consente al programma client di gestire le eccezioni in modo generico indipendentemente dal DBMS di destinazione. Non abbiamo utilizzato l'annotazione @Repository nel nostro codice Java. Pertanto, la riga 38 non è necessaria. L'abbiamo lasciata solo a scopo informativo.

Abbiamo terminato con il file di configurazione Spring. È stato tratto dalla documentazione Spring. Adattarlo a varie situazioni spesso si riduce a due modifiche:

  • il database di destinazione: righe 24–29,
  • l'implementazione JPA: righe 12–20.

Quando il codice verrà eseguito, tutti i bean presenti nel file di configurazione verranno istanziati. Vedremo come.

4.1.7. La classe di test JUnit

Abbiamo testato il livello [DAO] del progetto EJB con un test JUnit. Faremo lo stesso per il livello [DAO] del progetto Spring:

  • in [1] e [2], copiando e incollando il test JUnit tra i due progetti,
  • in [3], il test importato mostra degli errori nel nuovo ambiente.

L'errore segnalato in [1] è che l'interfaccia remota dell'EJB non esiste più. Inoltre, il codice di inizializzazione per il campo [dao] alla riga 19 era una chiamata JNDI specifica per EJB (righe 25–28). Per istanziare il campo [dao] alla riga 19, dobbiamo utilizzare il file di configurazione Spring. Questo viene fatto come segue:

  • riga 21: il tipo di interfaccia è diventato [IDao],
  • riga 28: istanzia tutti i bean dichiarati nel file [spring-config-dao.xml], in particolare questo:

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • La riga 29 richiede un riferimento al bean con id="dao" dal contesto Spring della riga 28. Si ottiene quindi un riferimento al singleton [DaoJpa] (classe sopra) che Spring ha istanziato.

Le righe 28–29 costruiscono i seguenti blocchi (linee tratteggiate rosa):

Quando vengono eseguiti i test client JUnit, il livello [DAO] è stato istanziato. Possiamo quindi testarne i metodi. Si noti che non è necessario alcun server per eseguire questo test, a differenza del test EJB [DAO], che richiedeva il server Glassfish. Qui, tutto viene eseguito all'interno della stessa JVM.

Ora possiamo eseguire il test JUnit. Il server MySQL deve essere in esecuzione. I risultati sono i seguenti:

Il test JUnit è stato superato. Esaminiamo i log di test come abbiamo fatto durante il test 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]; root of factory hierarchy
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]]
  • righe 1-4: log di Spring,
  • righe 5-10: log di Hibernate,
  • riga 11: Spring riporta tutti i bean che ha istanziato. Per primo, vediamo il bean [dao],
  • righe 12 e seguenti: log dei test JUnit,
  • righe 60–65: vediamo chiaramente l'eccezione causata dall'aggiunta di un appuntamento già esistente nel database. Ricordiamo che con EJB non abbiamo riscontrato questa eccezione a causa di un problema di serializzazione.

Il livello [dao] è operativo. Ora costruiremo il livello [business].

4.2. Il livello [business]

Procediamo allo stesso modo del livello [DAO], copiando e incollando dal progetto EJB al progetto Spring.

4.2.1. Il progetto NetBeans

Stiamo creando un nuovo progetto Maven di tipo [Applicazione Java], eliminando tutto ciò che non vogliamo conservare [1]:

4.2.2. Dipendenze del progetto

Nell'architettura:

il livello [business] si basa sul livello [DAO]. Aggiungiamo quindi una dipendenza dal progetto precedente:

  • In [1] e [2], aggiungiamo una dipendenza dal progetto del livello [DAO];
  • in [3], questa dipendenza ha introdotto altre dipendenze, quelle del progetto del livello [DAO].
  • In [1] e [2], copiamo i sorgenti Java dal progetto EJB al progetto Spring,
  • in [3], i sorgenti importati contengono errori nel loro nuovo ambiente.

Iniziamo rimuovendo dal livello [business] le interfacce remote e locali che non esistono più [4]:

  • In [5], gli errori nella classe [Business] hanno diverse cause:
    • l'uso del pacchetto [javax.ejb], che non esiste più;
    • l'uso dell'interfaccia [IDaoLocal], che non esiste più;
    • l'uso delle interfacce [IMetierRemote] e [IMetierLocal], che non esistono più.

Eliminiamo

  • eliminiamo tutte le righe errate relative al pacchetto [javax.ejb],
  • sostituiamo l'interfaccia [IDaoLocal] con l'interfaccia [IDao],
  • sostituiamo le interfacce [IMetierRemote] e [IMetierLocal] con l'interfaccia [IMetier].
  • In [6], con la classe corretta come descritto,
  • in [7] non ci sono più errori.

Abbiamo rimosso i riferimenti agli EJB, ma ora dobbiamo recuperare le loro proprietà:

 
  • riga 22: avevamo un singleton. Questo comportamento sarà ottenuto rendendo la classe un bean gestito da Spring,
  • riga 23: ogni metodo veniva eseguito all'interno di una transazione. Questo sarà ottenuto utilizzando l'annotazione Spring @Transactional,
  • righe 27–28: il riferimento al livello [DAO] veniva ottenuto tramite l'iniezione del contenitore EJB. Useremo l'iniezione Spring.

Il codice per la classe [Metier] nel progetto Spring si evolve quindi come segue:

Questo è tutto per quanto riguarda il codice Java. Il resto avviene nel file di configurazione di Spring.

4.2.3. Il file di configurazione Spring

Copiamo il file di configurazione Spring dal progetto del livello [DAO] nel progetto del livello [Business]. Iniziamo creando il ramo [Other Resources] nel progetto del livello [Business] se non esiste:

  • In [1], nella scheda [Files], creare una sottocartella all'interno della cartella [main],
  • in [2], dovrebbe essere denominata [resources],
  • in [3], nella scheda [Progetti], è stato creato il ramo [Altre fonti].

Ora possiamo copiare e incollare il file di configurazione Spring:

  • in [1], copiare il file dal progetto [DAO] nel progetto [business] [2],
  • in [3], il file copiato.

Il file di configurazione che è stato copiato configura il livello [DAO]. Aggiungiamo un bean per configurare il livello [business]:

1
2
3
4
5
  <!-- application layers -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • riga 2: il bean del livello [DAO],
  • righe 3–5: il bean del livello [business],
  • riga 3: il bean si chiama métier (attributo id) ed è un'istanza della classe [rdvmedecins.metier.service.Metier] (attributo class). Questo bean verrà istanziato come gli altri all'avvio dell'applicazione.

Rivediamo il codice del bean [rdvmedecins.metier.service.Metier]:


package rdvmedecins.metier.service;
 
...
 
public class Metier implements IMetier, Serializable {
 
  // dao layer
  private IDao dao;
 
  public Metier() {
}
  • Riga 8: Il campo [dao] verrà istanziato da Spring contemporaneamente al business bean. Torniamo alla definizione di questo bean nel file di configurazione di Spring:
1
2
3
4
5
  <!-- application layers -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • Riga 4: Il tag <property> viene utilizzato per inizializzare i campi del bean istanziato. Il nome del campo è specificato dall'attributo name. Pertanto, verrà istanziato il campo dao della classe [rdvmedecins.metier.service.Metier]. Ciò avviene tramite un metodo setDao, che deve esistere. Il valore assegnatogli è quello dell'attributo ref. In questo caso, tale valore è il riferimento al bean dao della riga 2.

In parole più semplici, nel codice:


package rdvmedecins.metier.service;
 
...
 
public class Metier implements IMetier, Serializable {
 
  // dao layer
  private IDao dao;
 
  public Metier() {
}

Il campo dao alla riga 19 verrà inizializzato da Spring con un riferimento al livello [dao]. Questo è proprio ciò che volevamo. Il campo dao verrà inizializzato da Spring tramite un setter che dobbiamo aggiungere:


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

Rinominiamo il file di configurazione Spring per riflettere le modifiche:

Ora siamo pronti per eseguire un test. Useremo il test da console utilizzato per testare l'EJB [Business].

4.2.4. Test del livello [business]

Il test verrà eseguito con la seguente architettura:

Copiamo il test della console dal progetto EJB nel progetto Spring:

  • In [1] e [2], copia e incolla tra i due progetti;
  • in [3], il codice importato contiene degli errori.
 

Il codice importato contiene due tipi di errori:

  • riga 13: l'interfaccia [IMetierRemote] è stata sostituita dall'interfaccia [IMetier],
  • righe 24–27: il livello [business] non viene più istanziato tramite una chiamata JNDI, ma istanziando i bean dal file di configurazione Spring.

Corregiamo questi due problemi:

  • riga 22: viene utilizzato il file [spring-config-metier-dao.xml]. Tutti i bean presenti in questo file vengono quindi istanziati. Tra questi vi sono i seguenti:

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

Questi due bean rappresentano i livelli [DAO] e [business] dell'architettura di test:

Una volta fatto questo, il test può essere eseguito:

  

I log dei test sono i seguenti:

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]; root of factory hierarchy
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]]
  • Righe 1–4: log di Spring e Hibernate,
  • riga 5: bean istanziati da Spring. Notare i bean DAO e di business,
  • righe 6–53: i log di test. Corrispondono ai risultati ottenuti dal test del progetto EJB. Rimandiamo il lettore ai commenti su quel test (sezione 3.5.3).

Abbiamo realizzato il livello [business]. Passiamo ora al livello finale, il livello [web].

4.3. Il livello [web]

Per creare il livello [web], procederemo allo stesso modo degli altri due livelli, copiando e incollando dal livello [web] del progetto EJB.

4.3.1. Il progetto NetBeans

Per prima cosa, creiamo un progetto web:

  • in [1], creare un nuovo progetto,
  • in [2], un progetto Maven di tipo [Web Application],
  • in [3], gli diamo un nome,
  • in [4], questa volta scegliamo il server Tomcat invece di GlassFish, che era stato utilizzato per il progetto EJB,
  • in [5], il progetto risultante,
  • in [6], il progetto dopo aver rimosso [index.jsp] e il pacchetto [Source Packages].

4.3.2. Dipendenze del progetto

Diamo un'occhiata all'architettura del progetto:

Il livello [web] dipende dai livelli [business] [DAO] [JPA]. Questi fanno parte dei due progetti che abbiamo appena realizzato. Di conseguenza, esiste una dipendenza da ciascuno di questi progetti:

  • in [1], aggiungiamo la dipendenza dal progetto Spring / business,
  • in [2], il progetto Spring / business è stato aggiunto. Poiché esso stesso aveva una dipendenza dal progetto Spring / DAO / JPA, quest'ultimo è stato automaticamente aggiunto alle dipendenze [3].

Torniamo alla struttura della nostra applicazione:

Il livello web è un livello JSF. Abbiamo quindi bisogno delle librerie Java Server Faces. Il server Tomcat non le possiede. La dipendenza non avrà quindi un ambito [provided], come nel caso del server Glassfish, ma un ambito [compile], che è l'ambito predefinito quando non viene specificato alcun ambito.

Aggiungiamo queste dipendenze direttamente nel file [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>
  • Le righe 7–16 sono state aggiunte al file [pom.xml]. Si tratta delle dipendenze per JSF. Sono quelle utilizzate nel progetto EJB/GlassFish. Si noti che non hanno il tag <scope>. Pertanto, hanno l'ambito [compile] per impostazione predefinita. La libreria JSF sarà quindi incorporata nell'archivio [war] del progetto web.

Dopo aver aggiunto queste dipendenze al file [pom.xml], compiliamo il progetto in modo che vengano scaricate.

4.3.3. Portare il progetto JSF/Glassfish al progetto JSF/Tomcat

Copiamo tutto il codice dal progetto JSF/Glassfish al progetto JSF/Tomcat:

  • [1, 2, 3]: copiamo le pagine web dal vecchio progetto a quello nuovo,
  • [1, 2, 3]: copia il codice Java dal vecchio progetto a quello nuovo. Ci sono degli errori. È normale. Li sistemeremo,
  • in [1], nella scheda [File] di NetBeans, creare una sottocartella [resources] all'interno della cartella [main],
  • questo crea il ramo [Altre sorgenti] nella scheda [Progetti] [3],
  • [1, 2, 3]: Copiare i file dei messaggi dal vecchio progetto al nuovo progetto.

4.3.4. Modifiche al progetto importato

Abbiamo notato che il codice Java importato conteneva degli errori. Esaminiamoli:

  • In [1], solo il bean [Application] è errato;
  • in [2], l'errore è dovuto esclusivamente all'interfaccia [IMetierLocal], che non esiste più. In questo caso, potrebbe sorprendere il fatto che la riga 20 non sia contrassegnata come errore. L'annotazione @EJB fa esplicito riferimento agli EJB ed è riconosciuta in questo contesto. Ciò è dovuto alla presenza della dipendenza [javaee-web-api-6.0] [3]. Java EE 6 ha introdotto un'architettura che consente di distribuire un'applicazione web basata su EJB senza un'interfaccia remota su server che non dispongono di un contenitore EJB. Il server deve semplicemente fornire la dipendenza [javaee-web-api-6.0]. Possiamo vedere che questa dipendenza ha l'ambito [provided] [3].

Qui non useremo la dipendenza [javaee-web-api-6.0]. La rimuoviamo [1]:

Questo causa nuovi errori [2]. Cominciamo con quelli nel bean [Form]:

  • In [1], le righe errate sono legate alla perdita del pacchetto [javax]. Le rimuoviamo tutte [2]. Le righe errate rendevano la classe [Form] un bean con ambito di sessione (righe 18–20 di [1]). Inoltre, il bean [Application] veniva iniettato alla riga 25. Queste informazioni verranno spostate nel file di configurazione JSF [faces-config.xml].

Passiamo ora al bean [Application]:

Rimuoviamo tutte le righe errate da [1] e modifichiamo l'interfaccia [IMetierLocal] nelle righe 13 e 21 in [IMetier]. In [2] non ci sono più errori. In [1] abbiamo rimosso le righe 15–16, che rendevano la classe [Application] un bean a livello di applicazione. Queste informazioni verranno spostate nel file di configurazione JSF [faces-config.xml]. Abbiamo anche rimosso la riga 20, che iniettava un riferimento dal livello [business] nel bean. Ora, questo verrà inizializzato da Spring. Abbiamo già il file di configurazione necessario; è quello del progetto Spring / Business. Lo copiamo:

  • in [1, 2], copiamo il file di configurazione Spring dal progetto Spring / Business al progetto Spring / JSF,

In [3], il risultato.

Nel bean [Application], dobbiamo utilizzare questo file di configurazione per ottenere un riferimento al livello [business]. Ciò avviene nel suo metodo [init]:


package beans;
 
...
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class Application {
 
  // business layer
  private IMetier metier;
...
 
  public Application() {
  }
 
  @PostConstruct
  public void init() {
    try {
      // instantiation layer [business]
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
      metier = (IMetier) ctx.getBean("metier");
      // caching doctors and customers
...
    } catch (Throwable th) {
...
    }
...
  }
  • riga 20: vengono istanziati i bean dal file di configurazione Spring,
  • riga 21: viene richiesto un riferimento al business bean, ovvero nel livello [business].

In generale, i bean Spring dovrebbero essere istanziati nel metodo init del bean con ambito applicativo. Esiste un altro metodo in cui i bean vengono istanziati da un servlet Spring. Ciò comporta la modifica del file [web.xml] e l'aggiunta di una dipendenza dall'artefatto [spring-web]. In questo caso non l'abbiamo fatto per mantenere la coerenza con quanto utilizzato nel codice precedente.

Abbiamo rimosso le annotazioni nelle classi [Application] e [Form] che le rendevano bean JSF. Queste classi devono rimanere bean JSF. Al posto delle annotazioni, ora utilizziamo il file di configurazione JSF [WEB-INF/faces.config.xml] per dichiarare i bean.

Il file ora ha questo aspetto:


<?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>
    <!-- message file -->
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
    <!-- the applicationBean bean -->
    <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>
    <!-- the bean form -->
    <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>
  • Le righe 10–19 configurano il file dei messaggi. Questa era l'unica configurazione presente nel progetto JSF/EJB,
  • Le righe 21–35 dichiarano i bean dell'applicazione JSF. Questo era l'approccio standard con JSF 1.x. JSF 2 ha introdotto le annotazioni, ma il metodo di JSF 1.x è ancora supportato,
  • Righe 21–25: dichiarano l'applicationBean,
  • riga 22: il nome del bean. Potresti essere tentato di usare il nome “application”. Evitalo, poiché è il nome di un bean JSF predefinito,
  • riga 23: il nome completo della classe del bean,
  • riga 24: il suo ambito,
  • righe 27–35: definiscono il bean del modulo,
  • riga 28: il nome del bean,
  • riga 29: il nome completo della classe del bean,
  • riga 30: il suo ambito,
  • righe 31–34: definiscono una proprietà della classe [beans.Form],
  • riga 32: nome della proprietà. La classe [beans.Form] deve avere un campo con questo nome e il setter corrispondente,
  • riga 33: il valore del campo. Qui, è il riferimento all'applicationBean definito alla riga 21. Stiamo quindi iniettando il bean con ambito applicativo nel bean con ambito di sessione in modo che quest'ultimo abbia accesso ai dati con ambito applicativo.

Abbiamo accennato in precedenza che il campo [application] del bean [beans.Form] sarebbe stato inizializzato tramite un setter. Dobbiamo quindi aggiungerlo alla classe [beans.Form] se non esiste già:


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

4.3.5. Test dell'applicazione

La nostra applicazione è ora priva di errori e pronta per essere testata:

  • in [1], il progetto corretto,
  • in [2], lo compiliamo,
  • in [3], lo eseguiamo. Il DBMS MySQL deve essere in esecuzione. Il server Tomcat si avvierà quindi [4] se non era già in esecuzione, e verrà visualizzata la home page dell'applicazione [5]:

Da lì, vediamo l'applicazione che abbiamo studiato. Lasceremo che sia il lettore a verificare che funzioni. Ora fermiamo l'applicazione:

  • in [1], scarichiamo l'applicazione,
  • in [2], non c'è più.

Ora diamo un'occhiata ai log di Tomcat in :

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

Le righe 2 e 4 indicano un malfunzionamento all'arresto dell'applicazione. La riga 4 indica che esiste un probabile rischio di perdita di memoria. In effetti, ciò si verifica e, dopo un po', NetBeans diventa inutilizzabile. Questo problema è particolarmente frustrante perché NetBeans deve essere riavviato ogni volta che si esegue il progetto. Questo problema è già stato riscontrato nel documento "Introduzione a Struts 2 con esempi" [http://tahe.developpez.com/java/struts2].

Online sono disponibili molte informazioni su questo errore. Si verifica quando si carica e scarica ripetutamente un'applicazione da Tomcat. Dopo un po' compare l'errore java.lang.OutOfMemoryError: PermGen space. Sembra che non ci sia una soluzione per prevenire questo errore quando deriva da librerie di terze parti (JAR), come nel caso in questione. È quindi necessario riavviare Tomcat per risolverlo.

Tuttavia, è possibile ritardare il verificarsi di questo errore. Innanzitutto, aumentare lo spazio di memoria che è andato in overflow.

  • In [1], accedere alle proprietà del server Tomcat,
  • in [2], nella scheda [Platform], imposta il valore per l'overflow di memoria. Qui, lo abbiamo impostato a 1 GB perché avevamo un totale di 8 GB di memoria. Puoi impostarlo a 512 MB (512 megabyte) con una quantità di memoria inferiore.

Successivamente, colloca il driver JDBC di MySQL in <tomcat>/lib, dove <tomcat> è la directory di installazione di Tomcat.

  • In [1], nelle proprietà di Tomcat, prendere nota della directory di installazione <tomcat>,
  • in <tomcat>/lib [2], inserisci un driver JDBC MySQL recente [3].

Successivamente, rimuovere la dipendenza del progetto dal driver JDBC di MySQL [4].

Una volta fatto ciò, testare l'applicazione. Si noterà che è possibile caricare e scaricare l'applicazione ripetutamente. Tuttavia, i problemi di perdita di memoria non sono risolti; semplicemente si verificano in un secondo momento.

4.4. Conclusione

Abbiamo migrato l'applicazione JSF/EJB/GlassFish in un ambiente JSF/Spring/Tomcat. Ciò è stato fatto principalmente tramite copia e incolla tra i due progetti. Ciò è stato possibile perché Spring ed EJB3 condividono molte somiglianze. EJB3 è stato, infatti, creato dopo che Spring si è dimostrato più efficiente di EJB2. EJB3 ha quindi incorporato le migliori caratteristiche di Spring.

4.5. Test con Eclipse

  • In [1], importare i tre progetti Spring,
  • in [2], selezionare il test JUnit dal livello [DAO] ed eseguirlo in [3],
  • in [4], il test ha esito positivo,
  • in [5], la console registra un messaggio.
  • In [6A] [6B], esegui il client console per il livello [business],
  • in [7], l'output della console risultante,
  • In [8] [9], eseguiamo il progetto web su un server Tomcat 7 [10],
  • in [11], la home page dell'applicazione viene visualizzata nel browser interno di Eclipse.