MokaByte 49 - Febbraio 2001
Foto dell'autore non disponibile
di
Giovanni Puliti
Corso di EJB
IV Puntata: EJB tipo BMP
Queste mese  parlamo di EJB di tipo BMP, ovvero quei componenti i cui la gestione della persistenza è curaa direttamente dal bean stesso tramite i metodi di callback.

Introduzione
Nella  puntata precedente [1] si sono analizzati gli entity  bean di tipo  container managed persistence (CMP beans), ovvero quel particolare tipo di EJB in cui la persistenza viene direttamente gestita dal container in modo automatico utilizzando un database relazionale o ad oggetti per la memorizzazione dei dati.
Grazie alla gestione da parte del container, essi sono particolarmente utili nel caso in cui non si abbiamo particolari necessità o quando sia semplice effettuare un mapping bean-database. 
In tutti quei casi in cui invece il processo di persistenza del componente richieda un trattamento particolare, sia per la maggiore complessità del caso, sia per la necessità di soluzioni personalizzate, si può passare ad implementare una gestione manuale della sincronizzazione fra componenti e dati nel database: questi componenti sono detti entity beans di tipo Bean Managed Persistence (BMP).
In questo caso appare ovvio che il prezzo da pagare sia dato dalla maggiore complessità di cui si deve tener conto: fortunatamente però il passaggio da un tipo di componente ad un altro è relativamente semplice grazie alla filosofia di base del modello EJB. Infatti il tutto si traduce nel dover implementare i vari metodi di callback invocati dal server, ovvero i vari metodi creazionali, come l’ejbCreate(), quelli di sincronizzazione ejbStore() ed ejbLoad() e quelli di ricerca.
Dal punto di vista invece del client e di tutto il resto dell’applicazione, non  si ha nessun impatto, e questo rappresenta probabilmente il maggior vantaggio di tutta la filosofia EJB.
 
 
 

Gestione delle eccezioni
Dato che dover gestire la persistenza in modo manuale significa dover implementare metodi che secondo il modello RMI sono remoti, uno degli aspetti più importanti di cui si deve tener conto è la gestione delle eccezioni. 
La prerogativa principale di un metodo remoto è quella di poter generare  eccezioni remote, principalmente di tipo RemoteException. 
Nella versione 1.0 la specifica EJB impone che tutti i metodi remoti possano generare una eccezione di questo tipo; nel caso in cui all’interno dei vari metodi ejbCreate() o ejbStore() possano essere generate altri tipi di eccezioni, queste dovranno essere catturate e prorogate all’interno di eccezioni remote.
Questa tecnica, tipica nel mondo RMI prende il nome exception wrapping, ed particolarmente utile in tutti quei casi in cui la business logic del bean si appoggia a sistemi sottostanti per la gestione di particolari risorse, come nel caso di JDBC o JNDI o JavaMail.
Un esempio di tale tecnica molto semplice potrebbe essere
 

public void ejbLoad() throws RemoteException{
  …
  try{
    … Esegue una ipotetica operazione con una sorgente JDBC
  }
  catch(SQLException sqle){
    throw new RemoteException(sqle);
  }
}
 

Con il passaggio alla versione 1.1, le eccezioni generati devono essere wrappate in EJBException, che discendendo dalla RuntimeException, non richiede la dichiarazione della clausola throws nella firma del metodo. Il pezzo di codice appena visto quindi, nella versione 1.1 potrebbe essere riscritto nel seguente modo

public void ejbLoad(){
  …
  try{
  … Esegue una ipotetica operazione con una sorgente JDBC
  }
  catch(SQLException sqle){
   throw new EJBException(sqle);
  }
}
 

Entity Context 1.0 e 1.1
Il contesto di esecuzione offre al bean una serie di informazioni e servizi molto utili durante il processo di salvataggio o di caricamento dei dati del bean.
Un EntityContext è assegnato alla istanza del bean ogni volta che un nuovo bean viene creato, all’inizio del suo ciclo di vita, prima che sia reso disponibile per servire i vari client. 
Tale reference in genere viene posto in una variabile di istanza del bean  e mantenuto per tutto il suo ciclo di vita. 
Se si riconsidera il ciclo di vita di un componente si rammenterà il meccanismo di swapping messo in atto sui vari componenti in esecuzione sul container. Durante tale cambio di contesto il container agisce anche sull’ EntityContext del bean tramite il metodo setEntityContext().
Sia l’interfaccia SessionContest che la EntityContext estendono la EJBContext; ecco la definizione di tale interfaccia
 

