Skip to content

6. Versione 2: Architettura OpenEJB / JPA

6.1. Introduzione ai principi del porting

Qui presentiamo i principi che regoleranno il porting di un'applicazione JPA / Spring / Hibernate verso un'applicazione JPA / OpenEJB / EclipseLink. La creazione dei progetti Maven verrà effettuata nel paragrafo 6.2.

6.1.1. Le due architetture

L'implementazione attuale con Spring / Hibernate

L'implementazione da realizzare con OpenEJB / EclipseLink

6.1.2. Le librerie dei progetti

  • i livelli [DAO] e [metier] non vengono più istanziati da Spring, ma dal contenitore OpenEJB.
  • Le librerie del contenitore Spring e la relativa configurazione vengono sostituite dalle librerie del contenitore OpenEJB e dalla relativa configurazione.
  • Le librerie del livello JPA / Hibernate vengono sostituite da quelle del livello JPA / EclipseLink

  • Il file [META-INF/persistence.xml] che configura il livello JPA diventa il seguente:
<?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="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
     <!-- entità JPA -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
     <!-- il fornitore JPA è EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
     <!-- proprietà del provider -->
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • riga 3: le transazioni in un contenitore EJB sono di tipo JTA (Java Transaction API). Con Spring erano di tipo RESOURCE_LOCAL.
  • riga 9: l'implementazione JPA utilizzata è EclipseLink
  • righe 5-7: le entità gestite dal livello JPA
  • righe 11-13: proprietà del provider EclipseLink
  • riga 12: ad ogni esecuzione, verranno create le tabelle

Le caratteristiche JDBC della fonte dati JTA utilizzata dal contenitore OpenEJB saranno specificate dal seguente file di configurazione [conf/openejb.conf]:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • riga 3: si utilizza l'ID Default JDBC Database quando si lavora con un contenitore OpenEJB incorporato (embedded) nell'applicazione stessa.
  • riga 5: utilizziamo un database MySQL [dbpam_eclipselink]

6.1.4. Implementazione del livello [DAO] tramite EJB

  • Le classi che implementano il livello [DAO] diventano EJB. Prendiamo ad esempio la classe [CotisationDao]:

L'interfaccia [ICotisationDao] nella versione Spring era la seguente:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
   // crea un nuovo contributo
  Cotisation create(Cotisation cotisation);
   // modifica un contributo esistente
  Cotisation edit(Cotisation cotisation);
   // eliminare un contributo esistente
  void destroy(Cotisation cotisation);
   // cercare un contributo specifico
  Cotisation find(Long id);
   // recuperare tutti gli oggetti «Contributo»
  List<Cotisation> findAll();

}

EJB implementerà questa stessa interfaccia in due forme diverse: una locale e una remota. L'interfaccia locale può essere utilizzata da un client in esecuzione nello stesso JVM, mentre l'interfaccia remota da un client in esecuzione in un altro JVM.

L'interfaccia locale:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}
  • riga 6: l'interfaccia [ICotisationDaoLocal] eredita dall'interfaccia [ICotisationDao] per riprenderne tutti i metodi. Non ne aggiunge di nuovi.
  • riga 5: l'annotazione @Local la rende un'interfaccia locale per l'interfaccia EJB che la implementerà.

L'interfaccia remota:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
  • riga 6: l'interfaccia [ICotisationDaoRemote] eredita dall'interfaccia [ICotisationDao] per riprenderne tutti i metodi. Non ne aggiunge di nuovi.
  • riga 5: l'annotazione @Remote la rende un'interfaccia remota per EJB, che la implementerà.

Il livello [DAO] è implementato da un EJB che implementa entrambe le interfacce (non è obbligatorio):

1
2
3
4
5
6
@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
  • riga 1: l'annotazione @Stateless che rende la classe un EJB
  • riga 2: l'annotazione @TransactionAttribute che fa sì che ogni metodo della classe venga eseguito all'interno di una transazione.
  • riga 5: l'annotazione @PersistenceContext che inietta nella classe [CotisationDao] l'EntityManager del livello JPA. È identica a quella presente nella versione Spring.

