Skip to content

6. Versione 2: Architettura OpenEJB / JPA

6.1. Introduzione ai principi di porting

Qui presentiamo i principi che regoleranno il porting di un'applicazione JPA / Spring / Hibernate in un'applicazione JPA / OpenEJB / EclipseLink. Attenderemo fino alla Sezione 6.2 per creare i progetti Maven.

6.1.1. Le due architetture

L'attuale implementazione con Spring / Hibernate

L'implementazione da realizzare con OpenEJB / EclipseLink

6.1.2. Librerie del progetto

  • I livelli [DAO] e [business] non vengono più istanziati da Spring. Vengono istanziati dal contenitore OpenEJB.
  • Le librerie del contenitore Spring e la relativa configurazione sono sostituite dalle librerie del contenitore OpenEJB e dalla relativa configurazione.
  • Le librerie del livello JPA / Hibernate sono 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">
     <!-- entities JPA -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
     <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
     <!-- provider properties -->
    <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: Entità gestite dal livello JPA
  • righe 11–13: proprietà del provider EclipseLink
  • riga 12: le tabelle verranno create ad ogni esecuzione

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: utilizziamo l'ID "Default JDBC Database" quando lavoriamo con un contenitore OpenEJB incorporato all'interno dell'applicazione stessa.
  • Riga 5: utilizziamo un database MySQL [dbpam_eclipselink]

6.1.4. Implementazione del livello [DAO] utilizzando gli EJB

  • Le classi che implementano il livello [DAO] diventano EJB. Prendiamo l'esempio della classe [CotisationDao]:

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

package dao;

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

public interface ICotisationDao {
   // create a new contribution
  Cotisation create(Cotisation cotisation);
   // modify an existing contribution
  Cotisation edit(Cotisation cotisation);
   // delete an existing contribution
  void destroy(Cotisation cotisation);
   // search for a specific contribution
  Cotisation find(Long id);
   // get all objects Contribution
  List<Cotisation> findAll();

}

L'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 nella stessa JVM, mentre l'interfaccia remota può essere utilizzata da un client in esecuzione in una JVM diversa.

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 adottare tutti i suoi metodi. Non ne aggiunge di nuovi.
  • Riga 5: l'annotazione @Local la rende un'interfaccia locale per l'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 adottare tutti i suoi metodi. Non ne aggiunge di nuovi.
  • Riga 5: L'annotazione @Remote la rende un'interfaccia remota per l'EJB che la implementerà.

