Bean Managed Persistence Entity Bean lezione 14
Queste tecniche si utilizzano per dati sensibili che devono sopravvivere a tutte le evenienze (crash del sistema, interruzioni di servizio, ecc). Gli AS garantiscono affidabilità per operazioni di persistenza useremo gli EBMP .
Vediamo il tutto con un esempio, sotto una semplice tabella del db.
CREATE TABLE 'simplebank' ( 'id' varchar(255) NOT NULL default '', 'nome' varchar(255) NOT NULL default '', 'saldo' double NOT NULL default '0', PRIMARY KEY ('id') )
Creiamo l'interfaccia remota e locale del componente
package it.html.ejb.entity.bmp; import java.rmi.*; import javax.ejb.*; public interface BankAccount extends EJBObject { //Metodi di logica (deposito e prelievo) public void deposito(double amt)throws RemoteException,AccountException; public void prelievo(double amt)throws RemoteException,AccountException; //Metodi setter delle proprietà public double getSaldo()throws RemoteException; public String getNome()throws RemoteException; public void setNome(String name)throws RemoteException; public String getAccountID()throws RemoteException; public void setAccountID(String id)throws RemoteException; }
package it.html.ejb.entity.bmp; import javax.ejb.EJBLocalObject; public interface BankAccountLocal extends EJBLocalObject { //Metodi di logica (deposito e prelievo) public void deposito(double amt)throws AccountException; public void prelievo(double amt)throws AccountException; //Metodi setter delle proprietà public double getSaldo(); public String getNome(); public void setNome(String name); public String getAccountID(); public void setAccountID(String id); }
Vi sono per le interfacce i metodi di logica deposito e prelievo e i metodi set e get per le operazioni di base.
Creiamo anche le interfacce home remota e locale
package it.html.ejb.entity.bmp; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; import javax.ejb.FinderException; /** * Interfaccia Factory remota. Oltre ai metodi per la creazione del componente, sarà necessario * prevedere i metodi finder ed eventuali metodi di utilità (numero di conti attivi, valore totale * della banca, ecc). */ public interface BankAccountHome extends EJBHome { //Metodo di creazione: deve esistere il relativo ejbCreate sul bean public BankAccount create(String id, String ownerName) throws RemoteException, CreateException; //Metodo finder: deve esistere il relativo ejbFindByPrimaryKey sul bean public BankAccount findByPrimaryKey(AccountPK key) throws RemoteException, FinderException; //Metodo di utilità: deve esistere il relativo ejbHomeGetNumeroContiAperti public int getNumeroContiAperti() throws RemoteException; }
package it.html.ejb.entity.bmp; import javax.ejb.CreateException; import javax.ejb.EJBLocalHome; import javax.ejb.FinderException; /** * Interfaccia Factory locale. Oltre ai metodi per la creazione del componente, sarà necessario * prevedere i metodi finder ed eventuali metodi di utilità (numero di conti attivi, valore totale * della banca, ecc). */ public interface BankAccountLocalHome extends EJBLocalHome { //Metodo di creazione: deve esistere il relativo ejbCreate sul bean public BankAccountLocal create(String id, String ownerName) throws CreateException; //Metodo finder: deve esistere il relativo ejbFindByPrimaryKey sul bean public BankAccountLocal findByPrimaryKey(AccountPK key) throws FinderException; //Metodo di utilità: deve esistere il relativo ejbHomeGetNumeroContiAperti public int getNumeroContiAperti(); }
Possono essere presenti più metodi create in base alle esigenze.
Il metodo findByPrimaryKey permette l'interrogazione di un conto ed eventualmente la modifica perchè viene restituita una istanza dell'EBMP. Questo è solo un esempio perchè per effetturare la ricerca si sarebbe potuto usare un altro parametro invece della chiave primaria.
Il metodo getNumeroContiAperti è invece un metodo di logica, l'inserimento di metodi di logica nelle interfacce è possibile dalla specifica 2.0 degli EJB .
Ad ogni metodo home è associato un relativo metodo sulla classe del bean nominato come ejbHome<>. Nel nostro caso avremo quindi ejbHomeGetNumeroContiAperti(), che farà un'operazione di selezione sul database. Nella classe BankAccountBean vi è il codice.
Vediamo la classe che fa da chiave primaria del db
package it.html.ejb.entity.bmp; import java.io.Serializable; public class AccountPK implements Serializable { private String accountID; public AccountPK(String id){ accountID=id; } public String getId(){ return accountID; } public String toString(){ return accountID; } public int hashCode(){ return accountID.hashCode(); } public boolean equals(Object account){ return ((AccountPK)account).getId().equals(accountID); } }
La classe ha al suo interno una variabile String che rappresenta il codice identificativo del conto. Deve implementare Serializable e possibilmente ridefinire i metodi equals e hashCode che vengono utilizzati dal Container per un migliore gestione.
La parte più importante è quella del BankAccountBean.java
package it.html.ejb.entity.bmp; import java.rmi.RemoteException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.ejb.CreateException; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.FinderException; import javax.ejb.RemoveException; public class BankAccountBean implements EntityBean { //Variabili di istanza private EntityContext ctx; private String accountID; private String nome; private double saldo; //Costruttore public BankAccountBean(){ System.out.println("New Bank Account creating..."); } //Metodi di business public void deposito(double amt){ System.out.println("Deposito sul conto "+accountID+" di "+amt+" Euro"); saldo+=amt; } public void prelievo(double amt)throws AccountException{ System.out.println("Prelievo dal conto "+accountID+" di "+amt+" Euro"); if ( amt>saldo ) throw new AccountException(); saldo-=amt; } //Metodi getter / setter public double getSaldo(){ return saldo; } public String getNome(){ return nome; } public void setNome(String name){ nome=name; } public String getAccountID(){ return accountID; } public void setAccountID(String id){ accountID=id; } /** * Metodo utilizzato per creare una connessione JDBC verso un database. * * ATTENZIONE: qui non viene mostrato il modo migliore per utilizzare le connessioni JDBC. * In futuro utilizzeremo il servizio JNDI per creare le connessioni sull'application * server (attraverso i DataSource) ed utilizzarle direttamente dai componenti. * Si è preferito lasciare ai seguenti * capitoli la spiegazione per una maggior comprensione del componente in questione. */ private Connection getConnection(){ //Dovete settare le variabili con i valori relativi ai driver utilizzati String class_driver="org.gjt.mm.mysql.Driver"; String url_connect="jdbc:mysql://127.0.0.1:3306/bmpdatabase?user=root&password=squake"; try{ Class.forName(class_driver); }catch(Exception e){ e.printStackTrace(); System.exit(-1); } try { return DriverManager.getConnection(url_connect); } catch (SQLException e) { e.printStackTrace(); } return null; } //Metodi Finder /** * Il metodo ricerca l'account in base al suo identificativo univoco: il componente * deve sempre avere almento questo metodo finder. * * La logica sarà recuperare (con una query sql) i valori dal database e restituire la * relativa chiave. */ public AccountPK ejbFindByPrimaryKey(AccountPK key) throws FinderException { System.out.println("ejbFindByPrimaryKey("+key+")"); PreparedStatement pstmt=null; Connection conn=null; try{ conn=getConnection(); pstmt=conn.prepareStatement("SELECT id FROM simlebank WHERE id=?"); //Setto il valore nello statement pstmt.setString(1,key.getId()); //Eseguo la query e recupero il risultato ResultSet rs=pstmt.executeQuery(); //Se esiste un record, vuol dire che abbiamo trovato l'account if (rs.next()); return key; }catch(SQLException e){ } finally{ //Chiudiamo le connessioni aperte in precedenza try { pstmt.close(); } catch (SQLException e) { // NOOP } try { conn.close(); } catch (SQLException e) { // NOOP } } throw new FinderException(); } //Metodi di utilità //Restituisce il numero di conti correnti attualmente presenti nel database public int ejbHomeGetNumeroContiAperti(){ System.out.println("ejbGetNumeroContiAperti()"); int toRet=0; PreparedStatement pstmt=null; Connection conn=null; try{ conn=getConnection(); pstmt=conn.prepareStatement("SELECT count(id) AS totale FROM simplebank"); ResultSet rs=pstmt.executeQuery(); //L'operazione non dovrebbe sollevare eccezioni toRet=rs.getInt("totale"); rs.close(); }catch (Exception e){ //NOOP }finally{ try { pstmt.close(); } catch (SQLException e) { // NOOP } try { conn.close(); } catch (SQLException e) { // NOOP } } //Restituiamo il valore recuperato dalla conta SQL return toRet; } //Metodi callback (ejbCreate, ejbPostCreate, ejbLoad, ecc) /** * METODO EJBCREATE * * In questo metodo bisogna codificare la creazione di un istanza: * nel nostro caso l'operazione di insert. */ public AccountPK ejbCreate(String accountID, String name) throws CreateException{ System.out.println("ejbCreate("+accountID+","+name+")"); Connection conn=null; PreparedStatement pstmt=null; //Settiamo le variabili del componente this.accountID=accountID; this.nome=name; this.saldo=0; //Salvataggio dell'informazione try{ conn=getConnection(); pstmt=conn.prepareStatement("" + "INSERT INTO simplebank ( id, nome, saldo ) values " + "('"+accountID+"','"+nome+"',"+saldo+")"); pstmt.execute(); return new AccountPK(accountID); }catch(Exception e){ e.printStackTrace(); }finally{ try { pstmt.close(); } catch (SQLException e) { // NOOP } try { conn.close(); } catch (SQLException e) { // NOOP } } //Se arriviamo qui, qualcosa è andato storto throw new CreateException(); } /** * Metodo callback chiamato subito dopo la ejbCreate: a noi in questo caso * non serve, percui lasciamo il corpo vuoto. */ public void ejbPostCreate(String s,String s2) throws CreateException { //NOOP } /** * METODO EJBREMOVE * * In questo metodo bisogna codificare la distruzione di una riga del database (operazione * di delete). Attenzione: questo metodo non elimina il componente dal sistema, in quanto * può essere riutilizzato da altri utenti (con i nuovi valori settati). */ public void ejbRemove() throws RemoveException { /* * Per prima cosa recuperiamo l'account da eliminare, e lo recuperiamo * dall'oggetto ctx (il Context) che virtualizza l'ambiente di esecuzione * e conosce la primary key legata all'istanza attuale del BMP. * */ AccountPK key=(AccountPK) ctx.getPrimaryKey(); System.out.println("ejbRemove() account "+key); Connection conn=null; PreparedStatement pstmt=null; //Eliminazione dell'informazione dal database try{ conn=getConnection(); pstmt=conn.prepareStatement("DELETE FROM simplebank WHERE id=?"); //Settiamo il valore di ? pstmt.setString(1, key.getId()); //Eseguiamo pstmt.execute(); }catch(Exception e){ e.printStackTrace(); }finally{ try { pstmt.close(); } catch (SQLException e) { // NOOP } try { conn.close(); } catch (SQLException e) { // NOOP } } } /** * METODO EJBLOAD * * Rappresenta il modo con cui i valori del bean vengono recuperati dal database (operazione * di SELECT). * * La logica pertanto prevede il recupero dei valori legati alla chiave associata al componente * ed il setting degli stessi, in maniera da poterli usare e manipolare (secondo i metodi di * logica previsti). */ public void ejbLoad() { //Recuperiamo dal context la Primary Key associata al componente AccountPK key=(AccountPK) ctx.getPrimaryKey(); System.out.println("ejbLoad() account "+key); Connection conn=null; PreparedStatement pstmt=null; //Recupero le informazioni associate all'account dal database try{ conn=getConnection(); pstmt=conn.prepareStatement("Select * FROM simplebank WHERE id=?"); //Settiamo il valore di ? pstmt.setString(1, key.getId()); //Eseguiamo ResultSet rs=pstmt.executeQuery(); if (rs.next()){ //settiamo il risultato nelle variabili di istanza nome=rs.getString("nome"); saldo=rs.getDouble("saldo"); } rs.close(); }catch(Exception e){ e.printStackTrace(); }finally{ try { pstmt.close(); } catch (SQLException e) { // NOOP } try { conn.close(); } catch (SQLException e) { // NOOP } } } /** * METODO EJBSTORE * * Rappresenta il modo con cui i valori del bean vengono salvati sul database (operazione * di UPDATE). * * La logica pertanto prevede il recupero dei valori legati alla chiave associata al componente * ed il salvataggio (update) sul database in modo da mantenerli persistenti. */ public void ejbStore() { System.out.println("ejbStore() account "+accountID); Connection conn=null; PreparedStatement pstmt=null; //Aggioriamo l'account sul database try{ conn=getConnection(); pstmt=conn.prepareStatement("UPDATE simplebank set nome=?, saldo=? WHERE id=?"); //Settiamo il valore di ? con i relativi valori puntati dal componente pstmt.setString(1, this.nome); pstmt.setDouble(2, this.saldo); pstmt.setString(3, this.accountID); //Eseguiamo pstmt.execute(); }catch(Exception e){ e.printStackTrace(); }finally{ try { pstmt.close(); } catch (SQLException e) { // NOOP } try { conn.close(); } catch (SQLException e) { // NOOP } } } //Metodi callback utilizzati per l'attivazione e la passivazione del componente. public void ejbActivate() { //NOOP } public void ejbPassivate() { //NOOP } /** * Metodi callback utilizzati dal container per settare l'ambiente di esecuzione (Context) * * Attraverso il context, il componente può comunicare con il container e recuperarne * servizi in maniera immediata utilizzato il naming JNDI. * */ public void setEntityContext(EntityContext entityContext) { this.ctx = entityContext; } public void unsetEntityContext() { ctx = null; } }
Spieghiamo alcune parti del codice:
- Variabili di istanza e metodi getter/setter che rappresentano le info sulla tabella ed eventualmente la maniera di accedervi. Se non si vuole che un campo sia modificabile direttamente si omette il suo metodo set.
- Metodi di buisness che sono le operazioni a disposizione del componente. Conviene lasciare al session bean queste operazioni e non metterle nel componente entity lasciando a questo solo operazioni di persistenza.
- Metodi finder/ejbHome sono quelli che interrogano il database, tramite SELECT ecc.
- Metodi di callback: vi sono i metodi che realizzano la persistenza sul db vi sono le INSERT, le UPDATE, le DELETE ecc.
Le operazioni più comuni sono quelle di load e store che effettuano la sincronizzazione con della logica applicativa con lo strato di persistenza.
La logica per il recupero della connessione con i driver jdbc conviene effettuarla tramite i servizi dell'AS vedremo in seguito come fare.
Inoltre serve il descrittore del deploy
<?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> <ejb-name>BankAccount</ejb-name> <home>it.html.ejb.entity.bmp.BankAccountHome</home> <remote>it.html.ejb.entity.bmp.BankAccount</remote> <local-home>it.html.ejb.entity.bmp.BankAccountLocalHome</local-home> <local>it.html.ejb.entity.bmp.BankAccountLocal</local> <ejb-class>it.html.ejb.entity.bmp.BankAccountBean</ejb-class> <persistence-type>Bean</persistence-type> <prim-key-class>it.html.ejb.entity.bmp.AccountPK</prim-key-class> <reentrant>False</reentrant> </entity> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>BankAccount</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>