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
6.1.3. Configurazione del livello JPA / EclipseLink / OpenEJB
- Il file [META-INF/persistence.xml] che configura il livello JPA diventa il seguente:
- 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]:
- 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:
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:
- 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:
- 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):
- 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:
Si creano un'interfaccia locale e un'interfaccia remota a partire dall'interfaccia precedente:
Il EJB del livello [metier] implementa queste due interfacce:
- 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:
- 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:
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]:
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
![]() |
6.2.1. Configurazione del database [dbpam_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]:
![]() |
- 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:
Per ottenere i pacchetti corretti import, eseguire [clic droit sur le code / Fix Imports].
L'interfaccia remota ICotisationDaoRemote:
Quindi modifichiamo la classe [CotisationDao] per trasformarla in una EJB:
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:
È 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:
- 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:
- 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:
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].
Fatto ciò, in [3] modifichiamo la classe [Metier] affinché diventi una EJB:
- 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:
- 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.
- 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:
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:
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]:
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.




