Il livello [DAO] è implementato da un EJB che implementa entrambe le interfacce (questo 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 garantisce che ogni metodo della classe venga eseguito all'interno di una transazione.
  • riga 5: l'annotazione @PersistenceContext, che inietta l'EntityManager del livello JPA nella classe [CotisationDao]. È identica a quella che avevamo nella versione Spring.

Quando viene utilizzata l'interfaccia locale del livello [DAO], il client di questa interfaccia viene eseguito nella stessa JVM.

In precedenza, i livelli [business] e [DAO] si scambiano oggetti per riferimento. Quando un livello modifica l'oggetto condiviso, l'altro livello rileva tale modifica.

Quando si utilizza l'interfaccia remota del livello [DAO], il client di tale interfaccia viene solitamente eseguito in un'altra JVM.

Nel diagramma sopra, i livelli [business] e [DAO] scambiano oggetti per valore (serializzazione dell'oggetto scambiato). Quando un livello modifica un oggetto condiviso, l'altro livello vede questa modifica solo se l'oggetto modificato gli viene restituito.

6.1.5. Implementazione del livello [business] utilizzando un EJB

  • La classe che implementa il livello [business] diventa anche un EJB che implementa un'interfaccia locale e una remota. L'interfaccia [IMetier] iniziale era la seguente:
package metier;

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

public interface IMetier {
   // get your payslip
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // list of employees
  List<Employe> findAllEmployes();
}

Creiamo un'interfaccia locale e un'interfaccia remota basate sull'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{
}

L'EJB nel livello [business] implementa queste due interfacce:

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

   // reference to local [DAO] layers
  @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 all'interno di una transazione.
  • riga 7: un riferimento all'interfaccia locale dell'EJB [CotisationDao].
  • riga 6: l'annotazione @EJB indica al contenitore EJB di iniettare un riferimento all'interfaccia locale dell'EJB [CotisationDao].
  • Righe 8–11: procediamo allo stesso modo per le interfacce locali degli EJB [EmployeeDao] e [CompensationDao].

In definitiva, quando l'EJB [Metier] viene istanziato, i campi nelle righe 7, 9 e 11 verranno inizializzati con riferimenti alle interfacce locali dei tre EJB nel livello [DAO]. Stiamo quindi supponendo qui che i livelli [business] e [DAO] verranno eseguiti nella stessa JVM.

6.1.6. Client EJB

Nel diagramma sopra riportato, per comunicare con il livello [business], il livello [ui] deve ottenere un riferimento all'interfaccia remota dell'EJB nel livello [business].

Nel diagramma sopra riportato, per comunicare con il livello [business], il livello [UI] deve ottenere un riferimento all'interfaccia locale dell'EJB del livello [business]. Il metodo per ottenere questi riferimenti varia da un contenitore all'altro. Per il contenitore OpenEJB, è possibile procedere come segue:

Riferimento all'interfaccia locale:

1
2
3
4
5
6
7
8
9
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation layers 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: Abbiamo un contesto JNDI (Java Naming and Directory Interface) che ci permette di ottenere riferimenti agli EJB. Ogni EJB è identificato da un nome JNDI:
  • (continua)
    • per l'interfaccia locale, aggiungere "Local" al nome dell'EJB (righe 7-9)
    • Per l'interfaccia remota, aggiungiamo "Remote" al nome dell'EJB

Con Java EE 5, queste regole variano a seconda del contenitore EJB. Questa è una sfida. Java EE 6 ha introdotto una notazione JNDI che è portabile su tutti i server applicativi.

Il codice precedente recupera i riferimenti alle interfacce locali degli EJB tramite i loro nomi JNDI. Abbiamo già accennato al fatto che questi potrebbero essere ottenuti anche tramite l'annotazione @EJB. Potremmo quindi scrivere:

@EJB
private IemployeDaoLocal employeDaoLocal ;

L'annotazione @EJB viene presa in considerazione solo se appartiene a una classe caricata dal contenitore EJB. Questo sarà il caso, ad esempio, della classe [Metier]. Il codice sopra riportato, tuttavia, appartiene a una classe console che non verrà caricata dal contenitore EJB. Siamo quindi costretti a utilizzare i nomi JNDI degli EJB.

Di seguito è riportato il codice per ottenere un riferimento all'interfaccia remota dell'EJB [Metier]:

1
2
3
4
5
6
7
8
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // context initialization JNDI of container EJB
    InitialContext initialContext = new InitialContext(properties);

     // remote business layer instantiation
metier = (IMetierRemote) initialContext.lookup("MetierRemote");

6.2. Esercizio pratico

Proponiamo di migrare l'applicazione NetBeans Spring/Hibernate verso 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 sue tabelle. Creare una connessione NetBeans a questo database come descritto nella sezione 6.2.1.

6.2.2. Configurazione iniziale del progetto NetBeans

  • Caricare il progetto Maven [mv-pam-spring-hibernate]
  • Creare un nuovo progetto Java Maven [mv-pam-openejb-eclipselink] [1]
  • Nella scheda [Files] [2], creare una cartella [conf] [3] nella directory principale del progetto
  • Inserisci il seguente file [openejb.conf] [4] in questa cartella:
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>
  • Crea la cartella [src/main/resources/META-INF] [5]
  • Inserisci al suo interno il seguente file [persistence.xml] [6]:

<?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">
    <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- jpa entities -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- properties 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: richiediamo log dettagliati da EclipseLink,
  • riga 13: le tabelle verranno create all'istanziazione del livello JPA,
  • Aggiungi le librerie OpenEJB ed EclipseLink, oltre al 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 di MySQL

6.2.3. Porting del livello [DAO]

Eseguiremo 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 sono dovuti al fatto che il livello [DAO] copiato utilizza Spring e le librerie Spring non fanno più parte del progetto.

6.2.3.1. L'EJB [CotisationDao]

Creiamo le interfacce locali e remote per il 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 garantire il corretto import dei pacchetti, clicca con il tasto destro del mouse sul codice e seleziona [Correggi importazioni].

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 un EJB:

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

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

  @PersistenceContext
  private EntityManager em;
...  

L'importazione che questa classe effettuava sul framework Spring scompare. Eseguire un [Clean and Build] sul progetto:

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

6.2.3.2. Gli EJB [EmployeDao] e [IndemniteDao]

Ripetiamo lo stesso processo per gli altri elementi del livello [DAO]:

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

Una volta fatto questo, non ci sono più errori nel progetto [2].

6.2.3.3. La classe [PamException]

La classe [PamException] rimane la stessa di prima, con una piccola modifica:

package exception;

import javax.ejb.ApplicationException;

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

   // error code
  private int code;
...

È stata aggiunta la riga 5. Per ottenere le importazioni corrette, fare clic su [Correggi importazioni].

Per comprendere l'annotazione alla riga 5, ricorda che ogni metodo negli EJB del nostro livello [DAO]:

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

Quando il livello [business] chiama un metodo M del livello [DAO], questa chiamata viene intercettata dal contenitore EJB. È come se ci fosse una classe intermedia tra il livello [business] e il livello [DAO] — qui chiamata [EJB Proxy] — che intercetta tutte le chiamate al livello [DAO]. Quando la chiamata al metodo M del livello [DAO] viene intercettata, l'EJB Proxy avvia una transazione e poi passa il controllo al metodo M del livello [DAO], che viene quindi eseguito all'interno di quella transazione. Il metodo M può completarsi con o senza un'eccezione.

  • Se il metodo M termina senza un'eccezione, il controllo ritorna all'EJB proxy, che termina la transazione eseguendone il commit. Il controllo viene quindi restituito al metodo chiamante nel livello [business]
  • Se il metodo M termina con un'eccezione, l'esecuzione ritorna al proxy EJB, che termina la transazione eseguendone il rollback. Inoltre, avvolge questa eccezione in un'EJBException. L'esecuzione torna quindi al metodo chiamante nel livello [business], che riceve quindi un'EJBException. L'annotazione alla riga 5 sopra impedisce questo incapsulamento. Il livello [business] riceverà quindi una PamException. Inoltre, l'attributo rollback=true indica al proxy EJB che, quando riceve una PamException, deve eseguire il rollback della transazione.

6.2.3.4. Test del livello [DAO]

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

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

Per prima cosa, modifichiamo la classe [JUnitInitDBLocal] come segue:

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 {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation of local DAO layers
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
}
  • righe 3-5: riferimenti alle interfacce locali degli EJB nel 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 del contenitore EJB.
  • riga 13: disponiamo di un contesto JNDI (Java Naming and Directory Interface) che consente l'accesso agli EJB tramite nomi. Con OpenEJB, l'interfaccia locale di un EJB è indicata da ELocal e l'interfaccia remota da ERemote.
  • Righe 15–17: richiediamo un riferimento alle interfacce locali degli EJB [EmployeDao, CotisationDao, IndemniteDao] dal contesto JNDI.

Compilare il progetto, avviare il server MySQL se necessario ed eseguire il test JUnitInitDBLocal. Si noti che il file [persistence.xml] è stato configurato per ricreare le tabelle ad ogni esecuzione. Prima di eseguire il test, è consigliabile eliminare tutte le tabelle presenti nel database MySQL [dbpam_eclipselink].

  • In [1], nella scheda [Servizi], eliminare le tabelle dalla connessione NetBeans stabilita nella sezione 6.2.1.
  • In [2], il database [dbpam_eclipselink] non contiene più alcuna tabella
  • 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 NetBeans
  • in [7], vediamo le 4 tabelle create dal livello JPA. Lo scopo del test era di popolarle. Visualizziamo il contenuto di una di esse
  • in [8], il contenuto della tabella [EMPLOYEES]

Il container OpenEJB ha visualizzato i log nella console:

...
  • righe 2-3: i due nomi JNDI dell'EJB [CotisationDaoLocal],
  • righe 4-5: i due nomi JNDI dell'EJB [CotisationDaoRemote],
  • righe 7-8: i due nomi JNDI dell'EJB [EmployeDaoLocal],
  • righe 9-10: i due nomi JNDI dell'EJB [EmployeDaoRemote],
  • righe 12-13: i due nomi JNDI dell'EJB [IndemniteDaoLocal],
  • righe 14–15: i due nomi JNDI dell'EJB [EmployeeDaoRemote].

Eseguiamo nuovamente lo stesso test, questa volta utilizzando l'interfaccia remota degli EJB.

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

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)