Quando viene utilizzata l’interfaccia locale del livello [DAO], il client di tale interfaccia viene eseguito nello stesso JVM.

Come illustrato sopra, i livelli [metier] e [DAO] scambiano oggetti per riferimento. Quando un livello modifica l’oggetto condiviso, l’altro livello rileva tale modifica.

Quando viene utilizzata l’interfaccia remota del livello [DAO], il client di tale interfaccia viene solitamente eseguito in un altro JVM.

Nell’esempio sopra riportato, i livelli [metier] e [DAO] scambiano oggetti per valore (serializzazione dell’oggetto scambiato). Quando un livello modifica un oggetto condiviso, l’altro livello rileva tale modifica solo se l’oggetto modificato gli viene rinviato.

6.1.5. Implementazione del livello [metier] tramite un EJB

  • Anche la classe che implementa il livello [metier] diventa a sua volta un EJB che implementa un'interfaccia locale e remota. L'interfaccia iniziale [IMetier] era la seguente:
package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // recuperare la busta paga
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // elenco dei dipendenti
  List<Employe> findAllEmployes();
}

Si creano un'interfaccia locale e un'interfaccia remota a partire dall'interfaccia precedente:

1
2
3
4
5
6
7
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{
}
1
2
3
4
5
6
7
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{
}

Il EJB del livello [metier] implementa queste due interfacce:

@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // Riferimento ai livelli locali [DAO]
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;
  • righe 1-2: definiscono un EJB in cui ogni metodo viene eseguito in una transazione.
  • riga 7: un riferimento all'interfaccia locale di EJB [CotisationDao].
  • riga 6: l'annotazione @EJB richiede che il contenitore EJB inserisca un riferimento all'interfaccia locale di EJB [CotisationDao].
  • righe 8-11: si ripete la stessa operazione per le interfacce locali di EJB, [EmployeDao] e [IndemniteDao].

Infine, quando EJB e [Metier] saranno istanziati, i campi delle righe 7, 9 e 11 saranno inizializzati con riferimenti alle interfacce locali dei tre EJB del livello [DAO]. Si ipotizza quindi che i livelli [metier] e [DAO] vengano eseguiti nello stesso JVM.

6.1.6. I client di EJB

Nello schema sopra riportato, per comunicare con il livello [metier], il livello [ui] deve ottenere un riferimento all’interfaccia remota del livello EJB dal livello [metier].

Nello schema sopra riportato, per comunicare con il livello [metier], il livello [ui] deve ottenere un riferimento all'interfaccia locale del livello EJB dal livello [metier]. Il metodo per ottenere tali riferimenti varia da un contenitore all’altro. Per il contenitore OpenEJB, è possibile procedere come segue:

Riferimento sull'interfaccia locale:

1
2
3
4
5
6
7
8
9
     // si configura il contenitore Open EJB incorporato
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // inizializzazione del contesto JNDI con le proprietà precedenti
    InitialContext initialContext = new InitialContext(properties);
     // istanziazione dei livelli DAO
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
  • righe 2-5: il contenitore OpenEJB viene inizializzato.
  • riga 5: si dispone di un contesto JNDI (Java Naming and Directory Interface) che consente di ottenere riferimenti ai EJB. Ogni EJB è identificato da un nome JNDI:
  • (continua)
    • per l’interfaccia locale si aggiunge «Local» al nome dell’EJB (righe 7-9)
    • per l’interfaccia remota si aggiunge «Remote» al nome dell’EJB

Con Java EE 5, queste regole variano a seconda del contenitore EJB. Si tratta di una difficoltà. Java EE 6 ha introdotto una notazione JNDI portabile su tutti i server delle applicazioni.

Il codice precedente recupera i riferimenti alle interfacce locali di EJB tramite i loro nomi JNDI. Abbiamo detto in precedenza che queste potevano essere ottenute anche tramite l’annotazione @EJB. Si potrebbe quindi voler scrivere:

@EJB
private IemployeDaoLocal employeDaoLocal ;

