MokaByte 47 - Dicembre 2000

di
Giovanni Puliti
Corso di EJB
III parte
Gli entity bean container managed
Questo mese affrontiamo gli Entity Bean di tipo container managed andando a analizzare nel dettaglio i vari aspetti legati alla gestione della persistenza ed al ciclo di vita

Introduzione
Un entity  bean è un particolare tipo di EJB il cui scopo è quello di rappresentare un oggetto, un elemento o un soggetto sul server. Per sua natura questo genere di componenti  delegato a memorizzare delle informazioni e di offrire una serie di metodi per la gestione da remoto da parte del client. 
In quest’ottica è possibile quindi suddividere le funzionalità (metodi) di un bean in due categorie: da una parte si trova tutto ciò che è relativo alla sua manipolazione da remoto direttamente tramite il client, ovvero la cosiddetta business logic, mentre dall’altro si trova ciò che è relativo alla gestione del ciclo di vita del componente del suo mantenimento dello stato e della gestione della sicurezza.
Già in precedenza [1] si è avuto modo di vedere come un client possa interagire con  un componente remoto, ricavandone prima la sua interfaccia remota e  poi invocandone i metodi pubblici. 
Uno degli aspetti più importanti degli entity bean è come questi implementano la persistenza dei dati all’interno del database. Due sono le possibilità: da un lato tutto il processo di sincronizzazione viene gestito dal container in modo trasparente (e si parla in questo caso di  persistenza container managed), mentre nell’altro caso tutte le informazioni sono salvate su disco direttamente tramite operazioni effettuate dal bean (è il caso della gestione bean managed). 
In questo articolo ci soffermeremo sulla gestione della persistenza tramite container, rimandando al mese prossimo il caso per così dire manuale.
 
 
 

Container Managed Persistence
In questo caso il server si preoccupa in modo del tutto trasparente di allineare le informazioni contenute nel bean al variare dello stato e soprattutto in funzione dello stato assunto all’interno del ciclo di vita del componente. Come detto in precedenza [2] il database di appoggio può  essere relazionale o ad oggetti.
Tranne per il caso delle variabili definite transient, tutti gli oggetti serializzabili, o le variabili primitive possono essere salvati. Nella maggior parte si tende ad utilizzare variabili primitive a causa della loro più semplice adattamento alla struttura relazionale del db.
La specifica EJB 1.1 permette di rendere persistenti anche campi di un bean che contengano riferimenti ad altri bean: in questo caso il costruttore del server deve implementare alcune tecniche piuttosto complesse al fine di effettuare un mapping il più sensato possibile. Normalmente questo si traduce nella memorizzazione della chiave primaria (oggetto Primari Key), degli oggetti Handle o HomeHandle,  o di altri riferimenti che identifichino univocamente il bean contenuto.
La gestione della persistenza a carico del container semplifica molto il lavoro di implementazione del bean, ma complica non poco la fase di progettazione del server; infatti in questo caso le tecniche di persistenza devono essere il più generiche e automatiche possibili, visto che non si può fare nessuna assunzione a priori su come sia fatto il componente. 
Nel caso invece dei bean-managed  si possono implementare tecniche ad hoc, a seconda del caso.
 
 

Identificazione di un bean: la Primary Key
Quando un client effettua una operazione di lookup su un oggetto remoto, si deve poterlo identificare un maniera univoca fra tutti quelli messi a disposizione dal server. 
Per questo motivo ad ogni bean è associata una chiave, detta Primary Key, che può essere costituita da una classe apposita, oppure identificata da un campo del bean stesso. 
Nel primo caso la classe ha un nome che segue lo schema NomeBeanPK: tale classe contiene al suo interno tutte le informazioni necessarie per individuare il bean. Ad esempio supponendo di avere un bean denominato MyBean, si potrebbe  scrivere

