Exercice 7 : Technologie JDBCTM
et persistence gérée par le bean

[<<RETOUR] [SOMMAIRE] [SUIVANT>>]

Jusqu'à maintenant l'application J2EE d'exemple a lu et enregistré les données dans la base de données Cloudscape sans que vous ayez écrit une ligne de code SQL. Cela est dû au fait que c'est le conteneur qui a géré le stockage et l'extraction des données à la place du bean d'entité. La peristence gérée par le conteneur (Container-Managed Persistence, CMP) est le terme utilisé pour désigner les situations où le conteneur gère les opérations de stockage et d'extraction. Cet exercice vous apprend comment surcharger la persistence par défaut, gérée par le conteneur, et implémenter de la peristence gérée par le bean.

La persistence gérée par le bean (Bean-Managed Persistence, BMP) est utilisée quand vous surchargez la peristence gérée par le conteneur et implémentez des méthodes dans les beans de session ou d'entité qui utilisent des commandes SQL. La peristence gérée par les beans peut s'avérer utile si vous voulez améliorer les performances ou transformer les données de plusieurs beans en une seule ligne de table de base de données.

Cet exercice modifie le bean d'entité de l'application J2EE d'exemple pour utiliser la peristence gérée par les beans.

Cycle de vie du bean

La section BonusBean de l'exercice 3 montre la classe BonusBean avec une persistence gérée par le conteneur. Les seules méthodes implémentées sont getBonus qui retourne la valeur du bonus, getSocSec qui retourne le numéro de sécurité sociale , et ejbCreate qui crée un bean d'entité avec les valeurs de bonus et socsec passées en argument. Le conteneur gére lui-même les opérations de création de lignes dans la base de données, et s'assure que les données en mémoire sont cohérentes avec celles en base. Avec la persistence gérée par les beans, c'est vous qui allez devoir implémenter ces comportements, ce qui signifie ajouter du code JDBC™ et SQL et implémenter les méthodes vides de l'exemple avec la persistence gérée par le conteneur.

Un bean de session ou d'entité comporte des méthodes métiers et des méthodes de gestion du cycle de vie. Dans l'exemple, CalcBean a deux méthodes métiers, calcBean et getRecord , et BonusBean a deux méthodes métiers, getBonus et getSocsec . CalcBean et BonusBean ont les méthodes de gestion du cycle de vie suivantes. Les méthodes métiers sont appelées par les clients et les méthodes de gestion du cycle de vie par le conteneur de bean.

Modifier le code de BonusBean

Ce chapitre présente le code de gestion de la persistence par le bean BonusBean. Première chose, vous remarquerez qu'il y a ici beaucoup plus de code que dans la version CMP.

Directives d'import

Les interfaces InitialContext , DataSource , et Connection sont importées pour établir une connexion à la base. L'interface PreparedStatement est importée pour être utilisée comme modèle pour la requête SQL. L'interface ResultSet est importée pour gèrer l'accès aux lignes de données retournées par une requête. Les classes FinderException et SQLException sont importées pour gérer les exceptions d'accès et de requêtes de la base de données.

package Beans;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.ejb.FinderException;
import java.sql.SQLException;

Variables d'Instance

Les variables d'instance utilisées dans cet exercice permettent d'établir et fermer la connexion avec la base de données. La chaîne java:comp/env/jdbc/BonusDB informe du nom de la ressource (resource reference name) de base de donnée, que vous pouvez aussi spécifier quand vous ajouterez le bean d'entité à l'application J2EE en utilisant l'outil de déploiement. Dans cet exemple la référence de ressource est un alias de la base de données Cloudscape ( CloudscapeDB ) où la table de données est stockée.

Après vous créerez la table BONUS dans CloudscapeDB , et durant le déploiement vous changerez jdbc/BonusDB en jdbc/CloudscapeDB .

public class BonusBean implements EntityBean {
  private EntityContext context;
  private Connection con;
  private String dbName = 
                 "java:comp/env/jdbc/BonusDB";
  private InitialContext ic = null;
  private PreparedStatement ps = null;
  private double bonus;
  private String socsec;

Méthodes métier

Les méthodes métier n'ont pas changé pour cet exercice si ce n'est les appels à System.out.println , qui nous permettrons de suivre l'ordre dans lequel les méthodes sont appelées à l'exécution.

