La technologie de persistance objets J2EE, les Entity,
a beaucoup évolué depuis sa création. Il reste pourtant toujours
une des bases de la théorie objet qui n'est pas couverte : l'héritage.
Cette faiblesse est souvent un argument utilisé par les détracteurs
des EJB Entity. Ce besoin est pourtant plus un confort des
technologies objets qu'une nécessité. Mais, comme avec l'ensemble
des technologies J2EE, un bon design patterns nous rendra accessible
cette fonctionnalité.
1. Problématique
Voici un exemple de modélisation que nous aimerions faire persister en
base de données avec les EJB CMP:
Sur ce diagramme de classe UML, il y a deux classes,
une première classe personne qui contient les attributs
id (identification), nom et prénom. Et une seconde classe salarié
qui hérite de la classe personne et qui ajoute l'attribut salaire.
Le salarié est une personne, le salaire est un attribut
qu'un salarié possède, mais qu'une personne ne possède pas forcément,
l'héritage se justifie donc ici. La méthode toString() a été ajoutée
pour des raisons techniques.
Voici un exemple d'utilisation de ces classes, TOTO est une personne,
il a comme attributs : id=1, nom=TOTO, prénom=Toto. Et BOB qui est un
salarié id=2, nom=BOB, prénom=Robert et salaire=12000.
Pour faire persister ces informations dans une base de données, nous
allons utiliser un modèle merisien MPD (Modèle physique de données):
La table PERSONNE contient les mêmes champs que les attributs
de la classe Personne, id est utilisé pour être la clé primaire.
La table SALARIE a la particularité de contenir une clé primaire
qui est également clé étrangère (PK/FK).
Voici les même données dans les deux tables:
Pour arriver à ce résultat, nous allons étudier cette modélisation:
De gauche à droite, nous trouvons:
Le client Programme console, web ou autres...
Le serveur J2EE avec un EJB session et deux Entity
La base de données où nous retrouvont nos deux tables
Ce diagramme représente une utilisation classique des Design Patterns
J2EE, Façade & Value Object (la classe Personne et Salarié)
aussi appelé DTO (Data Transfer Object).
Si vous n'êtes pas familiarisé avec ces deux Design Patterns, je vous
recommande, pour la Façade, la lecture de l'article
Design Patterns J2EE
et Design Patterns UML.
La technique du Value Object est, quand à elle, décrite dans l'article
Design Patterns J2EE.
L'idée dans cette architecture est de faire bénéficier au client d'une
réelle modélisation objet avec l'héritage que nous avons conçu
précédemment. Les classes Personne et Salarié seront de simples
classes Java que le développeur (le client) pourra utiliser sans
aucune contrainte.
Ces classes serviront aussi au transport d'informations entre le
client et le serveur grâce à la sérialisation Java.
Un EJB session se chargera de faire le travail désiré :
création, modification, recherche et suppression des objets.
Il déléguera les traitements à la base de données en utilisant
les EJB Entity adéquates.
3. Réalisation des objets sérialisés
3.1. Personne
Comme prévu, la classe personne est complètement neutre,
on peut noter son support de la sérialisation avec le mot clé
Serializable. Tous les attributs sont de la forme objet et
non pas natif Integer plutôt que int pour éviter les problèmes
de null de la base.
L'EJB session est utilisé dans cet exemple comme une façade de
tous les appels concernant les objets de type Personne
(et donc Salarie). Ses interfaces sont donc déclarées
en Remote pour les appels distants.
Les services suivants sont disponibles:
Find Recherche une personne (ou un salarié)
Create Création d'une personne (ou d'un salarié)
Update Modification d'une personne (ou d'un salarié)
Delete Suppression d'une personne (ou d'un salarié)
Voici la classe principale du projet, la facade, tout le travail
lui revient.
Il y a deux choses à observer lors de l'accès aux Entity:
La première chose, c'est la chaîne de référence utilisée dans le lookup.
Nous pouvons remarquer l'accès via "java:comp/env/ejb", j'ai configuré
dans les "ejb-local-ref" de l'EJB façade les références sur les
deux Entity. Cette manipulation permet de résoudre la localisation des
Entity dès le démarrage du serveur d'application. Ce type de paramètrage
évite les localisations des objets via le JNDI.
La seconde chose à remarque est l'absence des mots clés
PortableRemoteObject.narrow.Les deux CMP sont accédés par leur
interface Local et non par leur Remote. Cette optimisation permet de
faire des accès directs de classe à classe sans passer par les couches
réseaux ou autres. Ici, le cas est idéal, les Entity ne seront accédés
que par la façade.
Les implémentations font appel aux Entity en fonction du type
d'instance Personne envoyé.
La dernière chose à remarquer, est la gestion des exceptions, ici,
toutes les erreurs conduisent à une EJBException.
Ce type de fonctionnement provoque une annulation de transaction,
c'est ce qui est souhaitable ici, mais provoque aussi l'invalidation
de l'EJB Session pour rien. Une meilleure implémentation utilisera
des exceptions métiers pour aiguiller le cas d'une exception
normale ou pas.
C'est bon? Je n'ai pas perdu trop de lecteur ici? ;o)
Les deux EJB CMP sont utilisés pour la persistance des données,
un lien CMR unidirectionnel a été créé pour la navigation de
Salarié vers Personne. Ces deux EJB sont déclarés en local, seul
l'EJB session pourra accèder à ces deux objets.
Pour simplifier l'utilisation du modèle objet serveur, une classe utilitaire
a été ajoutée. Ce Design Pattern est appelé Wrapper
(ou Adapter) (ou Business Delegate). Vous pouvez trouver plus d'informations
sur l'article Design Patterns J2EE
et Design Patterns UML.
La méthode getFacade est intéressante, elle consiste à récupérer
l'interface Home de l'EJB Session Facade.
Après le premier appel, cette référence est conservée.
Cette technique simplifiée permet d'éviter de nombreuses localisations
sur le serveur. En plus élaboré et centralisé, vous la trouverez sous le
nom de Service Locator
package com.dvp.heritage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import com.dvp.heritage.ejb.PersonneFacade;
import com.dvp.heritage.ejb.PersonneFacadeHome;
publicclass PersonneUtil {
privatestatic PersonneFacadeHome personneFacadeHome;
protectedstatic PersonneFacade getFacade() throws Exception {
try {
//Si il n'y a pas de référence ou si la référence n'est plus validereturn personneFacadeHome.create();
}
catch (Exception ex) {
Context ctx = new InitialContext();
Object ref = ctx.lookup("com/dvp/heritage/PersonneFacade");
personneFacadeHome = (PersonneFacadeHome) PortableRemoteObject.narrow(ref,
PersonneFacadeHome.class);
}
return personneFacadeHome.create();
}
publicstatic com.dvp.heritage.Personne find(Integer id) throws Exception {
return getFacade().find(id);
}
publicstaticvoid create(Personne personne) throws Exception {
getFacade().create(personne);
}
publicstaticvoid delete(Integer id) throws Exception {
getFacade().delete(id);
}
publicstaticvoid update(Personne personne) throws Exception {
getFacade().update(personne);
}
}
7. Exemple d'utilisation
Nous pouvons enfin faire plusieurs tests pour valider le
code précédent. Voici les différentes étapes :
Création de deux personnes : Toto (Personne) et Bob (Salarié)
Affichage des données en utilisant le mécanisme du polymorphisme de la méthode toString().
Modification des deux personnes sans tenir compte de leur type.
La création fonctionne à merveille, le flux minimum a été généré. Bob
nécéssite deux INSERT. Regardons maintenant les opérations de type find:
SELECT ID, NOM, PRENOM FROM PERSONNE WHERE ID=1
SELECT ID, SALAIRE FROM SALARIE WHERE ID=1
Connection.commit
SELECT ID, NOM, PRENOM FROM PERSONNE WHERE ID=2
SELECT ID, SALAIRE FROM SALARIE WHERE ID=2
Connection.commit
La consultation des données se fait toujours en interrogeant les deux
tables, c'est normale pour Bob, c'est du travail supplémentaire pour Toto.
Le flux n'est pas optimal ici. Un moyen de s'en sortir est d'ajouter
une information de la classe réelle dans la table Personne.
Regardons maintenant les modifications:
SELECT ID, NOM, PRENOM FROM PERSONNE WHERE ID=1
UPDATE PERSONNE SET PRENOM='Toto modif' WHERE ID = 1
Connection.commit
SELECT ID, NOM, PRENOM FROM PERSONNE WHERE ID=2
SELECT ID, SALAIRE FROM SALARIE WHERE ID=2
UPDATE PERSONNE SET PRENOM='Bob modif' WHERE ID = 2
Connection.commit
Le serveur d'application commence par consulter les données de la base
et modifie uniquement les attributs changés. La modification dans les
deux cas est bien optimisée.
Etudions maintenant le dernier cas, la supression:
SELECT ID, SALAIRE FROM SALARIE WHERE ID=2
SELECT ID, NOM, PRENOM FROM PERSONNE WHERE ID=2
DELETEFROM SALARIE WHERE ID = 2
SELECT ID, SALAIRE FROM SALARIE WHERE ID=2
DELETEFROM PERSONNE WHERE ID = 2
Connection.commit
SELECT ID, NOM, PRENOM FROM PERSONNE WHERE ID=1
SELECT ID, SALAIRE FROM SALARIE WHERE ID=1
DELETEFROM PERSONNE WHERE ID = 1
Connection.commit
De nombreux select pourraient être évités ici, ils sont dus au findObject
qui sont appelés lors de ce traitement.
8. Conclusion
Avec cette illustration, vous venez de voir qu'il est tout à fait
possible d'utiliser un vrai mécanisme d'héritage avec vos projets
J2EE utilisant des objets Entiy. Cette technique, comme les designs
patterns J2EE, est cependant fastidieuse à implémenter. Ce travail sera
rentabilisé par une simplification du développement côté client.
Les derniers paragraphes mettent en évidence quelques lacunes des
flux SQL, ces dernières peuvent être améliorées par l'ajout d'une
information de type dans les tables de la base de données.
Toutes vos remarques et vos commentaires seront les bienvenus
pour améliorer cet article.
Vous pouvez trouvez l'ensemble des sources ici
(Projet JBuilder, facilement transportable sur d'autres IDE).
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.
La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de
l'autorisation de l'auteur.