L'annotazione @EJB viene rispettata solo se appartiene a una classe caricata dal contenitore EJB. Questo sarà il caso, ad esempio, della classe [Metier]. Il codice sopra riportato, invece, appartiene a una classe console che non verrà caricata dal contenitore EJB. È quindi necessario utilizzare i nomi JNDI o EJB.

Di seguito, il codice per ottenere un riferimento all’interfaccia remota di EJB e [Metier]:

1
2
3
4
5
6
7
8
     // si configura il contenitore Open EJB incorporato
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // inizializzazione del contesto JNDI del contenitore EJB
    InitialContext initialContext = new InitialContext(properties);

     // istanziazione del livello business remoto
metier = (IMetierRemote) initialContext.lookup("MetierRemote");

6.2. Esercitazione pratica

Ci proponiamo di trasferire l'applicazione NetBeans Spring / Hibernate su un'architettura OpenEJB / EclipseLink.

L'attuale implementazione con Spring / Hibernate

L'implementazione da realizzare con OpenEJB / EclipseLink

Se non esiste, creare il database MySQL [dbpam_eclipselink]. Se esiste, eliminare tutte le tabelle. Creare una connessione NetBeans a questo database come descritto nel paragrafo 6.2.1.

6.2.2. Configurazione iniziale del progetto NetBeans

  • caricare il progetto Maven [mv-pam-spring-hibernate]
  • Creare un nuovo progetto Maven Java [mv-pam-openejb-eclipselink] [1]
  • nella scheda [Files] [2], creare una cartella [conf] [3] nella directory principale del progetto
  • inserire in questa cartella il seguente file [openejb.conf] [4]:
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • creare la cartella [src / main/ resources/ META-INF] [5]
  • inserirvi il file [persistence.xml] [6] seguente:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence              http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- il provider JPA è EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- entità Jpa -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- proprietà del provider EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • riga 12: si richiedono log dettagliati a EclipseLink,
  • riga 13: le tabelle verranno create all'istanziazione del livello JPA,
  • Aggiungere le librerie OpenEJB, EclipseLink e il driver JDBC di MySQL al file [pom.xml] del progetto:

<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-pam-openejb-eclipselink</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-pam-openejb-eclipselink</name>
  <url>http://maven.apache.org</url>

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

  <dependencies>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>openejb-core</artifactId>
      <version>4.0.0</version>
    </dependency>                
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
  
  <repositories>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>
  </repositories>
  
</project>
  • righe 18-22: la dipendenza OpenEJB,
  • righe 30-39: le dipendenze EclipseLink,
  • righe 41-44: la dipendenza del driver JDBC da MySQL

6.2.3. Porting del livello [DAO]

Effettueremo il porting del livello [DAO] copiando i pacchetti dal progetto [mv-pam-spring-hibernate] al progetto [mv-pam-openejb-eclipselink].

  • copiare i pacchetti [dao, exception, jpa]
 

Gli errori segnalati sopra derivano dal fatto che il livello [DAO] copiato utilizza Spring e che le librerie Spring non fanno più parte del progetto.

6.2.3.1. L'EJB [CotisationDao]

Creiamo le interfacce locale e remota del futuro EJB [CotisationDao]:

L'interfaccia locale ICotisationDaoLocal:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}

Per ottenere i pacchetti corretti import, eseguire [clic droit sur le code / Fix Imports].

L'interfaccia remota ICotisationDaoRemote:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}

Quindi modifichiamo la classe [CotisationDao] per trasformarla in una EJB:

...
import javax.persistence.PersistenceContext;
import jpa.Cotisation;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
...  

L'import generato da questa classe sul framework Spring scompare. Creare un [Clean and Build] del progetto:

In [1], non ci sono più errori relativi alla classe [CotisationDao].

6.2.3.2. I file EJB, [EmployeDao] e [IndemniteDao]