public interface EntityContext extends EJBContext{
  public EJBObject getEJBObject() throws IllegalStateException;
  public Object getPrimaryKey() throws IllegalStateException;
}
 
 

L’EJBContext
Il contesto comune a tutti i tipi di componenti è dato dalla interfaccia EntityContext dalla quale discendono direttamente sia l’EJBContext che il SessionContext.
I metodi della EntityContext sono specificatamente pensati per consentire una migliore gestione del ciclo di vita, della sicurezza e gestione delle transazioni di un entity. 
I metodi propri della EJBContext invece sono più generici, ma ugualmente importanti: il getEJBObject() restituisce un reference remoto al bean object, per poter essere utilizzata dal client o da un altro bean.  Dal punto di vista del componente esso può essere utilizzato per avere un riferimento a se stesso tutte le volte che si deve effettuare una chiamata in loopback, ovvero quando un bean invoca un metodo di un altro bean passando come parametro un reference a se stesso. Ad esempio

public class MyBean extends EntityBean{

  public EntityContext context;

  public myMethod(){
    YourBean yb= …
    EJBObject obj= Context.getObject() ;
    MyBean mb= (MyBean) PortableObject.narrow(obj, MyBean.class);
    yb.yourMethod(mb);
  }
}

Come si può notare il passaggio di un reference a se stesso non è stato fatto tramite la keyword this, cosa non permessa, ma tramite un reference remoto ricavato dal contesto. Questo comportamento è dovuto alla particolarità delle chiamate rientranti come si è visto in precedenza [2]. I session bean a tal proposito definiscono un metodo getObject() nella interfaccia EntityContext, il cui funzionamento è esattamente lo stesso.
Anche il metodo  getEJBHome(), disponibile sia per i session che per gli entity beans, è definito nella EJBContext: tale metodo restituisce un reference remoto al bean, e può essere utilizzato sia per creare nuovi bean, sia per effettuare delle ricerche nel caso di entity bean.
Infine il metodo getPrimaryKey(), restituisce una copia della chiave primaria assegnata:  sebbene, il suo utilizzo al di fuori dei metodi ejbLoad() ed ejbStore() sia piuttosto raro, così come accade per il metodo ejbHome(), poter disporre della chiave primaria è un utile accessorio dove sia necessaria una gestione particolare del componente, proprio come accade usualmente nei  bean BMP.
Durante il ciclo di vita all’interno del suo contesto, le varie informazioni accessibili tramite l’EJBContext possono variare, ed è per questo che tutti i metodi possono generare una IllegaStateException: ad esempio perde di significato ricorrere alla chiave primaria quando un bean si trova in stato di swapped, ovvero quando non è assegnato a nessun EJBObject, anche se può disporre di un EJBContext.
 
 
 

L’EntityContext
Questa interfaccia mette a disposizione alcuni metodi utili durante il ciclo di vita del componente. Ecco la sua definizione 

public interface EJBContext{

  public EJBHome getEJBHome();

  // metodi per la security
  public java.security.Principal getCallerPrincipal();
  public boolean isCallerInRole(String roleName);

  // metodi deprecati in 1.1
  public java.security.Identity getCallerIdentity();
  public boolean isCallerInRole(java.security.Identity id);
  public Properties getEnvironment();

  // metodi transazionali
  public UserTRansaction getUserTransaction()
                                  throws IllegalStateException;
  public boolean getRollbackOnly()throws IllegalStateException;
  public boolean setRollbackOnly()throws IllegalStateException;

}

Il metodo getEJBHome()  restituisce un reference alla propria HomeInterface. Questo può essere utile per effettuare ricerche o per ricavare reference di altri bean. 
Il getCallerPrincipal(), che dalla versione 1.1 sostituisce il getCallerIdentity(),  permette di ricavare il reference al client invocante. 
Quando invece si deve implementare un controllo a grana più fine sulla sicurezza, il metodo isCallerInRole() permette di controllare in modo veloce ed affidabile il ruolo di esercizio del client invocante.
Per quanto riguarda i metodi transazionali invece, essi saranno affrontati in seguito, quando si parlerà in dettaglio della gestione delle transazioni.
 
 
 

