Introduction▲
La technologie de persistance objet 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 objet qu'une nécessité. Mais, comme avec l'ensemble des technologies J2EE, un bon design patterns nous rendra accessible cette fonctionnalité.
I. 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êmes données dans les deux tables :
I-A. Script SQL▲
CREATE
TABLE
PERSONNE (
ID INTEGER
NOT
NULL
,
NOM VARCHAR
(
100
)
,
PRENOM VARCHAR
(
100
)
,
CONSTRAINT
PK_PERSONNE PRIMARY
KEY
(
ID)
)
;
CREATE
TABLE
SALARIE (
ID INTEGER
NOT
NULL
,
SALAIRE DOUBLE
,
CONSTRAINT
PK_SALARIE PRIMARY
KEY
(
ID)
)
;
ALTER
TABLE
SALARIE
ADD
CONSTRAINT
FK_SALARIE_PERSONNE FOREIGN
KEY
(
ID)
REFERENCES
PERSONNE (
ID)
;
II. Modélisation▲
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 retrouvons 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, quant à 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.
III. Réalisation des objets sérialisés▲
III-A. 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.
package
com.dvp.heritage;
import
java.io.Serializable;
public
class
Personne implements
Serializable {
private
Integer id;
private
String nom;
private
String prenom;
public
Personne
(
) {
}
public
Personne
(
Integer id, String nom, String prenom) {
this
.id =
id;
this
.nom =
nom;
this
.prenom =
prenom;
}
public
Integer getId
(
) {
return
id;
}
public
String getNom
(
) {
return
nom;
}
public
String getPrenom
(
) {
return
prenom;
}
public
void
setId
(
Integer id) {
this
.id =
id;
}
public
void
setNom
(
String nom) {
this
.nom =
nom;
}
public
void
setPrenom
(
String prenom) {
this
.prenom =
prenom;
}
public
String toString
(
) {
return
return
"Type="
+
this
.getClass
(
).getName
(
) +
" id="
+
id +
" nom="
+
nom +
" prenom="
+
prenom;
}
}
III-B. Salarie▲
L'implémentation est identique à la classe Personne, nous pouvons constater le lien d'héritage avec la classe ancêtre avec le mot clé extends.
package
com.dvp.heritage;
public
class
Salarie extends
Personne {
private
Double salaire;
public
Salarie
(
) {
super
(
);
}
public
Salarie
(
Integer id, String nom, String prenom, Double salaire) {
super
(
id, nom, prenom);
this
.salaire=
salaire;
}
public
Double getSalaire
(
) {
return
salaire;
}
public
void
setSalaire
(
Double salaire) {
this
.salaire =
salaire;
}
public
String toString
(
) {
return
super
.toString
(
)+
" salaire="
+
salaire;
}
}
IV. Réalisation de l'EJB session (Facade)▲
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é).
IV-A. Interface Remote: PersonneFacade▲
Rien de spécial pour cette classe.
package
com.dvp.heritage.ejb;
import
java.rmi.RemoteException;
import
javax.ejb.EJBObject;
import
com.dvp.heritage.Personne;
public
interface
PersonneFacade extends
EJBObject {
public
Personne find
(
Integer id) throws
RemoteException;
public
void
create
(
Personne personne) throws
RemoteException;
public
void
delete
(
Integer id) throws
RemoteException;
public
void
update
(
Personne personne) throws
RemoteException;
}
IV-B. Implémentation Bean: PersonneBean▲
Voici la classe principale du projet, la façade, 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éseau 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 lecteurs ici ? ;o)
package
com.dvp.heritage.ejb;
import
javax.ejb.CreateException;
import
javax.ejb.EJBException;
import
javax.ejb.ObjectNotFoundException;
import
javax.ejb.SessionBean;
import
javax.ejb.SessionContext;
import
javax.naming.Context;
import
javax.naming.InitialContext;
import
javax.naming.NamingException;
import
com.dvp.heritage.Personne;
import
com.dvp.heritage.Salarie;
public
class
PersonneFacadeBean implements
SessionBean {
SessionContext sessionContext;
public
static
PersonneCMPHome getPersonneCMPHome
(
) throws
NamingException {
Context ctx =
new
InitialContext
(
);
return
(
PersonneCMPHome) ctx.lookup
(
"java:comp/env/ejb/com/dvp/heritage/PersonneCMP"
);
}
public
static
SalarieCMPHome getSalaireCMPHome
(
) throws
NamingException {
Context ctx =
new
InitialContext
(
);
return
(
SalarieCMPHome) ctx.lookup
(
"java:comp/env/ejb/com/dvp/heritage/SalarieCMP"
);
}
public
void
ejbCreate
(
) throws
CreateException {
}
public
void
ejbRemove
(
) {
}
public
void
ejbActivate
(
) {
}
public
void
ejbPassivate
(
) {
}
public
void
setSessionContext
(
SessionContext sessionContext) {
this
.sessionContext =
sessionContext;
}
public
com.dvp.heritage.Personne find
(
Integer id) {
try
{
PersonneCMP personneCMP =
getPersonneCMPHome
(
).findByPrimaryKey
(
id);
try
{
SalarieCMP salarieCMP =
getSalaireCMPHome
(
).findByPrimaryKey
(
id);
Salarie salarie =
new
Salarie
(
id, personneCMP.getNom
(
),
personneCMP.getPrenom
(
),
salarieCMP.getSalaire
(
));
return
salarie;
}
catch
(
ObjectNotFoundException ex) {
return
new
Personne
(
id, personneCMP.getNom
(
),
personneCMP.getPrenom
(
));
}
}
catch
(
Exception ex) {
//peut mieux faire...
throw
new
EJBException
(
ex);
}
}
public
void
create
(
Personne personne) {
try
{
PersonneCMP personneCMP =
getPersonneCMPHome
(
).create
(
personne.getId
(
),
personne.getNom
(
), personne.getPrenom
(
));
if
(
personne instanceof
Salarie) {
Salarie salarie =
(
Salarie) personne;
SalarieCMP salarieCMP =
getSalaireCMPHome
(
).create
(
personne.getId
(
),
salarie.getSalaire
(
));
}
}
catch
(
Exception ex) {
//Peut mieux faire...
throw
new
EJBException
(
ex);
}
}
public
void
delete
(
Integer id) {
try
{
PersonneCMP personneCMP =
getPersonneCMPHome
(
).findByPrimaryKey
(
id);
try
{
SalarieCMP salarieCMP =
getSalaireCMPHome
(
).findByPrimaryKey
(
id);
salarieCMP.remove
(
);
}
catch
(
ObjectNotFoundException ex) {
}
personneCMP.remove
(
);
}
catch
(
Exception ex) {
//Peut mieux faire...
throw
new
EJBException
(
ex);
}
}
public
void
update
(
Personne personne) {
try
{
PersonneCMP personneCMP =
getPersonneCMPHome
(
).findByPrimaryKey
(
personne.
getId
(
));
personneCMP.setNom
(
personne.getNom
(
));
personneCMP.setPrenom
(
personne.getPrenom
(
));
if
(
personne instanceof
Salarie) {
Salarie salarie =
(
Salarie) personne;
SalarieCMP salarieCMP =
getSalaireCMPHome
(
).findByPrimaryKey
(
personne.
getId
(
));
salarieCMP.setSalaire
(
salarie.getSalaire
(
));
}
}
catch
(
Exception ex) {
//Peut mieux faire...
throw
new
EJBException
(
ex);
}
}
}
IV-C. Interface Home: PersonneHome▲
Rien de spécial pour cette classe.
package
com.dvp.heritage.ejb;
import
java.rmi.RemoteException;
import
javax.ejb.CreateException;
public
interface
PersonneFacadeHome extends
javax.ejb.EJBHome {
public
PersonneFacade create
(
) throws
CreateException, RemoteException;
}
IV-D. Descripteur de déploiement▲
On peut remarquer le système transactionnel positionné à Required et la définition des deux références locales aux CMP.
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar>
<enterprise-beans>
<session>
<display-name>
PersonneFacade</display-name>
<ejb-name>
PersonneFacade</ejb-name>
<home>
com.dvp.heritage.ejb.PersonneFacadeHome</home>
<remote>
com.dvp.heritage.ejb.PersonneFacade</remote>
<ejb-class>
com.dvp.heritage.ejb.PersonneFacadeBean</ejb-class>
<session-type>
Stateless</session-type>
<transaction-type>
Container</transaction-type>
<ejb-local-ref>
<description />
<ejb-ref-name>
ejb/com/dvp/heritage/PersonneCMP</ejb-ref-name>
<ejb-ref-type>
Entity</ejb-ref-type>
<local-home>
com.dvp.heritage.ejb.PersonneCMPHome</local-home>
<local>
com.dvp.heritage.ejb.PersonneCMP</local>
<ejb-link>
PersonneCMP</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<description />
<ejb-ref-name>
ejb/com/dvp/heritage/SalarieCMP</ejb-ref-name>
<ejb-ref-type>
Entity</ejb-ref-type>
<local-home>
com.dvp.heritage.ejb.SalarieCMPHome</local-home>
<local>
com.dvp.heritage.ejb.SalarieCMP</local>
<ejb-link>
SalarieCMP</ejb-link>
</ejb-local-ref>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>
PersonneFacade</ejb-name>
<method-name>
*</method-name>
</method>
<trans-attribute>
Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
V. Réalisation des EJB Entity CMP▲
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.
V-A. Interface Locale : PersonneCMP▲
Définition des attributs de Personne.
package
com.dvp.heritage.ejb;
import
javax.ejb.EJBLocalObject;
public
interface
PersonneCMP extends
EJBLocalObject {
public
Integer getId
(
);
public
void
setNom
(
String nom);
public
String getNom
(
);
public
void
setPrenom
(
String prenom);
public
String getPrenom
(
);
}
V-B. Implémentation Bean: PersonneCMPBean▲
Rien de spécial pour cette implémentation.
package
com.dvp.heritage.ejb;
import
javax.ejb.CreateException;
import
javax.ejb.EntityBean;
import
javax.ejb.EntityContext;
import
javax.ejb.RemoveException;
abstract
public
class
PersonneCMPBean implements
EntityBean {
EntityContext entityContext;
public
Integer ejbCreate
(
Integer id, String nom, String prenom) throws
CreateException {
setId
(
id);
setNom
(
nom);
setPrenom
(
prenom);
return
null
;
}
public
void
ejbPostCreate
(
Integer id, String nom, String prenom) throws
CreateException {
}
public
void
ejbRemove
(
) throws
RemoveException {
}
public
abstract
void
setId
(
Integer id);
public
abstract
void
setNom
(
String nom);
public
abstract
void
setPrenom
(
String prenom);
public
abstract
Integer getId
(
);
public
abstract
String getNom
(
);
public
abstract
String getPrenom
(
);
public
void
ejbLoad
(
) {
}
public
void
ejbStore
(
) {
}
public
void
ejbActivate
(
) {
}
public
void
ejbPassivate
(
) {
}
public
void
unsetEntityContext
(
) {
this
.entityContext =
null
;
}
public
void
setEntityContext
(
EntityContext entityContext) {
this
.entityContext =
entityContext;
}
}
V-C. Interface Home : PersonneCMPHome▲
Rien de spécial pour cette implémentation.
package
com.dvp.heritage.ejb;
import
javax.ejb.CreateException;
import
javax.ejb.FinderException;
public
interface
PersonneCMPHome extends
javax.ejb.EJBLocalHome {
public
PersonneCMP create
(
Integer id, String nom, String prenom) throws
CreateException;
public
PersonneCMP findByPrimaryKey
(
Integer id) throws
FinderException;
}
V-D. Interface Locale : SalarieCMP▲
Définition des attributs de Salarie uniquement, on peut remarquer ici les accesseurs à la classe Personne.
package
com.dvp.heritage.ejb;
import
javax.ejb.EJBLocalObject;
public
interface
SalarieCMP extends
EJBLocalObject {
public
Integer getId
(
);
public
void
setSalaire
(
Double salaire);
public
Double getSalaire
(
);
public
void
setPersonneCMP
(
PersonneCMP personneCMP);
public
PersonneCMP getPersonneCMP
(
);
}
V-E. Implémentation Bean : SalarieCMPBean▲
Rien de spécial pour cette implémentation. Là aussi, on retrouve les accesseurs à la classe Personne.
package
com.dvp.heritage.ejb;
import
javax.ejb.CreateException;
import
javax.ejb.EntityBean;
import
javax.ejb.EntityContext;
import
javax.ejb.RemoveException;
abstract
public
class
SalarieCMPBean implements
EntityBean {
EntityContext entityContext;
public
Integer ejbCreate
(
Integer id, Double salaire) throws
CreateException {
setId
(
id);
setSalaire
(
salaire);
return
null
;
}
public
void
ejbPostCreate
(
Integer id, Double salaire) throws
CreateException {
}
public
void
ejbRemove
(
) throws
RemoveException {
}
public
abstract
void
setId
(
Integer id);
public
abstract
void
setSalaire
(
Double salaire);
public
abstract
void
setPersonneCMP
(
PersonneCMP personneCMP);
public
abstract
Integer getId
(
);
public
abstract
Double getSalaire
(
);
public
abstract
PersonneCMP getPersonneCMP
(
);
public
void
ejbLoad
(
) {
}
public
void
ejbStore
(
) {
}
public
void
ejbActivate
(
) {
}
public
void
ejbPassivate
(
) {
}
public
void
unsetEntityContext
(
) {
this
.entityContext =
null
;
}
public
void
setEntityContext
(
EntityContext entityContext) {
this
.entityContext =
entityContext;
}
}
V-F. Interface Home : SalarieCMPHome▲
Rien de spécial pour cette implémentation.
package
com.dvp.heritage.ejb;
import
javax.ejb.CreateException;
import
javax.ejb.FinderException;
import
javax.ejb.EJBLocalHome;
public
interface
SalarieCMPHome extends
EJBLocalHome {
public
SalarieCMP create
(
Integer id, Double salaire) throws
CreateException;
public
SalarieCMP findByPrimaryKey
(
Integer id) throws
FinderException;
}
V-G. Descripteur de déploiement▲
On peut remarquer le système transactionnel positionné à Required, la définition locale des CMP et la relation CMR entre les deux Entity.
<?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>
<display-name>
PersonneCMP</display-name>
<ejb-name>
PersonneCMP</ejb-name>
<local-home>
com.dvp.heritage.ejb.PersonneCMPHome</local-home>
<local>
com.dvp.heritage.ejb.PersonneCMP</local>
<ejb-class>
com.dvp.heritage.ejb.PersonneCMPBean</ejb-class>
<persistence-type>
Container</persistence-type>
<prim-key-class>
java.lang.Integer</prim-key-class>
<reentrant>
False</reentrant>
<abstract-schema-name>
PersonneCMP</abstract-schema-name>
<cmp-field>
<field-name>
id</field-name>
</cmp-field>
<cmp-field>
<field-name>
nom</field-name>
</cmp-field>
<cmp-field>
<field-name>
prenom</field-name>
</cmp-field>
<primkey-field>
id</primkey-field>
</entity>
<entity>
<display-name>
SalarieCMP</display-name>
<ejb-name>
SalarieCMP</ejb-name>
<local-home>
com.dvp.heritage.ejb.SalarieCMPHome</local-home>
<local>
com.dvp.heritage.ejb.SalarieCMP</local>
<ejb-class>
com.dvp.heritage.ejb.SalarieCMPBean</ejb-class>
<persistence-type>
Container</persistence-type>
<prim-key-class>
java.lang.Integer</prim-key-class>
<reentrant>
False</reentrant>
<abstract-schema-name>
SalarieCMP</abstract-schema-name>
<cmp-field>
<field-name>
id</field-name>
</cmp-field>
<cmp-field>
<field-name>
salaire</field-name>
</cmp-field>
<primkey-field>
id</primkey-field>
</entity>
</enterprise-beans>
<relationships>
<ejb-relation>
<ejb-relation-name>
salarie-personne</ejb-relation-name>
<ejb-relationship-role>
<description>
salarieCMP</description>
<ejb-relationship-role-name>
SalarieRelationshipRole</ejb-relationship-role-name>
<multiplicity>
One</multiplicity>
<relationship-role-source>
<description>
salarieCMP</description>
<ejb-name>
SalarieCMP</ejb-name>
</relationship-role-source>
<cmr-field>
<description>
personneCMP</description>
<cmr-field-name>
personneCMP</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<description>
personneCMP</description>
<ejb-relationship-role-name>
PersonneRelationshipRole</ejb-relationship-role-name>
<multiplicity>
One</multiplicity>
<relationship-role-source>
<description>
personneCMP</description>
<ejb-name>
PersonneCMP</ejb-name>
</relationship-role-source>
</ejb-relationship-role>
</ejb-relation>
</relationships>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>
PersonneCMP</ejb-name>
<method-name>
*</method-name>
</method>
<trans-attribute>
Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>
SalarieCMP</ejb-name>
<method-name>
*</method-name>
</method>
<trans-attribute>
Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
VI. Réalisation des objets clients▲
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;
public
class
PersonneUtil {
private
static
PersonneFacadeHome personneFacadeHome;
protected
static
PersonneFacade getFacade
(
) throws
Exception {
try
{
//s’il n'y a pas de référence ou si la référence n'est plus valide
return
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
(
);
}
public
static
com.dvp.heritage.Personne find
(
Integer id) throws
Exception {
return
getFacade
(
).find
(
id);
}
public
static
void
create
(
Personne personne) throws
Exception {
getFacade
(
).create
(
personne);
}
public
static
void
delete
(
Integer id) throws
Exception {
getFacade
(
).delete
(
id);
}
public
static
void
update
(
Personne personne) throws
Exception {
getFacade
(
).update
(
personne);
}
}
VII. 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 ;
- affichage à nouveau ;
- suppression des deux personnes.
package
com.dvp.heritage;
public
class
HeritageMain {
public
static
void
main
(
String argv[]) throws
Exception {
create
(
);
try
{
show
(
);
update
(
);
show
(
);
}
finally
{
delete
(
);
}
}
public
static
void
show
(
) throws
Exception {
Personne p1 =
PersonneUtil.find
(
new
Integer
(
1
));
Personne p2 =
PersonneUtil.find
(
new
Integer
(
2
));
System.out.println
(
"p1 = "
+
p1);
System.out.println
(
"p2 = "
+
p2);
}
public
static
void
delete
(
) throws
Exception {
PersonneUtil.delete
(
new
Integer
(
2
));
PersonneUtil.delete
(
new
Integer
(
1
));
}
public
static
void
update
(
) throws
Exception {
Personne p1 =
PersonneUtil.find
(
new
Integer
(
1
));
Personne p2 =
PersonneUtil.find
(
new
Integer
(
2
));
p1.setPrenom
(
p1.getNom
(
) +
" modif"
);
PersonneUtil.update
(
p1);
p2.setPrenom
(
p2.getNom
(
) +
" modif"
);
PersonneUtil.update
(
p2);
}
public
static
void
create
(
) throws
Exception {
Personne toto =
new
Personne
(
new
Integer
(
1
), "Toto"
, "Toto"
);
Salarie bob =
new
Salarie
(
new
Integer
(
2
), "Bob"
, "Robert"
, new
Double
(
12000
));
PersonneUtil.create
(
toto);
PersonneUtil.create
(
bob);
}
}
Voici le résultat de la console lors de l'exécution du programme
p1 = Type=com.dvp.heritage.Personne id=1 nom=Toto prenom=Toto
p2 = Type=com.dvp.heritage.Salarie id=2 nom=Bob prenom=Robert salaire=12000.0
p1 = Type=com.dvp.heritage.Personne id=1 nom=Toto prenom=Toto modif
p2 = Type=com.dvp.heritage.Salarie id=2 nom=Bob prenom=Bob modif salaire=12000.0
Voici les données après création :
Et les données après modification :
VII-A. Analyse des flux SQL▲
Nous allons étudier le flux SQL généré lors de l'exécution du programme.
Création de Toto et de Bob
INSERT
INTO
PERSONNE (
ID, NOM, PRENOM)
VALUES
(
1
, 'Toto'
, 'Toto'
)
Connection
.commit
INSERT
INTO
PERSONNE (
ID, NOM, PRENOM)
VALUES
(
2
, 'Bob'
, 'Robert'
)
INSERT
INTO
SALARIE (
ID, SALAIRE)
VALUES
(
2
, 12000
.0
)
Connection
.commit
La création fonctionne à merveille, le flux minimum a été généré. Bob nécessite 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 normal 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.
Étudions maintenant le dernier cas, la suppression :
SELECT
ID, SALAIRE FROM
SALARIE WHERE
ID=
2
SELECT
ID, NOM, PRENOM FROM
PERSONNE WHERE
ID=
2
DELETE
FROM
SALARIE WHERE
ID =
2
SELECT
ID, SALAIRE FROM
SALARIE WHERE
ID=
2
DELETE
FROM
PERSONNE WHERE
ID =
2
Connection
.commit
SELECT
ID, NOM, PRENOM FROM
PERSONNE WHERE
ID=
1
SELECT
ID, SALAIRE FROM
SALARIE WHERE
ID=
1
DELETE
FROM
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.
VIII. 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 trouver l'ensemble des sources ici (Projet JBuilder, facilement transportable sur d'autres IDE).