-
Les Enterprise JavaBean de type session avec état que nous avons vu précédemment ont été détruits lorsque la session du client s'est terminée. Ils ne peuvent donc pas être utilisé pour stocker les informations de l'application comme les factures ou les articles.
Les EJB entités peuvent répondre à ce besoin car ils sont persistants. C'est à dire que leur état est sauvegardé sur un support de stockage externe ( comme une base de données ).
Nous allons voir dans ce chapitre comment développer un Enterprise JavaBean qui représente un client.
-
Il existe deux types de persistances :
- Au niveau du composant ( BMP : Bean Managed Persistance ).
- Au niveau du conteneur ( CMP : Container Managed Persistance ).
Dans le premier cas, celui que nous allons voir dans ce chapitre, tous les appels provoquant un accès à la base de données sont dans le code de l'EJB :
- La méthode
ejbCreate() exécute une instruction SQL insert qui va crée un enregistrement dans la table.
- La méthode
ejbRemove() exécute une instruction SQL delete qui va supprimer l'enregistrement dans la table.
- La méthode
ejbLoad() exécute une instruction SQL select qui va récupérer les données depuis la table.
- La méthode
ejbstore() exécute une instruction SQL update qui va mettre à jour les données dans la table.
Dans le cas d'une persistance au niveau du conteneur, le code du composant ne fait aucun appel SQL car ceux-ci sont générés par le conteneur. Il y aura un mapping entre les propriétés de votre EJB et les champs d'une ou plusieurs tables de la base de données.
-
Notre premier EJB entité représentera un client. Celui ci aura trois caractéristiques : son code client, son nom et son prénom. Dans ce chapitre, la persistance sera gérée au niveau du composant.
-
Sur notre serveur de base de données MySQL, nous allons créer une base de données crm qui contiendra une table CLIENTS dont voici le script de création :
| clients.sql |
|
CREATE TABLE CLIENTS (
CODE_CLIENT varchar(255) NOT NULL,
NOM varchar(255) default NULL,
PRENOM varchar(255) default NULL,
PRIMARY KEY (CODE_CLIENT)
)
|
-
Dans la norme J2EE, le serveur d'application est chargé de fournir aux composants une DataSource. Par contre, la norme ne spécifie pas comment doit être paramétrée cette source de données.
note : Une DataSource est un objet qui identifie une base de données et un moyen d'y accéder. Son utilisation est indépendante du SGBD utilisé.
Chaque serveur J2EE a donc sa propre manière de définir une DataSource et voici comment cela se fait sous JOnAS :
- Dans le répertoire "C:\java\plateforme\jonas\config", nous allons créer un fichier
MySQL-CRM.properties qui contiendra les paramètres d'accès à notre base de données CRM de notre serveur MySQL.
| MySQL-CRM.properties |
|
datasource.name conMySQL_CRM
datasource.url jdbc:mysql://localhost/crm
datasource.classname com.mysql.jdbc.Driver
datasource.username root
datasource.password ####
|
La valeur de la propriété datasource.name est le nom JNDI de la source de données.
- Pour que JOnAS mappe cette source de données, il faut éditer le fichier de configuration
jonas.properties et modifier la valeur de la propriété jonas.service.dbm.datasources pour lui ajouter MySQL-CRM.
Désormais, tous les composants du conteneur pourront obtenir une référence à notre base CRM sur notre serveur MySQL à l'aide du nom JNDI : conMySQL_CRM.
-
-
Nous allons permettre à l'application cliente :
- Consulter le nom du client via la méthode
getNom().
- Consulter le prénom du client via la méthode
getPrenom().
- Modifier le prénom du client via la méthode
setPrenom().
Client.java
|
1 2 3 4 5 6 7 8 9 10
11 12 13 14
|
package crm_bmp;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Client extends EJBObject {
public String getNom() throws RemoteException;
public String getPrenom() throws RemoteException;
public void setPrenom(String prenom) throws RemoteException;
}
|
|
Java2html
|
-
"L'Interface locale définit les méthodes qu'un client peut invoquer pour créer, trouver ou supprimer un EJB."
Les interfaces locales des composants de session que nous avons étudiées jusqu'ici définissent une ou plusieurs méthodes pour permettre à un client de créer le composant. Etant donné que nos EJB entité sont persistants, un client peut créer une instance d'un composant ( c'est à dire faire un insert dans la table CLIENTS ) mais aussi en rechercher une existante ( c'est à dire faire un select dans la table CLIENTS ).
ClientHome.java
|
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16
|
package crm_bmp;
import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.*;
import java.util.*;
public interface ClientHome extends EJBHome {
public Client create(String codeClient, String nom, String prenom) throws RemoteException, CreateException;
public Client findByPrimaryKey(String codeClient) throws RemoteException, FinderException;
public Collection findByNom(String nom) throws RemoteException, FinderException;
}
|
|
Java2html
|
Nous avons aussi crée deux méthodes dites "finder" qui vont nous permettre de rechercher un composant.
note : Les méthodes dites "finder" sont celles qui commencent par le mot find.
-
Notre EJB va implémenter l'interface EntityBean. En plus des méthodes ejbActivate() et ejbPassivate(), il faut implémenter les méthodes suivantes :
ejbLoad()
ejbStore()
setEntityContext()
unsetEntityContext()
Ces méthodes seront invoquées par le conteneur pour notifier à l'EJB le cycle des évènements. Le client du composant n'est pas à autorisé à le faire.
ClientBean.java
|
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
101 102 103 104 105 106 107 108 109 110
111 112 113 114 115 116 117 118 119 120
121 122 123 124 125 126 127 128 129 130
131 132 133 134 135 136 137 138 139 140
141 142 143 144 145 146 147 148 149 150
151 152 153 154 155 156 157 158 159 160
161 162 163 164 165 166 167 168 169 170
171 172 173 174 175 176 177 178 179 180
181 182 183 184 185 186 187 188 189 190
191 192 193 194 195 196 197 198 199 200
201 202 203 204 205 206 207 208 209 210
211 212 213 214 215 216 217 218 219 220
|
package crm_bmp;
import java.sql.*;
import javax.sql.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;
public class ClientBean implements EntityBean {
// Données du client
private String codeClient;
private String nom;
private String prenom;
// Contexte de l'EJB
private EntityContext context;
// Connexion à la base de données
private Connection connexionBD;
// Chaine JNDI qui permettra de trouver notre connexion à la bd sur le serveur
private String dbName = "java:comp/env/jdbc/ClientDB";
// Fonction promise dans l'interface distante
public String getNom() {
return nom;
}
// Fonction promise dans l'interface distante
public String getPrenom() {
return prenom;
}
// Fonction promise dans l'interface distante
public void setPrenom(String prenom) {
this.prenom = prenom;
}
// Crée le composant et le sauvegarde dans la base de données avec un insert
public String ejbCreate(String codeClient, String nom, String prenom) throws CreateException {
String insertStatement = "INSERT INTO CLIENTS VALUES ( ? , ? , ? )";
// Execute la requête qui fait l'insert
try {
PreparedStatement ps = connexionBD.prepareStatement(insertStatement);
ps.setString(1, codeClient);
ps.setString(2, nom);
ps.setString(3, prenom);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
throw new CreateException("Erreur lors de la création du client " + codeClient + " : " + e.getMessage());
}
// rensigne les propriétés du composant
this.codeClient = codeClient;
this.nom = nom;
this.prenom = prenom;
return codeClient;
}
public void ejbPostCreate(String codeClient, String nom, String prenom) {
// Ne fait rien
}
// Recherche le composant qui a pour identifiant le codeClient passé en paramètre
public String ejbFindByPrimaryKey(String codeClient) throws ObjectNotFoundException, FinderException {
boolean found;
String selectStatement = "SELECT CODE_CLIENT FROM CLIENTS WHERE CODE_CLIENT = ? ";
// execute la requête qui recherche la ligne dans la table CLIENTS en fonction du code client passé en paramètre
try {
PreparedStatement ps = connexionBD.prepareStatement(selectStatement);
ps.setString(1, codeClient);
ResultSet rs = ps.executeQuery();
found = rs.next();
ps.close();
if (found)
// On retourne le codeClient si on trouve
return codeClient;
else
// Si on ne trouve pas, on déclenche une exception de type ObjectNotFoundException
throw new ObjectNotFoundException("Le client n'a pas été trouvé : " + codeClient);
} catch (SQLException e) {
throw new FinderException(e.getMessage());
}
}
// Recherche une collection de composants ayant pour nom, le nom passé en paramètre
public Collection ejbFindByNom(String nom) throws ObjectNotFoundException, FinderException {
String selectStatement = "SELECT CODE_CLIENT FROM CLIENTS WHERE NOM = ? ";
ResultSet rs = null;
ArrayList listeClients;
// Execute la recherche qui permet de faire la vérification
try {
PreparedStatement ps = connexionBD.prepareStatement(selectStatement);
ps.setString(1, nom);
rs = ps.executeQuery();
listeClients = new ArrayList();
// On construit la liste des clients à partir du résultat de la requête
while (rs.next()) {
String codeClient = rs.getString(1);
listeClients.add(codeClient);
}
ps.close();
} catch (SQLException e) {
throw new FinderException("ejbFindByFirstName " + e.getMessage());
}
if (listeClients.isEmpty())
// Si aucun composant ne correspond, on déclenche une exception
throw new ObjectNotFoundException("Aucun client ne correspond.");
else
// sinon, on retourne la liste des clients
return listeClients;
}
// Cette fonction permet de supprimer un composant, ce qui correspond à un delete dans la base de donnée
public void ejbRemove() throws RemoveException {
PreparedStatement ps;
// Execute le delete
try
{
ps = connexionBD.prepareStatement("delete from CLIENTS where CODE_CLIENT=?");
ps.setString(1, codeClient);
ps.executeUpdate();
} catch (SQLException e) {
throw new RemoveException ("Impossible de supprimer le client : " + e);
}
}
// Appelé juste après la création du composant,
// c'est ici qu'on va se connecter à la base de données à l'aide du nom JNDI
public void setEntityContext(EntityContext context) {
this.context = context;
try {
InitialContext ic = new InitialContext();
// Récupère la datasource auprès du conteneur à l'aide du nom JNDI
DataSource ds = (DataSource) ic.lookup(dbName);
// Récupère un objet Connection à partir du DataSource
connexionBD = ds.getConnection();
}
catch (Exception e) {
throw new EJBException("Connexion à la base de données impossible : " + e.getMessage());
}
}
// Appellé lorsque le composant est enlevé, on se déconnexte alors de la base de données
public void unsetEntityContext() {
try {
connexionBD.close();
}
catch (SQLException ex) {
throw new EJBException("unsetEntityContext: " + ex.getMessage());
}
}
// appellé lorsque l'objet redevient actif
public void ejbActivate() {
}
// appellé lorsque l'objet devient passif (économie de ressources)
public void ejbPassivate() {
}
// Cette fonction est une fonction de synchronisation, elle est appellée par le conteneur
// lorsqu'il veut lire les informations de l'EJB depuis le support de stockage
public void ejbLoad() {
String selectStatement = "SELECT CODE_CLIENT, NOM, PRENOM FROM CLIENTS WHERE CODE_CLIENT = ? ";
// Execute le select
try {
PreparedStatement ps = connexionBD.prepareStatement(selectStatement);
ps.setString(1, codeClient);
ResultSet rs = ps.executeQuery();
if (rs.next() == false) {
throw new EJBException("Impossible de charger le bean depuis la base de données");
}
// lit les données de la base
nom = rs.getString("NOM");
prenom = rs.getString("PRENOM");
} catch (SQLException e) {
throw new EJBException("Impossible de charger le bean depuis la base de données : " + e.getMessage());
}
}
// Cette fonction est une fonction de synchronisation, elle est appellée par le
// conteneur lorsqu'il veut sauvegarder les informations de l'EJB dans le support de stockage
public void ejbStore() {
String updateStatement = "UPDATE CLIENTS set NOM = ?, PRENOM = ? WHERE CODE_CLIENT = ? ";
// Execute l'update
try {
PreparedStatement ps = connexionBD.prepareStatement(updateStatement);
ps.setString(1, nom);
ps.setString(2, prenom);
ps.setString(3, codeClient);
ps.executeUpdate();
} catch (SQLException e) {
throw new EJBException("Impossible de stocker le bean dans la base de données : " + e.getMessage());
}
}
}
|
|
Java2html
|
Le code de notre EJB est long, nous allons décrire les principales méthodes :
- ejbCreate()
Tout EJB doit implémenter au moins une méthode ejbCreate() qui correspond à la signature d'une méthode create() de l'interface locale ( même signature signifie même paramètres ).
Lorsque le client va invoquer une méthode create() sur une instance de l'interface locale, le conteneur va rechercher dans l'EJB une méthode create() ayant la même signature.
Etant donné que les EJB entité gère la persistance, faire un create() devra crée le composant dans la base de données.
- ejbPostCreate()
Cette méthode est lié à la précédente, elle est invoquée par le conteneur juste après la méthode ejbCreate().
De la même façon que chaque méthode ejbCreate() doit correspondre à chaque méthode create() de l'interface locale, chaque méthode ejbCreate() doit être associée à une méthode ejbPostCreate().
- setEntityContext()
Le conteneur invoque la méthode setEntityContext() d'une instance juste après sa création.
La méthode commence par stocker l'objet EntityContext qu'elle reçoit en argument ( Cet objet permettra à notre EJB d'accéder aux services "offerts" par le conteneur ).
Etant donné que setEntityContext() est appelée une fois l'instance de l'EJB crée, c'est dans cette méthode que nous devons établir la connexion à la base de données.
- ejbLoad()
Lorsque le conteneur détermine qu'il faut synchroniser les variables d'instances du composant avec la base de données, il invoquera la méthode ejbLoad() qui ira lire les données depuis la table CLIENTS.
Cette méthode va donc faire un select SQL pour la lire la ligne identifiée par la clé primaire. Les données récupérées serviront à "rafraîchir" les variables d'instance de notre EJB.
note : seul le conteneur peut appeler cette méthode.
- ejbStore()
La méthode ejbStore() est invoquée chaque fois que le conteneur veut sauvegarder les valeurs des variables d'instance dans la base de données.
Cette méthode va donc faire un update SQL pour sauvegarder les valeurs des variables d'instance dans la table CLIENTS.
note : seul le conteneur peut appeler cette méthode.
- ejbFindByPrimaryKey() et ejbFindByNom() : Nos méthodes finder
Ces méthodes sont dites "finder" car elles permettent de retrouver les instances du composant ( C'est à dire créer un EJB à partir des données stockées dans la base de données ). Un peu à la manière de la méthode create(), les méthodes findByPrimaryKey() et findByNom() ont leur équivalent ejbFindByPrimaryKey() et ejbFindByNom() dans l'EJB.
Comme les méthodes create(), vous invoquerez findByPrimaryKey() et findByNom() sur l'interface locale et le conteneur se chargera d'appeler ejbFindByPrimaryKey() et ejbFindByNom() sur notre EJB.
note : La méthode ejbFindByPrimaryKey() doit être implémentée par tout EJB entité.
- ejbRemove()
La méthode ejbRemove() joue le rôle inverse de ejbCreate(). Si vous invoquez cette méthode, vous supprimerez l'entité, c'est à dire qu'un delete sera fait sur la ligne de la table CLIENTS correspondante.
- unsetEntityContext()
Avant que le conteneur ne détruise une instance d'une entité, il invoque cette méthode, c'est donc à cet endroit que l'ont doit refermer la connexion à la base de données.
- getNom(), getPrenom() et setPrenom() : nos business méthodes
Ces trois méthodes déclarées dans l'interface distante sont appelées "business methods", elles correspondent aux fonctionnalités de votre EJB. Voici ce qu'elles vont permettre :
- getNom() : Permet de récupérer le nom du client.
- getPrenom() : Permet de récupérer le prénom du client.
- setPrenom() : Permet de modifier le prénom du client.
-
-
Voici les différences que l'on a par rapport à nos descripteurs précédents :
- A la place de la balise
session que l'on avait dans nos descripteurs de déploiements précédents, nous avons ici une balise entity.
- Nous avons une balise
persistence-type qui va nous indiquer si la persistance est gérée par le conteneur ou par l'EJB.
- La balise
prim-key-class indique le type Java de la clé primaire.
- La balise
resource-ref contient les renseignements concernant une ressource utilisée par l'EJB, en l'occurrence, il s'agit de la DataSource. On a le nom JNDI, le type Java et qui gère l'autorisation d'accès à cette ressource.
| ejb-jar.xml |
|
<?xml version="1.0" encoding="ISO-8859-1"?>
<!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>
<description>Déscripteur de déploiement pour l'EJB client</description>
<display-name>EJB Client</display-name>
<enterprise-beans>
<entity>
<description>EJB Client ( BMP )</description>
<ejb-name>CompteurSimple</ejb-name>
<home>crm_bmp.ClientHome</home>
<remote>crm_bmp.Client</remote>
<ejb-class>crm_bmp.ClientBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<resource-ref>
<res-ref-name>jdbc/ClientDB</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>Client</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
|
-
Comme pour les EJB précédents, ce descripteur va définir quel sera le nom JNDI de l'objet qui est l'interface Home du bean.
Mais nous allons aussi définir que la ressource jdbc/ClientDB qui est mentionnée dans le code de notre EJB correspond, pour JOnAS, à la ressource qui a pour nom JNDI : conMySQL_CRM.
| jonas-ejb-jar.xml |
|
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE jonas-ejb-jar PUBLIC "-//ObjectWeb//DTD JOnAS 2.4//EN" "http://www.objectweb.org/jonas/dtds/jonas-ejb-jar_2_4.dtd">
<jonas-ejb-jar>
<jonas-session>
<ejb-name>Client</ejb-name>
<jndi-name>MyClient</jndi-name>
<jonas-resource>
<res-ref-name>jdbc/ClientDB</res-ref-name>
<jndi-name>conMySQL_CRM</jndi-name>
</jonas-resource>
</jonas-session>
</jonas-ejb-jar>
|
-
Aucune différence avec ce que l'on a vu dans les chapitres précédents. Voir le script fourni avec les sources.
-
Aucune différence avec ce que l'on a vu dans les chapitres précédents. Voir le script fourni avec les sources.
-
Test.java
|
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
|
package crm_bmp;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.transaction.UserTransaction;
import javax.rmi.PortableRemoteObject;
import java.util.Collection;
import java.util.Iterator;
public class Test {
public static void main(String args[]) {
try {
// Recherche de l'interface home de l'EJB
Context initialContext = new InitialContext();
Object objref = initialContext.lookup("MyClient");
// Référence à l'interface locale de l'EJB
ClientHome home = (ClientHome)PortableRemoteObject.narrow(objref, ClientHome.class);
// Création de deux clients dans la base de données
Client trom = home.create("STRAUMAT", "TRAUMAT", "Stephane");
Client hail = home.create("LGUEVARRA", "GUEVARRA", "Loreen");
Client pierre = home.create("PTRAUMAT", "TRAUMAT", "Pierrre");
// Objet de type client pour faire nos tests
Client test;
// Recherche d'un client par Primary key
test = home.findByPrimaryKey("LGUEVARRA");
System.out.println("Nom du client LGUEVARRA : " + test.getNom());
// Recherche par le nom de la personne
System.out.println("Liste des clients qui ont pour nom TRAUMAT :");
Collection cl = home.findByNom("TRAUMAT");
Iterator iLast = cl.iterator();
while (iLast.hasNext()) {
test = (Client)iLast.next();
System.out.println("->" + test.getPrenom());
}
// Création et suppression d'un client
test = home.create("TEST", "TEST", "TEST");
test.remove();
// Modification d'une donnée membre d'un EJB
pierre.setPrenom("Pierre");
} catch (Exception e) {
System.err.println("Erreur : " + e);
System.exit(2);
}
}
}
|
|
Java2html
|
Voici l'explication de ce que nous faisons dans cet exemple :
Tout d'abord, notre client va, comme dans tous nos autres exemples, rechercher l'interface locale de note EJB à l'aide de la méthode lookup().
Ensuite, pour créer 3 clients, nous allons tout simplement appeler le constructeur de l'interface locale. En faisant ainsi, le conteneur appellera les fonctions correspondantes de l'EJB pour que les clients soient crées dans la base de données.
On utilise après la méthode finder findByPrimaryKey pour trouver un composant existant. Cette fonction prend en paramètre la clé primaire de l'objet que l'on recherche et nous retourne une instance de cet objet.
Puis on utilise l'autre méthode finder : findByNom qui, elle, va nous retourner une Collection de clients qui ont le même nom.
Ensuite, l'utilisation de la méthode remove montre comment on détruit un objet. Attention, la destruction d'un objet signifie ici que l'objet sera détruit aussi sur le support de stockage, c'est à dire qu'un delete SQL sera fait dans la base.
Enfin, nous pouvons voir l'utilisation de la business method setPrenom qui va permettre de modifier le prénom d'un utilisateur.
Comme vous pouvez le constater, les méthodes ejbStore() et ejbLoad() ne sont jamais appelées par le client, c'est le conteneur qui s'en charge.
-
Pour réaliser la compilation, le packaging et le déploiement, nous allons utilisé le script build.bat que vous trouverez dans le répertoire "C:\java\dev\Client_BMP".
Ce script effectuera la compilation, le packaging et le déploiement de notre ejb et de notre client mais vous devrez ensuite éditer dans le fichier jonas.properties qui se trouve "C:\java\plateforme\jonas\config" pour modifier la ligne jonas.service.ejb.descriptors pour y ajouter Client.jar.
-
Déployons notre EJB et testons le avec notre application.
- cd C:\java\dev\Client_BMP
- build
- jonas start
- cd C:\java\dev\build
- jclient crm_bmp.Test
Voici le résultat :
Nom du client LGUEVARRA : GUEVARRA
Liste des clients qui ont pour nom TRAUMAT :
->Stephane
->Pierrre
Si on fait un select sur notre table CLIENTS, on obtient le résultat suivant :
| CODE_CLIENT |
NOM |
PRENOM |
| STRAUMAT |
TRAUMAT |
Stephane |
| LGUEVARRA |
GUEVARRA |
Loreen |
| PTRAUMAT |
TRAUMAT |
Pierre |
On voit bien que les insertions, les selections, les suppressions et les mises à jour se sont bien faites.
-
Nous avons pu voir dans ce chapitre comment gérer les objets persistants avec J2EE. Grâce à ce modèle, le code des applications clientes est complètement débarrassé des connexions aux bases de données et des requêtes SQL.
Dans le chapitre suivant, nous allons voir comment développer des EJB dont la persistance sera gérée directement par le conteneur, nous n'aurons donc plus à écrire les méthodes de synchronisation avec la base de données.
|
|