L’interfacciamento con il database: gestione di JDBC
Per poter gestire i dati relativi al bean memorizzati nel database,  si deve utilizzare una una connessione verso il database, la quale connessione può essere ottenuta direttamente tramite le tecniche standard JDBC, oppure ricavata per mezzo di JNDI da una sorgente dati opportunamente predisposta. 
Utilizzando questa seconda soluzione, il metodo getConnection(), utilizzato in seguito negli altri metodi del bean per poter leggere e scrivere i dati nel database, potrebbe essere così definito
 

private Connection getConnection() throws SQLExcepion {

  try{
    Context context= new InitialContext();
    DataSource source= (DataSource)context.lookup(SourceName);
    return source.getConnection();
  }
  catch(NamingException ne){
    throw new EJBException(ne);
  }

}
 
 

dove SourceName è il nome JNDI dato alla sorgente dati: ogni bean infatti può accedere ad una serie di risorse definite all’interno del JNDI Environment Naming Context (ENC), il quale viene definito al momento del deploy del componente tramite il cosiddetto deployment descriptor. 
Questa organizzazione, introdotta con la versione 1.1 della specifica, e valida non solo per le sorgenti JDBC, ma anche per il sistema JavaMail o  JMS, rende il componente ulteriormente indipendente dal contesto di esecuzione, rimandando al momento del deploy la sua configurazione tramite semplici file XML editabili con il tool di deploy utilizzato.
Ad esempio, supponendo che sia 

SourceName = “java:comp/env/jdbc/myDb”;

allora nel file deployment descriptor XML si avrebbe 


<resource-ref>
 <description>DataSource per il MyBean</description>
 <res-ref-name>jdbc/myDb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
… 

La modalità da seguire nel caso di EJB 1.0 è differente, ed anche se più standard per quanto riguarda la parte JDBC, vincola maggiormente il codice Java prodotto: anche in  questo caso però è utile notare quale sia la procedura da seguire.
Nel metodo getConnection() in questo caso si utilizzerà l’EJBContext per poter accedere alla nome della sorgente JDBC: ad esempio 

private Connection getConnection() throws SQLExcepion {
  Properties Props= context.getEnvironment();
  String JdbcUrl=Props.getProperty("jdbcUrl");
  return DriverManager.getConnection(JdbcUrl);
}
 

Dato che il deploy in EJB 1.0 deve avvenire tramite un programma Java apposito, comunemente detto MakeDD.java, si dovrà provvedere ad aggiungere in tale classe qualcosa del tipo

Properties Props=  new Properties();
Props.put("jdbcUrl","jdbc:<subprotocol>:<name>");
 
 
 