  public double getBonus() {
    System.out.println("getBonus");
    return this.bonus;
  }
  public String getSocSec() {
    System.out.println("getSocSec");
    return this.socsec;
  }

Méthodes du cycle de vie

Ces méthodes incluent des appels à System.out.println de façon à ce que l'on puisse suivre l'ordre dans lequel les méthodes du cycle de vie et métier sont appelées à l'exécution..

ejbCreate

La signature de la méthode ejbCreate de cet exercice déclare les exceptions RemoteException et SQLException en plus de CreateException . SQLException est nécessaire car la méthode ejbCreate de cet exercice fournit son propre code SQL (on ne s'appuie pas sur le conteneur pour le fournir), et RemoteException est nécessaire car la méthode effectue des accès réseaux.

Une chose à savoir à propos de cette classe est qu'elle retourne une valeur String qui est la clé primaire du bean, mais les déclarations de méthodes dans l'interface home s'attendent à recevoir une instance de classe Bonus . Le conteneur utilise la clé primaire retournée par cette méthode pour créer l'instance de Bonus.

  public String ejbCreate(double bonus, String socsec)
               throws RemoteException, CreateException, SQLException {
      this.socsec=socsec;
      this.bonus=bonus;

      System.out.println("Create Method");

      try {
       //etablir une connexion vers la base
       ic = new InitialContext();
       DataSource ds = (DataSource) ic.lookup(dbName);
       con = ds.getConnection();
       //Utiliser un PreparedStatement pour preparer la requete
       //SQL INSERT a inserer dans la table BONUS
       ps = con.prepareStatement(
           "INSERT INTO BONUS VALUES (? , ?)");
       //definit une premiere valeur marquee par ? avec socsec
       //et une seconde valeur marquee par ? avec bonus
       ps.setString(1, socsec);
       ps.setDouble(2, bonus);
       ps.executeUpdate();
      } catch (javax.naming.NamingException ex) {
       ex.printStackTrace();
      } finally {
       //ferme la connexion a la base
       ps.close();
       con.close();
      }
      //renvoit une cle primaire
      return socsec;
    }

ejbPostCreate

Cette méthode doit avoir la même signature que ejbCreate , mais pas d'implémentation car ce simple exemple n'effectue pas de traitement de post-création ou d'initialisation.

  public void ejbPostCreate(double bonus, String socsec)
     throws RemoteException, CreateException, SQLException {
      System.out.println("Post Create");
    }

ejbFindByPrimaryKey

La version CMP du bean de bonus n'inclut pas d'implémentation d'ejbFindByPrimaryKey car le conteneur peut localiser les enregistrements par leur clé primaire si vous choisissez la persistence gérée par le conteneur et indiquer le champ contenant la clé primaire durant le déploiement. Dans cet exercice, BonusBean est déployé avec la BMP aussi vous devez fournir une implémentation de cette méthode et gérer l'exception SQLException . La version CMP déclenche uniquement RemoteException et FinderException.

Si l'opération de recherche localise un enregistrement correspondant à la clé primaire passée en argument de ejbFindByPrimaryKey , la valeur de la clè primaire est retournée, ce qui permet au conteneur d'appeler la méthode ejbLoad pour intialiser le BonusBean avec les valeurs adèquates de bonus et socsec.

Il faut se rappeler que cette classe retourne une valeur de type String qui est la clé primaire, mais que la signature de cette méthode dans l'interface home attend une instance de la classe Bonus. Le conteneur utilise la clè primaire retournée par cette méthode pour créer une instance de Bonus.

public String ejbFindByPrimaryKey(String primaryKey)
   throws RemoteException,FinderException,SQLException {

    System.out.println("Find by primary key");
    try {
      //etablir la connection a la base
      ic = new InitialContext();
      DataSource ds = (DataSource) ic.lookup(dbName);
      con = ds.getConnection();
      //utiliser un PreparedStatement pour creer la requete SQL SELECT 
      //utilisee pour la selection dans la table BONUS
      ps = con.prepareStatement("SELECT socsec FROM BONUS WHERE socsec = ? ");
      ps.setString(1, primaryKey);
      //utiliser le ResultSet pour contenir les resultats de la requete SELECT
      ResultSet rs = ps.executeQuery();
      //si le resultat comporte une valeur alors la requete a reussie
      //donc initialisation et renvoit de la valeur
      if(rs.next()) {
        key = primaryKey;
      } else {
        System.out.println("Find Error");
      }
    } catch (javax.naming.NamingException ex) {
      ex.printStackTrace();
    } finally {
      //fermer la connection a la base
      ps.close();
      con.close();
    }
    //retourner la cle primaire
    return key;
  }

ejbLoad

Cette méthode est appelée après un appel réussi à ejbFindByPrimaryKey pour charger les données et synchroniser l'état du bean avec l'état en base de données.

  public void ejbLoad() {

      System.out.println("Load method");
      try {
       //creer la connection a la base de donnees
       ic = new InitialContext();
       DataSource ds = (DataSource) ic.lookup(dbName);
       con = ds.getConnection();
       //utiliser un PreparedStatement pour creer la requete SQL SELECT
       //de selection dans la table BONUS
       ps = con.prepareStatement("SELECT * FROM BONUS WHERE SOCSEC = ?");
       ps.setString(1, this.socsec);
       //utiliser le ResultSet pour contenir le resultat de la requete SELECT
       ResultSet rs = ps.executeQuery();
       //Si le ResultSet a une valeur la recherche a reussie
       if(rs.next()){
         this.bonus = rs.getDouble(2);
       } else {
         System.out.println("Load Error");
       }
      } catch (java.sql.SQLException ex) {
       ex.printStackTrace();
      } catch (javax.naming.NamingException ex) {
       ex.printStackTrace();
      } finally {
       try {
        //fermer la connection a la base
        ps.close();
        con.close();
      } catch (java.sql.SQLException ex) {
        ex.printStackTrace();
      }
    }
  }

ejbStore

Cette méthode est appelée quand le client lit ou stocke des données dans le bean pour transmettre les données de l'objet vers la base de données et maintenir synchronisé l'état des données dans l'objet avec l''état en base de données.

public void ejbStore() {
 
    System.out.println("Store method");
    try {
      //ouvrir une connection vers la base de donnees
      DataSource ds = (DataSource)ic.lookup(dbName);
      con = ds.getConnection();
      //utiliser un PreparedStatement pour creer la requete SQL UPDATE
      //pour mettre a jour la table BONUS
      ps = con.prepareStatement("UPDATE BONUS SET BONUS = ? WHERE SOCSEC = ?");
      //Affecter bonus a la premiere marque ? et 
      //socsec a la seconde marque ?bonus and the 2nd value marked by ?) with socsec
      ps.setDouble(1, bonus);
      ps.setString(2, socsec);
      int rowCount = ps.executeUpdate();
    } catch (javax.naming.NamingException ex) {
      ex.printStackTrace();
    } catch (java.sql.SQLException ex) {
      ex.printStackTrace();
    } finally {
      try {
//Close database connection
        ps.close();
        con.close();
      } catch (java.sql.SQLException ex) {
        ex.printStackTrace();
      }
    }
  }

ejbRemove

Cette méthode est appelée quand un client appelle la méthode remove de l'interface home du bean. Le client JavaBean de cet exemple ne fournit pas de méthode remove qui puisse être appelée par un client pour supprimer le bean de son conteneur. Cependant une implémentation de la méthode ejbRemove est montrée ici. Quand le conteneur appelle ejbRemove , ejbRemove obitent la clé primaire (socsec) à partir de la variable d'instance socsec, ce qui supprime le bean de son conteneur, et efface la ligne correspondante de base de données.

  public void ejbRemove() throws RemoteException {
     System.out.println("Remove method");
     try {
      DataSource ds = (DataSource)ic.lookup(dbName);
      con = ds.getConnection();
      ps = con.prepareStatement("DELETE FROM BONUS WHERE SOCSEC = ?");
      ps.setString(1, socsec);
      ps.executeUpdate();
    } catch (java.sql.SQLException ex) {
      ex.printStackTrace();
    } catch (Exception ex) {
      ex.printStackTrace();
      try {
        ps.close();
        con.close();
      } catch (java.sql.SQLException ex) {
        ex.printStackTrace();
      }
    }
  }

ejbActivate

Quand un bean n'a pas été utilisé depuis longtemps, le conteneur peut le rendre passif ou le déplacer dans un stockage temporaire d'où le conteneur pourra facilement réactiver le bean lorsqu'un client fera appel à l'un de ses méthodes métiers. Cette méthode appelle la méthode getPrimaryKey du contexte d'entité pour rendre la clé accessible aux clients qui appellent le bean. Lorsqu'une requête sur le bean est faite le conteneur charge les données du bean en utilisant la clé primaire.

  public void ejbActivate() {
    System.out.println("Activate method");
    socsec = (String)context.getPrimaryKey();
  }

ejbPassivate

Quand un bean n'a pas été utilisé depuis longtemps, le conteneur peut le rendre passif ou le déplacer dans un stockage temporaire d'où le conteneur pourra facilement réactiver le bean lorsqu'un client fera appel à l'un de ses méthodes métiers. Cette méthode affecte la valeur nulle à la clé primaire pour libérer la mémoire lorsque le bean est rendu passif.

  public void ejbPassivate() {
    System.out.println("Passivate method");
    socsec = null;
  }

setEntityContext

Cette méthode est appelée par le conteneur pour initialiser la variable d'instance context du bean. Cela est nécessaire car la méthode ejbActivate appelle la méthode getPrimarykey de la vriable d'instance context pour changer l'état d'un bean de passif en actif.

  public void setEntityContext(
                  javax.ejb.EntityContext ctx){
    System.out.println("setEntityContext method");
    this.context = ctx;
  }

unsetEntityContext

Cette méthode est appelée par le conteneur pour mettre la variable d'instance context à null après un appel à la méthode ejbRemove pour détruire le bean. Seuls les beans d'entité ont une méthode unsetEntityContext.

  public void unsetEntityContext(){
      System.out.println("unsetEntityContext method");
      ctx = null;
    }
}

Modifier le code de CalcBean et JBonusBean

Comme BonusBean fournit son propre code SQL, la méthode CalcBean.calcbonus , qui crée les instances de BonusBean, doit être modifiée pour déclencher java.sql.SQLException . Voici une façon de faire cette modification:

public class CalcBean implements SessionBean {

