Bean Managed Persistence

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>
Salvo diversa indicazione, il contenuto di questa pagina è sotto licenza Creative Commons Attribution-ShareAlike 3.0 License