La nuova gestione del ciclo di vita
La gestione della persistenza secondo la modalità manuale impatta su tutti i metodi relativi al ciclo di vita, visto che ogni volta che un componente subisce un cambio di contesto, che viene creato o distrutto, devono  essere modificati i dati presenti nel database.
Nella versione CMP dei bean non era necessario preoccuparsi troppo di questi aspetti:  nei BMP invece una buona implementazione è di fondamentale importanza. 
Fortunatamente, pur passando ad una modalità manuale, non ci si deve preoccupare della gestione del ciclo di vita: il programmatore infatti dovrà concentrare l’attenzione ed i propri sforzi sul funzionamento  dei vari ejbCreate(), ejbLoad() o ejbStore(),  ma non del momento in cui tali metodi debbano essere invocati, compito svolto dal server.
Di seguito sono riportate le considerazioni più importanti relative ad ognuno di questi metodo:
 

  • ejbCreate(): tale metodo viene invocato indirettamente dal client quando questo invoca il metodo create della interfaccia home, ovvero quando genera un nuovo componente. Nel caso dei BMP questo metodo è responsabile di creare i dati relativi al bean all’interno del database. Oltre alla diversa implementazione del corpo del metodo, rispetto ai CMP la differenza principale è il diverso valore ritornato: in questo caso infatti è obbligatorio che sia ritornata la chiave primaria  del nuovo entity, e non void  (null in EJB 1.0) come per i bean CMP. 

  • Da un punto di vista strettamente implementativo, nel caso in cui il database d’appoggio scelto sia  di tipo relazionale, all’interno del metodo troveremo delle istruzioni insert SQL, che effettueranno una o più insert delle varie variabili d’istanza del componente. Si ricordi che in questo caso tutte le eccezioni SQL dovranno essere wrappate e propagate come EJBExceptions o RemoteExceptions come detto in precedenza.
  • Il metodo ejbPostCreate() invece, non è particolarmente interessato dalla strategia di persistenza  utilizzata e deve sempre ritornare  void. 
  • ejbLoad() ejbStore(): questi due  metodi sono invocati tutte le volte che il container decide che sia necessaria una sincronizzazione fra i dati memorizzati nel database, e le variabili del componente. Il primo verrà invocato in concomitanza di una transazione o prima di un business method, in modo da avere la certezza che il bean contenga i dati più recenti contenuti nel database. 

  • Il corpo del metodo non sarà molto differente da ejbCreate(), tranne che in questo caso, sempre nell’ipotesi di utilizzare un database relazionale,  si troveranno delle SQL update piuttosto che delle insert. 
    Continuano a valere anche in questo caso le considerazioni sulla  gestione wrappata delle eccezioni. 
  • ejbRemove(): considerazioni praticamente analoghe  a quelle dei casi precedenti sono quelle relative al metodo ejbRemove(), dove di fatto si deve provvedere alla cancellazione dei dati nel database. 
  • Metodi di ricerca:  per ogni metodo di ricerca presente nella home interface, dovrà essere presente un analogo nel bean. Si ricordi infatti che il client effettua le invocazioni direttamente sulla home che poi inoltra tali chiamate direttamente al bean.  Nel caso dei BMP, i metodi di ricerca sono responsabili di trovare nel database  i vari record che corrispondono ai criteri di ricerca impostati. 

  • Ad esempio se nella home è presente una cosa del tipo
    public interface MyHome extends javax.ejb.EJBHome{
      public MyBean findByPrimaryKey(MyBeanPK pk) 
                        throws FinderException, RemoteException;
            public Enumration findByName(String name)
                        throws FinderException,  RemoteException;
    }

    allora si avrà

public class MyBean extends javax.ejb.EntityBean{
  public MyBeanPK ejbFindByPrimaryKey(MyBeanPK pk) 
                 throws FinderException, RemoteException{}
  public Enumration ejbFindByName(String name)
                 throws FinderException, RemoteException{}
}


dove in questo caso FinderException è stata definita appositamente per segnalare l’insorgere di problemi durante la fase di ricerca.
Si noti inoltre anche i differenti tipi di valori ritornati dai metodi dell’interfaccia e del bean. 
Anche in questo caso la differenza principale fra EJB 1.0 e 1.1 è data dal tipo ritornato nel caso in cui nessun elemento sia trovato: nella versione precedente infatti si ottiene un null, mentre nella 1.1 sono ritornate collezioni vuote di elementi.
 
 
 