Si ripete la stessa procedura per gli altri elementi del livello [DAO]:

  • interfacce IEmployeDaoLocal e IEmployeDaoRemote derivate da IEmployeDao
  • EJB e EmployeDao che implementano queste due interfacce
  • interfacce IIndemniteDaoLocal, IIndemniteDaoRemote derivate da IIndemniteDao
  • EJB e IndemniteDao che implementano queste due interfacce

A questo punto, non ci sono più errori nel progetto [2].

6.2.3.3. La classe [PamException]

La classe [PamException] rimane invariata, tranne che per un dettaglio:

package exception;

import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class PamException extends RuntimeException {

   // codice di errore
  private int code;
...

È stata aggiunta la riga 5. Per ottenere i import corretti, modificare in [Fix imports].

Per comprendere l’annotazione della riga 5, occorre ricordare che ogni metodo dei EJB del nostro livello [DAO]:

  • viene eseguito in una transazione avviata e terminata dal contenitore EJB
  • genera un'eccezione di tipo [PamException] non appena si verifica un errore

Quando il livello [metier] chiama un metodo M del livello [DAO], tale chiamata viene intercettata dal contenitore EJB. È come se ci fosse una classe intermedia tra il livello [metier] e il livello [DAO], qui denominata [Proxy EJB], che intercetta tutte le chiamate dirette al livello [DAO]. Quando la chiamata al metodo M del livello [DAO] viene intercettata, il proxy EJB avvia una transazione e poi passa il controllo al metodo M del livello [DAO], che viene quindi eseguito all’interno di tale transazione. Il metodo M termina con o senza eccezioni.

  • Se il metodo M termina senza eccezioni, l’esecuzione torna al proxy EJB, che conclude la transazione convalidandola tramite un commit. Il flusso di esecuzione viene quindi restituito al metodo chiamante del livello [metier]
  • Se il metodo M termina con un'eccezione, l'esecuzione torna al proxy EJB, che conclude la transazione invalidandola tramite un rollback. Inoltre, incapsula tale eccezione in un tipo EJBException. Il flusso di esecuzione viene quindi restituito al metodo chiamante del livello [metier], che riceve quindi un EJBException. L’annotazione alla riga 5 sopra riportata impedisce tale incapsulamento. Il livello [metier] riceverà quindi un PamException. Inoltre, l’attributo rollback=true indica al proxy EJB che, quando riceve un PamException, deve invalidare la transazione.

6.2.3.4. Test del livello [DAO]

Il nostro layer [DAO], implementato da EJB, può essere testato. Iniziamo copiando il pacchetto [dao] da [Test Packages] del progetto [mv-pam-springhibernate] nel progetto attualmente in fase di sviluppo [1]:

Conserviamo solo il test [JUnitInitDB] che inizializza il database con alcuni dati [2]. Rinominiamo la classe [ JUnitInitDbLocal] in [3]. La classe [JUnitInitDBLocal] utilizzerà l'interfaccia locale di EJB del livello [DAO].

Modifichiamo innanzitutto la classe [JUnitInitDBLocal] nel modo seguente:

public class JUnitInitDBLocal {

  static private IEmployeDaoLocal employeDao = null;
  static private ICotisationDaoLocal cotisationDao = null;
  static private IIndemniteDaoLocal indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // si configura il contenitore Open EJB incorporato
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // inizializzazione del contesto JNDI con le proprietà precedenti
    InitialContext initialContext = new InitialContext(properties);
     // istanza dei livelli locali DAO
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
}

...
  • righe 3-5: riferimenti alle interfacce locali di EJB del livello [DAO]
  • riga 7: @BeforeClass annota il metodo eseguito all’avvio del test JUnit
  • righe 10-13: inizializzazione del contenitore OpenEJB. Questa inizializzazione è proprietaria e varia a seconda di ciascun contenitore EJB.
  • riga 13: è presente un contesto JNDI (Java Naming and Directory Interface) che consente di accedere ai EJB tramite nomi. Con OpenEJB, l’interfaccia locale di un EJB E è indicata da ELocal e l’interfaccia remota da ERemote.
  • righe 15-17: si richiede al contesto JNDI un riferimento alle interfacce locali di EJB e [EmployeDao, CotisationDao, IndemniteDao].

Si compila il progetto (Build), si avvia il server MySQL se necessario, si esegue il test JUnitInitDBLocal. Si ricorda che il file [persistence.xml] è stato configurato per ricreare le tabelle ad ogni esecuzione. Prima di eseguire il test, è preferibile eliminare eventuali tabelle dal database MySQL [dbpam_eclipselink].

  • in [1], nella scheda [Services], si eliminano le tabelle della connessione NetBeans stabilita al paragrafo 6.2.1.
  • in [2], il database [dbpam_eclipselink] non contiene più tabelle
  • in [3], il progetto viene compilato
  • in [4], viene eseguito il test JUnitInitDBLocal
  • in [5], il test è stato superato
  • in [6], si aggiorna la connessione a NetBeans
  • in [7], si vedono le 4 tabelle create dal livello JPA. Lo scopo del test era quello di riempirle. Si visualizza il contenuto di una di esse
  • in [8], il contenuto della tabella [EMPLOYES]

Il contenitore OpenEJB ha visualizzato dei log nella console:

Infos - PersistenceUnit(name=dbpam_eclipselinkPU, provider=org.eclipse.persistence.jpa.PersistenceProvider) - provider time 396ms
Infos - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao) --> Ejb(deployment-id=IndemniteDao)
Infos - existing thread singleton service in SystemInstance() org.apache.openejb.cdi.ThreadSingletonServiceImpl@624a240d
Infos - OpenWebBeans Container is starting...
Infos - Adding OpenWebBeansPlugin : [CdiPlugin]
Infos - All injection points were validated successfully.
Infos - OpenWebBeans Container has started, it took [70] ms.
Infos - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Deployed Application(path=D:\data\istia-1112\netbeans\glassfish\mv-pam\tmp\mv-pam-openejb-eclipselink\classpath.ear)
  • righe 2-3: i due nomi JNDI di EJB e [CotisationDaoLocal],
  • righe 4-5: i due nomi JNDI relativi a EJB e [CotisationDaoRemote],
  • righe 7-8: i due nomi JNDI relativi a EJB e [EmployeDaoLocal],
  • righe 9-10: i due nomi JNDI relativi a EJB e [EmployeDaoRemote],
  • righe 12-13: i due nomi JNDI relativi a EJB e [IndemniteDaoLocal],
  • righe 14-15: i due nomi JNDI di EJB e [EmployeDaoRemote].