public void MyBeanPK{
    public int beanId;
    public MyBeanPK(){}
    public MyBeanPK(int id){
    beanId=id;
 

    public boolean equals(Object obj){
        if (obj == null || !(obj instanceof MyBeanPK))
            return false;
  else if(((MyBeanPk)obj).beanId == this.beanId)
            return true;
       else 
            return false; 
    }
 

    public int hashCode(){
        return this.beanId
    }

    // converte  in stringa il valore della chiave
    public String toString(){
        return this.beanId+””;
    }
}
 

In questo caso questa classe funge da wrapper per una variabile intera che rappresenta la chiave del bean. Per questo motivo sono stati ridefiniti i metodi equals() ed hashCode() in modo da operare su tale variabile: nel primo caso viene fatto un controllo con la chiave dell’oggetto passato, mentre nel secondo si forza la restituzione di tale intero (cosa piuttosto comoda ad esempio nel caso in cui si vogliano memorizzare i bean in una tabella hash, per cui due codici hash potrebbero essere differenti anche se fanno riferimento allo stesso Id).

Si noti la presenza del costruttore vuoto, necessario per poter permettere al container  di istanziare un nuovo componente in modo automatico e di popolarne gli attributi con valori prelevati direttamente dal database utilizzato per la persistenza. Questa operazione effettuata in automatico, si svolge piuttosto frequentemente durante il ciclo di vita del componente, come visto in [2], ed è alla base della gestione della persistenza  ad opera del container.
Per questo motivo tutti i campi della Primary Key devono essere pubblici, in modo che tramite la reflection  il container possa accedervi in modo automatico. 
Alcuni server permettono di accedere, tramite tecniche alternative, anche a variabili con differente livello di accesso, ma questo diminuisce la portabilità della Primary Key. Inoltre una Primary Key deve sottostare alle regole imposte per l’invocazione remota basata su RMI: questo significa che possono essere chiavi classi serializzabili o remote dove sia stato ridefinito i metodi equals() ed hashCode().

Si è detto che una chiave può non essere una  Primary Key definita come come classe a  se stante: infatti in alternativa è possibile utilizzare direttamente classi String o wrapper di tipi primitivi (ad esempio Integer); in questo caso si parla di chiave semplice. Il metodo di ricerca in questo caso agisce direttamente sul campo del bean, come ad esempio

public interface MyBeanHome implements javax.ejb.EJBHome{
    public MyBean findByPrimaryKey(java.lang.Integer key)
throws FinderException, RemoteExcepion;
}
 

In  questo caso anche se la chiave primaria non può essere costituita direttamente  un tipo primitivo, ma occorre utilizzare sempre un wrapper, al fine di garantire una maggiore standardizzazione della interfaccia; ad esempio il metodo getPrimaryKey() restituisce un Object generico, che verrà convertito a seconda del caso.
L’utilizzo di una classe Primary Key apposita è da preferirsi nel caso in cui si desideri consentire ricerche basate su chiavi composte (uno o più campi), oppure quando sia necessaria una maggiore flessibilità  e scalabilità del codice.

La specifica EJB 1.0 lasciava indefinito questo aspetto, rimandando al costruttore del server la scelta di supportare o meno le chiavi semplici: in tal caso deve essere presente un solo  campo di questo tipo.
Con la versione 1.1 invece le cose sono state definite in modo molto più preciso: in  questo caso infatti, nel caso di single field key, la scelta del campo da utilizzare come chiave non viene fatta durante la progettazione del componente, ma piuttosto durante la fase di deploy grazie ad un apposito documento XML. Ad esempio si potrebbe decidere di scegliere un campo beanId contenuto nel bean stesso tramite la seguente sequenza di script XML

<ejb-jar>
   <enterprise-beans>
      <entity>
         <primary-field>beanId</primary-field>
      </entity>
   </enterprise-beans>
</ejb-jar>
 

Il poter definire in fase di deploy quale sia la chiave da utilizzare ha una importante impatto sulla portabilità: in EJB 1.0 infatti il progettista deve decidere a priori quale sarà il campo da utilizzare per tale scopo, dovendo quindi fare una serie di assunzioni su quello che sarà il database utilizzato per la persistenza (relazionale o ad oggetti) e sulla struttura dati da esso offerta (ad esempio struttura delle tabelle). 
Rimandando alla fase di deploy tale scelta di fatto permette di svincolare completamente la fase di progettazione ed utilizzazione del componente dalla quella di utilizzo finale.
E’ per questo motivo quindi che tutti i metodi che hanno a che fare con la chiave lavorano con parametri di tipo Object, rendendoli  del tutto slegati dalla particolare scelta sul campo scelto come chiave.
In definitiva questa impostazione facilita la creazione di un mercato di componenti terze parti pronti per l’uso indipendenti dalla piattaforma d’utilizzo.
 
 
 

La ricerca tramite i metodi EJBFind
La ricerca di un bean, una volta definite le chiavi, avviene tramite i metodi di find messi a disposizione dalla interfaccia remota. 
La specifica infatti dice che la Remote Interface deve fornire zero o più metodi creazionali, ed uno o più metodi di ricerca. 
Per quanto riguarda i metodi di ricerca, nel caso dei container managed, tali metodi sono creati automaticamente al momento del deploy, in base alla politica particolare implementata dal container per effettuare la ricerca, ed in base a quanto specificato nei parametri di deploy. In alcuni casi il server permette di specificare attraverso il tool di deploy anche il comportamento di tali metodi.

Il findByPrimaryKey() è il metodo standard, ovvero quello che consente la ricerca in base alla chiave primaria, ma non è detto che sia il solo messo a disposizione dalla Remote. Ad esempio potrebbe essere comodo effettuare ricerche sulla base di particolari proprietà del bean: in questo caso i nomi dei metodi seguono lo schema findByNomeCampo(). Secondo le specifiche infatti, questa deve fornire oltre ai metodi creazioni
Ovviamente nel caso di ricerche per campi differenti dalla chiave primaria possono, si possono avere risultati multipli: per questo con la specifica 1.1 il risultato della ricerca può essere un oggetto di tipo Collection. 
I metodi di ricerca che ritornano un solo oggetto devono generare  una eccezione di tipo FinderException in caso di errore, mentre una ObjectNotFoundException nel caso in cui non sia stato trovato niente.
 
 
 

L’interfaccia EntityBean e la gestione del contesto
L’interfaccia EntityBean definisce una serie di metodi di callback atti alla gestione dello stato di un bean. 

public interface EntityBean extends EnterpriseBean{
public abstract void ejbActivate() throws RemoteExcetpion;
public abstract void ejbPassivate() throws RemoteExcetpion;
public abstract void ejbLoad() throws RemoteExcetpion;
public abstract void ejbStore() throws RemoteExcetpion;
public abstract void ejbRemove() throws RemoteExcetpion;
public abstract void setEntityContext(EntityContext ec)
throws RemoteExcetpion;
}
 

La maggior parte di tali metodi però non sono  particolarmente utili nel caso in cui sia il container a dover gestire la persistenza dei dati memorizzati nel componente. Per questo motivo parleremo in seguito in modo più dettagliato di tali aspetti, fatta eccezione per quello che riguarda la gestione del contesto. 
L’interfaccia EntityContext fornisce una serie di informazioni  utili durante il ciclo di vita di un bean, indipendentemente dalla politica adottata per il mantenimento dello stato, ed è inoltre il tramite per il collegamento fra il bean ed il proprio container.

Il primo metodo invocato successivamente alla creazione della istanza del  bean  è il setEntityContext() il quale passa al componente una istanza del contesto in cui agisce: esso viene invocato prima che il bean appena creato entri nel pool delle istanze pronte per l’utilizzo (vedi [2]). 

Ripensando al ciclo di vita di un bean, visto in precedenza, si potrà rammentare come in 
questa situazione le varie istanze non sono ancora state popolate con i dati prelevati dal database, rendendole di fatto tutte equivalenti. E’ al momento della invocazione da parte del client uno degli elementi del pool viene contestualizzato: le sue proprietà vengono istanziate, con dati prelevati dal database, e l’istanza viene associata o “wrapperizzata” con un EJB object per l’interfacciamento con il client. 
In questa fase viene creato un contesto per il bean, sia da un punto di vista astratto, sia concretamente assegnando un oggetto EntityContext apposito. Tale contesto rappresenta di fatto il tramite fra il bean ed l’EJB Object, oltre a fornire ad esempio al bean stesso la sua Primary Key.

Il meccanismo di pool e  di swap di istanze al suo interno, si è detto necessario per garantire maggiori prestazioni e ridurre al contempo lo spreco di risorse. 
Da un punto di vista implementativo questo viene resto possibile grazie al continuo cambio di contesto associato al bean stesso il quale non necessita di sapere cosa sta succedendo fuori dal suo container o quale sia il suo stato corrente, dato che vive il mondo esterno grazie all’utilizzo di un context personalizzato.
Fino a quando il metodo ejbCreate() non ha terminato la sua esecuzione nessun contesto è assegnato al bean, e quindi il bean non può accedere alle informazioni relative al suo contesto come la chiave primaria o dati relativi al client invocante: tali informazioni sono invece disponibili durante l’invocazione del metodo ejbPostCreate(). 
Invece tutte le informazioni relative al contesto di esecuzione come ad esempio le variabili d’ambiente sono già disponibili durante la creazione. 
Quando un bean termina il suo ciclo di vita, il contesto viene rilasciato grazie al metodo unsetEntityContext(). 

Per quanto riguarda invece i metodi creazionali, benché acquistino particolare importanza nel caso di gestione della persistenza bean managed, possono rivelarsi utili per ottimizzare la gestione del componente e delle sue variabili.
Ogni invocazione di un metodo di questo tipo sulla Home Interface viene propagata sul bean  in modo del tutto analogo a quanto avviene per i business methods invocati sulla remote Interface. Questo significa che si dovrà implementare un metodo ejbCreate()  per ogni corrispondente della Home Interface. 
La loro implementazione così come la loro firma dipende da quello che si desidera venga compiuto in fase di creazione. Tipicamente si tratta di una inizializzazione delle variabili di istanza. Ad esempio si potrebbe avere

// versione EJB 1.1  che ritorna un bean
public MyBean ejbCreate(int id, String name, ….){
this.beanId=id;
this.beanName=name;

return null;
}

// versione EJB 1.0  che ritorna un void
public void  ejbCreate(int id, String name, ….){
this.beanId=id;
this.beanName=name;

}

Come  si può notare la differenza fondamentale fra la specifica EJB 1.0 e 1.1 è il valore ritornato. 
Nella 1.0 infatti il metodo crea un componente senza ritornare niente, mentre nella 1.1 viene ritornato una istanza null della classe che si sta creando. In definitiva il risultato finale è lo stesso, dato che il valore ritornato viene ignorato in entrambi i casi. Il motivo di questa scelta è infatti di tipo progettuale: la scelta fatta in 1.1 permette infatti un subclassing del codice più semplice permettendo l’estensione da parte di un componente bean managed di un container managed bean in modo più semplice. Nella versione precedente questo non era possibile dato che in Java non è permesso l’overload di un metodo che differisca solo per il tipo ritornato.
La nuova specifica permette quindi ai costruttori di server di supportare la persistenza container managed semplicemente estendendo un bean container managed con un bean generato di tipo bean managed.
 

La sincronizzazione  con il database: i metodi ejbLoad() ed ejbStore()
Anche nel caso di un bean di tipo container managed, se si ha la necessità di effettuare particolari operazioni di conversione o preparazione del bean prima che questo sia reso persistente, o viceversa subito dopo la lettura dal database, allora i metodi ejbLoad() ed ejbStore() possono essere d’aiuto.
Si pensi ad esempio al caso in cui uno dei campi del bean sia costituito da un oggetto non serializzabile: nel pezzo di codice che segue ad esempio si è preso in esame un oggetto di una ipotetica classe MyTable non serializzabile. In questo caso l’istanza è indicata transient, in modo da impedire la memorizzazione automatica nel db nella tabella del database, rimandando tale operazione sull’oggetto strTable, il quale offre una  rappresentazione a stringa della tabella. 

public class MyBean extends EntityBean{
public transient MyTable table;
public String strTable;

public void ejbLoad(){
strTable=table.toString();
}
public void ejbStore(){
 table= new MyTable(strTable);
}
 ... 
}

Senza entrare nei dettagli dell’implementazione della classe MyTable, sarà sufficiente dire che il costruttore accetterà come parametro una stringa contenente i valori riga-colonna che costituiscono la tabella, e che il metodo toString() effettuerà l’operazione opposta. Anche se l’esempio è volutamente poco significativo da un punto di vista applicativo, mostra come con estrema semplicità sia possibile implementare uno strato di pre e post elaborazione intorno all’operazione di salvataggio su database. Dovrebbe essere altresì chiaro come questa organizzazione permette di risolvere in modo estremamente elegante problemi complessi.
 
 
 

Il deploy In 1.0 e 1.1
Dopo aver visto tutte le varie parti che compongono un entity bean, resta da vedere come sia possibile rendere funzionante tale componente, ovvero come effettuare il deploy di un EJB nel server. Le cose sono molto differenti a seconda che si desideri seguire la specifica 1.0 o 1.1: nel secondo caso le cose si sono semplificate moltissimo rispetto al passato, dato che è sufficiente scrivere in un documento XML tutte le informazioni necessarie. Molto spesso tale documento viene generato in automatico dal tool di sviluppo: ad esempio JBuilder dalla versione 4 offre un editor visuale delle varie proprietà di funzionamento del bean, editor che poi rende possibile la creazione del file  XML. Un esempio che si può ottenere con tale  prodotto potrebbe essere il seguente

<?xml version="1.0" encoding="Cp1252"?>

<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN' 'http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd'>

<ejb-jar>
  <enterprise-beans>
    <session>
      <ejb-name>TellerBean</ejb-name>
      <home>quickstart.TellerHome</home>
      <remote>quickstart.Teller</remote>
      <ejb-class>quickstart.TellerBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
    </session>
    <entity>
      <ejb-name>Accounts</ejb-name>
      <home>quickstart.AccountsHome</home>
      <remote>quickstart.Accounts</remote>
      <ejb-class>quickstart.AccountsBean</ejb-class>
      <persistence-type>Container</persistence-type>
      <prim-key-class>java.lang.String</prim-key-class>
      <reentrant>False</reentrant>
      <cmp-field>
 <field-name>name</field-name>
      </cmp-field>
      <cmp-field>
 <field-name>balance</field-name>
      </cmp-field>
      <primkey-field>name</primkey-field>
      <resource-ref>
 <res-ref-name>jdbc/accounts</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
      </resource-ref>
    </entity>
  </enterprise-beans>
  <assembly-descriptor>
    <container-transaction>
      <method>
 <ejb-name>TellerBean</ejb-name>
 <method-name>*</method-name>
      </method>
      <method>
 <ejb-name>Accounts</ejb-name>
 <method-name>*</method-name>
      </method>
      <trans-attribute>Required</trans-attribute>
    </container-transaction>
  </assembly-descriptor>
</ejb-jar>

L’esempio a cui si riferisce tale file XML è uno di quelli presenti nel tutorial Borland scaricabile direttamente dal sito della casa americana.
Per quanto riguarda invece il deploy di componenti secondo la specifica 1.0, le cose sono alquanto più complesse. In questo caso infatti il deploy descriptor è costituito da un oggetto Java serializzato istanza di una classe che nel caso degli entity è la EntityDescriptor, che deriva dalla più generica DeploymentDescriptor. Nel caso dei session bean la classe da utilizzare sarebbe stata la SessionDescriptor.
Dopo aver istanziato un oggetto tramite il costruttore

EntityDescriptor ed =  new  EntityDescriptor(); 

sarà possibile memorizzare tutte le informazioni relative al bean tramite i metodi messi a disposizione. Ad esempio si potrà indicare i nomi della classi che compongono complessivamente il bean nel seguente modo

ed.setEnterpriseBeanClassName(“MyBean”);
ed.setHomeInterfaceClassName(“MyBeanHome”);
ed.setRemoteInterfaceClassName(“MyRemoteBean”);
ed.setPrimaryKeyClassName(“MyBeanPK”);

Per quanto riguarda la persistenza poi attraverso le seguenti righe di codice è possibile indicare quali campi debbano essere memorizzati nel database

Class BeanClass = MyBean.Class;
Field []persistentFields= new Field[n];
PersistentFields[0] = BeanClass.getDeclaredField(“beanId”);
PersistentFields[1] = BeanClass.getDeclaredField(“beanName”);

Tramite l’istruzione 

ed.setReentrant(false);

è possibile avvertire il container che l’oggetto in questione contiene metodi non rientranti (vedi [2]). Grazie all’utilizzo di un oggetto di tipo ControlDescriptor è poi possibile definire gli attributi relativi alla transazionalità e sicurezza. Ad esempio 

ControlDescriptor cd = new ControlDescriptor();
cd.setIsolationLevel(ControlDescriptor.TRANSACTION_READ_COMMITTED);
cd.setTransactionAttribute(ControlDescriptor.TX_REQUIRED);
cd.setRunAsMode(ControlDescriptor.CLIENT_IDENTITY);

Avendo letto la puntata precedente del corso ([2]) si dovrebbe intuire piuttosto facilmente il significato di tali istruzioni. In ogni caso riprenderemo ed approfondiremo in una delle prossime puntate gli aspetti relativi alla gestione delle transazioni.
E’ da notare l’opzione RunAsMode, con la quale si specifica il modo con cui i metodi del bean potranno agire: in questo caso ogni invocazione altro metodo o risorsa accedute verranno validate in base ai permessi del client.
 
 
 

Conclusione
Si  conclude qui la trattazione relativa agli entity bean di tipo bean managed. Resta da vedere il mese prossimo la persistenza bean managed. 
Rimando per maggiori approfondimenti alla voce [3] della bibliografia.
 
 
 

Bibliografia
[1] – “Enterprise Java Beans” – di Giovanni Puliti - www.mokabyte.it/2000/05/ejb.htm
[2] – “Corso EJB: i servizi di sistema” – di Giovanni Puliti - www.mokabyte.it/2000/11/ejb_2.htm
[3] – ”Enterprise Java Beans, 2’ Edizione” – di Richard Monson Haefel – Ed. O’Reilly 
 
 

 

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


MokaByte®  è un marchio registrato da MokaByte s.r.l.
Java® è un marchio registrato da Sun Microsystems; tutti i diritti riservati
E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it