Ciclo di vita  di un entity bean
Concludendo la sezione dedicata agli entity  bean, sia CMP che BMP, è necessario affrontare nel dettaglio il ciclo di vita di questi componenti, in modo da avere chiaro quale sia il significato dei vari metodi visti fino  a questo punto. Si ricordi infatti che, tralasciando per un momento i dettagli legati alla sintassi o alla differente modalità di invocazione, da un punto di vista generale, non esiste particolare differenza fra un CMP e BMP se non per quello che sta dietro alla differente modalità di gestione del database sottostante.
Vedremo quindi di analizzare tutto il percorso che un componente compie durante i passaggi di stato all’interno del ciclo di vita per comprendere come e quando i vari metodi siano invocati.
Come prima cosa è bene ricordare che il ciclo di vita del bean è gestito in modo automatico dal container sia in funzione delle chiamate da parte del client sulla interfaccia remota, sia direttamente sui metodi della home che poi si riflettono in callback sul bean stesso.
 

  • Stato pre-caricamento

  • In questo stato il bean ancora non esiste come entità astratta, ma piuttosto come collezione di file .class e di descriptor files: devono essere forniti al server la primari key, la remote interface e la home, oltre a tutti i file generati i modo automatico dal deploy.
     
  • Pooled State

  • Nel momento della partenza del server, questo carica in memoria tutti i bean di cui sia stato effettuato correttamente il deploy, posizionandoli nel pool dei bean pronti (vedi [2]).  In tale fase viene creata un’istanza del componente utilizzando il metodo bean.newIstance(), il quale a sua volta invoca il costruttore del bean: come accade in RMI, anche in questo caso per il bean non deve essere specificato nessun costruttore, e l’invocazione avviene su quello di default. In questa fase tutte le variabili d’istanza assumono il loro valore di default, ed il container assegna l’EntityContext tramite il metodo setEntityContext(). In questo momento il componente è disponibile per essere utilizzato quando ci sarà bisogno di servire le richieste del client. Tale situazione perdurerà fino a quando non verrà invocato un metodo di ricerca. 
    Nessun bean in questo caso è assegnato ad un EJBObject, e non contiene informazioni significative.
     
  • Ready State

  • Il passaggio al Ready State può avvenire o per esplicita chiamata di una find da parte del client o per creazione diretta per mezzo dell’invocazione del metodo create.
    Nel secondo caso per prima cosa viene creato un EJBObject, lo skeleton dell’oggetto secondo la terminologia RMI, ed assegnazione di un bean prelevato dal pool. In questo momento caso l’invocazione del metodo create da parte del client, viene  propagata all’ejbCreate(): al suo termine una chiave primaria viene generata ed assegnata all’EJBObject. In questo momento sono effettuate tutte le operazioni di preparazione dei dati sul database in uno dei due modi, a seconda della politica scelta per la gestione della persistenza (CMP/BMP).  Successivamente il controllo passa  al metodo ejbPostCreate() per terminare la fase di inizializzazione. 
    Infine il client riceve lo stub dell’oggetto remoto, con il quale potrà effettuare tutte le invocazioni da remoto in perfetto accordo con quanto avviene nel modello RMI.
    Tutto questo processo viene realizzato in modo analogo nel caso di una invocazione di una find, su tutti i componenti trovati.
    Leggermente differente è invece il caso relativo al passaggio al ready state tramite attivazione. In questo caso il bean viene prelevato dal pooled state dove era finito in seguito ad una passivazione: qui, benché il bean non fosse presente in  memoria, il container aveva mantenuto un legame fra lo stub del client e  l’EJBObject: durante l’attivazione quindi i dati sono prelevati dal database e rassegnati al bean appena creato prima che questo sia accoppiato con l’EJBObject.
    Analogamente il passaggio inverso, ovvero dal ready al pooled avviene ugualmente sotto il verificarsi di vari eventi, ovvero sia su invocazione diretta del client oppure in base al meccanismo di passivazione attuato dal server.
    Anche in questo caso i metodi ejbStore() ed ejbPassivate() sono invocati in modo da mantenere sincronizzato il bean con i record del database. La sequenza precisa con cui tali invocazioni sono effettuate dipende dalla implementazione del server.
    Anche in questo caso, le cose sono analoghe sia nel caso di CMP che BMP.
     
     
     
     
Conclusione
Si conclude qui la trattazione degli entity bean. Dalla prossima puntata inizieremo a parlare di session beans, analizzandone sia il funzionamento che lo scopo principale.
Ricordo che fra le molte pagine pubblicate questo mese ed i precedenti è possibile trovare approfondimenti ed esempi applicativi della tecnologia EJB.
 
 
 

Bibliografia
[1] – “Corso di EJB: III parte - Gli entity CMP” di Giovanni Puliti, 
MokaByte 47 – www.mokabyte.it/2000/12

[2] – “Corso di EJB – II parte: i servizi di sistema” di Giovanni Puliti, 
MokaByte 46 – www.mokabyte.it/2000/11

[3] – “ EJB’s Design Techniques – I parte” – di Raffaele Spazzoli,
MokaByte 47 – www.mokabyte.it/2001/01

[4] – “ EJB’s Design Techniques – II parte” – di Raffaele Spazzoli,
MokaByte 48 – www.mokabyte.it/2001/02

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


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
mokainfo@mokabyte.it