Ripetiamo lo stesso test, utilizzando questa volta l'interfaccia remota di EJB.

In [1], la classe [JUnitInitDBLocal] è stata duplicata (copia/incolla) in [JUnitInitDBRemote]. In questa classe, sostituiamo le interfacce locali con quelle remote:

public class JUnitInitDBRemote {

  static private IEmployeDaoRemote employeDao = null;
  static private ICotisationDaoRemote cotisationDao = null;
  static private IIndemniteDaoRemote indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // si configura il contenitore Open EJB integrato
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // inizializzazione del contesto JNDI con le proprietà precedenti
    InitialContext initialContext = new InitialContext(properties);
     // istanziazione dei livelli remoti DAO
    employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
}

Una volta fatto ciò, è possibile eseguire la nuova classe di test. In precedenza, con la connessione NetBeans [dbpam_eclipselink], eliminare le tabelle dal database [dbpam_eclipselink].

 

Con la connessione NetBeans [dbpam_eclipselink], verificare che il database sia stato popolato.

6.2.4. Porting del livello [metier]

Effettueremo il trasferimento del livello [metier] copiando i pacchetti dal progetto [mv-pam-spring-hibernate] al progetto [mv-pam-openejb-eclipselink].

Gli errori segnalati sopra relativi a [1] derivano dal fatto che il livello [metier] copiato utilizza Spring e che le librerie Spring non fanno più parte del progetto.

6.2.4.1. EJB [Metier]

Seguiamo la stessa procedura descritta per EJB e [CotisationDao]. Per prima cosa creiamo in [2] le interfacce locale e remota del futuro EJB [Metier]. Entrambe derivano dall'interfaccia iniziale [IMetier].

