MokaByte 70- Gennaio 2003 
EJB 2.0
Il CMP 2.0 e l'Abstract Persistent Model
di
Giovanni Puliti
Il nuovo CMP 2.0 introduce importanti novità fra cui l'Abstract Programming Model ed i campi relazionali. Questo mese vedremo il primo dei due, rimandando al mese successivo la trattazione delle relazioni fra entity beans.

Introduzione
Con l'avvento della specifica EJB 2.0 sono state introdotte alcune radicali modifiche relativamente agli entità beans di tipo CMP.
Anche se gli application server dovranno ancora supportare entrambe le specifiche CMP (1.1 e 2.0), i due sistemi sono incompatibili fra loro tanto che il programmatore dovrà scegliere quale dei due adottare.
Le innovazioni riguardano tre aspetti principali:
  • la possibilità di definire lo schema di persistenza del bean in modo astratto, più potente ed a granularità più fine
  • la gestione automatica dei legami relazionali fra entity bean differenti
  • l'introduzione di EJBQL un linguaggio standard per la definizione delle procedure di ricerca tramite script da inserire nel deployment file.

Concettualmente le prime due modifiche sono molto importanti, mentre il linguaggio di query, sebbene rappresenti un importante passo in avanti nel processo di standardizzazione, non costituisce una novità a tutti gli effetti dato che i vari produttori già implementavano soluzioni analoghe (ma essendo proprietarie limitavano fortemente la portabilità delle applicazioni da un application server all'altro).

 

Abstract Programming Schema
Così come avveniva con la versione 1.1 anche in CMP 2.0 tutto il processo di sincronizzazione dello stato di un entity viene gestito dal container. Adesso però il programmatore possiede alcuni strumenti in più per astrarre maggiormente l'applicazione dal sistema sottostante di persistenza (container+database).
In CMP 2.0 nasce il concetto di campo virtuale (virtual field), così chiamato perché, sebbene faccia parte a tutti gli effetti del set di attributi del bean, non è implementato direttamente dal programmatore ma dal container al momento del deploy.
Con il termine di campo virtuale, ci si riferisce quindi alla coppia di metodi set/get e non al campo in se che non esiste prima del deploy. I campi virtuali si suddividono poi in relazionali (vera innovazione di CMP 2.0) e di persistenza.
L'insieme dei campi virtuali, ovvero dei corrispondenti metodi astratti, danno luogo al concetto di Abstract Programming Model. Considerando invece il codice XML ad essi associati nel deployment descriptor si parla di Abstract Programming Schema.
Adottare l' Abstract Programming Model significa trasformare gli entity in classi astratte le cui implementazioni verranno realizzate dal container al momento del deploy: tali classi concrete sono dette classi di persistenza ed includono in modo esplicito il codice relativo all'accesso al database per realizzare il processo di persistenza sui dati; tale codice è ottimizzando in funzione del tipo e del prodotto specifico di database scelto.
Riconsiderando l'esempio della comunità virtuale di MokaByte si potrebbe immaginare l'utilizzo di un entity, CommunityUser che rappresenti un utente iscritto. Tale bean conterrà una serie di campi virtuali rappresentanti i dati dell'utente e come si avrà modo di vedere in seguito potrebbe essere collegato con altri entity con una legame di tipo relazionale.
La definizione della interfaccia locale potrebbe essere

public interface CommunityUser extends javax.ejb.EJBLocalObject {
  public void setUserLastName(String userLastName);
  public String getUserLastName();
  public void setUserFirstName(String userFirstName);
  public String getUserFirstName();
  public void setUserEmailAddress(String userEmailAddress);
  public String getUserEmailAddress();
  public void setUserAddress(String userAddress);
  public String getUserAddress();
  public UserInfo getUSerInfo();
}

mentre l'implementazione corrispondente, in ottica CMP 2.0,

abstract public class CommunityUserBean implements EntityBean {
  EntityContext entityContext;
  public String ejbCreate(String userLastName, String
                          userFirstName, String userEmailAddress,
                          String userAddress)
                          throws CreateException {
    setUserLastName(userLastName);
    setUserFirstName(userFirstName);
    setUserEmailAddress(userEmailAddress);
    setUserAddress(userAddress);
    return null;
    }
  public java.lang.String ejbPostCreate(String userLastName,
                                        String userFirstName,
                                        String userEmailAddress,
                                        String userAddress)
                                        throws CreateException {
    …
  }
  public void ejbRemove() throws RemoveException {
    …
  }
  public abstract void setUserLastName(String userLastName);
  public abstract void setUserFirstName(String userFirstName);
  public abstract void setUserEmailAddress(String usrEmailAddress);
  public abstract void setUserAddress(String userAddress);
  public abstract String getUserLastName();
  public abstract String getUserFirstName();
  public abstract String getUserEmailAddress();
  public abstract String getUserAddress();
  public void ejbLoad() {
    …
  }
  public void ejbStore() {
    …
  }
  public void ejbActivate() {
    …
  }
  public void ejbPassivate() {
    …
  }
  public void unsetEntityContext() {
    this.entityContext = null;
  }
  public void setEntityContext(EntityContext entityContext) {
    this.entityContext = entityContext;
  }
  public com.mokabyte.community.UserInfo getUSerInfo() {
    …
    return null;
  }
}

Si noti come la classe sia contemporaneamente classe astratta ed implementi la EntityBean. Definire l'entity ed i suoi metodi astratti serve per rafforzare il fatto che il bean non si possa considerare completo fino al momento del deploy.
Il metodo getUserInfo() unitamente alla classe UserInfo è particolarmente importante ai fini del pattern progettuale della applicazione non della teoria EJB; si parlerà più oltre di questi aspetti.
Come noto il metodo ejbPostCreate() entra in funzione dopo la creazione del bean ma prima che questo sia disponibile ai client: in questo caso, fra le varie cose questo può essere utile per interagire con i campi relazionali, dato che un istante prima essi non sono utilizzabili.

 

L'abstract programming model e le interfacce remote
Tutti i metodi presenti nel bean che si desidera esporre ai client remoti devono essere presenti nella interfaccia remota: i metodi relativi all'Abstract Programming Model però non è necessario siano inseriti nella interfaccia remota, ed anzi in genere è preferibile il contrario per limitare al minimo l'interfaccia remota (e quindi il client-layer) dal meccanismo di persistenza astratto.
Inoltre la specifica proibisce che siano esposti metodi relativi ai campi relazionali: come si avrà modo di vedere più avanti, questa regola alquanto saggia, evita che si creino situazioni non coerenti con lo schema relazionale del dominio dei dati.
Infine, come risulta ovvio, l'interfaccia remota non può esporre metodi di modifica della chiave primaria dell'entity.
Relativamente alla interfaccia home esistono importanti differenze rispetto alla precedente specifica: il metodo di ricerca per chiave primaria (findByPrimaryKey()) non è presente, dato che verrà implementato al momento del deploy, mentre gli altri di ricerca verranno definiti tramite la definizione di EJBQL inserite nel file di deploy.

 

Deployment descriptor
Nel deployment descriptor sono state introdotte alcune importanti novità, prima fra tutte il tag <cmp-version> che serve per specificare la versione del CMP utilizzata.
Inserendo

<cmp-version>2.x<cmp-version>

si specifica che si sta utilizzando il CMP 2.0, mentre 1.x è il valore da utilizzare per la versione precedente. Il primo è il valore di default per cui tale tag non è obbligatorio se si usa CMP 2.0.
Il resto del file XML è praticamente identico a prima: ecco un esempio relativo al bean CommunityUser

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<entity>
<display-name>CommunityUser</display-name>
<ejb-name>CommunityUser</ejb-name>
<local-home>com.mokabyte.community.CommunityUserHome</local-home>
<local>com.mokabyte.community.CommunityUser</local>
<ejb-class>com.mokabyte.community.CommunityUserBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>Community</abstract-schema-name>
<cmp-field>
<field-name>userLastName</field-name>
</cmp-field>
<cmp-field>
<field-name>userFirstName</field-name>
</cmp-field>
<cmp-field>
<field-name>userEmailAddress</field-name>
</cmp-field>
<cmp-field>
<field-name>userAddress</field-name>
</cmp-field>
<primkey-field>userLastName</primkey-field>
</entity>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>CommunityUser</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>

 

Persistent fields e Dependent Classes
I campi di persistenza possono essere di tipo primitivo (int, float, ..), tipi Java wrapper di primitivi (Byte, Boolean, Short, Integer, Long, Double e Float) oltre al tipo String e Date. Inoltre si possono utilizzare anche tipi Java serializzabili definiti dall'utente: in tal caso questi campi non vengono mappati con i tipi del database ma sono salvati un qualche formato binario.
E' fortemente sconsigliato utilizzare questo genere di oggetti, detti dependents classes, a meno che non si debba memorizzare dati particolari come immagini o dati multimediali; molto probabilmente questi campi possono essere resi persistenti con i tipi standard oppure con un legame relazionale con un altro entity bean (di cui si parlerà nel prossimo articolo). Per i tipi custom serializzabili vale la regola base della serializzazione Java: gli oggetti non si spostano ma verranno passati al client per copia (e non per reference remota); per poter modificare tali attributi si dovrà accedere ai metodi setXXX direttamente sull'entity bean.
Un buon utilizzo dei dependents object è quello che li vede come oggetti di trasporto dati fra il client ed il bean. Ad esempio invece di invocare i metodi getFirstName(), getLastName(), getEmailAddress() etc. è preferibile utilizzare un unico metodo getUserInfo() che ritorni un bean serializzabile contenente tutte le informazioni relative all'utente.
Ad esempio si potrebbe scrivere

public com.mokabyte.community.UserInfo getUSerInfo() {
  return new UserInfo(getUserFirstName(),
  
                    getUserLastName(),
  
                    getUserEmailAddress(),
  
                    getUserAddress());
}

Questo metodo non è parte dell'Abstract Persistent Schema: non è astratto ed il campo UserField non è incluso nel deployment descriptor XML. Deve quindi considerarsi come normale metodo remoto di business logic. Il tipo ritornato da getUserInfo() in questo caso è UserInfo, un oggetto serializzabile appositamente creato, ma si potrebbe utilizzare anche un oggetto Properties (o collezioni di vario tipo come Hashtable, List o Collection), oppure un oggetto serializzabile di tipo custom.
Nel secondo caso si ha il vantaggio di non richiedere al client la conoscenza della struttura del tipo ritornato (l'oggetto UserInfo non deve essere presente sul client), anche se il client deve conoscere esattamente il nome degli attributi inseriti nella collection.
Utilizzare un oggetto custom offre invece una maggiore potenza semantica: ad esempio si potrebbero eliminare in UserInfo tutti i metodi di tipo setXXX, a sottolineare che si tratta di un oggetto immutabile. Il client non può effettuarvi modifiche, che peraltro non avrebbero senso, visto che il tipo è ritornato per copia.
L'utilizzo di metodi di questo genere deve essere pensata attentamente, valutando l'ipotesi di altri pattern architetturali (Session Façade, DAO, o altri descritti in [ejbdesign]) o di interfacce locali.
Dato che queste considerazioni esulano dagli scopi di questo articolo, si rimanda alla lettura sui pattern EJB (ottimo il libro [ejbdesign]) per maggiori approfondimenti.

 

Relational fields
Infine un entity bean CMP 2.0 può contenere campi virtuali che instaurano legami con altri entity. Tali attributi sono detti relational fields e sono strettamente legati ad una altra importante novità introdotta in EJB 2.0, le interfacce locali (vedi [local]). Dato che una completa trattazione di tali argomenti richiederebbe più spazio di quello qui a disposizione, rimanderemo la trattazione dei relational fields ad un prossimo articolo.
Conclusione
In questo articolo si è iniziato a parlare del nuovo modello di persistenza introdotto per i CMP nella specifica 2.0 di EJB.
Da più parti gli entity beans ricevono molte critiche per il loro negativo impatto sulle performance, specialmente i CMP. Anche se non esiste una risposta esaustiva ed univoca che possa dare conferma o smentita di tali critiche, l'utilizzo dovrebbe essere subordinato ad una attenta progettazione che faccia uso dei pattern J2EE più importanti.
E' altresì vero che il nuovo modello offre una serie di importanti strumenti tali da rendere il CMP 2.0 molto più potente flessibile ed astraendo gli entity dalla particolare implementazione e dall'application server utilizzato.

 

Bibliografia
[ejbspec] - " EJB 2.0 specification ", java.sun.com/products/ejb
[ejb] - " Enterprise Java Beans, sviluppare componenti Enterprise Java ", di R. Monson-Hefel, Edizioni Hops-O'Reilly.
[ejbdesgin] - "EJB Design Pattern" di Floy Marinescu, ed. Wiley
[local] - "EJB 2.0: la Client Local API" di Giovanni Puliti, MokaByte 70, Gennaio 2003, www.mokabyte.it/2003/03

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it