Una volta fatto ciò, è possibile eseguire la nuova classe di test. Prima di farlo, utilizzando 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 [business]

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

Gli errori segnalati sopra [1] sono dovuti al fatto che il livello [business] copiato utilizza Spring e le librerie Spring non fanno più parte del progetto.

6.2.4.1. L'EJB [Business]

Seguiamo la stessa procedura descritta per l'EJB [CotisationDao]. Innanzitutto, in [2], creiamo le interfacce locali e remote per il futuro EJB [Metier]. Entrambe derivano dall'interfaccia iniziale [IMetier].

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 {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation of remote DAO layers
    employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
}
1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{

}

Una volta fatto ciò, in [3] modifichiamo la classe [Business] in modo che diventi un EJB:

1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{

}
  • riga 1: l'annotazione @Stateless rende la classe un EJB
  • riga 2: ogni metodo della classe verrà eseguito all'interno di una transazione
  • riga 3: l'EJB [Metier] implementa sia l'interfaccia locale che quella remota che abbiamo appena definito
  • riga 7: l'EJB [Metier] utilizzerà l'EJB [CotisationDao] tramite la sua interfaccia locale. Ciò significa che i livelli [business] e [DAO] devono essere eseguiti nella stessa JVM.
  • Riga 6: l'annotazione @EJB garantisce che il contenitore EJB inietti il riferimento all'interfaccia locale dell'EJB [CotisationDao] stesso. L'altro approccio che abbiamo incontrato consiste nell'utilizzare un contesto JNDI.
  • Righe 8–11: Lo stesso meccanismo viene utilizzato per gli altri due EJB nel livello [DAO].

