Sviluppare Applicazioni (Java) Web Cluster Aware – Parte II

Nella prima parte di questo post abbiamo visto le accortezze che lo sviluppatore deve adottare per fare sì che la sessione venga correttamente serializzata quando è deployata su un Application Server in Cluster.

Ok, la mia sessione è serializzabile. E se uso JPA?


Utilizzando gli oggetti (POJO) per rappresentare entità di un database con JPA ci sono casi in cui la serializzazione che si innesca in modo automatico causa effetti indesiderati. Le relazioni one-to-many ad esempio sono per default caricate in un secondo momento rispetto a quando si carica la classe Parent, ovvero solo quando saranno invocate (Lazy Loading), in modo da evitare che vengano portate in memoria più informazioni di quelle necessarie al momento.

EclipseLink (implementazione JPA di Eclipse Foundation, precedentemente il TopLink di Oracle) ad esempio esegue a runtime un weaving dinamico sugli oggetti Java che modellano il database, inserendo una serie di informazioni che servono al corretto funzionamento del framework: queste informazioni permettono di caricare le relazioni one-to-many in modo differito; EclipseLink definisce questo comportamento come Indirection: le istanze reali di List Set iniettate dal framework JPA saranno rispettivamente IndirectList IndirectSet. Quando l’oggetto Parent viene serializzato (in un momento che allo sviluppatore non è dato sapere) le relazioni one-to-many che esso possiede causano questo comportamento:

  • IndirectList: nel momento della serializzazione, il lazy loading viene risolto immediatamente e in cascata. Questo significa che se ho n relazioni one-to-many in cascata queste vengono risolte tutte immediatamente portando potenzialmente (e probabilmente) alla saturazione della memoria
  • IndirectSet: in fase di serializzazione, viene salvato esclusivamente lo stato degli oggetti in memoria, senza eseguire il fetch degli oggetti lazy. Questo comportamento ha il vantaggio di non avviare una serie di query inutili e non richieste, però apre un nuovo problema. L’oggetto ricreato dopo la serializzazione perde il riferimento alla unit of work da cui proveniva (questo perché il weaving non viene serializzato e l’oggetto risulta detached): nel momento in cui si tenterà di risolvere il lazy loading, il framework JPA lancerà una ValidationException, informandoci che stiamo tentando di risolvere una chiamata lazy dopo aver serializzato l’oggetto. Al momento, l’unica soluzione che ho trovato per risolvere questo problema è effettuare il merge dell’entità prima della chiamata alla collezione lazy

Come vivere con JPA e la serializzazione

Ci sono varie soluzioni:

  1. Non inserire entità JPA in sessione. So che può sembrare banale, ma in molti casi riflettendo con spirito Zen sul codice si scopre che se ne poteva fare a meno
  2. Se le entità sono contenute come field di un oggetto che viene messo in sessione (ad esempio un managed-bean JSF) reimplementare il metodo readObject chiamato durante il ripristino dell’oggetto serializzato: tramite reflection Java, si possono trovare tutti campi dell’oggetto che corrispondono a entità o collezioni di entità ed effettuare il merge in modo trasparente al programmatore
  3. Fare implementare l’interfaccia Externalizable a ciascuna entità, reimplementando i metodi readExternal writeExternal, che saranno usati in fase rispettivamente di deserializzazione e serializzazione dell’entità al posto dei metodi di default
  4. import java.io.Externalizable;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectOutput;
    
    public class OneCustomSerializableClass implements Externalizable
    {
    	@Override
    	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
    	{
    		// TODO Auto-generated method stub
    	}
    
    	@Override
    	public void writeExternal(ObjectOutput out) throws IOException
    	{
    		// TODO Auto-generated method stub
    	}
    }
  5. Non annotare le relazioni one-to-many nel caso in cui il lato con molteplicità n prevede che n possa essere elevato. Infatti anche se non ci fosse la serializzazione e anche se il caricamento venisse innescato nel momento voluto (click sul dettaglio di una testata in una form) il caricamento sarebbe comunque dispendioso e potrebbe in ogni caso saturare la memoria dell’Application Server. Questo tipo di relazioni dovranno quindi essere paginate e controllate tramite una named-query apposita

Uso JSF: il fatto che nessuno dei suoi componenti sia serializable mi spezza il cuore!

Talvolta è necessario inserire in un managed-bean con scope session oggetti del framework JSF che non sono serializable (ad esempio per effettuare il binding con un tag nella pagina JSF, allo scopo di effettuare validazioni incrociate). In questo caso però otteniamo lo stesso comportamento desiderato anche se indichiamo all’algoritmo di serializzazione che questo oggetto è sì in sessione, ma non abbiamo intenzione che il suo stato venga salvato. Questa cosa è ottenibile utilizzando la parola chiave Java transient davanti al field che si desidera marcare come “non da salvare”.

Concludendo…

Se il vostro Application Server è in Cluster:

  • Mettete in sessione solo oggetti serializable, se non sono oggetti che potete modificare e non sono serializable marcateli come transient
  • Attenzione alle relazioni one-to-many delle vostre entità JPA e in generale a tutte quelle definite lazy e che possono creare problemi in caso di caricamento non previsto

Manuele Piastra

Sono uno Scrum Master e Project Manager i cui skill tecnici sono focalizzati al momento sullo sviluppo di applicazioni Java EE su IBM Websphere 7.0 utilizzando JSF (RichFaces), JPA (EclipseLink) ed EJB3. Presso OmniaGroup ricopro il ruolo di Training Manager: seleziono il personale tecnico, mi occupo della sua crescita formativa organizzando Corsi e Workshop sia interni che esterni, molti dei quali hanno visto me come docente. LinkedIn Profile - Google+