1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{

}
1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{

}

Fatto ciò, in [3] modifichiamo la classe [Metier] affinché diventi una EJB:

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // riferimenti al livello locale [DAO]
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;

   // Ottenimento della busta paga
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
     // si recuperano le informazioni relative al dipendente
...
  • riga 1: l'annotazione @Stateless rende la classe una EJB
  • riga 2: ogni metodo della classe verrà eseguito all’interno di una transazione
  • riga 3: la classe EJB [Metier] implementa entrambe le interfacce locale e remota che abbiamo appena definito
  • riga 7: EJB [Metier] utilizzerà EJB [CotisationDao] tramite la sua interfaccia locale. Ciò significa che i livelli [metier] e [DAO] devono essere eseguiti nello stesso JVM.
  • riga 6: l'annotazione @EJB fa sì che il contenitore EJB inserisca autonomamente il riferimento nell'interfaccia locale di EJB [CotisationDao]. L'altro metodo che abbiamo riscontrato consiste nell'utilizzare un contesto JNDI.
  • righe 8-11: lo stesso meccanismo viene utilizzato per gli altri due EJB del livello [DAO].

6.2.4.2. Test del livello [metier]

Il nostro livello [metier], implementato da un EJB, può essere testato. Iniziamo copiando il pacchetto [metier] da [Test Packages] del progetto [mv-pam-spring-hibernate] nel progetto in fase di sviluppo [1]:

  • in [1], il risultato della copia
  • in [2], si elimina il primo test
  • in [3], il test rimanente viene rinominato [JUnitMetierLocal]

La classe [JUnitMetierLocal] diventa la seguente:

public class JUnitMetierLocal {

// livello applicativo locale
  static private IMetierLocal metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configurazione del contenitore Open EJB integrato
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // inizializzazione del contesto JNDI con le proprietà precedenti
    InitialContext initialContext = new InitialContext(properties);

     // istanziazione dei livelli DAO locali
    IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
     // istanziazione del livello business locale
    metier = (IMetierLocal) initialContext.lookup("MetierLocal");

     // si svuota il database
...
}
  • riga 4: un riferimento all'interfaccia locale di EJB [Metier]
  • righe 8-12: configurazione del contenitore OpenEJB identica a quella effettuata nel test del livello [DAO]
  • righe 15-19: si richiedono al contesto JNDI della riga 12, i riferimenti ai 3 EJB del livello [DAO] e all'EJB del livello [metier]. I EJB del livello [DAO] serviranno per inizializzare la base, mentre l'EJB del livello [metier] servirà per eseguire test di calcolo delle retribuzioni.

L'esecuzione del test [JUnitMetierLocal] fornisce il seguente risultato [1]:

In [2], si duplica [JUnitMetierLocal] in [JUnitMetierRemote] per testare questa volta l'interfaccia remota di EJB e [Metier]. Il codice di [JUnitMetierRemote] viene modificato per utilizzare questa interfaccia remota. Il resto rimane invariato.

public class JUnitMetierRemote {

   // strato di business remoto
  static private IMetierRemote metier;

  @BeforeClass
  public static void init() throws NamingException {
     // si configura il contenitore Open EJB incorporato
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // inizializzazione del contesto JNDI con le proprietà precedenti
    InitialContext initialContext = new InitialContext(properties);

     // istanziazione dei livelli remoti DAO
    IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
     // istanziazione del livello business remoto
    metier = (IMetierRemote) initialContext.lookup("MetierRemote");

     // si svuota il database
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // riempimento del database
    Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
    Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
    indemnite1=indemniteDao.create(indemnite1);
    indemnite2=indemniteDao.create(indemnite2);
    employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }
}
  • righe 4 e 19: si utilizza l'interfaccia remota di EJB [Metier].
  • righe 15-17: si utilizzano le interfacce remote del livello [DAO]
  • righe 34-35: poiché con le interfacce remote gli oggetti scambiati tra client e server vengono passati per valore, è necessario recuperare il risultato restituito dal metodo create(Indemnite i). Ciò non era necessario con le interfacce locali, dove gli oggetti vengono passati per riferimento.