6.2.4.2. Test del livello [business]

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

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

La classe [JUnitMetierLocal] diventa la seguente:

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

   // references to local [DAO] layer
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;

   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
     // retrieve employee information
...
  • riga 4: un riferimento all'interfaccia locale dell'EJB [Metier]
  • righe 8-12: configurazione del contenitore OpenEJB identica a quella utilizzata nel test del livello [DAO]
  • righe 15–19: richiediamo riferimenti dal contesto JNDI alla riga 12 ai tre EJB nel livello [DAO] e all'EJB nel livello [business]. Gli EJB nel livello [DAO] saranno utilizzati per inizializzare il database, mentre l'EJB nel livello [business] sarà utilizzato per eseguire i test di calcolo dello stipendio.

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

In [2], duplichiamo [JUnitMetierLocal] come [JUnitMetierRemote] per testare questa volta l'interfaccia remota dell'EJB [Metier]. Il codice di [JUnitMetierRemote] viene modificato per utilizzare questa interfaccia remota. Il resto rimane invariato.

public class JUnitMetierLocal {

// local business layer
  static private IMetierLocal metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);

     // instantiation of local DAO layers
    IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
     // local business layer instantiation
    metier = (IMetierLocal) initialContext.lookup("MetierLocal");

     // empty the base
...
}
  • righe 4 e 19: utilizziamo l'interfaccia remota dell'EJB [Business].
  • righe 15–17: utilizziamo le interfacce remote del livello [DAO]
  • righe 34–35: poiché con le interfacce remote gli oggetti scambiati tra il client e il server vengono passati per valore, dobbiamo recuperare il risultato restituito dal metodo create(Indemnite i). Ciò non era necessario con le interfacce locali, dove gli oggetti vengono passati per riferimento.

Una volta fatto ciò, è possibile compilare il progetto ed eseguire il test [JUnitMetierRemote]:

  

6.2.5. Porting del livello [console]

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

Gli errori segnalati sopra [1] derivano dal fatto che il livello [business] copiato utilizza Spring, mentre le librerie Spring non fanno più parte del progetto. In [2], la classe [Main] viene rinominata [MainLocal]. Utilizzerà l'interfaccia locale dell'EJB [Business].

Il codice della classe [MainLocal] cambia come segue:

public class JUnitMetierRemote {

   // remote business layer
  static private IMetierRemote metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);

     // instantiation of remote DAO layers
    IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
     // remote business layer instantiation
    metier = (IMetierRemote) initialContext.lookup("MetierRemote");

     // empty the base
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // fill it
    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));
  }
}

Le modifiche si trovano alle righe 13–25. In questo modo otteniamo un riferimento al livello [business], che è stato modificato (righe 17–22). Non spiegheremo il nuovo codice, poiché è già stato trattato negli esempi precedenti. Una volta apportate queste modifiche, il progetto non presenta più alcun errore (vedi [3]).

Configuriamo il progetto per l'esecuzione con argomenti [1]:

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

  public static void main(String[] args) {
     // local data
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
...
     // mistakes?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // it's OK - we can ask for the payslip at the [trade] layer
    IMetierLocal metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // configure the embedded Open EJB container
      Properties properties = new Properties();
      properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
       // initialization of JNDI context with previous properties
      InitialContext initialContext = new InitialContext(properties);
       // local business layer instantiation
      metier = (IMetierLocal) initialContext.lookup("MetierLocal");
       // wage sheet calculation
      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;
    }
     // detailed display
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
....

La riga 14, che causava la ricreazione delle tabelle del database ad ogni esecuzione, è stata commentata. È necessario ricompilare il progetto (Clean e Build) affinché questa modifica abbia effetto. Una volta fatto ciò, è possibile eseguire il programma. Se tutto va bene, l'output della console sarà simile al 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">
    <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- jpa entities -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- properties provider EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <!--
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
      -->
    </properties>
  </persistence-unit>
</persistence>

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

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

.......
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)

Sono state apportate modifiche alle righe 2 e 8. Il progetto è configurato [2] per eseguire la classe [MainRemote]. L'esecuzione produce gli stessi risultati di prima.

6.3. Conclusione

Abbiamo dimostrato come migrare un'architettura Spring/Hibernate a un'architettura OpenEJB/EclipseLink.

L'architettura Spring/Hibernate

L'architettura OpenEJB/EclipseLink

Il processo di porting è andato liscio perché l'applicazione originale era stata strutturata a livelli. È importante comprendere questo punto.