Gli Application Server (AS) basati sulle specifiche Java EE ci permettono spostare una applicazione da uno all’altro senza problemi, e allo stesso tempo ci mettono in condizione di essere in grado di lavorare indistintamente con uno o l’altro AS… questo almeno in teoria! In pratica invece ci scontriamo con tutte le configurazioni specifiche di ogni AS, nonché con deployment descriptors proprietari che variano da server a server. Di solito quindi, una volta scelto l’AS su cui lavorare, è bene affidarsi a tutte le implementazioni delle specifiche che esso porta con se, per avere meno problemi possibile. Prendiamo per esempio JBoss 7, che come implementazione di JPA ha Hibernate. Finché usiamo questa implementazione della specifica JPA, funziona tutto correttamente: se però abbiamo bisogno di passare per esempio ad EclipseLink, perché magari preferiamo qualche sua caratteristica peculiare, dobbiamo armarci di pazienza e rimboccarci le maniche!
Perché EclipseLink?
Dalla versione 2.0 questa implementazione di JPA si è guadagnata il titolo di Reference Implementation (RI) della specifica: se non è un buon motivo per spingervi alla migrazione, basta dire che non soffrirete più di LazyInitializationException
… può bastare? 😉
Migrare da Hibernate ad EcliseLink: sondiamo il terreno
In teoria, per cambiare implementazione JPA, basta modificare il persistence.xml in modo da definire il nuovo Persistence Provider e inserire le properties necessarie a configurarlo.
Quanto segue è vero esclusivamente per la coppia JBoss AS 7.0.2 ed EclipseLink 2.3. L’altra coppia quasi perfetta è JBoss AS 7.1 ed EclipseLink 2.4 (che affronteremo prossimamente): accoppiamenti misti non mi hanno dato buoni risultati. In questa prima parte vediamo cosa succede con la prima coppia.
Un persistence.xml valido per eseguire EclipseLink 2.3 su JBoss 7.0.2 dovrebbe essere:
org.eclipse.persistence.jpa.PersistenceProvider jdbc/MySQL it.cosenonjaviste.jpa.entities.Order it.cosenonjaviste.jpa.entities.OrderInfo it.cosenonjaviste.jpa.entities.OrderDetail
All’inizio del persistence unit si definisce il nuovo provider di persistenza org.eclipse.persistence.jpa.PersistenceProvider
che attiva EclipseLink. Vengono poi dichiarate le entità in gioco, legate come segue:
In seguito, si definiscono tutti i parametri di configurazione tra cui:
- eclipselink.target-server: specifica l’AS su cui girerà l’applicazione.
- eclipselink.target-database: specifica il dialetto con cui costruire le query.
In teoria dovrebbero essere sufficienti queste semplici configurazioni, ma all’avvio del server ci scontriamo subito in una NameNotFoundException
perché EclipseLink sta cercando il Transaction Manager di JBoss sotto il nome JNDI java:/TransactionManager. Spulciando un po’ lo stacktrace e il sorgente di EclipseLink si scopre che è la classe JBossTransactionController
che esegue il lookup JNDI con quel nome che non esiste: dalla console di JBoss 7.0.2 (e solo su questa versione!!) infatti scopriamo che il nome del Transaction Manager è java:jboss/TransactionManager. Come fare a specificare il nuovo nome? Ricordate la proprietà eclipselink.target-server? E’ il momento di sfruttarla appieno.
Personalizziamo EclipseLink
Dal momento che EclipseLink può funzionare sia con diversi DBMS che AS, è stata prevista una opportuna API (si direbbe SPI in questo caso?!) per definire gli interfacciamenti con i differenti ambienti. Come spiega il wiki di EclipseLink, è possibile per esempio modificare l’interfacciamento con l’AS implementando l’interfaccia org.eclipse.persistence.platform.ServerPlatform
. Cominciamo per adesso col creare una versione del Transaction Controller ad hoc per JBoss 7.0.2 che esegua correttamente il lookup del Transaction Manager.
public class JBoss7TransactionController extends JTATransactionController { public static final String JNDI_TRANSACTION_MANAGER_NAME = "java:jboss/TransactionManager"; /** * INTERNAL: * Obtain and return the JTA TransactionManager on this platform */ protected TransactionManager acquireTransactionManager() throws Exception { return (TransactionManager)jndiLookup(JNDI_TRANSACTION_MANAGER_NAME); } }
Una volta definito, creiamo anche un nuovo ServerPlatform per JBoss 7, che usa il nostro TransactionController come segue:
public class JBoss7Platform extends JBossPlatform { public JBoss7Platform(DatabaseSession newDatabaseSession) { super(newDatabaseSession); } @Override public Class> getExternalTransactionControllerClass() { if (externalTransactionControllerClass == null) { externalTransactionControllerClass = JBoss7TransactionController.class; } return externalTransactionControllerClass; } }
Invece che reimplementare tutta l’interfaccia, modifichiamo solo il metodo che ci interessa in modo che venga usato il Transaction Controller che ci interessa. A questo punto modifichiamo il valore di eclipselink.target-server nel persistence.xml come segue:
avviamo JBoss e godiamoci la nostra applicazione con EclipseLink!!
Non è tutto oro quel che luccica
A prima vista sembra filare tutto liscio, ma se diamo un’occhiata alla console notiamo con dispiacere un messaggio di questo tipo:
The temporary classLoader for PersistenceLoadProcessor [JBoss7EclipseLinkJPA] is not available. Switching classLoader to [ModuleClassLoader for Module "deployment.JBoss7TestEAR.ear:main" from Service Module Loader]. Weaving has been disabled for this session. EclipseLink may be unable to get a spec mandated temporary class loader from the server, you may be able to use static weaving as an optional workaround.
In poche parole, EclipseLink si è avviato senza weaving… che significa? Provate a dichiarare un FetchType.LAZY su una qualsiasi relazione @OneToOne (per esempio tra Order
e OrderInfo
) e noterete tristemente che verrà ignorata 🙁 Che fare? Cercando un po’ in rete sembra che tutto derivi da un bug non ancora chiuso EJBTHREE-572 legato al ClassLoader. Tornando al sorgente di EclipseLink si scopre che il warning famoso viene creato da un metodo della classe JBossPlatform.. proprio quella che abbiamo esteso!! Dando un’occhiata a come sono state implementate le altre TargetPlatform
, possiamo aggiungere un metodo al nostro JBoss7Platform
:
@Override public JPAClassLoaderHolder getNewTempClassLoader(PersistenceUnitInfo puInfo) { return new JPAClassLoaderHolder(puInfo.getNewTempClassLoader(), true); }
purtroppo con il solo risultato di far sparire il warning dalla console (anche se passare il ClassLoader corretto non guasta mai…). Non ci resta che ripiegare al piano B: ovvero il weaving statico!
JBoss, EclipseLink e Weaving Statico: ménage à trois
Fortunatamente configurare il weaving statico è estremamente semplice e, data la scarsa mutabilità di un progetto JPA, non è che crei grossi disagi lanciare uno script di ANT se si modifica qualche entità. Creiamo quindi il classico build.xml nella root del progetto JPA:
Sarà necessario quindi avere gli archivi eclipselink.jar, javax.persistence_2.0.3.jar (presenti nel pacchetto di download di EclipseLink) e lo schema “persistence 2.0” nella cartella lib del progetto JPA per definire il nuovo task weave. Il target del nuovo task sarà un nuovo file jar che provvederemo ad aggiungere all’EAR al posto del progetto JPA. La prima volta lanciamo l’ANT da dentro Eclipse cliccando con il tasto destro sul file -> Run As -> Ant Build… e aggiungiamo i jar della lib anche al classpath:
Al termine, un bel refresh del progetto e vedremo apparire il nuovo jar nella cartella build_woven. Andiamo quindi nel Deployment Assembly dell’EAR e sostituiamo il progetto JPA con il jar appena creato!
Conclusioni
Alla fine, per usare JBoss con EclipseLink pare che sia davvero necessario ricorrere al weaving statico, in modo che il lazy loading si comporti come dovrebbe in una piattaforma Java EE. Alla fine non è una grossa seccatura, basta solo ricordarsi di rilanciare l’ANT quando viene modificata qualche entità! Se qualcuno trovasse il modo di far funzionare il weaving dinamico fatemi sapere!!
Pingback: ()