Fatto ciò, è possibile compilare il progetto ed eseguire il test [JUnitMetierRemote]:

  

6.2.5. Porting del livello [console]

Effettueremo il porting del livello [console] copiando i pacchetti dal progetto [mv-pam-spring-hibernate] al progetto [mv-pam-openejb-eclipselink].

Gli errori segnalati sopra relativi a [1] derivano dal fatto che il livello [metier] copiato utilizza Spring e che le librerie Spring non fanno più parte del progetto. In [2], la classe [Main] viene rinominata [MainLocal]. Utilizzerà l’interfaccia locale di EJB [Metier].

Il codice della classe [MainLocal] viene modificato come segue:

  public static void main(String[] args) {
     // dati locali
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
...
     // Ci sono errori?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // Tutto a posto - è possibile richiedere la busta paga al livello [métier]
    IMetierLocal metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // si configura il contenitore Open EJB integrato
      Properties properties = new Properties();
      properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
       // inizializzazione del contesto JNDI con le proprietà precedenti
      InitialContext initialContext = new InitialContext(properties);
       // istanziazione del livello business locale
      metier = (IMetierLocal) initialContext.lookup("MetierLocal");
       // calcolo della busta paga
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.toString());
      return;
    }
     // visualizzazione dettagliata
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
....

Le modifiche si trovano nelle righe 13-25. È il modo in cui viene effettuato il riferimento al livello [metier] che cambia (righe 17-22). Non spieghiamo il nuovo codice, che è già stato illustrato negli esempi precedenti. Una volta apportate queste modifiche, il progetto non presenta più errori (cfr. [3]).

Configuriamo il progetto affinché venga eseguito con gli argomenti [1]:

Affinché l’applicazione da console funzioni correttamente, è necessario che il database contenga dei dati. A tal fine, occorre modificare il file [META-INF/persistence.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence              http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- il fornitore JPA è EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- entità Jpa -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- proprietà del provider EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <!--
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
      -->
    </properties>
  </persistence-unit>
</persistence>

La riga 14, che causava la ricreazione delle tabelle del database ad ogni esecuzione, viene commentata. È necessario ricompilare il progetto (Clean and Build) affinché questa modifica venga applicata. Una volta fatto ciò, è possibile eseguire il programma. Se tutto va bene, si ottiene un output della console simile al seguente:

.......
INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
INFO - Deployed Application(path=classpath.ear)
[EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse Persistence Services - 1.1.2.v20090612-r4475
[EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-metier-dao-openejb-eclipselink-0910/build/classes/-jpa login successful
Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé : 
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations : 
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités : 
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire : 
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

BUILD SUCCESSFUL (total time: 4 seconds)

Qui abbiamo utilizzato l’interfaccia locale del livello [metier]. Ora utilizziamo la sua interfaccia remota in una seconda classe console:

In [1], la classe [MainLocal] è stata duplicata in [MainRemote]. Il codice di [MainRemote] è stato modificato per utilizzare l'interfaccia remota del livello [metier]:

// Va bene: è possibile richiedere la busta paga al livello [metier]
    IMetierRemote metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // si configura il contenitore Open EJB integrato
...
       // istanziazione del livello business remoto
      metier = (IMetierRemote) initialContext.lookup("MetierRemote");
       // calcolo della busta paga
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
...
    } catch (Exception ex) {
...
    }

Le modifiche sono state apportate alle righe 2 e 8. Il progetto [2] è stato configurato per eseguire la classe [MainRemote]. La sua esecuzione produce gli stessi risultati di prima.

6.3. Conclusion

Abbiamo illustrato come migrare un'architettura Spring / Hibernate verso un'architettura OpenEJB / EclipseLink.

L'architettura Spring / Hibernate

L'architettura OpenEJB / EclipseLink

Il porting è stato possibile senza troppe difficoltà poiché l’applicazione iniziale era stata strutturata a livelli. È importante comprendere questo aspetto.