   BonusHome homebonus;

   public Bonus calcBonus(int multiplier, double bonus, String socsec)
     throws RemoteException,SQLException, CreateException {

    Bonus theBonus = null;
    double calc = (multiplier*bonus);

    try {
      InitialContext ctx = new InitialContext();
      Object objref = ctx.lookup("bonus");
      homebonus = (BonusHome)PortableRemoteObject.narrow(objref, BonusHome.class);
    } catch (Exception NamingException) {
      NamingException.printStackTrace();
    }

    //Stocker les données dans un bean d'entie
    theBonus=homebonus.create(calc, socsec);
    return theBonus;
  }

La classe JBonusBean doit être modifiée pour gérer la SQLException déclenchée par CalcBean . DuplicateKeyExcpetion est une sous classe de CreateException , elle sera donc interceptée par l'instruction catch (javax.ejb.CreateException e).

  public double getBonusAmt() {
    if(strMult != null){
      Integer integerMult = new Integer(strMult);
      int multiplier = integerMult.intValue();
      try {
        double bonus = 100.00;
        theCalculation = homecalc.create();
        Bonus theBonus = theCalculation.calcBonus(
                         multiplier, bonus, socsec);
        Bonus record = theCalculation.getRecord(
                       socsec);
        bonusAmt = record.getBonus();
        socsec = record.getSocSec();
      } catch (java.sql.SQLException e) {
        this.bonusAmt = 0.0;
        this.socsec = "000";
        this.message = e.getMessage();
      } catch (javax.ejb.CreateException e) {
        this.bonusAmt = 0.0;
        this.socsec = "000";
        this.message = e.getMessage();
      } catch (java.rmi.RemoteException e) {
        this.bonusAmt = 0.0;
        this.socsec = "000";
        this.message = e.getMessage();
      }
       //...
      return this.bonusAmt;
    } else {
      this.bonusAmt = 0;
      this.message = "None.";
      return this.bonusAmt;
    }
  }

Créer la table de base de données

Comme cet exemple utilise la persistence gérée par le bean,vous devez créer la table de base de données BONUS dans la base de données CloudscapeDB. Avec la persistence gérée par le conteneur, cette table est crée pour vous.

Pour rendre les choses plus faciles, la table de base de données est crée avec deux scripts: createTable.sql et cloudTable.sh (Unix) ou cloudTable.bat (Windows/NT). Pour cet exemple le script createTable.sql doit aller dans le répertoire ~/tp/bmp, et le script cloudTable.sh (Unix) ou cloudTable.bat (Windows/NT) dans le répertoire ~/tp/bmp.

Pour exécuter les scripts, aller dans le répertoire ~/tp/bmp et taper les commandes suivantes:

Unix:

../cloudTable.sh

Windows/NT:

..\cloudTable.bat

createTable.sql

drop table bonus;

create table bonus
(socsec varchar(9) constraint pk_bonus primary key,
bonus decimal(10,2));

exit;

cloudTable.bat

rem cloudTable.bat
rem Creates BONUS table in CloudscapeDB.
rem
rem Change this next line to point to *your*
rem j2ee installation
rem
set J2EE_HOME=d:\j2ee
rem
rem Everything below goes on one line
java -Dij.connection.CloudscapeDB=
jdbc:rmi://localhost:1099/jdbc:cloudscape:
CloudscapeDB\;create=true -Dcloudscape.system.home=
%J2EE_HOME%\cloudscape -classpath  
%J2EE_HOME%\lib\cloudscape\client.jar;
%J2EE_HOME%\lib\cloudscape\ tools.jar;
%J2EE_HOME%\lib\cloudscape\cloudscape.jar;
%J2EE_HOME%\lib\cloudscape\RmiJdbc.jar;
%J2EE_HOME%\lib\cloudscape\license.jar;
%CLASSPATH% -ms16m -mx32m 
COM.cloudscape.tools.ij createTable.sql

cloudTable.sh

#!/bin/sh
J2EE_HOME=/net/home/lmaitre/j2ee
java -Dij.connection.CloudscapeDB=jdbc:rmi:
//localhost:1099/jdbc:cloudscape:CloudscapeDB\;
create=true -Dcloudscape.system.home=
$J2EE_HOME/cloudscape -classpath
$J2EE_HOME/lib/cloudscape/client.jar:
$J2EE_HOME/lib/cloudscape/tools.jar:
$J2EE_HOME/lib/cloudscape/cloudscape.jar:
$J2EE_HOME/lib/cloudscape/RmiJdbc.jar:
$J2EE_HOME/lib/cloudscape/license.jar:
${CLASSPATH} -ms16m -mx32m 
COM.cloudscape.tools.ij createTable.sql

Supprimer le fichier JAR

Vous devez mettre à jour le fichier JAR du bean avec le nouveau code de bean d'entité. Si vous avez les deux beans dans le même fichier JAR, vous devez supprimer 2BeansJar et en créer un nouveau. Les étapes pour ajouter CalcBean sont le même que celles dans Créer le fichier JAR de bean de session Les étapes pour ajouter BonusBean sont sensiblement difféentes et décrites ici.

Si vous avez les beans dans des fichiers JARs différents vous devez supprimer le fichier JAR de BonusBean et en créer un nouveau comme décrit ici.

Ces instructions débutent au moment où vous ajoutez les interfaces et les classes de BonusBean dans le fichier JAR.

EJB JAR :

Enterprise Bean JAR classes :

EJB JAR :

General :

Entity Settings:

Environment Entries:

Enterprise Bean References:

Resource References:

Security:

Transaction Management :

Review Settings:

Inspecting window:

Vérifier et déployer l'application

Avant de d"éployer l'application c'est une bonne idée d'exécuter le vérificateur. Le vérificateur fait des tests sur les composants et détecte des erreurs telles que des méthodes manquantes dans les enterprise bean que le compilateur ne prend pas en compte.

Note : Si vous obtenez une erreur de sauvegarde pendant que vous utiliser le vérificateur ou l'outil de déploiement, redémarrez le serveur et les utilistaires.

Vérification:

Déploiement:

Exécuter l'application

Le serveur web fonctionne sur le port 8000 par défaut. Pour ouvrir la page bonus.jsp dirigez votre navigateur sur la page http://localhost:8000/JSPRoot/bonus.jsp , qui est l'adresse où l'outil de déploiement à installé la page JSP.

Le sortie du serveur J2EE doit afficher le message suivant doit être affiché à chaque accès à la base de donnée. Il indique qu'aucun nom d'utilisateur ni mot de passe n'ont été fournit pour accéder à la base. Vous pouvez ignorer ce message car un nom d'utilisateur et un mot de passe ne sont pas obligatoires pour accèder à la base de données Cloudscap, et cet exemple fonctionne bien en dépit de ce message.

Cannot find principal mapping information for data source with JNDI name jdbc/Cloudscape

Voici une version expurgée de la sortie du serveur J2EE (les messages ci-dessous en ont été extraits).

setEntityContext method
Create Method
Post Create
setEntityContext method
Find by primary key
Load method
getBonus
Store method
Load method
getSocSec
Store method
Find by primary key
Load method
getSocSec
Store method
Load method
getBonus
Store method

<?xml version="1.0"?>
<report>
  <bonusCalc ssnum="777777777" bonusAmt="300.0" />
</report>

Plus d'informations

Vous pouvez obtenir plus d'informations sur les beans d'entité et la persistence gérée par les beans ici :
http://java.sun.com/j2ee/j2sdkee/techdocs/guides/ejb/html/Entity.fm.html

Pour obtenir plus d'informations sur la façon de créer des connections vers une base de données visiter :
http://java.sun.com/j2ee/j2sdkee/techdocs/guides/ejb/html/Database.fm.html

[RETOUR]