Jee
Jee
par l'exemple
1/290
INTRODUCTION
Ce document reprend un prcdent document crit en 2010 et intitul "Introduction Java EE avec Netbeans 6.8 et le serveur
Glassfish v3". Celui-ci amne principalement les changements suivants :
la partie JSF (Java Server Faces) est traite dans un document part : " Introduction Java Server Faces, Primefaces et
Primefaces mobile " disponible l'URL [[Link] On y utilise des caractristiques
de la version 2 de JSF,
les projets sont des projets Maven.
Java EE signifie Java Enterprise Edition. J2EE (Java 2 Enterprise Edition) tait le terme prcdent. J2EE dsigne les
technologies Java utilises pour crer des applications d'entreprise avec le JDK 1.4 ou antrieur. En mme temps que le JDK 1.5
amenait de nombreuses nouveauts dans le langage Java, Sun introduisait de nouvelles technologies s'appuyant sur ce langage
amlior afin de remdier des lacunes de ces mmes technologies dans J2EE. Le terme Java EE 5 a alors t utilis pour dsigner
l'ensemble des technologies qui concourent crer une application d'entreprise avec la plate-forme Java. Au moment de la mise
jour de ce document, la dernire version de Java EE est Java EE 6.
Les livres d'Antonio Goncalves :
sont d'excellents livres pour dcouvrir les technologies de Java EE 5 et Java EE 6. Toutes les technologies importantes de Java EE y
sont passes en revue dans le contexte d'tudes de cas ralistes. L'auteur a un site [[Link] que le lecteur
est invit visiter.
Le document prsent tudie certaines des technologies de Java EE 5. Nous y crons une application basique trois couches
[prsentation, mtier, accs aux donnes] dcline en plusieurs versions :
Une application web avec les technologies suivantes :
EJB3 ou Spring, JPA/Hibernate, JPA/EclipseLink : pour crer diffrentes couches d'accs aux donnes ;
Une application client / serveur avec les technologies suivantes :
2/290
1.
2.
3.
4.
5.
Persistance Java 5 par la pratique : [[Link] - donne les outils pour construire la couche
d'accs aux donnes avec JPA (Java Persistence API) ;
Introduction au langage Java [[Link] - pour les dbutants ;
Introduction par l'exemple Java Server Faces, Primefaces et Primefaces mobile
[[Link]
Introduction au langage Java et l'cosystme Spring au travers d'une tude de cas
[[Link]
Exploiter une base de donnes relationnelle avec l'cosystme Spring
[[Link]
Ces supports de cours sont par la suite rfrencs [ref1] [ref2] [ref3] [ref4] [ref5].
Ce document est accompagn d'un support de TD disponible l'adresse [[Link]
Serge Tah, septembre 2016.
Notes :
En utilisant ce document, vous devez faire preuve de souplesse. Vous allez peut-tre utiliser une version de Netbeans
diffrente de celle utilise pour l'criture de ce document. Ainsi certaines copies d'cran peuvent avoir chang ou disparu.
Il en est de mme pour les options de menu. A chaque fois, il faut vous adapter ;
3/290
4/290
5/290
6/290
7/290
utilisateur
Couche mtier
[metier]
Couche interface
utilisateur [ui]
1
Donnes
la couche [1], appele ici [ui] (User Interface) est la couche qui dialogue avec l'utilisateur, via une interface graphique
Swing, une interface console ou une interface web. Elle a pour rle de fournir des donnes provenant de l'utilisateur la
couche [2] ou bien de prsenter l'utilisateur des donnes fournies par la couche [2] ;
la couche [2], appele ici [metier] est la couche qui applique les rgles dites mtier, c.a.d. la logique spcifique de
l'application, sans se proccuper de savoir d'o viennent les donnes qu'on lui donne, ni o vont les rsultats qu'elle
produit ;
la couche [3], appele ici [DAO] (Data Access Object) est la couche qui fournit la couche [2] des donnes prenregistres (fichiers, bases de donnes, services web, ...) et qui enregistre certains des rsultats fournis par la couche [2] ;
Il existe diffrentes possibilits pour implmenter la couche [DAO] en relation avec une base de donnes. Examinons-en quelquesunes :
utilisateur
Couche ui
[ui]
1
Couche mtier
[metier]
2
Couche
[JDBC]
Couche d'accs
aux donnes
[DAO]
Base de
Donnes
3
La couche [JDBC] ci-dessus est la couche standard utilise en Java pour accder des bases de donnes. Elle isole la couche [DAO]
du SGBD qui gre la base de donnes. On peut thoriquement changer de SGBD sans changer le code de la couche [DAO]. Malgr
cet avantage, l'API JDBC prsente certains inconvnients :
toutes les oprations sur le SGBD sont susceptibles de lancer l'exception contrle (checked) SQLException. Ceci oblige
le code appelant (la couche [DAO] ici) les entourer par des try / catch rendant ainsi le code assez lourd.
la couche [DAO] n'est pas compltement insensible au SGBD. Ceux-ci ont par exemple des mthodes propritaires quant
la gnration automatique de valeurs de cls primaires que la couche [DAO] ne peut ignorer. Ainsi lors de l'insertion d'un
enregistrement :
avec Oracle, la couche [DAO] doit d'abord obtenir une valeur pour la cl primaire de l'enregistrement puis insrer
celui-ci ;
avec SQL Server, la couche [DAO] insre l'enregistrement qui se voit donner automatiquement une valeur de cl
primaire par le SGBD, valeur rendue la couche [DAO] ;
Ces diffrences peuvent tre gommes via l'utilisation de procdures stockes. Dans l'exemple prcdent, la couche [DAO]
appellera une procdure stocke dans Oracle ou SQL Server qui prendra en compte les particularits du SGBD. Celles-ci
seront caches la couche [DAO]. Nanmoins, si changer de SGBD n'impliquera pas de rcrire la couche [DAO], cela
implique quand mme de rcrire les procdures stockes. Cela peut ne pas tre considr comme rdhibitoire.
De multiples efforts ont t faits pour isoler la couche [DAO] des aspects propritaires des SGBD. Une solution ayant eu un vrai
succs dans ce domaine ces dernires annes, est celle d'Hibernate :
4 Objets image
de la BD
Couche
[Hibernate]
5
Couche
[JDBC]
6
Base de
Donnes
7
8/290
La couche [Hibernate] vient se placer entre la couche [DAO] crite par le dveloppeur et la couche [JDBC]. Hibernate est un ORM
(Object Relational Mapper), un outil qui fait le pont entre le monde relationnel des bases de donnes et celui des objets manipuls
par Java. Le dveloppeur de la couche [DAO] ne voit plus la couche [JDBC] ni les tables de la base de donnes dont il veut exploiter
le contenu. Il ne voit que l'image objet de la base de donnes, image objet fournie par la couche [Hibernate]. Le pont entre les tables
de la base de donnes et les objets manipuls par la couche [DAO] est fait principalement de deux faons :
par des annotations Java dans le code, technique disponible seulement depuis le JDK 1.5 ;
La couche [Hibernate] est une couche d'abstraction qui se veut la plus transparente possible. L'idal vis est que le dveloppeur de
la couche [DAO] puisse ignorer totalement qu'il travaille avec une base de donnes. C'est envisageable si ce n'est pas lui qui crit la
configuration qui fait le pont entre le monde relationnel et le monde objet. La configuration de ce pont est assez dlicate et
ncessite une certaine habitude.
La couche [4] des objets, image de la BD est appele "contexte de persistance". Une couche [DAO] s'appuyant sur Hibernate fait
des actions de persistance (CRUD, create - read - update - delete) sur les objets du contexte de persistance, actions traduites par
Hibernate en ordres SQL excuts par la couche JDBC. Pour les actions d'interrogation de la base (le SQL Select), Hibernate
fournit au dveloppeur, un langage HQL (Hibernate Query Language) pour interroger le contexte de persistance [4] et non la BD
elle-mme.
Hibernate est populaire mais complexe matriser. La courbe d'apprentissage souvent prsente comme facile est en fait assez raide.
Ds qu'on a une base de donnes avec des tables ayant des relations un--plusieurs ou plusieurs--plusieurs, la configuration du
pont relationnel / objets n'est pas la porte du premier dbutant venu. Des erreurs de configuration peuvent conduire des
applications peu performantes.
Devant le succs des produits ORM, Sun le crateur de Java, a dcid de standardiser une couche ORM via une spcification
appele JPA (Java Persistence API) apparue en mme temps que Java 5. La spcification JPA a t implmente par divers produits :
Hibernate, Toplink, OpenJpa, .... Avec JPA, l'architecture prcdente devient la suivante :
4
Objets image
de la BD
Interface
[JPA]
Implmentation JPA
[Hibernate / ...]
5
Couche
[JDBC]
6
Base de
Donnes
7
La couche [DAO] dialogue maintenant avec la spcification JPA, un ensemble d'interfaces. Le dveloppeur y a gagn en
standardisation. Avant, s'il changeait sa couche ORM, il devait galement changer sa couche [DAO] qui avait t crite pour
dialoguer avec un ORM spcifique. Maintenant, il va crire une couche [DAO] qui va dialoguer avec une couche JPA. Quelque soit
le produit qui implmente celle-ci, l'interface de la couche JPA prsente la couche [DAO] reste la mme.
Dans ce document, nous utiliserons une couche [DAO] s'appuyant sur une couche JPA/Hibernate ou JPA/EclipseLink. Par ailleurs
nous utiliserons le framework Spring pour lier ces couches entre-elles.
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Couche
[JDBC]
6
Spring
Le grand intrt de Spring est qu'il permet de lier les couches par configuration et non dans le code. Ainsi si l'implmentation JPA /
Hibernate doit tre remplace par une implmentation Hibernate sans JPA, parce que par exemple l'application s'excute dans un
environnement JDK 1.4 qui ne supporte pas JPA, ce changement d'implmentation de la couche [DAO] n'a pas d'impact sur le
code de la couche [mtier]. Seul le fichier de configuration Spring qui lie les couches entre elles doit tre modifi.
Avec Java EE 5, une autre solution existe : implmenter les couches [metier] et [DAO] avec des EJB3 (Enterprise Java Bean version
3) :
9/290
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Couche
[JDBC]
6
conteneur Ejb3
Nous verrons que cette solution n'est pas trs diffrente de celle utilisant Spring. L'environnement Java EE5 est disponible au sein
de serveurs dits serveurs d'applications tels que Sun Application Server 9.x (Glassfish), Jboss Application Server, Oracle Container for Java
(OC4J), ... Un serveur d'applications est essentiellement un serveur d'applications web. Il existe galement des environnements EE 5
dits "stand-alone", c.a.d. pouvant tre utiliss en-dehors d'un serveur d'applications. C'est le cas de JBoss EJB3 ou OpenEJB.
Dans un environnement EE5, les couches sont implmentes par des objets appels EJB (Enterprise Java Bean). Dans les
prcdentes versions d'EE, les EJB (EJB 2.x) taient rputs difficiles mettre en uvre et tester et considrs comme peu
performants. On distingue les EJB2.x "entity" et les EJB2.x "session". Pour faire court, un EJB2.x "entity" est l'image d'une ligne de
table de base de donnes et EJB2.x "session" un objet utilis pour implmenter les couches [metier], [DAO] d'une architecture
multicouche. L'un des principaux reproches faits aux couches implmentes avec des EJB est qu'elles ne sont utilisables qu'au sein
de conteneurs EJB, un service dlivr par l'environnement EE. Cet environnement, plus complexe mettre en oeuvre qu'un
environnement SE (Standard Edition), peut dcourager le dveloppeur faire frquemment des tests. Nanmoins, il existe des
environnements de dveloppement Java qui facilitent l'utilisation d'un serveur d'applications en automatisant le dploiement des
EJB sur le serveur : Eclipse, Netbeans, JDeveloper, IntelliJ IDEA. Nous utiliserons ici Netbeans 8.2 et le serveur d'application Glassfish 4.1.
Le framework Spring est n en raction la complexit des EJB2. Spring fournit dans un environnement SE un nombre important
des services habituellement fournis par les environnements EE. Ainsi dans la partie "Persistance de donnes", Spring fournit les
pools de connexion et les gestionnaires de transactions dont ont besoin les applications. L'mergence de Spring a favoris la culture
des tests unitaires, devenus plus faciles mettre en oeuvre dans le contexte SE que dans le contexte EE. Spring permet
l'implmentation des couches d'une application par des objets Java classiques (POJO, Plain Old/Ordinary Java Object), permettant
la rutilisation de ceux-ci dans un autre contexte. Enfin, il intgre de nombreux outils tiers de faon assez transparente, notamment
des outils de persistance tels que Hibernate, EclipseLink, Ibatis, ...
Java EE5 a t conu pour corriger les lacunes de la spcification EJB2. Les EJB 2.x sont devenus les EJB3. Ceux-ci sont des
POJOs tagus par des annotations qui en font des objets particuliers lorsqu'ils sont au sein d'un conteneur EJB3. Dans celui-ci,
l'EJB3 va pouvoir bnficier des services du conteneur (pool de connexions, gestionnaire de transactions, ...). En-dehors du
conteneur EJB3, l'EJB3 devient un objet Java normal. Ses annotations EJB sont ignores.
Ci-dessus, nous avons reprsent Spring et un conteneur EJB3 comme infrastructure (framework) possible de notre architecture
multicouche. C'est cette infrastructure qui dlivrera les services dont nous avons besoin : un pool de connexions et un gestionnaire
de transactions.
avec Spring, les couches seront implmentes avec des POJOs. Ceux-ci auront accs aux services de Spring (pool
de connexions, gestionnaire de transaction) par injection de dpendances dans ces POJOs : lors de la
construction de ceux-ci, Spring leur injecte des rfrences sur les services dont il vont avoir besoin ;
avec le conteneur EJB3, les couches seront implmentes avec des EJB. Une architecture en couches
implmentes avec des EJB3 est peu diffrente de celles implmentes avec des POJO instancis par Spring.
Nous trouverons beaucoup de ressemblances ;
Couche
[web]
1
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Couche
[JDBC]
6
Spring ou Ejb3
10/290
2.1
2.1.1
Maven
Introduction
Maven est intgr dans Netbeans et nous allons l'utiliser pour une seule de ses caractristiques : la gestion des bibliothques d'un
projet. Celles-ci sont formes de l'ensemble des archives jars qui doivent tre dans le Classpath du projet. Elles peuvent tre trs
nombreuses. Par exemple, nos projets futurs vont utiliser l'ORM (Object Relational Mapper) Hibernate. Cet ORM est compos de
dizaines d'archives jar. L'intrt de Maven est qu'il nous affranchit de les connatre toutes. Il nous suffit d'indiquer dans notre projet
que nous avons besoin d'Hibernate, en donnant toutes les informations utiles pour trouver l'archive principale de cet ORM. Maven
tlcharge alors galement toutes les bibliothques ncessaires Hibernate. On appelle cela les dpendances d'Hibernate. Une
bibliothque ncessaire Hibernate peut elle mme dpendre d'autres archives. Celles-ci seront galement tlcharges. Toutes ces
bibliothques sont places dans un dossier appel le dpt local de Maven.
Un projet Maven est facilement partageable. Si on le transfre d'un poste un autre et que les dpendances du projet ne sont pas
prsentes dans le dpt local du nouveau poste, elles seront tlcharges.
Maven peut tre utilis seul ou intgr un EDI (Environnement de Dveloppement Intgr) tel Netbeans ou Eclipse.
Crons un projet Maven dans Netbeans :
11/290
12/290
Le fichier [[Link]] configure le projet Maven. Son contenu est pour l'instant le suivant :
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.
les lignes 4-7 dfinissent l'objet (artifact) Java qui va tre cr par le projet Maven, ici une archive jar. Ces informations
proviennent de l'assistant qui a t utilis lors de la cration du projet :
13/290
ligne 4 : [groupId] : une information qui ressemble un nom de package. Ainsi les bibliothques du framework Spring ont
groupId=[Link], celles du framework JSF ont groupId=[Link],
ligne 5 : [artifactId] : le nom de l'objet Maven. Dans le groupe [[Link]] on trouve ainsi les artifactId
suivants : spring-context, spring-core, spring-beans, ... Dans le groupe [[Link]], on trouve l'artifactId JSF-api,
ligne 6 : [version] : n de version de l'artifact Maven. Ainsi l'artifact [Link]-core a les versions suivantes :
2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1, ...
ligne 7 : [packaging] : la forme prise par l'artifact, le plus souvent war ou jar.
Notre projet Maven va gnrer un [jar] (ligne 7) dans le groupe [[Link]] (ligne 4), nomm [mv-exemple] (ligne 5) et de version [1.0SNAPSHOT] (ligne 6). Ces quatre informations dfinissent de faon unique un artifact Maven.
Les lignes 8-21 listent les dpendances du projet Maven, c'est dire la liste des bibliothques ncessaires au projet. Chaque
bibliothque est dfinie par les quatre informations (groupId, artifactId, version, packaging). Lorsque l'information packaging est
absente, le packaging jar est utilis. On peut y ajouter une autre information, scope qui fixe quels moments de la vie du projet on a
besoin de la bibliothque. La valeur par dfaut est compile qui indique que la bibliothque est ncessaire la compilation et
l'excution. La valeur test signifie que la bibliothque n'est ncessaire que pour excuter les tests du projet. C'est le cas ici avec les
bibliothques JUnit (ligne 13) et Hamcrest (ligne 19). Si ces dpendances ne sont pas prsentes dans le dpt local du poste (notion
dfinie plus loin), elles seront tlcharges.
2.1.2
Excution du projet
14/290
package [Link];
public class Main {
public static void main(String[] args) {
[Link]("Hello world!");
}
}
En [1], le projet Maven est construit puis excut [2]. Les logs dans la console Netbeans sont les suivants :
1.
2.
3.
4.
5.
6.
7.
8.
cd U:\Temp\16-08-23\mv-exemple; "JAVA_HOME=C:\\Program Files\\Java\\jdk1.8.0_77" "M2_HOME=C:\\Program Files\\apache-maven3.3.9" cmd /c "\"\"C:\\Program Files\\apache-maven-3.3.9\\bin\\[Link]\" -[Link]=\"-classpath %classpath
[Link]\" -[Link]=\"C:\\Program Files\\Java\\jdk1.8.0_77\\bin\\[Link]\"
-[Link]=runtime -[Link]=\"C:\\Program Files\\NetBeans 8.1\\java\\maven-nblib\\[Link]\" -[Link]=UTF-8 process-classes [Link]:exec-maven-plugin:1.2.1:exec\""
Scanning for projects...
-----------------------------------------------------------------------Building mv-exemple 1.0-SNAPSHOT
-------------------------------------------------------------------------- maven-resources-plugin:2.6:resources (default-resources) @ mv-exemple ---
15/290
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
2.1.3
2.1.4
Nous avons dit que Maven tlchargeait les dpendances ncessaires au projet et les stockait localement. On peut explorer ce dpt
local [1-3] :
16/290
2.1.5
en [3], on voit l'artifact Maven [[Link], mv-exemple, [Link]] cr par notre projet lors du [Clean and Build] ;
Apprenons maintenant chercher un artifact avec Maven. Partons de la liste des dpendances actuelles du projet :
Les dpendances supprimes n'apparaissent plus dans [[Link]]. Maintenant, recherchons-les dans les dpts Maven.
17/290
En [8], les dpendances ajoutes apparaissent dans le projet. Le fichier [[Link]] reflte ces changements :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
18/290
14.
<scope>test</scope>
15.
</dependency>
16.
<properties>
17.
<[Link]>UTF-8</[Link]>
18.
<[Link]>1.8</[Link]>
19.
<[Link]>1.8</[Link]>
20.
</properties>
21. </project>
On notera que fichier [[Link]] ne mentionne pas la dpendance [hamcrest-core-1.3] que nous voyons en [8]. Cela parce que c'est
une dpendance de JUnit 4.12 et non du projet lui-mme. Cela est signal par une icne diffrente dans la branche [Dependencies].
Elle a t tlcharge automatiquement.
Supposons maintenant qu'on ne connaisse pas le [groupId] de l'artifact que l'on dsire. Par exemple, on veut utiliser Hibernate
comme ORM (Object Relational Mapper) et c'est tout ce qu'on sait. On peut aller alors sur le site [[Link] :
En [2], on peut taper des mots cls. Tapons hibernate et lanons la recherche [3-4] :
19/290
en [5-6], nous obtenons le code Maven coller dans le fichier [[Link]]. Nous le faisons :
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.
20/290
en [7], la nouvelle dpendance apparat. Une icne spciale indique qu'elle n'est pas encore tlcharge ;
pour tlcharger la nouvelle dpendance, on fait un [Clean and Build] [8] du projet ;
Maven entreprend alors le tlchargement des nouvelles dpendances. Le projet volue comme suit :
en [9], la dpendance [hibernate-core-5.2.2-Final]. Dans le dpt o il a t trouv, cet [artifactId] est lui aussi dcrit par un
fichier [[Link]]. Ce fichier a t lu et Maven a dcouvert que l'[artifactId] avait des dpendances. Il les tlcharge
galement. Il fera cela pour chaque [artifactId] tlcharg. Au final, on trouve en [10] des dpendances qu'on n'avait pas
demandes directement. Elles sont signales par une icne diffrente de celle de l'[artifactId] principal ;
Dans ce document, nous utilisons Maven principalement pour cette caractristique. Cela nous vite de connatre toutes les
dpendances d'une bibliothque que l'on veut utiliser. On laisse Maven les grer. Par ailleurs, en partageant un fichier [[Link]]
entre dveloppeurs, on est assur que chaque dveloppeur utilise bien les mmes bibliothques.
Dans les exemples qui suivront, nous donnerons toujours le fichier [[Link]] utilis. Le lecteur n'aura qu' l'utiliser pour se trouver
dans les mmes conditions que le document. Par ailleurs les projets Maven sont reconnus par les principaux IDE Java (Eclipse,
Netbeans, IntelliJ, JDeveloper). Aussi le lecteur pourra-t-il utiliser son IDE favori pour tester les exemples.
21/290
3 JPA en rsum
Nous nous proposons d'introduire JPA (Java Persistence API) avec quelques exemples. JPA est dvelopp dans le cours :
Persistance Java 5 par la pratique : [[Link] - donne les outils pour construire la couche
d'accs aux donnes avec JPA
Nous prsentons tout d'abord les fondements de JPA. On attendra le paragraphe 3.4, page 33 pour crer une application
exemple. D'ici l, il n'y a que de la lecture faire.
3.1
Le lecteur est invit relire le dbut de ce document (paragraphe 1, page 8) qui explique le rle de la couche JPA dans une
architecture en couches. La couche JPA s'insre dans les couches d'accs aux donnes :
Objets image
de la BD
Interface
[JPA]
Implmentation JPA
[Hibernate / ...]
Couche
[JDBC]
Base de
Donnes
La couche [DAO] dialogue avec la spcification JPA. Quelque soit le produit qui implmente celle-ci, l'interface de la couche JPA
prsente la couche [DAO] reste la mme. Nous prsentons dans la suite quelques exemples tirs de [ref1] qui nous permettront
de construire notre propre couche JPA.
3.2
3.2.1
[Link]
JPA exemples
Exemple 1 - Reprsentation objet d'une table unique
La table [personne]
Considrons une base de donnes ayant une unique table [personne] dont le rle est de mmoriser quelques informations sur des
individus :
ID
cl primaire de la table
VERSION
version de la ligne dans la table. A chaque fois que la personne est modifie, son n de version est
incrment.
NOM
nom de la personne
PRENOM
son prnom
DATENAISSANCE
sa date de naissance
22/290
MARIE
NBENFANTS
[Link]
L'entit [Personne]
Programme de test
console [main]
3
4
Objets image
de la BD
Interface
[JPA]
5
Implmentation
6 [Eclipselink
/ Hibernate]
Couche
[JDBC]
Base de
Donnes
7
La couche JPA [5] doit faire un pont entre le monde relationnel de la base de donnes [7] et le monde objet [4] manipul par les
programmes Java [3]. Ce pont est fait par configuration et il y a deux faons de le faire :
1. avec des fichiers XML. C'tait quasiment l'unique faon de faire jusqu' l'avnement du JDK 1.5 ;
2. avec des annotations Java depuis le JDK 1.5 ;
Dans ce document, nous utiliserons exclusivement la seconde mthode.
L'objet [Personne] image de la table [personne] prsente prcdemment pourrait tre le suivant :
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.
...
@SuppressWarnings("unused")
@Entity
@Table(name="Personne")
23/290
53. }
La configuration se fait l'aide d'annotations Java @Annotation. Les annotations Java sont soit exploites par le compilateur, soit
par des outils spcialiss, au moment de l'excution. En-dehors de l'annotation de la ligne 3 destine au compilateur, toutes les
annotations sont ici destines l'implmentation JPA utilise, Hibernate ou EclipseLink. Elles seront donc exploites l'excution.
En l'absence des outils capables de les interprter, ces annotations sont ignores. Ainsi la classe [Personne] ci-dessus pourrait tre
exploite dans un contexte hors JPA.
Il faut distinguer deux cas d'utilisation des annotations JPA dans une classe C associe une table T :
1.
2.
la table T existe dj : les annotations JPA doivent alors reproduire l'existant (nom et dfinition des colonnes, contraintes
d'intgrit, cls trangres, cls primaires, ...) ;
la table T n'existe pas et elle peut alors tre cre d'aprs les annotations trouves dans la classe C ;
Le cas 2 est le plus facile grer. A l'aide des annotations JPA, nous indiquons la structure de la table T que nous voulons. Le cas 1
est souvent plus complexe. La table T a pu tre construite, il y a longtemps, en-dehors de tout contexte JPA. Sa structure peut alors
tre mal adapte au pont relationnel / objet de JPA. Pour simplifier, nous nous plaons dans le cas 2 o la table T associe la
classe C va tre cre d'aprs les annotations JPA de la classe C.
Commentons les annotations JPA de la classe [Personne] :
ligne 4 : l'annotation @Entity est la premire annotation indispensable. Elle se place avant la ligne qui dclare la classe et
indique que la classe en question doit tre gre par la couche de persistance JPA. En l'absence de cette annotation, toutes
les autres annotations JPA seraient ignores ;
ligne 5 : l'annotation @Table dsigne la table de la base de donnes dont la classe est une reprsentation. Son principal
argument est name qui dsigne le nom de la table. En l'absence de cet argument, la table portera le nom de la classe, ici
[Personne]. Dans notre exemple, l'annotation @Table est donc superflue ;
ligne 8 : l'annotation @Id sert dsigner le champ dans la classe qui est image de la cl primaire de la table. Cette
annotation est obligatoire. Elle indique ici que le champ id de la ligne 11 est l'image de la cl primaire de la table ;
ligne 9 : l'annotation @Column sert faire le lien entre un champ de la classe et la colonne de la table dont le champ est
l'image. L'attribut name indique le nom de la colonne dans la table. En l'absence de cet attribut, la colonne porte le mme
nom que le champ. Dans notre exemple, l'argument name n'tait donc pas obligatoire. L'argument nullable=false indique
que la colonne associe au champ ne peut avoir la valeur NULL et que donc le champ doit avoir ncessairement une
valeur ;
ligne 10 : l'annotation @GeneratedValue indique comment est gnre la cl primaire lorsqu'elle est gnre
automatiquement par le SGBD. Ce sera le cas dans tous nos exemples. Ce n'est pas obligatoire. Ainsi notre personne
pourrait avoir un n tudiant qui servirait de cl primaire et qui ne serait pas gnr par le SGBD mais fix par
l'application. Dans ce cas, l'annotation @GeneratedValue serait absente. L'argument strategy indique comment est
gnre la cl primaire lorsqu'elle est gnre par le SGBD. Les SGBD n'ont pas tous la mme technique de gnration des
valeurs de cl primaire. Par exemple :
Firebird
SQL server
Oracle
La couche JPA doit gnrer des ordres SQL diffrents selon les SGBD pour crer le gnrateur de valeurs. On lui indique
par configuration le type de SGBD qu'elle a grer. Du coup, elle peut savoir quelle est la stratgie habituelle de
gnration de valeurs de cl primaire de ce SGBD. L'argument strategy = [Link] indique la couche
JPA qu'elle doit utiliser cette stratgie habituelle. Cependant, si on souhaite changer d'implmentation JPA, il ne faut pas
utiliser cette stratgie mais en imposer une la couche JPA :
la statgie [[Link]] peut tre utilise avec les SGBD MySQL, SQL Server, IBM DB2 et
les versions rcentes d'Oracle ;
la statgie [[Link]] peut tre utilise avec les SGBD Firebird, Postgresql et les versions
anciennes d'Oracle ;
ligne 14 : l'annotation @Version dsigne le champ qui sert grer les accs concurrents une mme ligne de la table.
Pour comprendre ce problme d'accs concurrents une mme ligne de la table [personne], supposons qu'une
application web permette la mise jour d'une personne et examinons le cas suivant :
24/290
Au temps T1, un utilisateur U1 entre en modification dune personne P. A ce moment, le nombre denfants est 0. Il
passe ce nombre 1 mais avant quil ne valide sa modification, un utilisateur U2 entre en modification de la mme
personne P. Puisque U1 na pas encore valid sa modification, U2 voit sur son cran le nombre denfants 0. U2
passe le nom de la personne P en majuscules. Puis U1 et U2 valident leurs modifications dans cet ordre. Cest la
modification de U2 qui va gagner : dans la base, le nom va passer en majuscules et le nombre denfants va rester
zro alors mme que U1 croit lavoir chang en 1.
La notion de version de personne nous aide rsoudre ce problme. On reprend le mme cas dusage :
Au temps T1, un utilisateur U1 entre en modification dune personne P. A ce moment, le nombre denfants est 0 et
la version V1. Il passe le nombre denfants 1 mais avant quil ne valide sa modification, un utilisateur U2 entre en
modification de la mme personne P. Puisque U1 na pas encore valid sa modification, U2 voit le nombre denfants
0 et la version V1. U2 passe le nom de la personne P en majuscules. Puis U1 et U2 valident leurs modifications
dans cet ordre. Avant de valider une modification, on vrifie que celui qui modifie une personne P dtient la mme
version que la personne P actuellement en base. Ce sera le cas de lutilisateur U1. Sa modification est donc accepte
et la version de la personne modifie passe de V1 V2 pour noter le fait que la personne a subi un changement. Lors
de la validation de la modification de U2, on va sapercevoir que U2 dtient une version V1 de la personne P, alors
quactuellement la version de celle-ci en base est V2. On va alors pouvoir dire lutilisateur U2 que quelquun est
pass avant lui et quil doit repartir de la nouvelle version de la personne P. Il le fera, rcuprera une personne P de
version V2 qui a maintenant un enfant, passera le nom en majuscules, validera. Sa modification sera accepte si la
personne P enregistre a toujours la version V2. Au final, les modifications faites par U1 et U2 seront prises en
compte alors que dans le cas dusage sans version, lune des modifications tait perdue.
La couche [DAO] de l'application cliente peut grer elle-mme la version de la classe [Personne]. A chaque fois qu'il y
aura une modification d'un objet P, la version de cet objet sera incrmente de 1 dans la table. L'annotation
@Version permet de transfrer cette gestion la couche JPA. Le champ concern n'a nul besoin de s'appeler version
comme dans l'exemple. Il peut porter un nom quelconque.
Les champs correspondant aux annotations @Id et @Version sont des champs prsents cause de la persistance.
On n'en aurait pas besoin si la classe [Personne] n'avait pas besoin d'tre persiste. On voit donc qu'un objet n'a pas
la mme reprsentation selon qu'il a besoin ou non d'tre persist.
ligne 17 : de nouveau l'annotation @Column pour donner des informations sur la colonne de la table [personne] associe
au champ nom de la classe Personne. On trouve ici deux nouveaux arguments :
unique=true indique que le nom d'une personne doit tre unique. Cela va se traduire dans la base de donnes
par l'ajout d'une contrainte d'unicit sur la colonne NOM de la table [personne] ;
length=30 fixe 30 le nombre de caractres de la colonne NOM. Cela signifie que le type de cette colonne sera
VARCHAR(30) ;
ligne 24 : l'annotation @Temporal sert indiquer quel type SQL donner une colonne / champ de type date / heure. Le
type [Link] dsigne une date seule sans heure associe. Les autres types possibles sont [Link]
pour coder une heure et [Link] pour coder une date avec heure ;
ligne 6 : la classe implmente l'interface Serializable. La srialisation d'un objet consiste le transformer en une suite de bits.
La dsrialisation est l'opration inverse. La srialisation / dsrialisation est notamment utilise dans les applications client /
serveur o des objets sont changs via le rseau. Les applications clientes ou serveur sont ignorantes de cette opration
qui est faite de faon transparente par les JVM. Pour qu'elle soit possible, il faut cependant que les classes des objets
changs soit " tagues " avec le mot cl Serializable ;
ligne 37 : un constructeur de la classe. On notera que les champs id et version ne font pas partie des paramtres. En effet,
ces deux champs sont grs par la couche JPA et non par l'application ;
lignes 51 et au-del : les mthodes get et set de chacun des champs de la classe. Il est noter que les annotations JPA
peuvent tre places sur les mthodes get des champs au lieu d'tre places sur les champs eux-mmes. La place des
annotations indique le mode que doit utiliser JPA pour accder aux champs :
si les annotations sont mises au niveau champ, JPA accdera directement aux champs pour les lire ou les crire ;
si les annotations sont mises au niveau get, JPA accdera aux champs via les mthodes get / set pour les lire ou
les crire ;
C'est la position de l'annotation @Id qui fixe la position des annotations JPA d'une classe. Place au niveau champ, elle
indique un accs direct aux champs et place au niveau get, un accs aux champs via les get et set. Les autres annotations
doivent alors tre places de la mme faon que l'annotation @Id.
25/290
3.2.2
Les tests de la couche JPA peuvent tre faits avec l'architecture suivante :
Programme de test
console [main]
3
Objets image
de la BD
4
Interface
[JPA]
5
Implmentation
6 [Hibernate]
Couche
[JDBC]
Base de
Donnes
7
en [7] : la base de donnes qui sera gnre partir des annotations de l'entit [Personne] ainsi que de configurations
complmentaires faites dans un fichier appel [[Link]]
en [5, 6] : une couche JPA implmente par Hibernate
en [4] : l'entit [Personne]
en [3] : un programme de test de type console
A l'excution, le fichier [META-INF/[Link]] est cherch dans le Classpath de l'application. Il doit donc s'y trouver.
Examinons la configuration de la couche JPA faite dans le fichier [[Link]] de notre projet :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <persistence version="1.0" xmlns="[Link]
3.
<persistence-unit name="JPA" transaction-type="RESOURCE_LOCAL">
4.
<!-- provider -->
5.
<provider>[Link]</provider>
6.
<properties>
7.
<!-- Classes persistantes -->
8.
<property name="[Link]" value="class, hbm" />
9.
<!-- logs SQL
10.
<property name="hibernate.show_sql" value="true"/>
11.
<property name="hibernate.format_sql" value="true"/>
12.
<property name="use_sql_comments" value="true"/>
13.
-->
14.
<!-- connexion JDBC -->
15.
<property name="[Link].driver_class" value="[Link]" />
16.
<property name="[Link]" value="jdbc:mysql://localhost:3306/JPA" />
17.
<property name="[Link]" value="JPA" />
18.
<property name="[Link]" value="JPA" />
19.
<!-- cration automatique du schma -->
20.
<property name="[Link]" value="create" />
21.
<!-- Dialecte -->
22.
<property name="[Link]" value="[Link].MySQL5InnoDBDialect" />
23.
<!-- proprits DataSource c3p0 -->
24.
<property name="hibernate.c3p0.min_size" value="5" />
25.
<property name="hibernate.c3p0.max_size" value="20" />
26.
<property name="[Link]" value="300" />
27.
<property name="hibernate.c3p0.max_statements" value="50" />
28.
<property name="hibernate.c3p0.idle_test_period" value="3000" />
29.
</properties>
30.
</persistence-unit>
31. </persistence>
Pour comprendre cette configuration, il nous faut revenir sur l'architecture de l'accs aux donnes de notre application :
26/290
Programme de test
console [main]
3
Couche
[JPA/Hibernate]
4
Pool de
connexions
[c3p0] 5
Couche
[JDBC]
6
Base de
Donnes
7
Maintenant voyons comment le fichier [[Link]] configure les couches [4, 5, 6] ci-dessus :
ligne 10 : pour afficher ou non les ordres SQL mis par Hibernate sur le SGBD. Ceci est utile lors de la phase
d'apprentissage. A cause du pont relationnel / objet, l'application travaille sur des objets persistants sur lesquels
elle applique des oprations de type [persist, merge, remove]. Il est trs intressant de savoir quels sont les ordres
SQL rellement mis sur ces oprations. En les tudiant, peu peu on en vient deviner les ordres SQL
qu'Hibernate va gnrer lorsqu'on fait telle opration sur les objets persistants et le pont relationnel / objet
commence prendre consistance dans l'esprit ;
ligne 11 : les ordres SQL affichs sur la console peuvent tre formats joliment pour rendre leur lecture plus
aise ;
lignes 24, 25 : le nombre minimal (dfaut 3) et maximal de connexions (dfaut 15) dans le pool. Le nombre initial
de connexions par dfaut est 3 ;
ligne 26 : dure maximale en milli-secondes d'attente d'une demande de connexion de la part du client. Pass ce
dlai, c3p0 lui renverra une exception ;
27/290
ligne 27 : pour accder la BD, Hibernate utilise des ordres SQL prpars (PreparedStatement) que c3p0 peut
mettre en cache. Cela signifie que si l'application demande une seconde fois un ordre SQL prpar dj en cache,
celui-ci n'aura pas besoin d'tre prpar (la prparation d'un ordre SQL a un cot) et celui qui est en cache sera
utilis. Ici, on indique le nombre maximal d'ordres SQL prpars que le cache peut contenir, toutes connexions
confondues (un ordre SQL prpar appartient une connexion) ;
ligne 28 : frquence de vrification en milli-secondes de la validit des connexions. Une connexion du pool peut
devenir invalide pour diverses raisons (le pilote JDBC invalide la connexion parce qu'elle est trop longue, le pilote
JDBC prsente des " bugs ", ...) ;
ligne 20 : on demande ici, qu' l'initialisation de l'unit de persistance, la base de donnes image des objets @Entity soit
gnre.
Hibernate a dsormais tous les outils pour mettre les ordres SQL de gnration des tables de la base de donnes :
la configuration des objets @Entity lui permet de connatre les tables gnrer ;
les lignes 15-18 et 24-28 lui permettent d'obtenir une connexion avec le SGBD ;
la ligne 22 lui permet de savoir quel dialecte SQL utiliser pour gnrer les tables ;
Le fichier [[Link]] utilis ici recre une base neuve chaque nouvelle excution de l'application. Les tables sont recres
(create table) aprs avoir t dtruites (drop table) si elles existaient. On notera que ce n'est videmment pas faire avec une base en
production.
3.2.3
[Link]
1.
alter table jpa06_article
2.
drop
3.
foreign key FKFFBDD9D8ECCE8750;
4.
5.
drop table if exists jpa06_article;
6.
7.
drop table if exists jpa06_categorie;
8.
9.
create table jpa06_article (
10.
id bigint not null auto_increment,
11.
version integer not null,
12.
nom varchar(30),
13.
categorie_id bigint not null,
14.
primary key (id)
15.
) ENGINE=InnoDB;
16.
17.
create table jpa06_categorie (
18.
id bigint not null auto_increment,
19.
version integer not null,
20.
nom varchar(30),
21.
primary key (id)
22.
) ENGINE=InnoDB;
23.
24.
alter table jpa06_article
25.
add index FKFFBDD9D8ECCE8750 (categorie_id),
26.
add constraint FKFFBDD9D8ECCE8750
27.
foreign key (categorie_id)
28. references jpa06_categorie (id);
Un article A(id, version, nom) appartient exactement une catgorie C(id, version, nom). Une catgorie C peut contenir 0, 1 ou
plusieurs articles. On a une relation un--plusieurs (Categorie -> Article) et la relation inverse plusieurs--un (Article ->
Categorie). Cette relation est matrialise par la cl trangre que possde la table [article] sur la table [categorie] (lignes 24-28 de la
DDL).
[Link]
package entites;
...
@Entity
28/290
5. @Table(name="jpa05_hb_article")
6. public class Article implements Serializable {
7.
8.
// champs
9.
@Id
10. @GeneratedValue(strategy = [Link])
11. private Long id;
12.
13. @SuppressWarnings("unused")
14. @Version
15. private int version;
16.
17. @Column(length = 30)
18. private String nom;
19.
20. // relation principale Article (many) -> Category (one)
21. // implmente par une cl trangre (categorie_id) dans Article
22. // 1 Article a ncessairement 1 Categorie (nullable=false)
23. @ManyToOne(fetch=[Link])
24. @JoinColumn(name = "categorie_id", nullable = false)
25. private Categorie categorie;
26.
27. // constructeurs
28. public Article() {
29. }
30.
31. // getters et setters
32. ...
33. // toString
34. public String toString() {
35.
return [Link]("Article[%d,%d,%s,%d]", id, version, nom, [Link]());
36. }
37.
38. }
ligne 23 : l'annotation ManyToOne. Le Many se rapporte l'@Entity Article dans lequel on se trouve et le One
l'@Entity Categorie (ligne 25). Une catgorie (One) peut avoir plusieurs articles (Many) ;
ligne 24 : l'annotation ManyToOne dfinit la colonne cl trangre dans la table [article]. Elle s'appellera (name)
categorie_id et chaque ligne devra avoir une valeur dans cette colonne (nullable=false) ;
ligne 25 : la catgorie laquelle appartient l'article. Lorsqu'un article sera mis dans le contexte de persistance, on
demande ce que sa catgorie n'y soit pas mise immdiatement (fetch=[Link], ligne 23) ;
package entites;
...
@Entity
@Table(name="jpa05_hb_categorie")
public class Categorie implements Serializable {
// champs
@Id
@GeneratedValue(strategy = [Link])
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// relation inverse Categorie (one) -> Article (many) de la relation Article (many) -> Categorie (one)
// cascade insertion Categorie -> insertion Articles
// cascade maj Categorie -> maj Articles
// cascade suppression Categorie -> suppression Articles
@OneToMany(mappedBy = "categorie", cascade = { [Link] })
private Set<Article> articles = new HashSet<Article>();
// constructeurs
public Categorie() {
}
29/290
29.
30.
// getters et setters
31. ...
32.
// toString
33.
public String toString() {
34.
return [Link]("Categorie[%d,%d,%s]", id, version, nom);
35.
}
36.
37.
// association bidirectionnelle Categorie <--> Article
38.
public void addArticle(Article article) {
39.
// l'article est ajout dans la collection des articles de la catgorie
40.
[Link](article);
41.
// l'article change de catgorie
42.
[Link](this);
43.
}
44. }
ligne 23 : l'annotation @OneToMany dsigne une relation un--plusieurs. Le One dsigne l'@Entity
[Categorie] dans laquelle on se trouve, le Many le type [Article] de la ligne 24 : une (One) catgorie a plusieurs
(Many) articles ;
ligne 23 : l'annotation est l'inverse (mappedBy) de l'annotation ManyToOne place sur le champ categorie de
l'@Entity Article : mappedBy=categorie. La relation ManyToOne place sur le champ categorie de l'@Entity
Article est la relation principale. Elle est indispensable. Elle matrialise la relation de cl trangre qui lie
l'@Entity Article l'@Entity Categorie. La relation OneToMany place sur le champ articles de l'@Entity Categorie
est la relation inverse. Elle n'est pas indispensable. C'est une commodit pour obtenir les articles d'une
catgorie. Sans cette commodit, ces articles seraient obtenus par une requte JPQL ;
ligne 23 : [Link] demande que les oprations (persist, merge, remove) faites sur une @Entity
Categorie soient cascades sur ses articles ;
ligne 24 : les articles d'une catgorie seront placs dans un objet de type Set<Article>. Le type Set n'accepte pas
les doublons. Ainsi on ne peut pas mettre deux fois le mme article dans l'objet Set<Article>. Que veut dire "le
mme article" ? Pour dire que l'article a est le mme que l'article b, Java utilise l'expression [Link](b). Dans la
classe Object, mre de toutes les classes, [Link](b) est vraie si a==b, c.a.d. si les objets a et b ont le mme
emplacement mmoire. On pourrait vouloir dire que les articles a et b sont les mmes s'ils ont le mme nom.
Dans ce cas, le dveloppeur doit redfinir deux mthodes dans la classe [Article] :
equals : qui doit rendre vrai si les deux articles ont le mme nom ;
hashCode : doit rendre une valeur entire identique pour deux objets [Article] que la mthode equals
considre comme gaux. Ici, la valeur sera construite partir du nom de l'article. La valeur rendue par
hashCode peut tre un entier quelconque. Elle est utilise dans diffrents conteneurs d'objets,
notamment les dictionnaires (Hashtable) ;
La relation OneToMany peut utiliser d'autres types que le Set pour stocker le Many, des objets List, par
exemple.
ligne 38 : la mthode [addArticle] nous permet d'ajouter un article une catgorie. La mthode prend soin de mettre jour
les deux extrmits de la relation OneToMany qui lie [Categorie] [Article] ;
Maintenant que nous avons dfini le rle des entits JPA, nous allons apprendre les manipuler via l'API JPA.
3.3
Interface [JPA] =
EntityManager
Client JPA
1
Base de
Donnes
4
Objets image de la BD =
Contexte de persistance
30/290
Nous savons que le couche JPA [2] cre un pont objet [3] / relationnel [4]. On appelle " contexte de persistance " l'ensemble des
objets grs par la couche JPA dans le cadre de ce pont objet / relationnel. Pour accder aux donnes du contexte de persistance,
un client JPA [1] doit passer par la couche JPA [2] :
1. il peut crer un objet et demander la couche JPA de le rendre persistant. L'objet fait alors partie du contexte de
persistance ;
2. il peut demander la couche [JPA] une rfrence d'un objet persistant existant ;
3. il peut modifier un objet persistant obtenu de la couche JPA ;
4. il peut demander la couche JPA de supprimer un objet du contexte de persistance ;
La couche JPA prsente au client une interface appele [EntityManager] qui, comme son nom l'indique permet de grer les objets
@Entity du contexte de persistance. Nous prsentons ci-dessous, les principales mthodes de cette interface :
void persist(Object entity)
void remove(Object entity)
<T> T merge(T entity)
Un objet EntityManager a un cycle de vie qui n'est pas forcment celui de l'application. Il a un dbut et une fin. Ainsi un client
JPA peut travailler successivement avec diffrents objets EntityManager. Le contexte de persistance associ un EntityManager a le
mme cycle de vie que lui. Ils sont indissociables l'un de l'autre. Lorsqu'un objet EntityManager est ferm, son contexte de
persistance est, si ncessaire, synchronis avec la base de donnes puis il n'existe plus. Il faut crer un nouvel EntityManager pour
avoir de nouveau un contexte de persistance.
Le client JPA peut crer un EntityManager et donc un contexte de persistance avec l'instruction suivante :
EntityManagerFactory emf = [Link]("nom d'une unit de persistance");
[Link] est une classe statique permettant d'obtenir une fabrique (factory) d'objets EntityManager.
Cette fabrique est lie une unit de persistance prcise. On se rappelle que le fichier de configuration [METAINF/[Link]] permet de dfinir des units de persistance et que celles-ci ont un nom :
Ci-dessus, l'unit de persistance s'appelle elections-dao-jpa-mysql-01PU. Avec elle, vient toute une configuration qui lui est
propre, notamment le SGBD avec lequel elle travaille. L'instruction [[Link]("elections-daojpa-mysql-01PU")] cre une fabrique d'objets de type EntityManagerFactory capable de fournir des objets EntityManager
destins grer des contextes de persistance lis l'unit de persistance nomme elections-dao-jpa-mysql-01PU.
L'obtention d'un objet EntityManager et donc d'un contexte de persistance se fait partir de l'objet EntityManagerFactory de
la faon suivante :
EntityManager em = [Link]();
Les mthodes suivantes de l'interface [EntityManager] permettent de grer le cycle de vie du contexte de persistance :
void close()
le contexte de persistance est ferm. Force la synchronisation du contexte de persistance avec la base de donnes :
si un objet du contexte n'est pas prsent dans la base, il y est mis par une opration SQL INSERT ;
si un objet du contexte est prsent dans la base et qu'il a t modifi depuis qu'il a t lu, une opration
SQL UPDATE est faite pour persister la modification ;
si un objet du contexte a t marqu comme " supprim " l'issue d'une opration remove sur lui, une
31/290
void clear()
void flush()
Le client JPA peut forcer la synchronisation du contexte de persistance avec la base de donnes avec la mthode
[EntityManager].flush prcdente. La synchronisation peut tre explicite ou implicite. Dans le premier cas, c'est au client de faire des
oprations flush lorsqu'il veut faire des synchronisations, sinon celles-ci se font certains moments que nous allons prciser. Le
mode de synchronisation est gr par les mthodes suivantes de l'interface [EntityManager] :
void setFlushMode(FlushModeType
flushMode)
FlushModeType getFlushMode()
Rsumons. En mode [Link] qui est le mode par dfaut, le contexte de persistance sera synchronis avec la base
de donnes aux moments suivants :
1. avant chaque opration SELECT sur la base
2. la fin d'une transaction sur la base
3. la suite d'une opration flush ou close sur le contexte de persistance
En mode [Link], c'est la mme chose sauf pour l'opration 1 qui n'a pas lieu. Le mode normal d'interaction
avec la couche JPA est un mode transactionnel. Le client fait diverses oprations sur le contexte de persistance, l'intrieur d'une
transaction. Dans ce cas, les moments de synchronisation du contexte de persistance avec la base de donnes sont les cas 1 et 2 cidessus en mode AUTO, et le cas 2 uniquement en mode COMMIT.
Terminons par l'API de l'interface Query, interface qui permet d'mettre des ordres JPQL sur le contexte de persistance ou bien
des ordres SQL directement sur la base pour y retrouver des donnes. L'interface Query est la suivante :
3
1
2
32/290
1 - la mthode getResultList execute un SELECT qui ramne plusieurs objets. Ceux-ci seront obtenus dans un objet List.
Cet objet est une interface. Celle-ci offre un objet Iterator qui permet de parcourir les lments de la liste L sous la forme
suivante :
1.
2.
3.
4.
5.
for (Object o : L) {
// exploiter objet o
}
3.4
2 - la mthode getSingleResult excute un ordre JPQL / SQL SELECT qui ramne un unique objet ;
3 - la mthode executeUpdate excute un ordre SQL update ou delete et rend le nombre de lignes affectes
l'opration ;
4 - la mthode setParameter(String, Object) permet de donner une valeur un paramtre nomm d'un ordre JPQL
paramtr ;
5 - la mthode setParameter(int, Object) mais le paramtre n'est pas dsign par son nom mais par sa position dans
l'ordre JPQL ;
Note : le projet Netbeans et le script SQL de la base de donnes de ce paragraphe sont disponibles dans le support du document.
JPQL (Java Persistence Query Language) est le langage de requtes de la couche JPA. Le langage JPQL est apparent au langage
SQL des bases de donnes. Alors que SQL travaille avec des tables, JPQL travaille avec les objets images de ces tables. Nous allons
tudier un exemple au sein de l'architecture suivante :
couche
[DAO]
Couche
[JPA /
Hibernate]
Couche
[JDBC]
SGBD
BD
La base de donnes qu'on appellera [dbrdvmedecins2] est une base de donnes MySQL5 avec quatre tables :
Elle rassemble des informations permettant de grer les rendez-vous d'un groupe de mdecins.
3.4.1
La table [MEDECINS]
33/290
3.4.2
La table [CLIENTS]
Les clients des diffrents mdecins sont enregistrs dans la table [CLIENTS] :
3.4.3
La table [CRENEAUX]
34/290
La seconde ligne de la table [CRENEAUX] (cf [1] ci-dessus) indique, par exemple, que le crneau n 2 commence 8 h 20 et se
termine 8 h 40 et appartient au mdecin n 1 (Mme Marie PELISSIER).
3.4.4
La table [RV]
Cette table a une contrainte d'unicit sur les valeurs des colonnes jointes (JOUR, ID_CRENEAU) :
ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);
Si une ligne de la table[RV] a la valeur (JOUR1, ID_CRENEAU1) pour les colonnes (JOUR, ID_CRENEAU), cette valeur ne peut
se retrouver nulle part ailleurs. Sinon, cela signifierait que deux RV ont t pris au mme moment pour le mme mdecin. D'un
point de vue programmation Java, le pilote JDBC de la base lance une SQLException lorsque ce cas se produit.
La ligne d'id gal 3 (cf [1] ci-dessus) signifie qu'un RV a t pris pour le crneau n 20 et le client n 4 le 23/08/2006. La table
[CRENEAUX] nous apprend que le crneau n 20 correspond au crneau horaire 16 h 20 - 16 h 40 et appartient au mdecin n 1
(Mme Marie PELISSIER). La table [CLIENTS] nous apprend que le client n 4 est Melle Brigitte BISTROU.
3.4.5
Gnration de la base
Pour crer les tables et les remplir on pourra utiliser le script [[Link]] (cf support du cours). Avec [WampServer], on
pourra procder comme suit :
35/290
en [2], on cre une base de donnes dont on a donn le nom [4] et l'encodage [5],
en [7], la base a t cre. On clique sur son lien,
36/290
Par la suite, nous ne reviendrons plus sur cette base. Le lecteur est cependant invit consulter son contenu pour savoir ce que
doivent produire les requtes JPQL qu'il va crire.
3.4.6
La couche [JPA]
couche
[DAO]
Couche
[JPA /
Hibernate]
Couche
[JDBC]
SGBD
BD
3.4.7
Le projet Netbeans
C'est le suivant :
37/290
3.4.8
couche
[DAO]
Couche
[JPA /
Hibernate]
Couche
[JDBC]
SGBD
BD
Avec Netbeans, il est possible de gnrer automatiquement la couche [JPA] . Il est intressant de connatre ces mthodes de
gnration automatique car le code gnr donne de prcieuses indications sur la faon d'crire des entits JPA.
[Link]
dans l'onglet [Services] [1], dans la branche [Databases] [2], slectionner le pilote JDBC MySQL [3],
puis slectionner l'option [4] "Connect Using" permettant de crer une connexion avec une base MySQL,
en [5], donner les informations qui sont demandes. En [6], le nom de la base, en [7] l'utilisateur de la base et son mot de
passe,
en [8], on peut tester les informations qu'on a fournies,
en [9], le message attendu lorsque celles-ci sont bonnes,
38/290
[Link]
en [10], la connexion est cre. On y voit les quatre tables de la base de donnes connecte.
couche
[DAO]
Couche
[JPA /
Hibernate]
Couche
[JDBC]
SGBD
BD
Nous sommes en train de construire la couche [JPA]. La configuration de celle-ci est faite dans un fichier [[Link]] dans
lequel on dfinit des units de persistance. Chacune d'elles a besoin des informations suivantes :
cliquer droit sur le projet et choisir la cration d'une unit de persistance [1-4],
en [5-6], crer une unit de persistance,
39/290
ligne 3 : le type de transactions avec la base de donnes. Ici, RESOURCE_LOCAL indique que l'application va grer ellemme ses transactions,
40/290
Pour avoir des logs d'Hibernate lors de l'excution du projet, nous compltons le fichier [[Link]] de la faon suivante :
1.
2.
7.
8.
9.
10.
11.
12.
13.
14.
15.
3.
4.
5.
6.
41/290
21.
<dependency>
22.
<groupId>[Link]</groupId>
23.
<artifactId>[Link]</artifactId>
24.
<version>SNAPSHOT</version>
25.
<scope>provided</scope>
26.
</dependency>
27.
</dependencies>
28.
<properties>
29.
<[Link]>UTF-8</[Link]>
30.
<[Link]>1.8</[Link]>
31.
<[Link]>1.8</[Link]>
32.
</properties>
33. </project>
[Link]
42/290
en [8], on donne un nom aux classes Java associes aux quatre tables. Ici on a enlev le pluriel des noms des classes,
ainsi qu'un nom de paquetage [9],
en [10], JPA rassemble des lignes de tables de BD dans des collections. Nous choisissons la liste comme collection,
43/290
[Link]
L'entit [Medecin] est l'image de la table [MEDECINS]. La classe Java est truffe d'annotations qui rendent le code peu lisible au
premier abord. Si on ne garde que ce qui est essentiel la comprhension du rle de l'entit, on obtient le code suivant :
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.
package [Link];
import [Link];
...
@Entity
@Table(name = "medecins")
public class Medecin implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = [Link], mappedBy = "idMedecin")
private List<Creneau> creneauList;
public Medecin() {
}
public Medecin(Long id) {
[Link] = id;
}
public Medecin(Long id, String titre, String nom, int version, String prenom) {
[Link] = id;
[Link] = titre;
[Link] = nom;
[Link] = version;
[Link] = prenom;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? [Link]() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Medecin)) {
return false;
44/290
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73. }
}
Medecin other = (Medecin) object;
if (([Link] == null && [Link] != null) || ([Link] != null && )) {
return false;
}
return true;
@Override
public String toString() {
return "[Link][ id=" + id + " ]";
}
// getters et setters
...
ligne 6, l'annotation @Entity fait de la classe [Medecin], une entit JPA, c.a.d. une classe lie une table de BD via l'API
JPA,
ligne 7, le nom de la table de BD associe l'entit JPA. Chaque champ de la table fait l'objet d'un champ dans la classe
Java,
ligne 8, la classe implmente l'interface Serializable. Ceci est ncessaire dans les applications client / serveur, o les entits
sont srialises entre le client et le serveur.
lignes 11-14 : le champ id de la classe [Medecin] correspond au champ [ID] (ligne 10) de la table [medecins],
lignes 16-17 : le champ titre de la classe [Medecin] correspond au champ [TITRE] (ligne 13) de la table [medecins],
lignes 19-20 : le champ nom de la classe [Medecin] correspond au champ [NOM] (ligne 16) de la table [medecins],
lignes 22-23 : le champ version de la classe [Medecin] correspond au champ [VERSION] (ligne 19) de la table [medecins].
Ici, l'assistant ne reconnat pas le fait que la colonne est en fait un colonne de version qui doit tre incrmente chaque
modification de la ligne laquelle elle appartient. Pour lui donner ce rle, il faut ajouter l'annotation @ Version. Nous le
ferons dans une prochaine tape,
lignes 25-26 : le champ prenom de la classe [Medecin] correspond au champ [PRENOM] de la table [medecins],
lignes 11-14 : le champ id correspond la cl primaire [ID] de la table. Les annotations des lignes 8-9 prcisent ce point,
ligne 11 : l'annotation @Id indique que le champ annot est associ la cl primaire de la table,
ligne 12 : la couche [JPA] va gnrer la cl primaire des lignes qu'elle insrera dans la table [Medecins]. Il y a plusieurs
stratgies possibles. Ici la stratgie [Link] indique que la couche JPA va utiliser le mode auto_increment
de la table MySQL,
lignes 28-29 : la table [creneaux] a une cl trangre sur la table [medecins]. Un crneau appartient un mdecin.
Inversement, un mdecin a plusieurs crneaux qui lui sont associs. On a donc une relation un (mdecin) plusieurs
(crneaux), une relation qualifie par l'annotation @OneToMany par JPA (ligne 28). Le champ de la ligne 26 contiendra
tous les crneaux du mdecin. Ceci sans programmation. Pour comprendre totalement la ligne 28, il nous faudra prsenter
la classe [Creneau] ;
lignes 53-64 : deux entits [Medecin] seront dites gales si elles ont la mme cl primaire [id] ;
lignes 46-51 : le hashCode de l'entit. Ici la mthode assure que deux entits [Medecin] de mme [id] auront le mme
hashCode ;
package [Link];
import [Link];
...
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID")
private Long id;
@Column(name = "MDEBUT")
private int mdebut;
@Column(name = "HFIN")
private int hfin;
@Column(name = "HDEBUT")
private int hdebut;
@Column(name = "MFIN")
private int mfin;
@Column(name = "VERSION")
45/290
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. }
Le lien de cl trangre entre l'entit [Creneau] et l'entit [Medecin] est donc matrialis par deux annotations :
dans l'entit [Creneau] :
1.
2.
3.
1.
2.
Les deux annotations refltent la mme relation : celle de la cl trangre de la table [CRENEAUX] vers la table [MEDECINS]. On
dit qu'elles sont inverses l'une de l'autre. Seule la relation @ManyToOne est indispensable. Elle qualifie sans ambigut la relation
de cl trangre. La relation @OneToMany est facultative. Si elle est prsente, elle se contente de rfrencer la relation
@ManyToOne laquelle elle est associe. C'est le sens de l'attribut mappedBy de la ligne 1 de l'entit [Medecin]. La valeur de cet
attribut est le nom du champ de l'entit [Creneau] qui a l'annotation @ManyToOne qui spcifie la cl trangre. Toujours dans
cette mme ligne 1 de l'entit [Medecin], l'attribut cascade=[Link] fixe le comportement de l'entit [Medecin] vis
vis de l'entit [Creneau] :
46/290
si on insre une nouvelle entit [Medecin] dans la base, alors les entits [Creneau] du champ de la ligne 2 doivent tre
insres elles-aussi,
si on modifie une entit [Medecin] dans la base, alors les entits [Creneau] du champ de la ligne 2 doivent tre modifies
elles-aussi,
si on supprime une entit [Medecin] dans la base, alors les entits [Creneau] du champ de la ligne 2 doivent tre
supprimes elles-aussi.
Nous donnons le code des deux autres entits sans commentaires particuliers puisqu'elles n'introduisent pas de nouvelles notations.
L'entit [Client]
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.
package [Link];
les lignes 24-25 refltent la relation de cl trangre entre la table [rv] et la table [clients].
...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = [Link], mappedBy = "idClient")
private List<Rv> rvList;
// constructeurs
...
// getters et setters
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
L'entit [Rv] :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
package [Link];
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID")
private Long id;
@Column(name = "JOUR")
@Temporal([Link])
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau idCreneau;
47/290
19.
20.
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
21.
@ManyToOne(optional = false)
22.
private Client idClient;
23.
24.
// constructeurs
25. ...
26.
27.
// getters et setters
28. ...
29.
30.
@Override
31.
public int hashCode() {
32.
...
33.
}
34.
35.
@Override
36.
public boolean equals(Object object) {
37.
...
38.
}
39.
40.
@Override
41.
public String toString() {
42.
...
43.
}
44.
45. }
la ligne 13 qualifie le champ jour de type Java Date. On indique que dans la table [rv], la colonne [JOUR] (ligne 12) est de
type date (sans heure),
lignes 16-18 : qualifient la relation de cl trangre qu'a la table [RV] vers la table [CRENEAUX],
lignes 20-22 : qualifient la relation de cl trangre qu'a la table [RV] vers la table [CLIENTS].
La gnration automatique des entits JPA nous permet d'obtenir une base de travail. Parfois elle est suffisante, parfois pas. C'est le
cas ici :
il faut ajouter l'annotation @Version aux diffrents champs version des entits,
il faut crire des mthodes toString plus explicites que celles gnres,
les entits [Medecin] et [Client] sont analogues. On va les faire driver d'une classe [Personne],
on va supprimer les relations @OneToMany inverses des relations @ManyToOne. Elles ne sont pas indispensables et
elles amnent parfois des complications de programmation. On supprime et l'annotation et le champ annot.
package [Link];
import [Link];
import [Link].*;
@MappedSuperclass
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "TITRE")
private String titre;
@Basic(optional = false)
@Column(name = "NOM")
private String nom;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@Basic(optional = false)
@Column(name = "PRENOM")
private String prenom;
// constructeurs
public Personne() {
}
48/290
35.
36.
public Personne(Long id) {
37.
[Link] = id;
38.
}
39.
40.
public Personne(Long id, String titre, String nom, int version, String prenom) {
41.
[Link] = id;
42.
[Link] = titre;
43.
[Link] = nom;
44.
[Link] = version;
45.
[Link] = prenom;
46.
}
47.
48. // getters et setters
49.
...
50.
51.
@Override
52.
public String toString() {
53.
return [Link]("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
54.
}
55.
56. }
ligne 6 : on notera que la classe [Personne] n'est pas elle-mme une entit (@Entity). Elle va tre la classe parent d'entits.
L'annotation @MappedSuperClass dsigne cette situation.
L'entit [Client] encapsule les lignes de la table [clients]. Elle drive de la classe [Personne] prcdente :
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.
package [Link];
import [Link];
import [Link].*;
@Entity
@Table(name = "clients")
public class Client extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// constructeurs
public Client() {
super();
}
public Client(Long id) {
super(id);
}
public Client(Long id, String titre, String nom, int version, String prenom) {
super(id, titre, nom, version, prenom);
}
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return [Link]("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
L'entit [Medecin] qui encapsule les lignes de la table [MEDECINS] suit le mme modle :
1.
2.
3.
4.
5.
6.
7.
8.
package [Link];
import [Link];
import [Link].*;
@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
49/290
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. }
package [Link];
import [Link];
import [Link];
import [Link].*;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = [Link])
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "MDEBUT")
private int mdebut;
@Basic(optional = false)
@Column(name = "HFIN")
private int hfin;
@Basic(optional = false)
@NotNull
@Column(name = "HDEBUT")
private int hdebut;
@Basic(optional = false)
@Column(name = "MFIN")
private int mfin;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin medecin;
// constructeurs
...
// getters et setters
...
@Override
public int hashCode() {
...
50/290
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65. }
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
...
}
@Override
public String toString() {
return [Link]("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
}
les lignes 40-42 modlisent la relation "plusieurs un" qui existe entre la table [creneaux] et la table [medecins] de la base
de donnes : un mdecin a plusieurs crneaux, un crneau appartient un seul mdecin.
package [Link];
les lignes 27-29 modlisent la relation "plusieurs un" qui existe entre la table [RV] et la table [CLIENTS] (un client peut
apparatre dans plusieurs Rv) de la base de donnes et les lignes 23-25 la relation "plusieurs un" qui existe entre la table
[RV] et la table [CRENEAUX] (un crneau peut apparatre dans plusieurs Rv).
3.4.9
import [Link];
import [Link];
import [Link].*;
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = [Link])
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "JOUR")
@Temporal([Link])
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau creneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client client;
// constructeurs
...
// getters et setters
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return [Link]("Rv[%s, %s, %s]", id, creneau, client);
}
}
Nous allons ajouter maintenant au projet, le code d'accs aux donnes via la couche JPA :
51/290
couche
[console]
Couche
[JPA /
Hibernate]
Couche
[JDBC]
SGBD
BD
package [Link];
ligne 12 : cration de l'EntityManagerFactory associ l'unit de persistance que nous avons cre prcdemment. le
paramtre de la mthode createEntityManagerFactory est le nom de cette unit de persistance :
1.
2.
3.
import
import
import
import
[Link];
[Link];
[Link];
[Link];
52/290
Travail faire : donner les requtes JPQL permettant d'obtenir les informations suivantes :
3.5
Note : jusqu' la fin du chapitre 3, il n'y a pas de projets construire. Il faut simplement lire le cours.
Nous allons tudier une application qui montre les liens entre le contexte de persistance construit par une couche JPA et la base de
donnes laquelle est adosse cette couche JPA. Les lments utiliss par ce paragraphe sont prsents dans le dossier [chap-03] du
support du TD :
3.5.1
L'architecture de l'application
Couche
[console]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
BD
53/290
3.5.2
La base de donnes
La base de donnes est une base MySQL et s'appelle [dbpersonnes2]. Son script de gnration se trouve dans le dossier [database] :
Travail : crez la base de donnes [dbpersonnes2] avec WampServer et PhpMyAdmin. La base de donnes gnre est sans tables.
Nous allons gnrer celles-ci partir des entits JPA.
3.5.3
3.5.4
Configuration Maven
54/290
24.
<[Link]>1.8</[Link]>
25.
<[Link]>1.8</[Link]>
26.
</properties>
27. </project>
3.5.5
3.5.6
ligne 12 : le mode [drop-and-create] d'Hibernate va crer les tables de la base de donnes [dbpersonnes2] (ligne 7)
chaque instanciation de la couche JPA. Normalement, une unique fois au dmarrage de l'application. Si les tables existent
dj, elle seront tout d'abord supprimes (drop) avant d'tre recres (create) ;
lignes 13-15 : demandent ce que les ordres SQL mis par Hibernate soient logus sur la console ;
La base de donnes contiendra une table [PERSONNES] contenant des informations sur des personnes. L'entit JPA [Personne]
correspondante est la suivante :
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.
package jpa;
import
import
import
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@Entity
@Table(name = "personnes")
public class Personne implements Serializable {
// cl primaire
@Id
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = [Link])
private Integer id;
// n de version
@Column(name = "VERSION", nullable = false)
@Version
private int version;
// nom
@Column(name = "NOM", length = 30, nullable = false, unique = true)
private String nom;
// prnom
@Column(name = "PRENOM", length = 30, nullable = false)
55/290
37.
private String prenom;
38.
39.
// date de naissance
40.
@Column(name = "DATENAISSANCE", nullable = false)
41.
@Temporal([Link])
42.
private Date datenaissance;
43.
44.
// tat marital
45.
@Column(name = "MARIE", nullable = false)
46.
private boolean marie;
47.
48.
// nombre d'enfants
49.
@Column(name = "NBENFANTS", nullable = false)
50.
private int nbenfants;
51.
52.
// constructeurs
53.
public Personne() {
54.
}
55.
56.
public Personne(String nom, String prenom, Date datenaissance, boolean marie, int nbenfants) {
57.
[Link] = nom;
58.
[Link] = prenom;
59.
[Link] = datenaissance;
60.
[Link] = marie;
61.
[Link] = nbenfants;
62.
}
63.
64.
// toString
65.
@Override
66.
public String toString() {
67.
return [Link]("[%d,%d,%s,%s,%s,%s,%d]", id, version, nom, prenom,
68.
new SimpleDateFormat("dd/MM/yyyy").format(datenaissance), marie, nbenfants);
69.
}
70.
71.
// getters and setters
72. ...
73. }
Les champs de cette entit ont t enrichis d'informations qui vont permettre de crer la table [personnes] lie l'entit (ligne 17) :
ligne 33 : le nom fait au plus 30 caractres et doit tre unique dans la table ;
lignes 32-50 : tous les champs ont l'attribut [nullable=false], ce qui va entraner que les colonnes de la table
[PERSONNES] qui leur sont lies auront, elles, l'attribut SQL [NOT NULL] ;
3.5.7
package main;
ligne 11 : on cre l'EntityManagerFactory correspondant l'unit de persistance [mv-personnesPU] dfinie dans le fichier
[[Link]] que nous avons prsent ;
ligne 13 : la cration d'un [EntityManager] partir de l'[EntityManagerFactory] prcdent va crer le contexte de
persistance et la couche JPA. A cause de la proprit [drop-and-create] prsente dans le fichier [[Link]], la table
[PERSONNES] associe l'entit JPA [Personne] va tre cre. Les attributs des diffrentes colonnes de cette table vont
tre tirs des attributs donns aux champs de l'entit JPA [Personne] ;
lignes 15-16 : on libre les ressources ;
import [Link];
import [Link];
import [Link];
public class Create {
public static void main(String[] args) {
// EntityManagerFactory
EntityManagerFactory emf = [Link]("mv-personnesPU");
// cration contexte de persistance
EntityManager em = [Link]();
// fermeture contexte de persistance
[Link]();
[Link]();
}
}
Hibernate:
56/290
2.
drop table if exists personnes
3. Hibernate:
4.
create table personnes (
5.
ID integer not null auto_increment,
6.
DATENAISSANCE date not null,
7.
MARIE bit not null,
8.
NBENFANTS integer not null,
9.
NOM varchar(30) not null,
10.
PRENOM varchar(30) not null,
11.
VERSION integer not null,
12.
primary key (ID)
13.
)
14. Hibernate:
15.
alter table personnes
16. add constraint UK_sc8py9yq9d9lpvyi6xv4vfqod unique (NOM)
3.5.8
La classe [Main] a pour but de montrer les liens entre contexte de persistance et base de donnes. Son code est le suivant :
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.
package main;
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
57/290
27.
log("dump");
28.
dump();
29.
30.
// test1
31.
log("test1");
32.
test1();
33.
34.
// test2
35.
log("test2");
36.
test2();
37.
38.
// fermeture EntityManagerFactory
39.
[Link]();
40.
}
41.
42.
// affichage contenu table
43.
private static void dump() {
44.
// contexte de persistance
45.
EntityManager em = [Link]();
46.
// dbut transaction
47.
EntityTransaction tx = [Link]();
48.
[Link]();
49.
// affichage personnes
50.
for (Object p : [Link]("select p from Personne p order by [Link] asc").getResultList()) {
51.
[Link](p);
52.
}
53.
// fin transaction
54.
[Link]();
55.
// fin contexte
56.
[Link]();
57.
}
58.
59.
// raz BD
60.
private static void clean() {
61.
// contexte de persistance
62.
EntityManager em = [Link]();
63.
// dbut transaction
64.
EntityTransaction tx = [Link]();
65.
[Link]();
66.
// supprimer les lments de la table PERSONNES
67.
[Link]("delete from " + TABLE_NAME).executeUpdate();
68.
// fin transaction
69.
[Link]();
70.
// fin contexte
71.
[Link]();
72.
}
73.
74.
// logs
75.
private static void log(String message) {
76.
[Link]("main : ----------- " + message);
77.
}
78.
79.
// gestion d'objets persists
80.
public static void test1() throws ParseException {
81.
...
82.
}
83.
84.
// gestion d'objets persists
85.
public static void test2() throws ParseException {
86. ...
87.
}
88. }
58/290
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.
Hibernate:
drop table if exists personnes
Hibernate:
create table personnes (
ID integer not null auto_increment,
DATENAISSANCE date not null,
MARIE bit not null,
NBENFANTS integer not null,
NOM varchar(30) not null,
PRENOM varchar(30) not null,
VERSION integer not null,
primary key (ID)
)
Hibernate:
alter table personnes
add constraint UK_sc8py9yq9d9lpvyi6xv4vfqod unique (NOM)
main : ----------- clean
Hibernate:
delete
from
PERSONNES
main : ----------- dump
Hibernate:
select
personne0_.ID as ID1_0_,
personne0_.DATENAISSANCE as DATENAIS2_0_,
personne0_.MARIE as MARIE3_0_,
personne0_.NBENFANTS as NBENFANT4_0_,
personne0_.NOM as NOM5_0_,
personne0_.PRENOM as PRENOM6_0_,
personne0_.VERSION as VERSION7_0_
from
personnes personne0_
order by
personne0_.NOM asc
[Link]
La mthode [test1]
59/290
60/290
lignes 40-46 des rsultats : une opration SQL INSERT est faite sur la base de donnes pour supprimer la personne p2. I y
a ici optimisation : on avait fait une modification de p2 suivie de sa suppression. Au moment de la synchronisation, seule
l'opration SQL DELETE est faite sur la personne p2 ;
ligne 37 du code des rsultats : le contexte de persistance est ferm. Cela a pour effet de le vider. Lorsque commencera la
mthode [test2], on partira d'un contexte de persistance vide. Les personnes [p1, p2] existent toujours mais ne sont pas
dans le contexte de persistance ;
lignes 47-59 des rsultats : une opration SQL SELECT est faite sur la base de donnes, provoque par la mthode [dump]
de la ligne 39 du code ;
ligne 60 des rsultats : on voit la personne p1 avec son nom modifi (P1 au lieu de p1). La personne p2 n'est plus l ;
[Link]
La mthode [test2]
61/290
41.
personne0_.PRENOM as PRENOM6_0_,
42.
personne0_.VERSION as VERSION7_0_
43.
from
44.
personnes personne0_
45.
order by
46.
personne0_.NOM asc
47. [1,2,P1,Paul,31/01/2000,false,2]
62/290
Couche
[ui]
Couche
[metier]
Objets image
de la BD
Couche
[DAO]
Interface
[JPA]
Implmentation
[Hibernate /
EclipseLink]
Couche
[JDBC]
BD
Spring
4.1
La base de donnes
Les donnes statiques utiles pour construire la fiche de paie seront places dans une base de donnes que nous dsignerons par la
suite dbpam. Cette base de donnes pourrait avoir les tables suivantes :
Table EMPLOYES : rassemble des informations sur les diffrentes assistantes maternelles
Structure :
ID
VERSION
SS
NOM
PRENOM
ADRESSE
VILLE
CODEPOSTAL
INDEMNITE_ID
cl primaire
n de version augmente chaque modification de la ligne
numro de scurit sociale de l'employ - unique
nom de l'employ
son prnom
son adresse
sa ville
son code postal
cl trangre sur le champ [ID] de la table [INDEMNITES]
Table COTISATIONS : rassemble des pourcentages ncessaires au calcul des cotisations sociales
Structure :
ID
VERSION
CSGRDS
CSGD
SECU
RETRAITE
cl primaire
n de version augmente chaque modification de la ligne
pourcentage : contribution sociale gnralise + contribution au remboursement de la dette
sociale
pourcentage : contribution sociale gnralise dductible
pourcentage : scurit sociale, veuvage, vieillesse
pourcentage : retraite complmentaire + assurance chmage
63/290
Les taux des cotisations sociales sont indpendants du salari. La table prcdente n'a qu'une ligne.
Table INDEMNITES : rassemble les lments permettant le calcul du salaire payer.
ID
VERSION
INDICE
BASEHEURE
ENTRETIENJOUR
REPASJOUR
INDEMNITESCP
cl primaire
n de version augmente chaque modification de la ligne
indice de traitement - unique
prix net en euro dune heure de garde
indemnit dentretien en euro par jour de garde
indemnit de repas en euro par jour de garde
indemnit de congs pays. C'est un pourcentage appliquer au salaire
de base.
On notera que les indemnits peuvent varier d'une assistante maternelle une autre. Elles sont en effet associes une assistante
maternelle prcise via l'indice de traitement de celle-ci. Ainsi Mme Marie Jouveinal qui a un indice de traitement de 2 (table
EMPLOYES) a un salaire horaire de 2,1 euro (table INDEMNITES).
4.2
Nous prsentons maintenant le mode de calcul du salaire mensuel d'une assistante maternelle. Il ne prtend pas tre celui utilis
dans la ralit. Nous prenons pour exemple, le salaire de Mme Marie Jouveinal qui a travaill 150 h sur 20 jours pendant le mois
payer.
Les lments suivants sont pris en compte
:
[TOTALHEURES]=150
[TOTALJOURS]= 20
[SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*
(1+[INDEMNITESCP]/100)
[SALAIREBASE]=(150*[2.1])*(1+0.15)= 362,25
CSGRDS : 12,64
CSGD : 22,28
Retraite : 28,55
[COTISATIONSSOCIALES]=[SALAIREBASE]*(CSGRD
S+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
[INDEMNITS]=[TOTALJOURS]*(ENTRETIENJOUR+R
EPASJOUR)
[INDEMNITES]=104
64/290
[TOTALHEURES]=150
[TOTALJOURS]= 20
4.3
[SALAIREBASE]-[COTISATIONSSOCIALES]+
[INDEMNITS]
[salaire NET]=368,77
On voit que :
lignes 9-14 : affichent les informations concernant l'employ dont on a donn le n de scurit sociale ;
lignes 17-20 : affichent les taux des diffrentes cotisations ;
lignes 23-26 : affichent les indemnits associes l'indice de traitement de l'employ (ici l'indice 2) ;
lignes 29-33 : affichent les lments constitutifs du salaire payer ;
65/290
4.4
L'application graphique permet le calcul des salaires des assistantes maternelles au travers d'un formulaire Swing :
6
1
2
3
4
les informations passes en paramtres au programme console, sont maintenant saisies au moyen des champs de saisie [1,
2, 3] ;
le bouton [4] demande le calcul du salaire ;
le formulaire affiche les diffrents lments du salaire jusqu'au salaire net payer [5] ;
La liste droulante [1, 6] ne prsente pas les ns SS des employs mais les noms et prnoms de ceux-ci. On fait ici l'hypothse qu'il
n'y a pas deux employs de mmes nom et prnom.
4.5
66/290
67/290
table EMPLOYES
table INDEMNITES
table COTISATIONS
4.6
4.6.1
Implmentation JPA
Couche JPA / Hibernate
Programme
console
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Base de
donnes
Nous crons le projet Maven [mv-pam-jpa-hibernate] [1] en suivant la procdure dcrite au paragraphe 2.1.1, page 11 :
68/290
[Link]
la base de donnes,
le pilote JDBC du SGBD MySQL,
la couche JPA / Hibernate (entits et configuration),
le programme console de test.
La liaison entre la couche JDBC et la base de donnes se fait dans le fichier [META-INF / [Link]] qui configure la couche
JPA. Ce fichier peut tre construit avec Netbeans :
dans l'onglet [services] [1], on se connecte la base de donnes avec le pilote JDBC de MySQL [2],
en [3], le nom de la base de donnes laquelle on veut se connecter,
en [4], l'URL JDBC de la base,
en [5], on se connecte en tant que root sans mot de passe,
en [6], la base laquelle on veut se connecter ;
en [7], on peut tester la connexion,
en [8], la connexion a russi.
69/290
70/290
le fichier apparat dans une nouvelle branche du projet, dans un dossier [META-INF] [19],
en [20], des dpendances sont apparues. Ce sont celles du fournisseur JPA / Hibernate.
ligne 3 : le nom de l'unit de persistance et le type de transactions. RESOURCE_LOCAL indique que le projet gre luimme les transactions. C'est ici le programme console qui devra le faire,
ligne 4 : l'implmentation JPA utilise est Hibernate,
lignes 6-9 : les caractristiques JDBC de la connexion la base de donnes,
ligne 11 : demande la cration des tables correspondant aux entits JPA si elles n'existent pas. Lorsque la base existe et
qu'on ne veut plus la modifier, cette ligne doit tre mise en commentaires,
Elles demandent Hibernate d'afficher les ordres SQL qu'il envoie la base de donnes. Le fichier complet est donc le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
71/290
9.
<property name="[Link]" value=""/>
10.
<property name="[Link].provider_class" value="[Link]"/>
11.
<property name="[Link]" value="create"/>
12.
<property name="hibernate.show_sql" value="true"/>
13.
<property name="hibernate.format_sql" value="true"/>
14.
<property name="use_sql_comments" value="true"/>
15.
</properties>
16.
</persistence-unit>
17. </persistence>
[Link]
Les dpendances
Programme
console
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Base de
donnes
Nous avons configur la couche JPA via le fichier [[Link]]. L'implmentation choisie a t Hibernate. Cela a amen des
dpendances dans le projet [20] :
Ces dpendances sont dues l'inclusion d'Hibernate dans le projet. Elles sont inscrites dans le fichier [[Link]] qui configure le
projet Maven :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
72/290
21.
<dependency>
22.
<groupId>[Link]</groupId>
23.
<artifactId>[Link]</artifactId>
24.
<version>SNAPSHOT</version>
25.
<scope>provided</scope>
26.
</dependency>
27.
</dependencies>
28.
<properties>
29.
<[Link]>UTF-8</[Link]>
30.
<[Link]>1.8</[Link]>
31.
<[Link]>1.8</[Link]>
32.
</properties>
33. </project>
Il nous faut ajouter une autre dpendance, celle du pilote JDBC de MySQL qui implmente la couche JDBC de l'architecture. Par
ailleurs, certaines des dpendances sont inutiles. Nous faisons voluer le fichier [[Link]] de la faon suivante :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="[Link] xmlns:xsi="[Link]
3.
xsi:schemaLocation="[Link] [Link]
4.
<modelVersion>4.0.0</modelVersion>
5.
<groupId>[Link]</groupId>
6.
<artifactId>mv-pam-jpa-hibernate</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
<dependencies>
10.
<!-- hibernate -->
11.
<dependency>
12.
<groupId>[Link]</groupId>
13.
<artifactId>hibernate-entitymanager</artifactId>
14.
<version>[Link]</version>
15.
</dependency>
16.
<!-- mysql -->
17.
<dependency>
18.
<groupId>mysql</groupId>
19.
<artifactId>mysql-connector-java</artifactId>
20.
<version>5.1.39</version>
21.
</dependency>
22.
</dependencies>
23.
<properties>
24.
<[Link]>UTF-8</[Link]>
25.
<[Link]>1.8</[Link]>
26.
<[Link]>1.8</[Link]>
27.
</properties>
28. </project>
Note : ci-dessus, nous n'avons pas choisi les versions 6.x car elles provoquaient des erreurs lors de l'excution du projet avec un
SGBD MySQL 5.x.
[Link]
73/290
[Link]
package main;
1.
2.
3.
import [Link];
import [Link];
import [Link];
public class Main {
74/290
[Link]
ligne 12 : on cre l'EntityManager. Cette cration cre la couche JPA. Le fichier [[Link]] va tre exploit et alors les
tables de la base de donnes vont tre cres,
lignes 14-15 : on libre les ressources.
Tests
Programme
console
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Base de
donnes
75/290
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.
configure
buildCreator
buildCreator
buildCreator
configure
stop
76/290
On trouve dans la console uniquement des logs d'Hibernate puisque le programme excut ne fait rien en-dehors d'instancier la
couche JPA. On notera les points suivants :
Dans Netbeans, on peut voir les tables dans la connexion qui a t cre prcdemment :
Hibernate a tent une seconde fois de recrer les tables de la base de donnes [dbpam_hibernate]. Il y a eu alors une erreur parce
que ces tables existent dj. Cette erreur peut tre vite de la faon suivante :
Nous slectionnons le fichier [[Link]] du projet et en [1-2] nous indiquons que la stratgie de gnration des tables est
[Drop and Create]. Ainsi si les tables existent, elles seront supprimes avant d'tre recres. Le fichier [[Link]] volue de la
faon suivante :
1.
2.
3.
4.
77/290
5.
<class>[Link]</class>
6.
<class>[Link]</class>
7.
<class>[Link]</class>
8.
<properties>
9.
..
10.
<property name="[Link]" value="drop-and-create"/>
11.
...
12.
</properties>
13.
</persistence-unit>
14. </persistence>
Si nous excutons de nouveau le projet, il n'y a cette fois-ci pas d'erreurs. Hibernate ajoute les logs suivants :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Hibernate:
alter table employes
drop
foreign key FK_7xa7rdyjqxrbvew7n6f83ey5h
Hibernate:
drop table if exists cotisations
Hibernate:
drop table if exists employes
Hibernate:
drop table if exists indemnites
lignes 1-4 : Hibernate supprime la cl trangre de la table [EMPLOYES] sur la table [INDEMNITES] ;
lignes 5-6 : suppression de la table [COTISATIONS] ;
lignes 7-8 : suppression de la table [EMPLOYES] ;
lignes 9-10 : suppression de la table [INDEMNITES] ;
Les tables cres dpendent la fois de l'implmentation de la couche JPA utilise, du SGBD utilis et des annotations JPA utilises
dans les entits. Ainsi une implmentation JPA / EclipseLink avec le mme SGBD peut gnrer des tables diffrentes de celles
gnres par une implmentation JPA / Hibernate. C'est ce que nous verrons prochainement.
Revenons sur la structure des tables gnres. Dans les logs, il est crit :
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.
Hibernate:
create table cotisations (
ID bigint not null auto_increment,
CSGD double precision not null,
CSGRDS double precision not null,
RETRAITE double precision not null,
SECU double precision not null,
VERSION integer not null,
primary key (ID)
)
Hibernate:
create table employes (
ID bigint not null auto_increment,
ADRESSE varchar(255) not null,
CP varchar(255) not null,
NOM varchar(255) not null,
PRENOM varchar(255) not null,
SS varchar(255) not null,
VERSION integer not null,
VILLE varchar(255) not null,
INDEMNITE_ID bigint not null,
primary key (ID)
)
Hibernate:
create table indemnites (
ID bigint not null auto_increment,
BASE_HEURE double precision not null,
ENTRETIEN_JOUR double precision not null,
INDEMNITES_CP double precision not null,
INDICE integer not null,
REPAS_JOUR double precision not null,
VERSION integer not null,
primary key (ID)
)
Hibernate:
alter table employes
add constraint FK_7xa7rdyjqxrbvew7n6f83ey5h
foreign key (INDEMNITE_ID)
references indemnites (ID)
lignes 94-100 : en l'absence d'informations, Hibernate a gnr des colonnes de 255 caractres. C'est trop ;
on voudrait par ailleurs, que le n SS de l'employ, ligne 98, soit unique dans la table [employes] ;
78/290
Ces contraintes sur la gnration des tables peuvent tre obtenues partir d'informations supplmentaires sur certains champs des
entits JPA. L'entit JPA [Employe] pourrait maintenant tre la suivante :
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.
@Entity
@Table(name = "employes")
@NamedQueries({
@NamedQuery(name = "[Link]", query = "SELECT e FROM Employe e")})
public class Employe implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = [Link])
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@Basic(optional = false)
@Column(name = "PRENOM", length = 30)
private String prenom;
@Basic(optional = false)
@Column(name = "NOM", length = 30)
private String nom;
@Basic(optional = false)
@Column(name = "SS", length = 15, unique = true)
private String ss;
@Basic(optional = false)
@Column(name = "ADRESSE", length = 50)
private String adresse;
@Basic(optional = false)
@Column(name = "CP", length = 5)
private String cp;
@Basic(optional = false)
@Column(name = "VILLE", length = 20)
private String ville;
@JoinColumn(name = "INDEMNITE_ID", referencedColumnName = "ID")
@ManyToOne(optional = false, fetch = [Link])
private Indemnite indemniteId;
lignes 20, 24, 28, 32, 36, 40 : avec l'attribut [length], on fixe la taille des colonnes associes ;
ligne 28 : l'attribut [unique=true] va forcer la gnration d'une contrainte d'unicit sur la colonne [SS] de la table
[employes] ;
lignes 4-8, 10 : les colonnes ont la taille fixe dans l'entit [Employe] ;
lignes 15-16 : la contrainte d'unicit de la colonne [SS] ;
Dans la mme veine, nous souhaitons que dans la table [INDEMNITES], la colonne [INDICE] ait l'attribut UNIQUE : deux lignes
ne peuvent avoir le mme indice. Nous faisons voluer le champ [[Link]] de la faon suivante :
1.
2.
79/290
4.6.2
Programme
console
Interface
[JPA]
Implmentation
[EclipseLink]
Couche
[JDBC]
Base de
donnes
crer une base de donnes MySQL [dbpam_eclipselink] sans tables pour le moment [1-6],
crer un nouveau projet Maven [mv-pam-jpa-eclipselink] en suivant la dmarche du paragraphe 2.1.1, page 11 :
en suivant la dmarche du paragraphe 3.4.8, page 38, crer le fichier [[Link]] du projet. Prendre l'implmentation
JPA 2.1 EclipseLink,
80/290
ajouter les entits JPA et le programme console du prcdent projet. Adaptez le programme console pour qu'il utilise la
bonne unit de persistance :
10.
11.
12.
13.
14.
15.
16.
17.
3.
4.
5.
6.
7.
8.
9.
81/290
ligne 14 : cette proprit nous permet de fixer le niveau de logs d'EclipseLink. Le niveau FINE nous permet de connatre
les ordres SQL qu'EclipseLink va mettre sur la base de donnes,
ligne 13 : l'instanciation de la couche JPA / EclipseLink, les tables des entits JPA seront dtruites puis cres.
82/290
55.
56.
57.
58.
59.
L'existence des tables gnres peut tre vrifie dans Netbeans [1-2] :
Ligne 47 des logs, on remarque que les colonnes de la table [EMPLOYES] n'ont pas l'attribut [NOT NULL] alors qu'avec
Hibernate elles avaient cet attribut. Avec [Hibernate], l'annotation JPA [@Bacic(optional=false)] avait suffi pour gnrer les attributs
[NOT NULL] des colonnes. Avec EclipseLink ce n'est pas le cas. Pour obtenir cet attribut [NOT NULL], on peut utiliser l'attribut
[nullable=false] :
1.
2.
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID", nullable = false)
private Long id;
@Column(name = "VERSION", nullable = false)
@Version
private int version;
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
@Column(name = "NOM", length = 30, nullable = false)
private String nom;
@Column(name = "SS", length = 13, nullable = false, unique = true)
private String ss;
@Column(name = "ADRESSE", length = 50, nullable = false)
private String adresse;
@Column(name = "CP", length = 5, nullable = false)
private String cp;
@Column(name = "VILLE", length = 20, nullable = false)
83/290
26.
private String ville;
27.
28.
@JoinColumn(name = "INDEMNITE_ID", referencedColumnName = "ID", nullable = false)
29.
@ManyToOne(optional = false, fetch = [Link])
30. private Indemnite indemniteId;
Avec ce changement, les logs de cration de la table [EMPLOYES] deviennent les suivants :
1.
[EL Fine]: sql: 2016-08-31 [Link].955--ServerSession(2032251042)--Connection(1883652579)--Thread(Thread[main,5,main])-CREATE TABLE employes (ID BIGINT AUTO_INCREMENT NOT NULL, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, NOM
VARCHAR(30) NOT NULL, PRENOM VARCHAR(30) NOT NULL, SS VARCHAR(15) NOT NULL UNIQUE, VERSION INTEGER NOT NULL, VILLE
VARCHAR(20) NOT NULL, INDEMNITE_ID BIGINT, PRIMARY KEY (ID))
Cette nouvelle version de l'entit [Employe] fonctionne galement avec l'implmentation JPA / Hibernate. Pour ces raisons, nous
garderons cette dfinition pour la suite. Nous faisons ce mme type de modifications dans les deux autres entits : [Cotisation] et
[Indemnite].
Maintenant, modifions le mode de gnration des cls primaires des entits JPA [Cotisation, Employe, Indemnite] :
1.
2.
3.
4.
5.
@Id
//@GeneratedValue(strategy = [Link])
@GeneratedValue(strategy = [Link])
@Basic(optional = false)
@Column(name = "ID")
ligne 2, on indique avec le mode [AUTO] qu'on laisse l'implmentation JPA dcider du mode de gnration des cls
primaires.
On fait cette modification dans les trois entits JPA. Puis procdez de la faon suivante :
On voit en [3] qu'une table [SEQUENCE] est apparue. Les logs de cration des tables sont les suivants :
1.
2.
3.
4.
[EL Fine]: sql: 2016-08-31 [Link].948--ServerSession(2032251042)--Connection(1883652579)--Thread(Thread[main,5,main])-CREATE TABLE cotisations (ID BIGINT NOT NULL, CSGD DOUBLE NOT NULL, CSGRDS DOUBLE NOT NULL, RETRAITE DOUBLE NOT NULL, SECU
DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: sql: 2016-08-31 [Link].026--ServerSession(2032251042)--Connection(1883652579)--Thread(Thread[main,5,main])-CREATE TABLE employes (ID BIGINT NOT NULL, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, NOM VARCHAR(30) NOT NULL,
PRENOM VARCHAR(30) NOT NULL, SS VARCHAR(15) NOT NULL UNIQUE, VERSION INTEGER NOT NULL, VILLE VARCHAR(20) NOT NULL,
INDEMNITE_ID BIGINT, PRIMARY KEY (ID))
[EL Fine]: sql: 2016-08-31 [Link].104--ServerSession(2032251042)--Connection(1883652579)--Thread(Thread[main,5,main])-CREATE TABLE indemnites (ID BIGINT NOT NULL, BASE_HEURE DOUBLE NOT NULL, ENTRETIEN_JOUR DOUBLE NOT NULL, INDEMNITES_CP
DOUBLE NOT NULL, INDICE INTEGER NOT NULL UNIQUE, REPAS_JOUR DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: sql: 2016-08-31 [Link].198--ServerSession(2032251042)--Connection(1883652579)--Thread(Thread[main,5,main])-ALTER TABLE employes ADD CONSTRAINT FK_employes_INDEMNITE_ID FOREIGN KEY (INDEMNITE_ID) REFERENCES indemnites (ID)
84/290
5.
6.
7.
[EL Fine]: sql: 2016-08-31 [Link].323--ServerSession(2032251042)--Connection(1883652579)--Thread(Thread[main,5,main])-CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
[EL Fine]: sql: 2016-08-31 [Link].37--ServerSession(2032251042)--Connection(1883652579)--Thread(Thread[main,5,main])-SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: sql: 2016-08-31 [Link].37--ServerSession(2032251042)--Connection(1883652579)--Thread(Thread[main,5,main])-INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values (SEQ_GEN, 0)
ligne 1 : cration de la table [COTISATIONS]. La cl primaire [ID] n'a pas l'attribut SQL [auto_increment] qu'elle avait
dans la version prcdente du projet. C'est le cas galement pour les deux autres tables (lignes 2 et 3),
ligne 4 : cration de la cl trangre de la table [EMPLOYES] vers la table [INDEMNITES],
ligne 5 : cration d'une table [SEQUENCE] avec deux colonnes [SEQ_NAME, SEQ_COUNT]. La colonne
[SEQ_NAME] est cl primaire,
ligne 7 : cration d'une ligne dans la table [SEQUENCE] avec les valeurs (SEQ_GEN, 0).
EclipseLink utilise la table [SEQUENCE] pour gnrer les cls primaires des tables [COTISATIONS, EMPLOYES,
INDEMNITES].
Donc, partir des mmes entits JPA, les implmentations JPA peuvent ne pas gnrer les mmes tables selon le mode de
gnration des cls primaires :
avec MySQL et Hibernate, les modes [[Link]] et [[Link]] gnrent les mmes trois
tables en utilisant le mode [autoincrement] pour la gnration des cls primaires,
avec MySQL, EclipseLink et le mode [[Link]], la base gnre est identique celle gnre par
Hibernate,
avec MySQL, EclipseLink et le mode [[Link]], la base gnre est diffrente des prcdentes : elle
contient quatre tables et la gnration des cls primaires n'utilise pas le mode [autoincrement] de MySQL mais une table
supplmentaire appele [SEQUENCE].
Dans la suite du document, quelque soit l'implmentation JPA utilise, Hibernate ou EclipseLink :
on utilisera la stratgie [[Link]] pour gnrer les cls primaires des entits JPA ;
4.6.3
Travail faire
4.6.4
crer et tester un projet [mv-pam-jpa-hibernate-oracle] utilisant une implmentation JPA Hibernate et un SGBD Oracle,
crer et tester un projet [mv-pam-jpa-hibernate-mssql] utilisant une implmentation JPA Hibernate et un SGBD SQL
Server,
crer et tester un projet [mv-pam-jpa-eclipselink-oracle] utilisant une implmentation JPA EclipseLink et un SGBD
Oracle,
crer et tester un projet [mv-pam-jpa-eclipselink-mssql] utilisant une implmentation JPA EclipseLink et un SGBD SQL
Server,
Lazy ou Eager ?
...
@Entity
@Table(name = "employes")
public class Employe implements Serializable {
private static final long serialVersionUID = 1L;
// cl primaire
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID")
private Long id;
// prnom
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
// nom
@Column(name = "NOM", length = 30, nullable = false)
private String nom;
// n de scu
@Column(name = "SS", length = 15, nullable = false)
85/290
24.
private String ss;
25.
26.
// adresse
27.
@Column(name = "ADRESSE", length = 50, nullable = false)
28.
private String adresse;
29.
30.
// code postal
31.
@Column(name = "CP", length = 5, nullable = false)
32.
private String cp;
33.
34. // ville
35.
@Column(name = "VILLE", length = 20, nullable = false)
36.
private String ville;
37.
38.
// n de version
39.
@Column(name = "VERSION", nullable = false)
40.
@Version
41.
private int version;
42.
43.
// indemnits
44.
@JoinColumn(name = "INDEMNITE_ID", referencedColumnName = "ID")
45.
@ManyToOne(optional = false, fetch = [Link])
46.
private Indemnite indemniteId;
47. ...
Les lignes 44-46 dfinissent la cl trangre de la table [EMPLOYES] vers la table [INDEMNITES]. L'attribut fetch de la ligne 46
dfinit la stratgie de recherche du champ Indemnite indemniteId de la ligne 46. Il y a deux modes :
[Link] : lorsqu'un employ est cherch, l'indemnit qui lui correspond n'est pas ramene. Elle le sera lorsque
le champ [Employe].indemniteId sera rfrenc pour la premire fois.
[Link] : lorsqu'un employ est cherch, l'indemnit qui lui correspond est ramene. C'est le mode par
dfaut lorsqu'aucun mode n'est prcis.
Pour comprendre l'intrt de l'option [Link], on peut prendre l'exemple suivant. Une liste d'employs sans les indemnits
est prsente dans une page web avec un lien [Details]. Un clic sur ce lien prsente alors les indemnits de l'employ slectionn. On
voit que :
pour afficher la premire page on n'a pas besoin des employs avec leurs indemnits. Le mode [Link] convient
alors,
pour afficher la seconde page avec les dtails, une requte supplmentaire doit tre faite la base de donnes pour avoir
les indemnits de l'employ slectionn.
Le mode [Link] vite de ramener des donnes dont l'application n'a pas besoin tout de suite. Voyons un exemple.
Le projet [mv-pam-jpa-hibernate] est dupliqu :
86/290
87/290
package main;
import
import
import
import
[Link];
[Link];
[Link];
[Link];
88/290
39. }
// premier essai
EntityManager em = [Link]();
Employe employe = (Employe) [Link]("select e from Employe e where [Link]=:nom").setParameter("nom",
"Jouveinal").getSingleResult();
[Link]();
// on affiche l'employ
try {
[Link]("Employ=" + employe);
} catch (Exception ex) {
[Link]("L'erreur suivante s'est produite : " + ex);
}
// deuxime essai
em = [Link]();
employe = (Employe) [Link]("select e from Employe e left join fetch [Link] where
[Link]=:nom").setParameter("nom", "Jouveinal").getSingleResult();
// librer les ressources
[Link]();
// on affiche l'employ
try {
[Link]("Employ=" + employe);
} catch (Exception ex) {
[Link]("L'erreur suivante s'est produite : " + ex);
}
lignes 7 et 18 : la mthode [Employe].toString() va tre appele. Celle-ci va rendre la chane jSON de l'objet [Employe]
suivant :
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.
package jpa;
import [Link];
...
@Entity
@Table(name = "employes")
public class Employe implements Serializable {
private static final long serialVersionUID = 1L;
// cl primaire
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID")
private Long id;
// prnom
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
// nom
@Column(name = "NOM", length = 30, nullable = false)
private String nom;
// n de scu
@Column(name = "SS", length = 15, nullable = false)
private String ss;
// adresse
@Column(name = "ADRESSE", length = 50, nullable = false)
private String adresse;
// code postal
@Column(name = "CP", length = 5, nullable = false)
private String cp;
// ville
89/290
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
// toString
@Override
public String toString() {
return [Link]("Employ=[id=%s, version=%s, prnom=%s, nom=%s, adresse=%s, ville=%s, code postal=%s, indemnite=
%s]",
59.
id, version, prenom, nom, adresse, ville, cp, indemniteId);
60.
}
61.
62.
// getters et setters --------63.
...
64.
65.
// galit d'instances -----------66.
...
67. }
ligne 49 : le champ indemniteId est ramen en mode LAZY. Pour produire la chane jSON de l'objet [Employe], la
bibliothque jSON va automatiquement vouloir produire la chane jSON du champ [Indemnite indemniteId] de la ligne
49. Elle va alors faire une opration [[Link]()] pour obtenir l'objet [Indemnite] associ l'employ. Avec
Hibernate, cette opration n'est possible que si le contexte de persistance est encore ouvert ;
lignes 6-10 : on devrait avoir une exception. En effet, la mthode toString va tre appele. Elle va utiliser le champ
indemniteId. Celui-ci va tre cherch. Comme le contexte de persistance a t ferm, une exception va se produire,
ligne 12 : on cre un nouvel EntityManager,
ligne 13 : on demande l'employ Jouveinal en demandant explicitement dans la requte JPQL l'indemnit qui va avec. Cette
demande explicite est ncessaire parce que le mode de recherche de cette indemnit est LAZY,
ligne 15 : on ferme l'EntityManager,
lignes 17-21 : on raffiche l'employ. Il ne devrait pas y avoir d'exception.
Pour excuter le projet, on a besoin d'une base de donnes remplie. On la crera en suivant la dmarche du paragraphe 4.5, page 66.
Par ailleurs, le fichier [[Link]] doit tre modifi :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
on a enlev l'option qui crait les tables. La base de donnes ici existe dj et est remplie,
on a enlev les options qui faisaient qu'Hibernate loguait les ordres SQL qu'il mettait vers la base de donnes.
90/290
2.
ligne 1 : l'exception qui s'est produite lorsqu'il a fallu chercher l'indemnit qui manquait, alors que la session tait ferme.
On voit que l'indemnit n'avait pas t ramene cause du mode LAZY,
ligne 2 : l'employ avec son indemnit obtenue par une requte qui a contourn le mode LAZY.
4.6.5
Travail faire
En suivant une dmarche analogue celle qui vient d'tre suivie, crez un projet [mv-pam-jpa-eclipselink-lazy] qui montre le
comportement d'EclipseLink face au mode LAZY.
EclipseLink ncessite une configuration spciale pour que le mode [LAZY] du champ [[Link]] soit respect. Si on suit
la mme dmarche que prcdemment avec les fichiers suivants :
[[Link]]
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="[Link] xmlns:xsi="[Link]
3.
xsi:schemaLocation="[Link] [Link]
4.
<modelVersion>4.0.0</modelVersion>
5.
<groupId>[Link]</groupId>
6.
<artifactId>mv-pam-jpa-eclipselink-lazy</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
<name>mv-pam-jpa-eclipselink-lazy</name>
10.
<dependencies>
11.
<!-- mysql -->
12.
<dependency>
13.
<groupId>mysql</groupId>
14.
<artifactId>mysql-connector-java</artifactId>
15.
<version>5.1.39</version>
16.
</dependency>
17.
<!-- [Link] -->
18.
<dependency>
19.
<groupId>[Link]</groupId>
20.
<artifactId>jackson-core</artifactId>
21.
<version>2.8.1</version>
22.
</dependency>
23.
<dependency>
24.
<groupId>[Link]</groupId>
25.
<artifactId>jackson-databind</artifactId>
26.
<version>2.8.1</version>
27.
</dependency>
28.
<!-- eclipselink -->
29.
<dependency>
30.
<groupId>[Link]</groupId>
31.
<artifactId>eclipselink</artifactId>
32.
<version>2.6.3</version>
33.
</dependency>
34.
</dependencies>
35.
<properties>
36.
<[Link]>UTF-8</[Link]>
37.
<[Link]>1.8</[Link]>
38.
<[Link]>1.8</[Link]>
39.
</properties>
40. </project>
[[Link]]
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
91/290
17. </persistence>
Ce log indique que le mode [LAZY] du champ [[Link]] ne sera pas respect (Reverting the lazy setting). La raison
de ce comportement est obscure : [since weaving was not enabled or did not occur].
L'URL [[Link] explique ce qu'est le
weaving des entits JPA et donne une solution pour l'activer. Il faut ajouter deux plugins Maven au fichier [[Link]] qui devient alors
le suivant :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="[Link] xmlns:xsi="[Link]
3.
xsi:schemaLocation="[Link] [Link]
4.
<modelVersion>4.0.0</modelVersion>
5.
<groupId>[Link]</groupId>
6.
<artifactId>mv-pam-jpa-eclipselink-lazy</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
<name>mv-pam-jpa-eclipselink-lazy</name>
10.
<dependencies>
11.
...
12.
</dependencies>
13.
<properties>
14.
...
15.
</properties>
16.
17.
<!-- plugins EclipseLink -->
18.
<build>
19.
<plugins>
20.
<!--[[Link] -->
21.
<!-- This plugin ensures the EclipseLink static weaving -->
22.
<plugin>
23.
<artifactId>staticweave-maven-plugin</artifactId>
24.
<groupId>[Link]</groupId>
25.
<version>1.0.0</version>
26.
<executions>
27.
<execution>
28.
<goals>
29.
<goal>weave</goal>
30.
</goals>
31.
<phase>process-classes</phase>
32.
<configuration>
33.
<logLevel>ALL</logLevel>
34.
<includeProjectClasspath>true</includeProjectClasspath>
35.
</configuration>
36.
</execution>
37.
</executions>
38.
<dependencies>
39.
<dependency>
40.
<groupId>[Link]</groupId>
41.
<artifactId>eclipselink</artifactId>
42.
<version>2.6.3</version>
43.
</dependency>
44.
</dependencies>
45.
</plugin>
46.
</plugins>
47.
<pluginManagement>
48.
<plugins>
49.
<!-- This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build
itself. -->
50.
<plugin>
51.
<groupId>[Link].m2e</groupId>
52.
<artifactId>lifecycle-mapping</artifactId>
53.
<version>1.0.0</version>
54.
<configuration>
55.
<lifecycleMappingMetadata>
56.
<pluginExecutions>
57.
<pluginExecution>
58.
<pluginExecutionFilter>
59.
<groupId>
60.
[Link]
61.
</groupId>
62.
<artifactId>
63.
staticweave-maven-plugin
64.
</artifactId>
65.
<versionRange>
66.
[1.0.0,)
67.
</versionRange>
92/290
68.
<goals>
69.
<goal>weave</goal>
70.
</goals>
71.
</pluginExecutionFilter>
72.
<action>
73.
<execute>
74.
<runOnIncremental>true</runOnIncremental>
75.
</execute>
76.
</action>
77.
</pluginExecution>
78.
</pluginExecutions>
79.
</lifecycleMappingMetadata>
80.
</configuration>
81.
</plugin>
82.
</plugins>
83.
</pluginManagement>
84.
</build>
85. </project>
On ajoute les lignes 17-84 au fichier [[Link]]. Ceci fait, on construit le projet (Clean and Build). Parmi les logs on trouve les lignes
suivantes :
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.
93/290
Ce weaving a enrichi les entits JPA de faon ce que le mode [LAZY] des champs [@ManyToOne] et [@OneToMany] soit
respect. On appelle statique ce weaving parce qu'il se fait la compilation du projet. On appelle dynamique un weaving qui se
fait en cours d'excution du projet. Ce n'est pas le cas ici.
Le fichier [[Link]] doit lui aussi tre modifi :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
94/290
lignes 15-17 : on dclare qu'un weaving statique est fait la compilation. Sans ces lignes et sous certaines conditions
(configuration Spring / EclipseLink), un weaving dynamique est tent et amne des erreurs. Dans ce projet, on ne verra
aucune diffrence selon que ces lignes sont ou non prsentes ;
A la ligne 18, on veut inspecter l'objet [Employe] obtenu ligne 17 pour voir si son champ [Indemnite indemniteId] a t initialis ou
non. Puis on excute l'application en mode dbogage [2-3]. A la ligne d'arrt, on obtient les rsultats suivants :
On voit en [4] que [indemniteId] vaut null indiquant par l que le mode [LAZY] a bien fonctionn.
Maintenant nous pouvons enlever les commentaires de la mthode [[Link]] :
1.
2.
3.
4.
5.
// toString
@Override
public String toString() {
return [Link]("Employ=[id=%s, version=%s, prnom=%s, nom=%s, adresse=%s, ville=%s, code postal=%s, indemnite=
%s]",id, version, prenom, nom, adresse, ville, cp, indemniteId);
}
Il tait important de les enlever pour la raison suivante : nous allons dcouvrir qu'EclipseLink :
respecte bien le mode [LAZY] du champ [Indemnite [Link]] comme nous venons de le voir ;
95/290
est malgr tout capable de rcuprer l'indemnit ds que le code fait rfrence au champ [Indemnite
[Link]]. C'est dire que ce qui n'tait pas possible avec Hibernate et provoquait une exception est possible
avec EclipseLink. La mthode [toString] fait rfrence au champ [indemniteId] (ligne 4). Donc le simple fait d'utiliser la
mthode [[Link]] va forcer la rcupration de l'indemnit de l'employ. Or le dbogueur, pour afficher un objet,
utilise la mthode [toString] de celui-ci. Donc avec la mthode [toString] dcommente ci-dessus, le dbogueur montre un
objet [Employe] avec son indemnit et on a alors l'impression que le mode [LAZY] n'a pas fonctionn. C'est pourquoi il a
t ncessaire de modifier la mthode [[Link]] ;
Avec cette configuration, les rsultats de l'excution du projet sont les suivants :
1.
2.
...
[EL Info]: connection: 2016-09-01 [Link].241--ServerSession(226710952)-Thread(Thread[main,5,main])--/file:/D:/data/istia-1617/netbeans/support-td/chap-04/mv-pam-jpa-eclipselinklazy/target/classes/_mv-pam-jpa-eclipselink-lazyPU login successful
3. [EL Fine]: sql: 2016-09-01 [Link].522--ServerSession(226710952)--Connection(737897289)--Thread(Thread[main,5,main])-SELECT ID, ADRESSE, CP, NOM, PRENOM, SS, VERSION, VILLE, INDEMNITE_ID FROM employes WHERE (NOM = ?)
4.
bind => [Jouveinal]
5. [EL Fine]: sql: 2016-09-01 [Link].569--ServerSession(226710952)--Connection(737897289)--Thread(Thread[main,5,main])-SELECT ID, BASE_HEURE, ENTRETIEN_JOUR, INDEMNITES_CP, INDICE, REPAS_JOUR, VERSION FROM indemnites WHERE (ID = ?)
6.
bind => [2]
7. Employ=Employ=[id=1, version=1, prnom=Marie, nom=Jouveinal, adresse=5 rue des oiseaux, ville=St Corentin, code
postal=49203, indemnite=Indemnite[id=2, version=1, indice=2, base heure=2.1, entretien jour=2.1, repas jour=3.1, indemnits
CP=15.0]
8. [EL Fine]: sql: 2016-09-01 [Link].584--ServerSession(226710952)--Connection(737897289)--Thread(Thread[main,5,main])-SELECT [Link], [Link], [Link], [Link], [Link], [Link], [Link], [Link], t1.INDEMNITE_ID, [Link], t0.BASE_HEURE,
t0.ENTRETIEN_JOUR, t0.INDEMNITES_CP, [Link], t0.REPAS_JOUR, [Link] FROM employes t1 LEFT OUTER JOIN indemnites t0 ON
([Link] = t1.INDEMNITE_ID) WHERE ([Link] = ?)
9.
bind => [Jouveinal]
10. Employ=Employ=[id=1, version=1, prnom=Marie, nom=Jouveinal, adresse=5 rue des oiseaux, ville=St Corentin, code
postal=49203, indemnite=Indemnite[id=2, version=1, indice=2, base heure=2.1, entretien jour=2.1, repas jour=3.1, indemnits
CP=15.0]
11. [EL Config]: connection: 2016-09-01 [Link].647--ServerSession(226710952)--Connection(737897289)-Thread(Thread[main,5,main])--disconnect
12. ...
4.7
ligne 7 : le 1er affichage. On pourrait croire, le voir, que le mode [LAZY] n'a pas fonctionn. Le dbogueur nous a
montr que ce n'tait pas le cas. On voit sur ces logs que :
lignes 3-4 : une requte a t faite pour obtenir l'employ ;
lignes 5-6 : une seconde requte a t faite pour satisfaire la mthode [[Link]] qui rfrence le champ
[[Link]]. L'indemnit a alors t cherche en base ;
ligne 10 : le second affichage. On voit lignes 8-9 que cette fois EclipseLink a fait une jointure pour ramener la fois
l'employ et son indemnit ;
Note : le prochain travail pratique est au paragraphe 4.8, page 110. D'ici l, il faut lire le cours.
L'architecture de l'application construire est la suivante :
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
Pour implmenter cette architecture, nous utiliserons le projet suivant prsent dans le support du TD :
96/290
4.7.1
Configuration Maven
97/290
33.
<!-- pool de connexions DBCP -->
34.
<dependency>
35.
<groupId>commons-dbcp</groupId>
36.
<artifactId>commons-dbcp</artifactId>
37.
<version>1.4</version>
38.
</dependency>
39.
<dependency>
40.
<groupId>commons-pool</groupId>
41.
<artifactId>commons-pool</artifactId>
42.
<version>1.6</version>
43.
</dependency>
44.
<!-- Spring framework -->
45.
<dependency>
46.
<groupId>[Link]</groupId>
47.
<artifactId>spring-tx</artifactId>
48.
<version>[Link]</version>
49.
</dependency>
50.
<dependency>
51.
<groupId>[Link]</groupId>
52.
<artifactId>spring-context</artifactId>
53.
<version>[Link]</version>
54.
</dependency>
55.
<dependency>
56.
<groupId>[Link]</groupId>
57.
<artifactId>spring-orm</artifactId>
58.
<version>[Link]</version>
59.
</dependency>
60.
<!-- interfaces swing -->
61.
<dependency>
62.
<groupId>[Link]</groupId>
63.
<artifactId>swing-layout</artifactId>
64.
<version>1.0.3</version>
65.
</dependency>
66.
</dependencies>
67.
68.
<properties>
69.
<[Link]>UTF-8</[Link]>
70.
<[Link]>1.8</[Link]>
71.
<[Link]>1.8</[Link]>
72.
</properties>
73. </project>
4.7.2
La couche [JPA]
La couche [JPA] utilisera les entits JPA construites au paraphaphe 4.6.1, page 68 avec le mode LAZY prsent au paragraphe 4.6.4,
page 85. Ces entits sont les suivantes :
L'entit [Employe]
1.
2.
3.
package jpa;
import [Link];
98/290
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.
import
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@Entity
@Table(name = "employes")
public class Employe implements Serializable {
private static final long serialVersionUID = 1L;
// cl primaire
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID")
private Long id;
// prnom
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
// nom
@Column(name = "NOM", length = 30, nullable = false)
private String nom;
// n de scu
@Column(name = "SS", length = 15, nullable = false, unique = true)
private String ss;
// adresse
@Column(name = "ADRESSE", length = 50, nullable = false)
private String adresse;
// code postal
@Column(name = "CP", length = 5, nullable = false)
private String cp;
// ville
@Column(name = "VILLE", length = 20, nullable = false)
private String ville;
// n de version
@Column(name = "VERSION", nullable = false)
@Version
private int version;
// indemnits
@JoinColumn(name = "INDEMNITE_ID", referencedColumnName = "ID")
@ManyToOne(optional = false, fetch = [Link])
private Indemnite indemnite;
// cl trangre
@Column(name = "INDEMNITE_ID", updatable = false, insertable = false, nullable = false)
private Long indemniteId;
// constructeurs
public Employe() {
}
public Employe(Long id) {
[Link] = id;
}
public Employe(Long id, int version, String prenom, String nom, String ss, String adresse, String ville, String cp,
Indemnite indemnite) {
[Link] = id;
[Link] = prenom;
[Link] = ss;
[Link] = adresse;
[Link] = cp;
[Link] = ville;
[Link] = nom;
[Link] = version;
[Link] = indemnite;
}
// toString
@Override
public String toString() {
return [Link]("Employ=[id=%s, version=%s, prnom=%s, nom=%s, adresse=%s, ville=%s, code postal=%s, indemniteId=
%s]",
99/290
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. }
ligne 58, on notera que l'entit [Indemnite] de l'employ est cherche en mode LAZY ;
lignes 62-63 : nous avons rajout un champ [indemniteId] pour rcuprer la valeur de la colonne [INDEMNITE_ID] de la
table [EMPLOYES] qui est une cl trangre sur la colonne [[Link]]. Ligne 71, l'annotation [@Column] a des
attributs pas encore rencontrs : updatable et insertable. Si on ne les met pas, on a une erreur l'excution qui dit qu'il y a un
conflit entre les champs [indemniteId] ligne 63 et [indemnite] ligne 59. Avant l'insertion du nouveau champ [indemniteId],
lorsqu'on persistait une entit [Employe] dans la table [EMPLOYES], la colonne [INDEMNITES_ID] de cette table tait
initialise avec la valeur du champ [[Link]]. Avec l'insertion du nouveau champ [indemniteId], la colonne
[INDEMNITES_ID] peut aussi tre mise jour par la valeur de ce champ. Il y a donc un conflit. On le lve en indiquant
ligne 62 par [insertable=false] que la colonne [INDEMNITES_ID] ne doit pas tre mise jour par la valeur du champ
[indemniteId] lors d'une opration INSERT. Le mme raisonnement s'applique lorsqu'il y a modification d'une entit
[Employe] qui va se traduire par une opration SQL UPDATE sur la table [EMPLOYES]. On indique ligne 71 par
[updatable=false] que la colonne [INDEMNITES_ID] ne doit pas tre mise jour par la valeur du champ [indemniteId]
lors d'une opration UPDATE.
L'ajout d'un champ pour la cl trangre [INDEMNITES_ID] est rendue ncessaire par le mode LAZY appliqu au
champ [Indemnite indemnite]. En effet, lorsqu'on rcupre des employs par une opration JPQL [select e from Employe
e], nous rcuprons des employs sans leurs indemnits. Avec le champ [long indemniteId] qui reprsente la cl primaire
de l'indemnit de l'employ, il devient possible de faire une requte pour obtenir l'indemnit complte d'un employ
lorsqu'on en a besoin ;
lignes 86-90 : la mthode [toString] d'un employ. On remarquera ligne 89 qu'on n'affiche pas le champ [Indemnite
indemnite] mais seulement le chap [long indemniteId]. Ceci parce qu'on est toujour sr d'avoir ce dernier champ, ce qui
n'est pas vrai pour le premier cause de son mode LAZY ;
L'entit [Indemnite]
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.
package jpa;
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@Entity
@Table(name = "indemnites")
public class Indemnite implements Serializable {
private static final long serialVersionUID = 1L;
// cl primaire
@Id
@GeneratedValue(strategy = [Link])
@Column(name = "ID", nullable = false)
private Long id;
// n de version
@Column(name = "VERSION", nullable = false)
100/290
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.
@Version
private int version;
// indemnit journalire d'entretien
@Column(name = "ENTRETIEN_JOUR", nullable = false)
private double entretienJour;
// indemnit journalire de repas
@Column(name = "REPAS_JOUR", nullable = false)
private double repasJour;
// indice d'indemnits
@Column(name = "INDICE", nullable = false, unique = true)
private int indice;
// indemnits de congs pays
@Column(name = "INDEMNITES_CP", nullable = false)
private double indemnitesCp;
// tarif horaire
@Column(name = "BASE_HEURE", nullable = false)
private double baseHeure;
public Indemnite() {
}
public Indemnite(Long id) {
[Link] = id;
}
public Indemnite(Long id, int version, int indice, double baseHeure, double entretienJour, double repasJour, double
indemnitesCp) {
[Link] = id;
[Link] = entretienJour;
[Link] = repasJour;
[Link] = indice;
[Link] = indemnitesCp;
[Link] = baseHeure;
[Link] = version;
}
@Override
public String toString() {
return [Link]("Indemnite[id=%s, version=%s, indice=%s, base heure=%s, entretien jour=%s, repas jour=%s,
indemnits CP=%s",
70.
id, version, indice, baseHeure, entretienJour, repasJour, indemnitesCp);
71.
}
72.
73.
// getters et setters -------------74. ...
75.
76.
// galit d'instances -----------77.
@Override
78.
public int hashCode() {
79.
int hash = 0;
80.
hash += (id != null ? [Link]() : 0);
81.
return hash;
82.
}
83.
84.
@Override
85.
public boolean equals(Object object) {
86.
// TODO: Warning - this method won't work in the case the id fields are not set
87.
if (!(object instanceof Indemnite)) {
88.
return false;
89.
}
90.
Indemnite other = (Indemnite) object;
91.
return !(([Link] == null && [Link] != null) || ([Link] != null && ));
92.
}
93.
94. }
L'entit [Cotisation]
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
package jpa;
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@Entity
@Table(name = "cotisations")
101/290
4.7.3
102/290
Couche
[ui]
Couche
[metier]
Objets image
de la BD
Couche
[DAO]
Interface
[JPA]
Implmentation
[Hibernate /
EclipseLink]
Couche
[JDBC]
Spring
Dans l'architecture ci-dessus, quelle interface doit offrir la couche [DAO] la couche [metier] et quelle interface doit offrir la
couche [metier] la couche [ui] ? Une premire approche pour dfinir les interfaces des diffrentes couches est d'examiner les
diffrents cas d'usage (use cases) de l'application. Ici nous en avons deux, selon l'interface utilisateur choisie : console ou formulaire
graphique.
Examinons le mode d'utilisation de l'application console :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
103/290
Certaines informations doivent tre fournies par la couche [metier] la couche [ui] :
1.
2.
3.
4.
les informations lies une assistante maternelle identifie par son n de scurit sociale. On trouve ces informations dans
la table [EMPLOYES]. Cela permet d'afficher les lignes 6-8 ;
les montants des divers taux de cotisations sociales prlever sur le salaire brut. On trouve ces informations dans la table
[COTISATIONS]. Cela permet d'afficher les lignes 10-12 ;
les montants des diverses indemnits lies la fonction d'assistante maternelle. On trouve ces informations dans la table
[INDEMNITES]. Cela permet d'afficher les lignes 14-15 ;
les lments constitutifs du salaire affichs lignes 18-22 ;
De ceci, on pourrait dcider d'une premire criture de l'interface [IMetier] prsente par la couche [metier] la couche [ui] :
1.
2.
3.
4.
5.
6.
package metier;
ligne 1 : les lments de la couche [metier] sont mis dans le paquetage [metier] ;
ligne 5 : la mthode [ calculerFeuilleSalaire ] prend pour paramtres les trois informations acquises par la couche [ui] et
rend un objet de type [FeuilleSalaire] contenant les informations que la couche [ui] affichera sur la console ;
package metier;
ligne 9 : l'employ concern par la feuille de salaire - information n 1 affiche par la couche [ui] ;
ligne 10 : les diffrents taux de cotisation - information n 2 affiche par la couche [ui] ;
ligne 11 : les diffrentes indemnits lies l'indice de l'employ - information n 3 affiche par la couche [ui] ;
ligne 12 : les lments constitutifs de son salaire - information n 4 affiche par la couche [ui] ;
import [Link];
import [Link];
import [Link];
public class FeuilleSalaire {
// champs privs
private Employe employe;
private Cotisation cotisation;
private ElementsSalaire elementsSalaire;
...
}
On voit ci-dessus, que la liste droulante [1, 2] prsente tous les employs. Cette liste doit tre demande la couche [mtier].
L'interface de celle-ci volue alors de la faon suivante :
1.
2.
package metier;
104/290
3.
4.
5.
6.
7.
8.
9.
10.
11.
import [Link];
import [Link];
ligne [10] : la mthode qui va permettre la couche [ui] de demander la liste de tous les employs la couche [mtier] ;
La couche [metier] ne peut initialiser les champs [Employe, Cotisation, Indemnite] de l'objet [FeuilleSalaire] ci-dessus qu'en
questionnant la couche [DAO] car ces informations sont dans les tables de la base de donnes. Il en est de mme pour obtenir la
liste de tous les employs. On peut crer une interface [DAO] unique grant l'accs aux trois entits [Employe, Cotisation,
Indemnite]. Nous dcidons plutt ici de crer une interface [DAO] par entit.
L'interface [DAO] pour les accs aux entits [Cotisation] de la table [COTISATIONS] sera la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
package dao;
ligne 6, l'interface [ICotisationDao] gre les accs l'entit [Cotisation] et donc la table [COTISATIONS] de la base de
donnes. Notre application n'a besoin que de la mthode [findAll] de la ligne 16 qui permet de retrouver tout le contenu
de la table [COTISATIONS]. On a voulu ici se mettre dans un cas plus gnral o toutes les oprations CRUD (Create,
Read, Update, Delete) sont effectues sur l'entit ;
ligne 8 : la mthode [create] cre une nouvelle entit [Cotisation] ;
ligne 10 : la mthode [edit] modifie une entit [Cotisation] existante ;
ligne 12 : la mthode [destroy] supprime une entit [Cotisation] existante ;
ligne 14 : la mthode [find] permet de retrouver une entit [Cotisation] existante via son identifiant id ;
ligne 16 : la mthode [findAll] rend dans une liste toutes les entits [Cotisation] existantes ;
import [Link];
import [Link];
public interface ICotisationDao {
// crer une nouvelle cotisation
Cotisation create(Cotisation cotisation);
// modifier une cotisation existante
Cotisation edit(Cotisation cotisation);
// supprimer une cotisation existante
void destroy(Cotisation cotisation);
// chercher une cotisation particulire
Cotisation find(Long id);
// obtenir tous les objets Cotisation
List<Cotisation> findAll();
}
La mthode create a un paramtre cotisation de type Cotisation. Le paramtre cotisation doit tre persist, c.a.d. ici mis dans la table
[COTISATIONS]. Avant cette persistance, le paramtre cotisation a un identifiant id sans valeur. Aprs la persistance, le champ id a
une valeur qui est la cl primaire de l'enregistrement ajout la table [COTISATIONS]. Le paramtre cotisation est donc un
paramtre d'entre / sortie de la mthode create. Il ne semble pas ncessaire que mthode create rende le paramtre cotisation comme
rsultat. La mthode appelante dtenant une rfrence sur l'objet [Cotisation cotisation], si celui-ci est modifi, elle a accs l'objet
modifi puisqu'elle a une rfrence dessus. Elle peut donc connatre la valeur que la mthode create a donn au champ id de l'objet
[Cotisation cotisation]. La signature de la mthode pourrait donc tre plus simplement :
1.
2.
Lorsqu'on crit une interface, il est bon de se rappeler qu'elle peut tre utilise dans deux contextes diffrents : local et distant. Dans
le contexte local, la mthode appelante et la mthode appele sont excutes dans la mme JVM :
105/290
utilisateur
Couche interface
utilisateur [ui]
Couche mtier
[metier]
Donnes
JVM
Si la couche [metier] fait appel la mthode create de la couche [DAO], elle a bien une rfrence sur le paramtre [Cotisation
cotisation] qu'elle passe la mthode.
Dans le contexte distant, la mthode appelante et la mthode appele sont excutes dans des JVM diffrentes :
1
utilisateur
Couche
interface
utilisateur [ui]
JVM 1
Couche
mtier
[metier]
3
Rseau
tcp /ip
Donnes
JVM 2
Ci-dessus, la couche [metier] s'excute dans la JVM 1 et la couche [DAO] dans la JVM 2 sur deux machines diffrentes. Les deux
couches ne communiquent pas directement. Entre-elles s'intercale une couche qu'on appellera couche de communication [1]. Celleci est compose d'une couche d'mission [2] et d'une couche de rception [3]. Le dveloppeur n'a en gnral pas crire ces
couches de communication. Elles sont gnres automatiquement par des outils logiciels. La couche [metier] est crite comme si elle
s'excutait dans la mme JVM que la couche [DAO]. Il n'y a donc aucune modification de code.
Le mcanisme de communication entre la couche [metier] et la couche [DAO] est le suivant :
la couche [metier] fait appel la mthode create de la couche [DAO] en lui passant le paramtre [Cotisation cotisation1]
ce paramtre est en fait pass la couche d'mission [2]. Celle-ci va transmettre sur le rseau, la valeur du paramtre
cotisation1 et non sa rfrence. La forme exacte de cette valeur dpend du protocole de communication utilis ;
la couche de rception [3] va rcuprer cette valeur et reconstruire partir d'elle un objet [Cotisation cotisation2] image du
paramtre initial envoy par la couche [metier]. On a maintenant deux objets identiques (au sens de contenu) dans deux
JVM diffrentes : cotisation1 et cotisation2 ;
la couche de rception va passer l'objet cotisation2 la mthode create de la couche [DAO] qui va le persister en base de
donnes. Aprs cette opration, le champ id de l'objet cotisation2 a t initialis par la cl primaire de l'enregistrement ajout
la table [COTISATIONS]. Ce n'est pas le cas de l'objet cotisation1 sur lequel la couche [metier] a une rfrence. Si on veut
que la couche [metier] ait une rfrence sur l'objet cotisation2, il faut le lui envoyer. Aussi est-on amens changer la
signature de la mthode create de la couche [DAO] :
1.
2.
avec cette nouvelle signature, la mthode create va rendre comme rsultat l'objet persist cotisation2. Ce rsultat est rendu
la couche de rception [3] qui avait appel la couche [DAO]. Celle-ci va rendre la valeur (et non la rfrence) de cotisation2
la couche d'mission [2] ;
la couche d'mission [2] va rcuprer cette valeur et reconstruire partir d'elle un objet [Cotisation cotisation3] image du
rsultat rendu par la mthode create de la couche [DAO] ;
l'objet [Cotisation cotisation3] est rendu la mthode de la couche [metier] dont l'appel la mthode create de la couche
[DAO] avait initi tout ce mcanisme. La couche [metier] peut donc connatre la valeur de cl primaire donn l'objet
[Cotisation cotisation1] dont elle avait demand la persistance : c'est la valeur du champ id de cotisation3 ;
L'architecture prcdente n'est pas la plus courante. On trouve plus frquemment les couches [metier] et [DAO] dans la mme JVM
:
106/290
1
utilisateur
2
Couche
interface
utilisateur [ui]
JVM 1
3
Rseau
tcp /ip
Couche
mtier
[metier]
Couche d'accs
aux donnes
[DAO]
Donnes
JVM 2
Dans cette architecture, ce sont les mthodes de la couche [metier] qui doivent rendre des rsultats et non celles de la couche
[DAO]. Nanmoins la signature suivante de la mthode create de la couche [DAO] :
1.
2.
nous permet de ne pas faire d'hypothses sur l'architecture rellement mise en place. Utiliser des signatures qui fonctionneront
quelque soit l'architecture retenue, locale ou distante, implique que dans le cas o une mthode appele modifie certains de ses
paramtres :
la mthode appelante doit utiliser le rsultat de la mthode appele et non les rfrences des paramtres modifis qu'elle a
transmis la mthode appele ;
On se laisse ainsi la possibilit de passer une couche d'une architecture locale une architecture distante sans modification de code.
Rexaminons, cette lumire, l'interface [ICotisationDao] :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
package dao;
import [Link];
import [Link];
public interface ICotisationDao {
// crer une nouvelle cotisation
Cotisation create(Cotisation cotisation);
// modifier une cotisation existante
Cotisation edit(Cotisation cotisation);
// supprimer une cotisation existante
void destroy(Cotisation cotisation);
// chercher une cotisation particulire
Cotisation find(Long id);
// obtenir tous les objets Cotisation
List<Cotisation> findAll();
}
Au final, seule la signature de la mthode create doit tre adapte pour tre utilisable dans le cadre d'une architecture distante. Les
raisonnements prcdents seront valables pour les autres interfaces [DAO]. Nous ne les rpterons pas et utiliserons directement
des signatures utilisables aussi bien dans le cadre d'une architecture distante que locale.
L'interface [DAO] pour les accs aux entits [Indemnite] de la table [INDEMNITES] sera la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
package dao;
import [Link];
import [Link];
public interface IIndemniteDao {
// crer une entit Indemnite
Indemnite create(Indemnite indemnite);
// modifier une entit Indemnite
Indemnite edit(Indemnite indemnite);
// supprimer une entit Indemnite
void destroy(Indemnite indemnite);
// rechercher une entit Indemnite via son identifiant
107/290
14.
Indemnite find(Long id);
15.
// obtenir toutes les entits Indemnite
16.
List<Indemnite> findAll();
17. }
ligne 6, l'interface [IIndemniteDao] gre les accs l'entit [Indemnite] et donc la table [INDEMNITES] de la base de
donnes. Notre application n'a besoin que de la mthode [findAll] de la ligne 16 qui permet de retrouver tout le contenu
de la table [INDEMNITES]. On a voulu ici se mettre dans un cas plus gnral o toutes les oprations CRUD (Create,
Read, Update, Delete) sont effectues sur l'entit.
ligne 8 : la mthode [create] cre une nouvelle entit [Indemnite]
ligne 10 : la mthode [edit] modifie une entit [Indemnite] existante
ligne 12 : la mthode [destroy] supprime une entit [Indemnite] existante
ligne 14 : la mthode [find] permet de retrouver une entit [Indemnite] existante via son identifiant id
ligne 16 : la mthode [findAll] rend dans une liste toutes les entits [Indemnite] existantes
L'interface [DAO] pour les accs aux entits [Employe] de la table [EMPLOYES] sera la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
package dao;
ligne 6, l'interface [IEmployeDao] gre les accs l'entit [Employe] et donc la table [EMPLOYES] de la base de
donnes. Notre application n'a besoin que de la mthode [findAll] de la ligne 19 qui permet de retrouver tout le contenu
de la table [EMPLOYES]. On a voulu ici se mettre dans un cas plus gnral o toutes les oprations CRUD (Create, Read,
Update, Delete) sont effectues sur l'entit ;
ligne 9 : la mthode [create] cre une nouvelle entit [Employe] ;
ligne 11 : la mthode [edit] modifie une entit [Employe] existante ;
ligne 13 : la mthode [destroy] supprime une entit [Employe] existante ;
ligne 15 : la mthode [find] permet de retrouver une entit [Employe] existante via son identifiant id ;
ligne 17 : la mthode [find(String SS)] permet de retrouver une entit [Employe] existante via son n SS. Nous avons vu
que cette mthode tait ncessaire l'application console ;
ligne 19 : la mthode [findAll] rend dans une liste toutes les entits [Employe] existantes. Nous avons vu que cette
mthode tait ncessaire l'application graphique.
4.7.4
import [Link];
import [Link];
public interface IEmployeDao {
// crer une nouvelle entit Employe
Employe create(Employe employe);
// modifier une entit Employe existante
Employe edit(Employe employe);
// supprimer une entit Employe
void destroy(Employe employe);
// chercher une entit Employe via son identifiant id
Employe find(Long id);
// chercher une entit Employe via son n SS
Employe find(String SS);
// obtenir toutes les entits Employe
List<Employe> findAll();
}
La classe [PamException]
La couche [DAO] va travailler avec l'API JDBC de Java. Cette API lance des exceptions contrles de type [SQLException] qui
prsentent deux inconvnients :
elles alourdissent le code qui doit obligatoirement grer ces exceptions avec des try / catch ;
108/290
elles doivent tre dclares dans la signature des mthodes de l'interface [IDao] par un "throws SQLException". Ceci a
pour consquence d'empcher l'implmentation de cette interface par des classes qui lanceraient une exception contrle
d'un type diffrent de [SQLException] ;
Pour remdier ce problme, la couche [DAO] ne "remontera" que des exceptions non contrles de type [PamException].
PamException
Couche
[ui]
Couche
[metier]
[JPA]Exception
Couche
[DAO]
SQLException
Implmentation
[JPA / Hibernate]
Couche
[JDBC]
Spring
la couche [metier] n'aura pas l'obligation de grer les exceptions de la couche [DAO] avec des try / catch. Elle pourra
simplement les laisser remonter jusqu' la couche [ui] ;
les mthodes de l'interface [IDao] n'ont pas mettre dans leur signature la nature de l'exception [PamException], ce qui
laisse la possiblit d'implmenter cette interface avec des classes qui lanceraient un autre type d'exception non contrle ;
La classe [PamException] sera place dans le paquetage [exception] du projet Netbeans :
package exception;
import [Link];
@SuppressWarnings("serial")
public class PamException extends RuntimeException implements Serializable{
// code d'erreur
private int code;
public PamException(int code) {
super();
[Link] = code;
}
public PamException(String message, int code) {
super(message);
[Link] = code;
}
public PamException(Throwable cause, int code) {
super(cause);
[Link] = code;
109/290
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41. }
}
public PamException(String message, Throwable cause, int code) {
super(message, cause);
[Link] = code;
}
// getter et setter
public int getCode() {
return code;
}
public void setCode(int code) {
[Link] = code;
}
ligne 6 : [PamException] drive de [RuntimeException]. C'est donc un type d'exceptions que le compilateur ne nous oblige
pas grer par un try / catch ou mettre dans la signature des mthodes. C'est pour cette raison, que [PamException]
n'est pas dans la signature des mthodes de l'interface [IDao]. Cela permet cette interface d'tre implmente par une
classe lanant un autre type d'exceptions, pourvu que celui-ci drive galement de [RuntimeException] ;
pour diffrencier les erreurs qui peuvent se produire, on utilise le code erreur de la ligne 9. Les trois constructeurs des
lignes 16, 21 et 26 sont ceux de la classe parente [RuntimeException] auxquels on a rajout un paramtre : celui du code
d'erreur qu'on veut donner l'exception ;
la couche [DAO] encapsulera toute exception rencontre, dans une exception de type [PamException], et relancera cette
dernire pour la couche [mtier] ;
la couche [mtier] laissera remonter les exceptions lances par la couche [DAO]. Elle encapsulera toute exception
survenant dans la couche [mtier], dans une exception de type [PamException] et relancera cette dernire pour la couche
[ui] ;
la couche [ui] intercepte toutes les exceptions qui remontent des couches [mtier] et [DAO]. Elle se contentera d'afficher
l'exception sur la console ou l'interface graphique ;
4.8
110/290
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
4.8.1
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
Implmentation
Lectures conseilles : paragraphe 3.1.3 de [ref1], API JPA au paragraphe 3.3, page 30.
Travail faire : En utilisant l'intgration Spring / JPA, crire les classes [CotisationDao, IndemniteDao, EmployeDao]
d'implmentation des interfaces [ICotisationDao, IIndemniteDao, IEmployeDao]. Chaque mthode de classe interceptera une
ventuelle exception et l'encapsulera dans une exception de type [PamException] avec un code d'erreur propre l'exception
intercepte.
Note : les classes du paquetage [dao] auront la forme suivante :
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.
package dao;
ligne 6 : l'annotation Spring @Transactional. Elle indique Spring que chaque mthode de la classe soit se drouler dans
une transaction ;
ligne 9 : l'annotation Spring @PersistenceContext injecte dans le champ em de la ligne 10, le gestionnaire du contexte de
persistance de la couche JPA ;
lignes 17-24 : l'intrieur d'une mthode de la couche [DAO], on utilise l'EntityManager de la ligne 10. Grce lui, on
fait des oprations de persistance (persist, merge, remove, createQuery). On relira le paragraphe 3.3, page 30, qui prsente
l'API d'une couche JPA ;
parce que la mthode se droule dans une transaction, on est assur qu' la fin de la mthode, les modifications apportes
au contexte de persistance seront synchronises avec la base de donnes ;
on fera en sorte que le code de la mthode soit entour d'un try / catch arrtant toute ventuelle exception. On
encapsulera celle-ci dans un type [PamException] (ligne 22).
4.8.2
...
import [Link];
@Transactional
public class CotisationDao implements ICotisationDao {
@PersistenceContext
private EntityManager em;
// constructeur
public CotisationDao() {
}
// crer une cotisation
public Cotisation create(Cotisation cotisation) {
try{
[Link](cotisation);
return cotisation;
}catch (Throwable th){
throw new PamException(th,11);
}
}
....
}
111/290
Travail faire : crire le contenu de ces deux fichiers. On supposera que la base de donnes utilise est la base MySQL5
[dbpam_hibernate] gnre par le script SQL [[Link]]. Le fichier Spring dfinira les trois beans suivants :
employeDao de type EmployeDao, indemniteDao de type IndemniteDao, cotisationDao de type CotisationDao. Par ailleurs, l'implmentation
JPA utilise sera Hibernate.
Note 1 : suivre le paragraphe 3.1.5 de [ref1]
Note 2 : la configuration d'Hibernate dans [[Link]] sera la suivante :
1.
<!-- configuration JPA -->
2.
<bean id="entityManagerFactory" class="[Link]">
3.
<property name="dataSource" ref="dataSource"/>
4.
<property name="jpaVendorAdapter">
5.
<bean class="[Link]">
6.
<property name="databasePlatform" value="[Link].MySQL5InnoDBDialect"/>
7.
</bean>
8.
</property>
9.
<property name="jpaProperties">
10.
<props>
11.
<prop key="[Link]">drop-and-create</prop>
12.
<prop key="hibernate.show_sql">TRUE</prop>
13.
<prop key="hibernate.format_sql">TRUE</prop>
14.
<prop key="use_sql_comments">TRUE</prop>
15.
</props>
16.
</property>
17.
<property name="loadTimeWeaver">
18.
<bean class="[Link]"/>
19.
</property>
20. </bean>
4.8.3
Tests
Couche
[tests]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
112/290
[Link]
JUnitInitDB
Nous allons crer deux programmes de tests de la couche [DAO]. Ceux-ci seront placs dans le paquetage [dao] [2] de la branche
[Test Packages] [1] du projet Netbeans. Cette branche n'est pas incluse dans le projet gnr par l'option [Build project], ce qui nous
assure que les programmes de tests que nous y plaons ne seront pas inclus dans le .jar final du projet.
Les classes places dans la branche [Test Packages] ont connaissance des classes prsentes dans la branche [Source Packages] ainsi
que des bibliothques de classes du projet. Si les tests ont besoin de bibliothques autres que celles du projet, celles-ci doivent tre
dclares dans la branche [Test Libraries] [2].
Les classes de tests utilisent l'outil de tests unitaires JUnit :
[JUnitInitDB] ne fait aucun test. Elle remplit la base de donnes avec quelques enregistrements et affiche ensuite ceux-ci
sur la console ;
package dao;
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
113/290
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47. }
}
@Before()
public void clean() {
// on vide la base
...
}
@AfterClass
public static void terminate() throws Exception {
}
ligne 20 : la mthode [init] est excute avant le dbut de la srie des tests (annotation @BeforeClass). Elle instancie la
couche [DAO]. Elle doit tre statique (ligne 20) ce qui entrane que les champs qu'elle utilise doivent tre galement
statiques (lignes 15-17) ;
ligne 38 : la mthode [clean] est excute avant chaque test (annotation @Before). Elle vide la base de donnes ;
ligne 30 : la mthode [initDB] est un test (annotation @Test). C'est le seul. Un test doit contenir des instructions
d'assertion [Link]. Ici il n'y en aura aucune. La mthode est donc un faux test. Elle a pour rle de
remplir la base de donnes avec quelques lignes puis d'afficher le contenu de la base sur la console. Ce sont les mthodes
create et findAll des couches [DAO] qui sont ici utilises.
Travail faire : complter le code de la classe [JUnitInitDB]. On s'aidera de l'exemple du paragraphe 3.1.6 de [ ref1]. Le code
gnrera le contenu prsent page 63.
Note : on utilisera les mthodes [create] des classes [DAO]. Parce que l'entit [Employe] embarque l'entit [Indemnite], il faut
d'abord crer ces dernires avant de crer les employs qui vont les rfrencer. Par ailleurs, pour persister une entit en base, il faut
qu'elle ait son id gal null. En effet, c'est cette caractristique que la mthode [persist] de l'interface [EntityManager] sait qu'elle
doit persister ou non une entit en base. Par ailleurs, la version de l'entit cre sera mise 1.
[Link]
114/290
puis excutez le script SQL [dbpam_hibernate-[Link]] que vous trouverez dans le support du TD [5] : il cre la
base [dbpam_hibernate] avec des tables vides ;
les classes [1], les fichiers de configuration [2] et les classes de test de la couche [DAO] [3] sont mis en place ;
La fentre [Output] contient les logs de l'excution, ceux de Spring et ceux du test lui-mme. Les affichages faits par la classe
[JUnitInitDB] sont les suivants :
1.
2.
3.
4.
5.
6.
7.
8.
Employs ---------------------Employ=[id=496, version=1, prnom=Marie, nom=Jouveinal, adresse=5 rue des oiseaux, ville=St Corentin, code postal=49203]
Employ=[id=497, version=1, prnom=Justine, nom=Laverti, adresse=La brlerie, ville=St Marcel, code postal=49014]
Indemnits ---------------------Indemnite[id=642, version=1, indice=1, base heure=1.93, entretien jour=2.0, repas jour=3.0, indemnits CP=12.0
Indemnite[id=643, version=1, indice=2, base heure=2.1, entretien jour=2.1, repas jour=3.1, indemnits CP=15.0
Cotisations ---------------------Cotisations=[id=74, version=1, csgrds=3.49, csgd=6.15, secu=9.39, retraite=7.88
Les tables [EMPLOYES, INDEMNITES, COTISATIONS] ont t remplies. On peut le vrifier avec une connexion Netbeans la
base [dbpam_hibernate].
115/290
[Link]
en [1-4], dans l'onglet [services], on visualise les donnes de la table [employes] de la connexion [dbpam_hibernate] [2] ;
en [3] le rsultat ;
JUnitDao
package dao;
import
import
import
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
116/290
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.
117/290
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.
118/290
198.
199.
200.
201.
202.
203.
204.
205.
206. }
// on le vrifie
}
@Test
public void test11() {
// sert tester la mthode clean
}
Dans la classe de tests prcdente, la base est vide avant chaque test.
Travail faire : crire les mthodes suivantes :
- test02 : on s'inspirera de test01 ;
- test03 : un employ a un champ de type Indemnite. Il faut donc crer une entit Indemnite et une entit Employe ;
- test04 ;
En procdant de la mme faon que pour la classe de tests [JUnitInitDB], on obtient les rsultats suivants :
Provoquons une erreur pour voir comment cela est signal dans la page des rsultats :
1.
@Test
2.
public void test01() {
3.
log("test01");
4.
// liste des cotisations
5.
List<Cotisation> cotisations = [Link]();
6.
int nbCotisations = [Link]();
7.
// on ajoute une cotisation
8.
Cotisation cotisation = [Link](new Cotisation(3.49, 6.15, 9.39, 7.88));
9.
// on la demande
10.
cotisation = [Link]([Link]());
11.
// vrification
12.
[Link](cotisation);
13.
[Link](0, [Link](), 1e-6);
14.
[Link](6.15, [Link](), 1e-6);
15.
[Link](9.39, [Link](), 1e-6);
16.
[Link](7.88, [Link](), 1e-6);
17.
// on la modifie
18. ....
19. }
Ligne 13, l'assertion va provoquer une erreur, la valeur de [Csgrds] tant 3.49 (ligne 8). L'excution de la classe de tests donne les
rsultats suivants :
119/290
4.9
la page des rsultats [1] montre maintenant qu'il y a eu des tests non russis ;
en [2], un rsum de l'exception qui a fait chouer le test. On y trouve le n de la ligne du code Java o s'est produite
l'exception ;
Maintenant que la couche [DAO] a t crite, nous passons l'tude de la couche mtier [2] :
Couche
[ui]
Couche
[mtier]
Couche
[DAO]
Objets image
de la BD
4.9.1
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
package metier;
import [Link];
import [Link];
public interface IMetier {
// obtenir la feuille de salaire
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravailles, int nbJoursTravaills );
// liste des employs
List<Employe> findAllEmployes();
}
Le paquetage [metier] comprendra, outre l'interface [IMetier] et son implmentation [Metier], deux autres classes [FeuilleSalaire] et
[ElementsSalaire]. La classe [FeuilleSalaire] a t brivement prsente page 104. Nous revenons dessus maintenant.
120/290
4.9.2
La classe [FeuilleSalaire]
La mthode [calculerFeuilleSalaire] de l'interface [IMetier] rend un objet de type [FeuilleSalaire] qui reprsente les diffrents
lments d'une feuille de salaire. Sa dfinition est la suivante :
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.
package metier;
ligne 7 : la classe implmente l'interface Serializable parce que ses instances sont susceptibles d'tre changes sur le rseau ;
ligne 9 : l'employ concern par la feuille de salaire ;
ligne 10 : les diffrents taux de cotisation ;
ligne 11 : les diffrentes indemnits lies l'indice de l'employ ;
ligne 12 : les lments constitutifs de son salaire ;
lignes 14-22 : les deux constructeurs de la classe ;
lignes 25-27 : mthode [toString] identifiant un objet [FeuilleSalaire] particulier ;
lignes 30 et au-del : les accesseurs publics aux champs privs de la classe ;
import [Link];
import [Link];
import [Link];
public class FeuilleSalaire implements Serializable {
// champs privs
private Employe employe;
private Cotisation cotisation;
private ElementsSalaire elementsSalaire;
// constructeurs
public FeuilleSalaire() {
}
public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
[Link] = employe;
[Link] = cotisation;
[Link] = elementsSalaire;
}
// toString
@Override
public String toString() {
return [Link]("FeuilleSalaire[%s, %s, ,%s]", employe, cotisation, elementsSalaire);
}
// accesseurs
...
La classe [ElementsSalaire] rfrence ligne 11 de la classe [FeuilleSalaire] ci-dessus, rassemble les lments constituant une fiche de
paie. Sa dfinition est la suivante :
1. package metier;
2.
3. import [Link];
4.
5. public class ElementsSalaire implements Serializable {
6.
7.
// champs privs
8.
private double salaireBase;
9.
private double cotisationsSociales;
10.
private double indemnitesEntretien;
11.
private double indemnitesRepas;
12.
private double salaireNet;
13.
14.
// constructeurs
15.
public ElementsSalaire() {
16.
17.
}
18.
19.
public ElementsSalaire(double salaireBase, double cotisationsSociales, double indemnitesEntretien, double
indemnitesRepas, double salaireNet) {
20.
[Link] = salaireBase;
21.
[Link] = cotisationsSociales;
22.
[Link] = indemnitesEntretien;
23.
[Link] = indemnitesRepas;
24.
[Link] = salaireNet;
25.
}
26.
27.
// toString
28.
@Override
29.
public String toString() {
121/290
30.
ligne 5 : la classe implmente l'interface Serializable parce qu'elle est un composant de la classe FeuilleSalaire qui doit tre
srialisable.
ligne 8 : le salaire de base
ligne 9 : les cotisations sociales payes sur ce salaire de base
ligne 10 : les indemnits journalires d'entretien de l'enfant
ligne 11 : les indemnits journalires de repas de l'enfant
ligne 12 : le salaire net payer l'assistante maternelle
lignes 15-25 : les constructeurs de la classe
lignes 28-32 : mthode [toString] identifiant un objet [ElementsSalaire] particulier
lignes 34 et au-del : les accesseurs publics aux champs privs de la classe
4.9.3
package metier;
ligne 12 : l'annotation Spring @Transactional fait que chaque mthode de la classe se droulera au sein d'une transaction.
On avait dj mis cette annotation sur les classes de la couche [DAO]. Lorsqu'une mthode de la couche [mtier] appelle
une mthode de la couche [DAO], il n'y pas de nouvelle transaction cre. La mthode de la couche [DAO] utilise celle
cre par la couche [mtier]. Au final, la dure de vie du contexte de persistance JPA est celle de la transaction de la
couche [mtier] et non plus celle de la transaction de la couche [DAO] ;
lignes 16-17 : les rfrences sur les couches [DAO] des entits [Cotisation, Employe] ;
lignes 20-23 : la mthode [calculerFeuilleSalaire] ;
lignes 26-29 : la mthode [findAllEmployes] ;
ligne 31 et au-del : les accesseurs publics des champs privs de la classe.
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@Transactional
public class Metier implements IMetier {
// rfrences sur la couche [dao]
private ICotisationDao cotisationDao = null;
private IEmployeDao employeDao = null;
// obtenir la feuille de salaire
@Override
public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravailles, int nbJoursTravaills) {
...
}
// liste des employs
@Override
public List<Employe> findAllEmployes() {
...
}
// getters et setters
...
}
122/290
si le paramtre [SS] ne correspond aucun employ (la couche [DAO] a renvoy un pointeur null), la mthode lancera une
exception de type [PamException] avec un code d'erreur appropri.
4.9.4
Les classes de tests sont cres dans un paquetage [metier] [2] de la branche [Test Packages] [1] du projet.
Pour faire les tests, on mettra en commentaires dans le fichier [[Link]] les lignes qui font que les tables de la
base de donnes sont recres chaque nouvelle excution du projet :
1. <!-- configuration JPA -->
2.
<bean id="entityManagerFactory" class="[Link]">
3.
<property name="dataSource" ref="dataSource"/>
4.
<property name="jpaVendorAdapter">
5.
<bean class="[Link]">
6.
<property name="databasePlatform" value="[Link].MySQL5InnoDBDialect"/>
7.
</bean>
8.
</property>
9. <!-<property name="jpaProperties">
10.
<props>
11.
<prop key="[Link]">drop-and-create</prop>
12.
<prop key="hibernate.show_sql">TRUE</prop>
13.
<prop key="hibernate.format_sql">TRUE</prop>
14.
<prop key="use_sql_comments">TRUE</prop>
15.
</props>
16.
</property>
-->
17.
<property name="loadTimeWeaver">
18.
<bean class="[Link]"/>
19.
</property>
20.
</bean>
package metier;
import
import
import
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
123/290
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
[Link](employe);
});
[Link]().stream().forEach((cotisation) -> {
[Link](cotisation);
});
[Link]().stream().forEach((indemnite) -> {
[Link](indemnite);
});
// on la remplit
// Indemnite(Long id, int version, int indice, double baseHeure, double entretienJour, double repasJour, double
indemnitesCp)
Indemnite indemnite1 = [Link](new Indemnite(null, 1, 1, 1.93, 2, 3, 12));
Indemnite indemnite2 = [Link](new Indemnite(null, 1, 2, 2.1, 2.1, 3.1, 15));
// Employe(Long id, int version, String prenom, String nom, String ss, String adresse, String ville, String cp,
Indemnite indemnite)
[Link](new Employe(null, 1, "Marie", "Jouveinal", "254104940426058", "5 rue des oiseaux", "St Corentin",
"49203", indemnite2));
[Link](new Employe(null, 1, "Justine", "Laverti", "260124402111742", "La brlerie", "St Marcel", "49014",
indemnite1));
// Cotisation(Long id, int version, double secu, double retraite, double csgd, double csgrds)
[Link](new Cotisation(null, 1, 9.39, 7.88, 6.15, 3.49));
}
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. }
@AfterClass
public static void terminate() {
}
// logs
static private void log(String message) {
[Link]("----------- " + message);
}
// test
@Test
public void test01(){
// calcul de feuilles de salaire
[Link]([Link]("260124402111742",30, 5));
[Link]([Link]("254104940426058",150, 20));
try {
[Link]([Link]("xx", 150, 20));
} catch (PamException ex) {
[Link]([Link]("PamException[Code=%d, message=%s]",[Link](), [Link]()));
}
}
Il n'y a pas d'assertion [Link] dans la classe. On cherche simplement calculer quelques salaires afin de les
vrifier ensuite la main. L'affichage cran obtenu par l'excution de la classe prcdente est le suivant :
1.
3.
2.
Travail faire : la ligne 27 de [JUnitMetier_1] utilise le bean Spring nomm metier. Donner la dfinition de ce bean dans le fichier
[[Link]]. Ce fichier est identique au fichier Spring utilis pour tester la couche [DAO]. On y ajoute
simplement la dfinition du bean de la couche [mtier].
La classe [JUnitMetier_2] pourrait tre la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
package metier;
import
import
import
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
124/290
La classe [JUnitMetier_2] est une copie de la classe [JUnitMetier_1] o cette fois, des assertions ont t places dans la mthode
test01.
Travail faire : crire la mthode test01.
Lors de l'excution de la classe [JUnitMetier_2], on obtient les rsultats suivants si tout va bien :
4.10
Maintenant que la couche [mtier] a t crite, il nous reste crire la couche [ui] [1] :
125/290
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
Nous crerons deux implmentations diffrentes de la couche [ui] : une version console et une version graphique swing :
4.10.1
La classe [[Link]]
Nous nous intressons tout d'abord l'application console implmente par la classe [[Link]] ci-dessus. Son
fonctionnement a t dcrit au paragraphe 4.3, page 65. Le squelette de la classe [Main] pourrait tre le suivant :
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.
package [Link];
import [Link];
import [Link];
import [Link];
import
import
import
import
[Link];
[Link];
[Link];
[Link];
126/290
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.
4.10.2
Excution
127/290
128/290
4.11
Couche
[ui]
Couche
[metier]
Objets image
de la BD
Couche
[DAO]
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
129/290
4.11.1
Un rapide tutoriel
[11] : la liste des composants Swing disponibles pour un formulaire est trouve dans la fentre [Palette] ;
[12] : la fentre [Inspector] prsente l'arborescence des composants du formulaire. Les composants ayant une
reprsentation visuelle se retrouveront dans la branche [JFrame], les autres dans la branche [Other Components] ;
130/290
en [20] et [21] : dans la perspective [Source] du formulaire, du code Java a t ajout pour grer le JLabel ajout ;
131/290
4.11.2
JLabel1
JPanel1
JPanel2
132/290
JPanel3
JPanel4
JPanel5
4.11.3
133/290
Le code Java qui associe la mthode prcdente au clic sur le bouton [JButtonSalaire] est lui aussi gnr :
1.
2.
3.
4.
5.
6.
});
[Link]("Salaire");
[Link](new [Link]() {
public void actionPerformed([Link] evt) {
jButtonSalaireActionPerformed(evt);
}
Ce sont les lignes 2-5 qui indiquent que le clic (evt de type ActionPerformed) sur le bouton [jButtonSalaire] (ligne 2) doit tre gr par
la mthode [jButtonSalaireActionPerformed] (ligne 4).
Nous grerons galement, l'vnement [caretUpdate] (dplacement du curseur de saisie) sur le champ de saisie [jTextFieldHT]. Pour
crer le gestionnaire de cet vnement, nous procdons comme prcdemment :
Le code Java qui associe la mthode prcdente l'vnement [caretUpdate] sur le champ de saisie [jTextFieldHT] est lui aussi
gnr :
1.
2.
3.
4.
5.
[Link](new [Link]() {
public void caretUpdate([Link] evt) {
jTextFieldHTCaretUpdate(evt);
}
});
Les lignes 1-4 indiquent que l'vnement [caretUpdate] (ligne 2) sur le bouton [jTextFieldHT] (ligne 1) doit tre gr par la mthode
[ jTextFieldHTCaretUpdate] (ligne 3).
4.11.4
Couche
[ui]
Couche
[metier]
Objets image
de la BD
Couche
[DAO]
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
La couche [ui] a besoin d'une rfrence sur la couche [metier]. Rappelons comment cette rfrence avait t obtenue dans
l'application console :
1.
2.
134/290
3.
La mthode est la mme dans l'application graphique. Il faut que lorsque celle-ci s'initialise, la rfrence [IMetier metier] de la ligne
3 ci-dessus soit galement initialise. Le code gnr pour l'interface graphique est pour l'instant le suivant :
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.
package [Link];
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
Pour ajouter au code prcdent nos propres initialisations, nous pouvons procder comme suit :
1.
2.
3.
4.
5.
135/290
6. ...
7.
8.
// variables d'instance
9.
private IMetier metier=null;
10.
private List<Employe> employes=null;
11.
private String[] employesCombo=null;
12.
private double heuresTravailles=0;
13.
14.
// initialisations propritaires
15.
public final void doMyInit(){
16.
// init contexte
17.
try{
18.
// instanciation couche [metier]
19.
20.
// on rcupre la liste des employs
21.
}catch (PamException ex){
22.
[Link]([Link]());
23.
return;
24.
}
25.
// composants
26.
// bouton salaire inhib
27.
28.
// textAreaStatus visible
29.
30.
// spinner jours travaills initialis
31.
[Link](new SpinnerNumberModel(0,0,31,1));
32.
// combobox employs rempli
33.
employesCombo=new String[[Link]()];
34.
int i=0;
35.
for(Employe employe : employes){
36.
employesCombo[i++]=[Link]()+" "+[Link]();
37.
}
38.
[Link](new DefaultComboBoxModel(employesCombo));
39.
}
ligne 4 : on appelle une mthode propritaire pour faire nos propres initialisations. Celles-ci sont dfinies par le code des
lignes 15-39 ;
Travail faire : en vous aidant des commentaires, complter le code de la procdure [doMyInit].
4.11.5
Gestionnaires d'vnements
Travail faire : crire la mthode [jTextFieldHTCaretUpdate]. Cette mthode doit faire en sorte que si la donne prsente dans le
champ [jTextFieldHT] n'est pas un nombre rel >=0, alors le bouton [jButtonSalaire] doit tre inactif.
Travail faire : crire la mthode [jButtonSalaireActionPerformed] qui doit afficher la feuille de salaire de l'employ slectionn
dans [jComboBoxEmployes].
4.11.6
Le projet doit tre complet avec ses fichiers de configuration ([Link], [Link]) et la classe de
l'interface graphique. On lancera Le SGBD cible avant d'excuter le projet.
136/290
4.12
Nous nous intressons l'architecture suivante o la couche JPA est dsormais implmente par EclipseLink [1] :
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[EclipseLink]
Couche
[JDBC]
1
7
4.12.1
Spring
Le projet Netbeans
Le nouveau projet Netbeans est obtenu par recopie du projet prcdent [ 1-9] :
137/290
Le projet [5] doit tre modifi en deux points pour l'adapter la nouvelle couche JPA / EclipseLink :
1.
2.
en [11], les fichiers de configuration de Spring doivent tre modifis. On y trouve en effet la configuration de la couche
JPA ;
en [12], les bibliothques du projet doivent tre modifies : celles d'Hibernate doivent tre remplaces par celles de
EclipseLink.
Commenons par ce dernier point. Le fichier [[Link]] pour le nouveau projet sera celui-ci :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="[Link] xmlns:xsi="[Link]
3.
xsi:schemaLocation="[Link] [Link]
4.
5.
<!-- artifact -->
6.
<modelVersion>4.0.0</modelVersion>
7.
<groupId>[Link]</groupId>
8.
<artifactId>mv-pam-spring-eclipselink</artifactId>
9.
<version>1.0-SNAPSHOT</version>
10.
<packaging>jar</packaging>
11.
<name>mv-pam-spring-eclipselink</name>
12.
13.
<dependencies>
14.
<!-- eclipselink -->
15.
<dependency>
16.
<groupId>[Link]</groupId>
17.
<artifactId>eclipselink</artifactId>
18.
<version>2.6.3</version>
19.
</dependency>
20.
<!-- mysql -->
21.
...
22.
<!-- JUnit -->
23.
...
24.
<!-- pool de connexions DBCP -->
25.
...
26.
<!-- Spring framework -->
27.
...
28.
<!-- interfaces swing -->
29.
...
30.
</dependencies>
31.
32.
<!-- plugins EclipseLink -->
33.
<build>
34.
<plugins>
138/290
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.
<!--[[Link] -->
<!-- This plugin ensures the EclipseLink static weaving -->
<plugin>
<artifactId>staticweave-maven-plugin</artifactId>
<groupId>[Link]</groupId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>weave</goal>
</goals>
<phase>process-classes</phase>
<configuration>
<logLevel>ALL</logLevel>
<includeProjectClasspath>true</includeProjectClasspath>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.3</version>
</dependency>
</dependencies>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!-- This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build
itself. -->
<plugin>
<groupId>[Link].m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
[Link]
</groupId>
<artifactId>
staticweave-maven-plugin
</artifactId>
<versionRange>
[1.0.0,)
</versionRange>
<goals>
<goal>weave</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnIncremental>true</runOnIncremental>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
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. <properties>
102.
<[Link]>UTF-8</[Link]>
103.
<[Link]>1.8</[Link]>
104.
<[Link]>1.8</[Link]>
105. </properties>
106. </project>
Les fichiers de configuration de Spring doivent tre modifis pour indiquer que l'implmentation JPA a chang. Dans les deux
fichiers, seule la section configurant la couche JPA change. Par exemple dans [[Link]] on a actuellement le
code suivant :
1.
2.
139/290
3.
<property name="dataSource" ref="dataSource"/>
4.
<property name="jpaVendorAdapter">
5.
<bean class="[Link]">
6.
<property name="databasePlatform" value="[Link].MySQL5InnoDBDialect"/>
7.
</bean>
8.
</property>
9.
<property name="jpaProperties">
10.
<props>
11.
<prop key="[Link]">drop-and-create</prop>
12.
<prop key="hibernate.show_sql">TRUE</prop>
13.
<prop key="hibernate.format_sql">TRUE</prop>
14.
<prop key="use_sql_comments">TRUE</prop>
15.
</props>
16.
</property>
17.
<property name="loadTimeWeaver">
18.
<bean class="[Link]"/>
19.
</property>
20. </bean>
Les lignes 1-20 configurent la couche JPA. L'implmentation JPA utilise est Hibernate (ligne 5). Pour passer une implmentation
JPA / EclipseLink, les lignes ci-dessus sont remplaces par les lignes ci-dessous :
1.
<!-- configuration JPA -->
2.
<bean id="entityManagerFactory" class="[Link]">
3.
<property name="dataSource" ref="dataSource"/>
4.
<property name="jpaVendorAdapter">
5.
<bean class="[Link]">
6.
<property name="databasePlatform" value="[Link]" />
7.
</bean>
8.
</property>
9.
<property name="jpaProperties">
10.
<props>
11.
<prop key="[Link]">drop-and-create</prop>
12.
<prop key="[Link]">FINE</prop>
13.
<prop key="[Link]">static</prop>
14.
<prop key="[Link]">true</prop>
15.
<prop key="[Link]">true</prop>
16.
</props>
17.
</property>
18. </bean>
4.12.2
Avant de tester l'application entire, il est bon de vrifier que les tests JUnit passent avec la nouvelle implmentation JPA.
Nous construisons le projet (Clean and Build) puis nous excutons le test [JUnitInitDB] :
140/290
Employs ---------------------Employ=[id=7, version=1, prnom=Marie, nom=Jouveinal, adresse=5 rue des oiseaux, ville=St Corentin, code postal=49203,
indemniteId=null]
Employ=[id=8, version=1, prnom=Justine, nom=Laverti, adresse=La brlerie, ville=St Marcel, code postal=49014,
indemniteId=null]
Indemnits ---------------------Indemnite[id=7, version=1, indice=1, base heure=1.93, entretien jour=2.0, repas jour=3.0, indemnits CP=12.0]
Indemnite[id=8, version=1, indice=2, base heure=2.1, entretien jour=2.1, repas jour=3.1, indemnits CP=15.0]
Cotisations ---------------------Cotisations=[id=4, version=1, csgrds=3.49, csgd=6.15, secu=9.39, retraite=7.88]
On peut remarquer, lignes 2 et 3 qu'on n'a pas rcupr la cl trangre [indemniteId] des deux employs alors qu'avec Hibernate
on les avait. On retrouve de nouveau une diffrence de rsultats selon l'implmentation JPA. Ici, le fait qu'on n'ait pas pu obtenir la
cl trangre [indemniteId] des employs n'a pas d'importance. Au paragraphe 4.6.5, page 91, nous avons vu qu'EclipseLink
retrouvait l'entit [Indemnite [Link]] ds que ce champ de l'entit [Employe] tait rfrenc dans le code. Si nous
changeons la mthode [[Link]] de la faon suivante :
1.
2.
3.
4.
// toString
@Override
public String toString() {
return [Link]("Employ=[id=%s, version=%s, prnom=%s, nom=%s, adresse=%s, ville=%s, code postal=%s,
[Link]=%s]",
id, version, prenom, nom, adresse, ville, cp, [Link]());
}
5.
6.
Employs ---------------------Employ=[id=9, version=1, prnom=Marie, nom=Jouveinal, adresse=5 rue des oiseaux, ville=St Corentin, code postal=49203,
[Link]=10]
Employ=[id=10, version=1, prnom=Justine, nom=Laverti, adresse=La brlerie, ville=St Marcel, code postal=49014,
[Link]=9]
Indemnits ---------------------Indemnite[id=9, version=1, indice=1, base heure=1.93, entretien jour=2.0, repas jour=3.0, indemnits CP=12.0]
Indemnite[id=10, version=1, indice=2, base heure=2.1, entretien jour=2.1, repas jour=3.1, indemnits CP=15.0]
Cotisations ---------------------Cotisations=[id=5, version=1, csgrds=3.49, csgd=6.15, secu=9.39, retraite=7.88]
[Link]
JUnitDao
4.12.3
Une fois la couche [DAO] teste et considre correcte, on pourra passer aux tests de la couche [metier] et ceux du projet luimme dans sa version console ou graphique. On prendra soin auparavant de commenter les lignes de [[Link]] et
[[Link]] qui suppriment les tables de la base de donnes chaque excution du projet :
1.
<!-- configuration JPA -->
2.
<bean id="entityManagerFactory" class="[Link]">
3.
<property name="dataSource" ref="dataSource"/>
4.
<property name="jpaVendorAdapter">
5.
<bean class="[Link]">
6.
<property name="databasePlatform" value="[Link]" />
7.
</bean>
8.
</property>
9. <!-<property name="jpaProperties">
10.
<props>
11.
<prop key="[Link]">drop-and-create</prop>
12.
<prop key="[Link]">FINE</prop>
13.
</props>
14.
</property>
-->
15.
<property name="loadTimeWeaver">
16.
<bean class="[Link]"/>
17.
</property>
18. </bean>
141/290
Le changement d'implmentation JPA n'influe en rien sur les couches [metier] et [ui] et donc si ces couches fonctionnaient avec
Hibernate, elles fonctionneront avec EclipseLink quelques prcautions prs : il ne faut pas que la couche [DAO] lance des
exceptions propres l'implmentation JPA utilise. Celles-ci doivent tre encapsules dans des exceptions propres l'application.
4.12.4
Travail faire
Travail faire : refaire les tests des applications console et swing avec diffrents SGBD : Postgresql, Oracle XE, SQL Server.
142/290
Note : le TD est lire jusqu'au paragraphe 5.2, page 148 o commence le travail pratique. Il n'y a rien faire avant.
Nous prsentons ici les principes qui vont gouverner le portage d'une application JPA / Spring / Hibernate vers une application
JPA / OpenEJB / EclipseLink. On attendra le paragraphe 5.2, page 148 pour crer les projets Maven.
5.1.1
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
71
Interface
[JPA]
Implmentation
[Hibernate]
2
Couche
[JDBC]
Implmentation
[EclipseLink]
2
Couche
[JDBC]
Implmentation
[EclipseLink]
2
Couche
[JDBC]
Spring
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
OpenEjb
Dans cette implmentation, la couche [ui] sera un client local de la couche [mtier].
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
OpenEjb
Dans cette implmentation, la couche [ui] sera un client distant de la couche [mtier].
5.1.2
5.1.3
1.
143/290
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<persistence version="2.1"
xmlns="[Link]
xmlns:xsi="[Link]
xsi:schemaLocation="[Link]
[Link]
<persistence-unit name="mv-pam-jpa-eclipselinkPU" transaction-type="JTA">
<provider>[Link]</provider>
<class>[Link]</class>
<class>[Link]</class>
<class>[Link]</class>
<properties>
<property name="[Link]" value="drop-and-create"/>
<property name="[Link]" value="FINE"/>
</properties>
</persistence-unit>
</persistence>
ligne 5 : les transactions dans un conteneur EJB sont de type JTA (Java Transaction API). Elles taient de type
RESOURCE_LOCAL avec Spring ;
ligne 7 : l'implmentation JPA utilise est EclipseLink
lignes 8-10 : les entits gres par la couche JPA
lignes 11-14 : proprits du provider EclipseLink
ligne 12 : chaque excution, les tables seront cres
Les caractristiques JDBC de la source de donnes JTA utilise par le conteneur OpenEJB seront prcises par le fichier
de configuration [conf/[Link]] suivant :
1.
2.
3.
4.
5.
6.
7.
8.
<?xml version="1.0"?>
<openejb>
<Resource id="Default JDBC Database">
JdbcDriver [Link]
JdbcUrl jdbc:mysql://localhost:3306/dbpam_hibernate
UserName root
</Resource>
</openejb>
ligne 3 : on utilise l'id Default JDBC Database lorsqu'on travaille avec un conteneur OpenEJB embarqu (embedded)
dans l'application elle-mme ;
ligne 5 : nous utilisons le base MySQL [dbpam_hibernate] ;
5.1.4
Les classes implmentant la couche [DAO] deviennent des EJB. Prenons l'exemple de la classe [CotisationDao] :
L'interface [ICotisationDao] dans la version Spring tait la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
package dao;
import [Link];
import [Link];
import [Link];
public interface ICotisationDao {
// crer une nouvelle cotisation
Cotisation create(Cotisation cotisation);
// modifier une cotisation existante
Cotisation edit(Cotisation cotisation);
// supprimer une cotisation existante
void destroy(Cotisation cotisation);
// chercher une cotisation particulire
Cotisation find(Long id);
L'EJB va implmenter cette mme interface sous deux formes diffrentes : une locale et une distante. L'interface locale
peut tre utilise par un client s'excutant dans la mme JVM, l'interface distante par un client s'excutant dans une autre
JVM.
L'interface locale :
144/290
1.
2.
3.
4.
5.
6.
7.
package dao;
ligne 6 : l'interface [ICotisationDaoLocal] hrite de l'interface [ICotisationDao] pour en reprendre toutes les mthodes.
Elle n'en ajoute pas de nouvelles ;
ligne 5 : l'annotation @Local en fait une interface locale pour l'EJB qui l'implmentera ;
import [Link];
@Local
public interface ICotisationDaoLocal extends ICotisationDao {
}
L'interface distante :
1.
2.
3.
4.
5.
6.
7.
package dao;
ligne 6 : l'interface [ICotisationDaoRemote] hrite de l'interface [ICotisationDao] pour en reprendre toutes les mthodes.
Elle n'en ajoute pas de nouvelles.
ligne 5 : l'annotation @Remote en fait une interface distante pour l'EJB qui l'implmentera.
import [Link];
@Remote
public interface ICotisationDaoRemote extends ICotisationDao {
}
La couche [DAO] est implmente par un EJB implmentant les deux interfaces (ce n'est pas obligatoire) :
1.
2.
3.
4.
5.
6.
@Stateless
@TransactionAttribute([Link])
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {
@PersistenceContext
private EntityManager em;
utilisateur
Couche interface
utilisateur [ui]
Couche mtier
[metier]
Donnes
JVM
Ci-dessus, les couches [metier] et [DAO] changent des objets par rfrence. Lorsqu'une couche change l'objet partag,
l'autre couche voit ce changement.
Lorsque l'interface distante de la couche [DAO] est utilise, le client de cette interface s'excute habituellement dans une
autre JVM.
1
utilisateur
Couche
interface
utilisateur [ui]
JVM 1
Couche
mtier
[metier]
3
Rseau
tcp /ip
Donnes
JVM 2
145/290
Ci-dessus, les couches [metier] et [DAO] changent des objets par valeur (srialisation / dsrialisation de l'objet
chang). Lorsqu'une couche change un objet partag, l'autre couche ne voit ce changement que si l'objet modifi lui
est renvoy.
5.1.5
La classe implmentant la couche [metier] devient elle aussi un EJB implmentant une interface locale et distante. L'interface
initiale [IMetier] tait la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
package metier;
import [Link];
import [Link];
public interface IMetier {
// obtenir la feuille de salaire
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravailles, int nbJoursTravaills );
// liste des employs
List<Employe> findAllEmployes();
}
On cre une interface locale et une interface distante partir de l'interface prcdente :
1.
2.
3.
4.
5.
6.
7.
8.
package metier;
1.
2.
3.
4.
5.
6.
7.
8.
package metier;
import [Link];
@Local
public interface IMetierLocal extends IMetier{
}
import [Link];
@Remote
public interface IMetierRemote extends IMetier{
}
@Stateless
@TransactionAttribute([Link])
public class Metier implements IMetierLocal, IMetierRemote {
// rfrences sur la couche [dao]
@EJB
private ICotisationDaoLocal cotisationDao = null;
@EJB
private IEmployeDaoLocal employeDao = null;
lignes 1-2 : dfinissent un EJB dont chaque mthode s'excute dans une transaction ;
ligne 7 : une rfrence sur l'interface locale de l'EJB [CotisationDao] ;
ligne 6 : l'annotation @EJB demande ce que le conteneur EJB injecte une rfrence sur l'interface locale de l'EJB
[CotisationDao] ;
lignes 8-9 : on refait la mme chose pour l' interface locale de l'EJB [EmployeDao] ;
Au final, lorsque l'EJB [Metier] sera instanci, les champs des lignes 7 et 9 seront initialiss avec des rfrences sur les
interfaces locales des deux EJB de la couche [DAO]. On fait donc ici l'hypothse que les couches [metier] et [DAO]
s'excuteront dans la mme JVM.
1
utilisateur
2
Couche
interface
utilisateur [ui]
JVM 1
3
Rseau
tcp /ip
Couche
mtier
[metier]
Couche d'accs
aux donnes
[DAO]
Donnes
JVM 2
146/290
5.1.6
2
Couche
interface
utilisateur [ui]
JVM 1
Couche
mtier
[metier]
Rseau
tcp /ip
Couche d'accs
aux donnes
[DAO]
Donnes
JVM 2
Dans le schma ci-dessus, pour communiquer avec la couche [metier], la couche [ui] doit obtenir une rfrence sur l'interface
distante de l'EJB de la couche [metier].
utilisateur
Couche interface
utilisateur [ui]
Couche mtier
[metier]
Donnes
JVM
Dans le schma ci-dessus, pour communiquer avec la couche [metier], la couche [ui] doit obtenir une rfrence sur l'interface locale
de l'EJB de la couche [metier]. La mthode pour obtenir ces rfrences diffre d'un conteneur l'autre. Pour le conteneur
OpenEJB, on pourra procder comme suit :
Rfrence sur l'interface locale :
1.
2.
3.
4.
5.
6.
7.
Le code prcdent rcupre des rfrences sur les interfaces locales des EJB via leurs noms JNDI. Nous avons dit prcdemment
que celles-ci pouvaient galement tre obtenues via l'annotation @EJB. On pourrait donc vouloir crire :
@EJB
private IMetierLocal metier ;
L'annotation @EJB n'est honore que si elle appartient une classe charge par le conteneur EJB. Ce sera le cas de la classe
[Metier] par exemple. Le code ci-dessus lui, appartiendra une classe console qui ne sera pas charge par le conteneur EJB. On est
donc oblig d'utiliser les noms JNDI des EJB.
Ci-dessous, le code pour avoir une rfrence sur l'interface distante de l'EJB [Metier] :
1.
2.
3.
4.
5.
6.
7.
147/290
5.2
Travail pratique
On se propose de porter l'application Netbeans Spring / Hibernate vers une architecture OpenEJB / EclipseLink.
L'implmentation actuelle avec Spring / Hibernate
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
2
Couche
[JDBC]
Interface
[JPA]
Implmentation
[EclipseLink]
Couche
[JDBC]
Spring
L'implmentation construire avec OpenEJB / EclipseLink
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
OpenEjb
5.2.1
dans l'onglet [Files] [2], crer un dossier [conf] [3] sous la racine du projet ;
placer dans ce dossier, le fichier [[Link]] [4-10] suivant :
1.
2.
3.
4.
5.
6.
7.
8.
<?xml version="1.0"?>
<openejb>
<Resource id="Default JDBC Database">
JdbcDriver [Link]
JdbcUrl jdbc:mysql://localhost:3306/dbpam_hibernate
UserName root
</Resource>
</openejb>
148/290
crer le dossier [src / main/ resources/ META-INF] [11] (attention, il s'agit du mot anglais resources avec un seul s) ;
y mettre le fichier [[Link]] [11] suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
ligne 3 : on fera attention l'URL de cette ligne. Si on reprend celle prsente dans les fichiers [[Link]] que nous
avons utiliss jusqu' maintenant et qui avaient t gnrs par Netbeans, on a une erreur signale par OpenEJB et difficile
dboguer ;
ligne 13 : on demande des logs fins EclipseLink ;
ligne 12 : les tables seront cres l'instanciation de la couche JPA ;
ligne 13 : niveau de logs d'EclipseLink ;
lignes 15-17 : pour l'enrichissement statique des entit JPA / EclipseLink ;
ajouter les bibliothques OpenEJB, EclipseLink ainsi que le pilote JDBC de MySQL au fichier [[Link]] du projet :
149/290
5.2.2
Nous allons faire le portage de la couche [DAO] par copie de paquetages du projet [mv-pam-spring-hibernate] vers le projet [mvpam-openejb-eclipselink].
150/290
Les erreurs signales ci-dessus viennent du fait que la couche [DAO] copie utilise Spring et que les bibliothques Spring ne font
plus partie du projet.
[Link]
L'EJB [CotisationDao]
Nous suivons ce qui a t expliqu au paragraphe 5.1.4, page 144. Nous crons les interfaces locale et distante du futur EJB
[CotisationDao] :
L'interface locale ICotisationDaoLocal :
1.
2.
3.
4.
5.
6.
7.
package dao;
import [Link];
@Local
public interface ICotisationDaoLocal extends ICotisationDao {
}
Pour avoir les bons import de paquetages, faire [clic droit sur le code / Fix Imports].
L'interface distante ICotisationDaoRemote :
1.
2.
3.
4.
5.
6.
7.
package dao;
import [Link];
@Remote
public interface ICotisationDaoRemote extends ICotisationDao {
}
@Stateless
@TransactionAttribute([Link])
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {
@PersistenceContext
private EntityManager em;
L'import que faisait cette classe sur le framework Spring disparat. Faire un [Clean and Build] du projet :
151/290
[Link]
[Link]
La classe [PamException]
@ApplicationException(rollback=true)
public class PamException extends RuntimeException implements Serializable{
// code d'erreur
private int code;
La ligne 1 est ajoute. Pour avoir les bons import, faire [clic droit+Fix imports].
Pour comprendre l'annotation de la ligne 1, il faut se rappeler que chaque mthode des EJB de notre couche [DAO] :
lance une exception de type [PamException] ds que quelque chose se passe mal ;
utilisateur
Couche
interface
utilisateur [ui]
Couche
mtier
[metier]
Proxy
EJB
Donnes
Lorsque la couche [metier] appelle une mthode M de la couche [DAO], cet appel est intercept par le conteneur EJB. Tout se passe
comme s'il y avait une classe intermdiaire entre la couche [metier] et la couche [DAO], appele ici[Proxy EJB], interceptant tous les
appels vers la couche [DAO]. Lorsque l'appel la mthode M de la couche [DAO] est intercepte, le Proxy EJB dmarre une
transaction puis passe la main la mthode M de la couche [DAO] qui s'excute alors dans cette transaction. La mthode M se
termine avec ou sans exception.
si la mthode M se termine sans exception, l'excution revient au proxy EJB qui termine la transaction en la validant par
un commit. Le flux d'excution est ensuite rendu la mthode appelante de la couche [metier] ;
si la mthode M se termine avec exception, l'excution revient au proxy EJB qui termine la transaction en l'invalidant par
un rollback. De plus il encapsule cette exception dans un type EJBException. Le flux d'excution est ensuite rendu la
152/290
mthode appelante de la couche [metier] qui reoit donc une EJBException. L'annotation de la ligne 5 ci-dessus empche
cette encapsulation. La couche [metier] recevra donc une PamException. De plus, l'attribut rollback=true indique au
proxy EJB que lorsqu'il reoit une PamException, il doit invalider la transaction ;
[Link]
Entits srialisables
Revenons sur l'architecture o la couche [UI] est un client distant de la couche [mtier] :
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[EclipseLink]
Couche
[JDBC]
OpenEjb
Les couches [ui] et [metier] vont s'changer des objets. Dans la pratique, ces deux couches sont dans deux JVM diffrentes. Si la
couche [ui] veut passer un objet la couche [mtier], elle ne peut pas passer la rfrence de cet objet. La couche [mtier] ne peut en
effet rfrencer des objets qui ne sont pas dans sa JVM. La couche [ui] va alors passer la valeur de l'objet et non sa rfrence. On
appelle cela la srialisation de l'objet. La couche [mtier] va recevoir cette valeur et va crer un nouvel objet partir d'elle. On
appelle cela la dsrialisation de l'objet. La couche [ui] et la couche [mtier] ont alors deux objets identiques mais chacun dans sa
JVM.
Dans notre exemple, les types suivants peuvent tre changs entre les couches [ui] et [metier] : [Employe, Cotisation, Indemnite,
FeuilleSalaire, ElementsSalaire, PamException]. Ces classes doivent pouvoir tre srialises. Cela est obtenu par la simple
dclaration :
1.
2.
3.
On fera donc en sorte que les classes cites implmentent l'interface [Serializable]. Il n'y a rien d'autre faire pour qu'une classe
puisse tre srialise.
[Link]
Notre couche [DAO] implmente par des EJB peut tre teste. Nous commenons par copier le package [dao] de [Test Packages]
du projet [mv-pam-springhibernate] dans le projet en cours de construction [1] :
Nous ne conservons que le test [JUnitInitDB] qui initialise la base avec quelques donnes [2]. Nous renommons la classe
[ JUnitInitDbLocal] [3]. La classe [JUnitInitDBLocal] utilisera l'interface locale des EJB de la couche [DAO].
Nous modifions tout d'abord la classe [JUnitInitDBLocal] de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
153/290
10.
11.
12.
13.
14.
15.
16.
17.
18. }
19. ...
lignes 3-5 : des rfrences sur les interfaces locales des EJB de la couche [DAO] ;
ligne 7 : @BeforeClass annote la mthode excute au dmarrage du test JUnit ;
lignes 10-13 : initialisation du conteneur OpenEJB. Cette initialisation est propritaire et change avec chaque conteneur
EJB ;
ligne 13 : on a un contexte JNDI (Java Naming and Directory Interface) qui permet d'accder aux EJB via des noms. Avec
OpenEJB, l'interface locale d'un EJB E est dsigne par ELocal et l'interface distante par ERemote ;
lignes 15-17 : on demande au contexte JNDI, une rfrence sur les interfaces locales des EJB [EmployeDao,
CotisationDao, IndemniteDao].
Avant d'excuter le test [JUnitDBLocal], avec la connexion Netbeans [dbpam_hibernate], supprimez les tables de la base pour
vrifier qu'elles vont bien tre recres puis excutez le test :
On construit le projet (Clean and Build), on lance le serveur MySQL si besoin est, on excute le test JUnitInitDBLocal [2-3]. On
rappelle que le fichier [[Link]] a t configur pour recrer les tables chaque excution.
154/290
INFOS - ********************************************************************************
INFOS - OpenEJB [Link]
INFOS - Startup: Fri Sep 02 [Link] CEST 2016
INFOS - Copyright 1999-2015 (C) Apache OpenEJB/TomEE Project, All Rights Reserved.
INFOS - Version: 4.7.4
INFOS - Build date: 20160304
INFOS - Build time: 09:05
INFOS - ********************************************************************************
INFOS - [Link] = D:\data\istia-1617\netbeans\support-td\chap-05\mv-pam-openejb-eclipselink
INFOS - [Link] = D:\data\istia-1617\netbeans\support-td\chap-05\mv-pam-openejb-eclipselink
INFOS - Created new singletonService [Link]@668bc3d5
INFOS - Succeeded in installing singleton service
INFOS - openejb configuration file is 'D:\data\istia-1617\netbeans\support-td\chap-05\mv-pam-openejbeclipselink\conf\[Link]'
INFOS - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
INFOS - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction
Manager)
INFOS - Configuring Service(id=Default JDBC Database, type=Resource, provider-id=Default JDBC Database)
INFOS - Found EjbModule in classpath: d:\data\istia-1617\netbeans\support-td\chap-05\mv-pam-openejbeclipselink\target\classes
INFOS - Beginning load: d:\data\istia-1617\netbeans\support-td\chap-05\mv-pam-openejb-eclipselink\target\classes
INFOS - Configuring enterprise application: D:\data\istia-1617\netbeans\support-td\chap-05\mv-pam-openejbeclipselink\[Link]
INFOS - Auto-deploying ejb EmployeDao: EjbDeployment(deployment-id=EmployeDao)
INFOS - Auto-deploying ejb Metier: EjbDeployment(deployment-id=Metier)
INFOS - Auto-deploying ejb CotisationDao: EjbDeployment(deployment-id=CotisationDao)
INFOS - Auto-deploying ejb IndemniteDao: EjbDeployment(deployment-id=IndemniteDao)
INFOS - Configuring Service(id=Default Stateless Container, type=Container, provider-id=Default Stateless Container)
INFOS - Auto-creating a container for bean EmployeDao: Container(type=STATELESS, id=Default Stateless Container)
INFOS - Configuring PersistenceUnit(name=mv-pam-jpa-eclipselinkPU,
provider=[Link])
INFOS - Auto-creating a Resource with id 'Default JDBC DatabaseNonJta' of type 'DataSource for 'mv-pam-jpa-eclipselinkPU'.
INFOS - Configuring Service(id=Default JDBC DatabaseNonJta, type=Resource, provider-id=Default JDBC Database)
INFOS - Adjusting PersistenceUnit mv-pam-jpa-eclipselinkPU <jta-data-source> to Resource ID 'Default JDBC Database' from
'null'
INFOS - Adjusting PersistenceUnit mv-pam-jpa-eclipselinkPU <non-jta-data-source> to Resource ID 'Default JDBC
DatabaseNonJta' from 'null'
INFOS - Enterprise application "D:\data\istia-1617\netbeans\support-td\chap-05\mv-pam-openejb-eclipselink\[Link]"
loaded.
INFOS - Creating TransactionManager(id=Default Transaction Manager)
INFOS - Creating SecurityService(id=Default Security Service)
INFOS - Creating Resource(id=Default JDBC Database)
INFOS - Creating Resource(id=Default JDBC DatabaseNonJta)
INFOS - Creating Container(id=Default Stateless Container)
INFOS - Assembling app: D:\data\istia-1617\netbeans\support-td\chap-05\mv-pam-openejb-eclipselink\[Link]
[EL Fine]: 2016-09-02 [Link].84--Thread(Thread[main,5,main])--Configured server platform:
[Link]
[EL Config]: 2016-09-02 [Link].027--ServerSession(1544078442)--Thread(Thread[main,5,main])--The access type for the
persistent class [class [Link]] is set to [FIELD].
[EL Config]: 2016-09-02 [Link].043--ServerSession(1544078442)--Thread(Thread[main,5,main])--The access type for the
persistent class [class [Link]] is set to [FIELD].
[EL Config]: 2016-09-02 [Link].059--ServerSession(1544078442)--Thread(Thread[main,5,main])--The access type for the
persistent class [class [Link]] is set to [FIELD].
[EL Config]: 2016-09-02 [Link].059--ServerSession(1544078442)--Thread(Thread[main,5,main])--The target entity (reference)
class for the many to one mapping element [field indemnite] is being defaulted to: class [Link].
[EL Config]: 2016-09-02 [Link].059--ServerSession(1544078442)--Thread(Thread[main,5,main])--The alias name for the entity
class [class [Link]] is being defaulted to: Cotisation.
[EL Config]: 2016-09-02 [Link].074--ServerSession(1544078442)--Thread(Thread[main,5,main])--The alias name for the entity
class [class [Link]] is being defaulted to: Indemnite.
[EL Config]: 2016-09-02 [Link].09--ServerSession(1544078442)--Thread(Thread[main,5,main])--The alias name for the entity
class [class [Link]] is being defaulted to: Employe.
AVERTISSEMENT - JAVA AGENT NOT INSTALLED. The JPA Persistence Provider requested installation of a ClassFileTransformer
which requires a JavaAgent. See [Link]
[EL Info]: 2016-09-02 [Link].152--ServerSession(1544078442)--Thread(Thread[main,5,main])--EclipseLink, version: Eclipse
Persistence Services - 2.6.3.v20160428-59c81c5
[EL Fine]: 2016-09-02 [Link].481--Thread(Thread[main,5,main])--Detected database platform:
[Link]
155/290
156/290
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.
Employs ---------------------Employ=[id=1, version=1, prnom=Marie, nom=Jouveinal, adresse=5 rue des oiseaux, ville=St Corentin, code postal=49203,
[Link]=null]
157/290
3.
4.
5.
6.
7.
8.
Employ=[id=2, version=1, prnom=Justine, nom=Laverti, adresse=La brlerie, ville=St Marcel, code postal=49014,
[Link]=null]
Indemnits ---------------------Indemnite[id=1, version=1, indice=1, base heure=1.93, entretien jour=2.0, repas jour=3.0, indemnits CP=12.0]
Indemnite[id=2, version=1, indice=2, base heure=2.1, entretien jour=2.1, repas jour=3.1, indemnits CP=15.0]
Cotisations ---------------------Cotisations=[id=1, version=1, csgrds=3.49, csgd=6.15, secu=9.39, retraite=7.88]
Lignes 2 et 3, on remarquera que le champ [indemniteId] de l'employ ramen en mode LAZY vaut null.
Nous refaisons le mme test, en utilisant cette fois-ci l'interface distante des EJB.
En [1], la classe [JUnitInitDBLocal] a t duplique (copy / paste) dans [JUnitInitDBRemote]. Dans cette classe, nous remplaons
les interfaces locales par les interfaces distantes :
1. public class JUnitInitDBRemote {
2.
3.
static private IEmployeDaoRemote employeDao = null;
4.
static private ICotisationDaoRemote cotisationDao = null;
5.
static private IIndemniteDaoRemote indemniteDao = null;
6.
7.
@BeforeClass
8.
public static void init() throws Exception {
9.
// on configure le conteneur Open EJB embarqu
10.
Properties properties = new Properties();
11.
[Link](Context.INITIAL_CONTEXT_FACTORY, "[Link]");
12.
// initialisation du contexte JNDI avec les proprits prcdentes
13.
InitialContext initialContext = new InitialContext(properties);
14.
// instanciation couches DAO distantes
15.
employeDao = (IEmployeDaoRemote) [Link]("EmployeDaoRemote");
16.
cotisationDao = (ICotisationDaoRemote) [Link]("CotisationDaoRemote");
17.
indemniteDao = (IIndemniteDaoRemote) [Link]("IndemniteDaoRemote");
18. }
Ceci fait, la nouvelle classe de test peut tre excute. Auparavant, avec la connexion Netbeans [dbpam_hibernate], supprimez les
tables de la base pour vrifier qu'elles vont bien tre recres.
Excutez le test puis avec la connexion Netbeans [dbpam_hibernate], vrifiez que la base a t remplie.
Travail faire : portez le test [JUnitDao] du projet [mv-pam-spring-hibernate] dans le projet [mv-pam-openejb-eclipselink] sous la
forme locale [JUnitDaoLocal] et distante [JUnitDaoRemote]. Vrifiez que ces tests passent.
5.2.3
Nous allons faire le portage de la couche [metier] par copie de packages du projet [mv-pam-spring-hibernate] vers le projet [mvpam-openejb-eclipselink].
158/290
Les erreurs signales ci-dessus [1] viennent du fait que la couche [metier] copie utilise Spring et que les bibliothques Spring ne
font plus partie du projet.
[Link]
L'EJB [Metier]
Nous suivons la mme dmarche que celle dcrite pour l'EJB [CotisationDao]. Nous crons tout d'abord en [2] les interfaces locale
et distante du futur EJB [Metier]. Elles drivent toutes deux de l'interface initiale [IMetier].
1.
2.
3.
4.
5.
6.
7.
8.
package metier;
1.
2.
3.
4.
5.
6.
7.
8.
package metier;
import [Link];
@Local
public interface IMetierLocal extends IMetier{
}
import [Link];
@Remote
public interface IMetierRemote extends IMetier{
}
Ceci fait, en [3] nous modifions la classe [Metier] afin qu'elle devienne un EJB :
1.
2.
3.
4.
5.
6.
7.
8.
9.
@Stateless
@TransactionAttribute([Link])
public class Metier implements IMetierLocal, IMetierRemote {
[Link]
Notre couche [metier] implmente par un EJB peut tre teste. Nous commenons par copier le package [metier] de [Test
Packages] du projet [mv-pam-spring-hibernate] dans le projet en cours de construction [1] :
159/290
En [2], on duplique [JUnitMetierLocal] en [JUnitMetierRemote] pour tester cette fois-ci l'interface distante de l'EJB [Metier]. Le
code de [JUnitMetierRemote] est modifi pour utiliser cette interface distante. Le reste ne change pas.
1.
2.
3.
4.
5.
6.
160/290
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
5.2.4
Nous allons faire le portage de la couche [console] par copie de packages du projet [mv-pam-spring-hibernate] vers le projet [mvpam-openejb-eclipselink].
Les erreurs signales ci-dessus [1] viennent du fait que la couche [metier] copie utilise Spring et que les bibliothques Spring ne
font plus partie du projet. En [2], la classe [Main] est renomme [MainLocal]. Elle utilisera l'interface locale de l'EJB [Metier].
Le code de la classe [MainLocal] volue de la faon suivante :
1. public static void main(String[] args) {
2.
// donnes locales
3.
final String syntaxe = "pg num_securite_sociale nb_heures_travailles nb_jours_travaills";
4.
// on vrifie le nombre de paramtres
5.
if ([Link] != 3) {
6.
[Link]("Syntaxe : " + syntaxe);
7.
return;
8.
}
9.
// liste des erreurs
10.
List<String> erreurs = new ArrayList<>();
11.
...
12.
// des erreurs ?
13.
if (![Link]()) {
14.
for (int i = 0; i < [Link](); i++) {
15.
[Link]([Link](i));
16.
}
17.
return;
18.
}
19.
// c'est bon - on peut demander la feuille de salaire
161/290
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38. ...
FeuilleSalaire feuilleSalaire;
try {
// on configure le conteneur Open EJB embarqu
Properties properties = new Properties();
[Link](Context.INITIAL_CONTEXT_FACTORY, "[Link]");
// initialisation du contexte JNDI avec les proprits prcdentes
InitialContext initialContext = new InitialContext(properties);
// instanciation couche mtier locale
IMetierLocal metier = (IMetierLocal) [Link]("MetierLocal");
// calcul de la feuille de salaire
feuilleSalaire = [Link](args[0], nbHeuresTravailles, nbJoursTravaills);
} catch (PamException ex) {
[Link]([Link]("L'erreur suivante s'est produite : %s", [Link]()));
return;
} catch (Exception ex) {
[Link]([Link]("L'erreur suivante s'est produite : %s", ex));
return;
}
Les modifications se situent dans les lignes 22-28. C'est la faon d'avoir une rfrence sur la couche [metier] qui change (ligne 28).
Nous n'expliquons pas le nouveau code qui a dj t vu dans des exemples prcdents. Une fois ces modifications faites, le projet
ne prsente plus d'erreurs (cf [3]).
Nous configurons le projet pour qu'il soit excut avec des arguments [1-3] :
Pour que l'application console s'excute normalement, il faut qu'il y ait des donnes dans la base. Pour cela, il faut modifier le fichier
[META-INF/[Link]] :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
La ligne 12 qui faisait que les tables de la base de donnes taient recres chaque excution est mise en commentaires. Le projet
doit tre reconstruit (Clean and Build) pour que cette modification soit prise en compte. Si besoin est, excutez le test [JUnitInitDB]
du projet [mv-pam-openejb-eclipselink] pour remplir les tables. Ceci fait, on peut excuter le programme. Si tout va bien, on obtient
un affichage console analogue au suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
.......
INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
INFO - Deployed Application(path=[Link])
[EL Info]: 2009-09-30 [Link].109--ServerSession(16658781)--EclipseLink, version: Eclipse Persistence Services 1.1.2.v20090612-r4475
[EL Info]: 2009-09-30 [Link].937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-metier-dao-openejbeclipselink-0910/build/classes/-JPA login successful
Valeurs saisies :
N de scurit sociale de l'employ : 254104940426058
Nombre d'heures travailles : 150
Nombre de jours travaills : 20
162/290
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.
Informations Employ :
Nom : Jouveinal
Prnom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2
Informations Cotisations :
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Scurit sociale : 9.39 %
Informations Indemnits :
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congs Pays : 15.0 %
Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnits d'entretien : 42.0 euro
Indemnits de repas : 62.0 euro
Salaire net : 368.77 euro
BUILD SUCCESSFUL (total time: 4 seconds)
Nous avons utilis ici l'interface locale de la couche [metier]. Nous utilisons maintenant son interface distante dans une seconde
classe console :
En [1], la classe [MainLocal] a t duplique dans [MainRemote]. Le code de [MainRemote] est modifi pour utiliser l'interface
distante de la couche [metier] :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19. }
La modification est faite ligne 10. Le projet est configur [2] pour excuter la classe [MainRemote]. Son excution donne les mmes
rsultats que prcdemment.
5.2.5
Travail faire : portez dans le projet [mv-pam-openejb-eclipselink] l'application Swing [PamJFrame] du projet [mv-pam-spring-jpahibernate] en une version locale [PamJFrameLocal] et une version distante [PamJFrameRemote].
163/290
5.3
Conclusion
Nous avons montr comment porter une architecture Spring / Hibernate vers une architecture OpenEJB / EclipseLink.
L'architecture Spring / Hibernate
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Interface
[JPA]
Implmentation
[EclipseLink]
Couche
[JDBC]
Spring
L'architecture OpenEJB / EclipseLink
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
OpenEjb
Le portage a pu se faire sans trop de difficults parce que l'application initiale avait t structure en couches. Ce point est
important comprendre.
164/290
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[EclipseLink]
Couche
[JDBC]
OpenEjb
Ci-dessus, la couche [ui] utilise l'interface distante de la couche [metier].
Nous avons test deux contextes d'excution : local et distant. Dans ce dernier mode, la couche [ui] tait cliente de la couche [metier],
couche implmente par des EJB. Pour fonctionner en mode client / serveur, dans lequel le client et le serveur s'excutent dans
deux JVM diffrentes, nous allons placer les couches [metier, DAO, JPA] sur le serveur Java EE Glassfish. Ce serveur est livr avec
Netbeans.
L'implmentation construire avec le serveur Glassfish
Couche
[ui]
Jvm1 - Java SE
6.1
6.1.1
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
Nous tudions ici la partie serveur qui sera hberge par le conteneur EJB3 du serveur Glassfish :
165/290
Client
Java SE
Conteneur
Ejb3
Jpa / Eclipselink
Donnes
serveur Java EE
Il s'agit de faire un portage vers le serveur Glassfish de ce qui a dj t fait et test avec le conteneur OpenEJB. C'est l l'intrt de
OpenEJB et en gnral des conteneurs EJB embarqus : ils nous permettent de tester l'application dans un environnement
d'excution simplifi. Lorsque l'application a t teste, il ne reste plus qu' la porter sur un serveur cible, ici le serveur Glassfish.
[Link]
Le projet Netbeans
avec le bouton [4], choisir le dossier parent du dossier du projet ou taper son nom directement en [5] ;
en [6], donner un nom au projet ;
en [7], fixer le [groupId] de l'artifact Maven du projet ;
en [8], passer l'tape suivante ;
en [9], choisir le serveur d'application sur lequel il sera excut. Celui choisi ici est l'un de ceux visibles dans l'onglet
[Runtime / Servers] ;
166/290
[Link]
Configuration Maven
167/290
50.
<artifactId>maven-dependency-plugin</artifactId>
51.
<version>2.6</version>
52.
<executions>
53.
<execution>
54.
<phase>validate</phase>
55.
<goals>
56.
<goal>copy</goal>
57.
</goals>
58.
<configuration>
59.
<outputDirectory>${[Link]}</outputDirectory>
60.
<silent>true</silent>
61.
<artifactItems>
62.
<artifactItem>
63.
<groupId>javax</groupId>
64.
<artifactId>javaee-endorsed-api</artifactId>
65.
<version>7.0</version>
66.
<type>jar</type>
67.
</artifactItem>
68.
</artifactItems>
69.
</configuration>
70.
</execution>
71.
</executions>
72.
</plugin>
73.
</plugins>
74.
</build>
75.
76. </project>
lignes 18-23 : la seule dpendance dont le projet a besoin est celle de l'artifact [java-ee]. On notera l'attribut [provided] de
la ligne 22 : cela signifie que la dpendance [java-ee] ne sera pas incluse dans l'archive jar du projet. A l'excution, elle sera
trouve dans les archives du serveur Glassfish ;
lignes 26-74 : divers plugins Maven ncessaires la gnration de l'archive du projet EJB ;
La configuration Maven prcdente n'inclut pas les plugins ncessaires au weaving des entits JPA / EclipseLink. Nous les rajoutons ;
1. <build>
2.
<plugins>
3.
<!-- gnr par Netbeans-->
4.
<plugin>
5.
...
6.
</plugin>
7.
<plugin>
8.
...
9.
</plugin>
10.
<plugin>
11.
....
12.
</plugin>
13.
14.
<!-- weaving EclipseLink -->
15.
<!--[[Link] -->
16.
<!-- This plugin ensures the EclipseLink static weaving -->
17.
<plugin>
18.
<artifactId>staticweave-maven-plugin</artifactId>
19.
<groupId>[Link]</groupId>
20.
<version>1.0.0</version>
21.
<executions>
22.
<execution>
23.
<goals>
24.
<goal>weave</goal>
25.
</goals>
26.
<phase>process-classes</phase>
27.
<configuration>
28.
<logLevel>ALL</logLevel>
29.
<includeProjectClasspath>true</includeProjectClasspath>
30.
</configuration>
31.
</execution>
32.
</executions>
33.
<dependencies>
34.
<dependency>
35.
<groupId>[Link]</groupId>
36.
<artifactId>eclipselink</artifactId>
37.
<version>2.6.3</version>
38.
</dependency>
39.
</dependencies>
40.
</plugin>
41.
</plugins>
42.
43.
<!-- weaving EclipseLink -->
44.
<pluginManagement>
45.
<plugins>
46.
<!-- This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven
build itself. -->
47.
<plugin>
48.
<groupId>[Link].m2e</groupId>
168/290
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.
[Link]
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
[Link]
</groupId>
<artifactId>
staticweave-maven-plugin
</artifactId>
<versionRange>
[1.0.0,)
</versionRange>
<goals>
<goal>weave</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnIncremental>true</runOnIncremental>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Par configuration de la couche de persistance, nous entendons l'criture du fichier [[Link]] qui dfinit :
la dfinition de la source de donnes exploite par la couche JPA. Celle-ci sera une source JDBC gre par le serveur
Glassfish ;
Couche
[ui]
Java SE
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
Eclipselink]
Couche
[JDBC]
SGBD
BD
On pourra procder comme suit. Tout d'abord, dans l'onglet [Runtime / Databases], on crera [1] une connexion sur la base
MySQL5 [dbpam_hibernate] :
Ceci fait, on peut passer la cration de la ressource JDBC utilise par le module EJB :
169/290
en [1], crer un nouveau fichier on s'assurera que le projet EJB est slectionn avant de faire cette opration ;
en [2], le projet EJB ;
en [3], on slectionne la catgorie [Glassfish] ;
en [4], on veut crer une ressource JDBC ;
en [5], indiquer que la ressource JDBC va utiliser un nouveau pool de connexions. On rappelle qu'un pool de connexions
est un pool de connexions ouvertes qui sert acclrer les changes de l'application avec la base de donnes ;
en [6], donner un nom JNDI la ressource JDBC cre. Ce nom peut tre quelconque mais il a souvent la forme
jdbc/nom ; Ce nom JNDI sera utilis dans le fichier [[Link]] pour dsigner la source de donnes que
l'implmentation JPA doit utiliser ;
en [7], donner un nom qui peut tre quelconque au pool de connexions qui va tre cr ;
dans la liste droulante [8], choisir la connexion JDBC cre prcdemment sur la base MySQL / dbpam_hibernate ;
en [9], un rcapitulatif des proprits du pool de connexions - on ne touche rien ;
170/290
en [10], on peut prciser plusieurs des proprits du pool de connexions - on laisse ici les valeurs par dfaut ;
en [11], l'issue de l'assistant de cration d'une ressource JDBC pour le module EJB, un fichier [[Link]] a
t cr dans la branche [Other Sources]. C'est ici une erreur de Netbeans. Ce fichier devrait tre dans le dossier [setup].
On l'y dplace [13] ;
Le fichier [[Link]] est un fichier XML qui reprend toutes les donnes collectes par l'assistant. Il va tre utilis par
Netbeans pour, lors du dploiement du module EJB sur le serveur Glassfish, demander la cration de la ressource JDBC dont a
besoin ce module.
On peut dsormais crer le fichier [[Link]] qui va configurer la couche JPA du module EJB :
171/290
1.
2.
3.
4.
5.
6.
7.
8.
9.
<jta-data-source>jdbc/dbpam_hibernate</jta-data-source>
172/290
ligne 5 : les entits JPA ne sont pas prcises. Elles seront cherches dans le Classpath du module EJB ;
le nom de l'implmentation JPA (Hibernate, EclipseLink, ...) utilise n'est pas indique. Dans ce cas, Glassfish utilise par
dfaut EclipseLink ;
[Link]
Maintenant que le fichier [[Link]] a t dfini, nous pouvons passer l'insertion dans le projet, des couches [metier, dao,
JPA] de l'application :
Couche
[ui]
Java SE
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
Ces trois couches sont identiques ce qu'elles taient avec OpenEJB. On peut procder un simple copier / coller entre les deux
projets. C'est ce que nous faisons maintenant :
[Link]
en [1], le rsultat de la copie des paquetages [JPA, dao, metier, exception] du projet [mv-pam-openejb-eclipselink] dans le
module EJB [mv-pam-ejb-metier-dao-jpa-eclipselink]. Il ne doit pas y avoir d'erreurs ;
173/290
Le dploiement a galement cr la ressource JNDI [jdbc / dbpam_hibernate] [8] ainsi que le pool de connexions
[dbpamHibernateConnectionPool] [9]. On rappelle que ces informations sont prsentes dans le fichier [setup / [Link]] ;
Lors du dploiement, le serveur Glassfish logue dans la console des informations intressantes :
1.
...
174/290
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
Infos:
[Link] actually got transformed
Infos:
[Link] actually got transformed
Infos:
[Link] actually got transformed
Infos:
EclipseLink, version: Eclipse Persistence Services - 2.6.1.v20150605-31e8258
Infos:
/file:/D:/data/istia-1617/netbeans/support-td/chap-06/mv-pam-ejb-metier-dao-eclipselink/target/classes/_mv-pamejb-metier-dao-eclipselinkPU login successful
Infos:
Portable JNDI names for EJB IndemniteDao: [java:global/mv-pam-ejb-metier-dao-eclipselink/IndemniteDao!
[Link], java:global/mv-pam-ejb-metier-dao-eclipselink/IndemniteDao![Link]]
Infos:
Glassfish-specific (Non-portable) JNDI names for EJB IndemniteDao:
[[Link]#[Link], [Link]]
Infos:
Portable JNDI names for EJB Metier: [java:global/mv-pam-ejb-metier-dao-eclipselink/Metier![Link],
java:global/mv-pam-ejb-metier-dao-eclipselink/Metier![Link]]
Infos:
Glassfish-specific (Non-portable) JNDI names for EJB Metier: [[Link],
[Link]#[Link]]
Infos:
Portable JNDI names for EJB CotisationDao: [java:global/mv-pam-ejb-metier-dao-eclipselink/CotisationDao!
[Link], java:global/mv-pam-ejb-metier-dao-eclipselink/CotisationDao![Link]]
Infos:
Glassfish-specific (Non-portable) JNDI names for EJB CotisationDao: [[Link],
[Link]#[Link]]
Infos:
Portable JNDI names for EJB EmployeDao: [java:global/mv-pam-ejb-metier-dao-eclipselink/EmployeDao!
[Link], java:global/mv-pam-ejb-metier-dao-eclipselink/EmployeDao![Link]]
Infos:
Glassfish-specific (Non-portable) JNDI names for EJB EmployeDao: [[Link]#[Link],
[Link]]
...
7, 9, 11et 13 les noms portables JNDI des EJB dploys. Java EE 6 a introduit la notion de nom JNDI portable. Cela
dnote un nom JNDI reconnu par tous les serveurs Java EE 6. Avec Java EE 5, les noms JNDI sont spcifiques au serveur
utilis ;
8, 10, 12, 14 : les noms JNDI des EJB dploys sous une forme spcifique Glassfish ;
Ces noms seront utiles l'application console que nous allons crire pour utiliser le module EJB dploy.
6.1.2
Maintenant que nous avons dploy la partie serveur de notre application client / serveur, nous en venons tudier la partie client
[1] :
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Java SE
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
Nous crons un nouveau projet Maven de type [Java Application] nomm [mv-pam-client-ejb-metier-dao-eclipselink] :
175/290
4
5
6
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
<properties>
18.
<[Link]>UTF-8</[Link]>
19.
<[Link]>1.8</[Link]>
20.
<[Link]>1.8</[Link]>
21.
</properties>
22. </project>
lignes 10-14 : la dpendance sur le projet Maven du module EJB. Nous voulons rcuprer ici les dfinitions des entits JPA
et celles des diffrentes interfaces ainsi que celle de la classe d'exception [PamException] ;
Si nous compilons Clean and Build) le projet client, nous obtenons des erreurs :
1.
2.
3.
4.
5.
6.
7.
8.
COMPILATION ERROR :
------------------------------------------------------------ui/console/[Link]:[76,25] cannot access [Link]
class file for [Link] not found
ui/console/[Link]:[77,25] cannot access [Link]
class file for [Link] not found
ui/console/[Link]:[78,25] cannot access [Link]
class file for [Link] not found
176/290
Les messages d'erreur sont peu clairs mais on comprend qu'ils concernent EclipseLink. On rajoute dans le [[Link]] la
dpendance sur EclipseLink, lignes 9-13 ci-dessous :
1.
<dependencies>
2.
<!-- projet EJB -->
3.
<dependency>
4.
<groupId>${[Link]}</groupId>
5.
<artifactId>mv-pam-ejb-metier-dao-eclipselink</artifactId>
6.
<version>${[Link]}</version>
7.
</dependency>
8.
<!-- eclipselink -->
9.
<dependency>
10.
<groupId>[Link]</groupId>
11.
<artifactId>eclipselink</artifactId>
12.
<version>2.6.3</version>
13.
</dependency>
14. </dependencies>
...
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19. ..
1.
Infos:
Portable JNDI names for EJB Metier: [java:global/mv-pam-ejb-metier-dao-eclipselink/Metier![Link],
java:global/mv-pam-ejb-metier-dao-eclipselink/Metier![Link]]
Infos:
Glassfish-specific (Non-portable) JNDI names for EJB Metier: [[Link],
[Link]#[Link]]
2.
Ligne 1, le nom JNDI utilisable avec tout serveur d'applications JAVA EE 6. Ligne 2, le nom JNDI spcifique Glassfish.
Dans le code, ligne 8, nous utilisons le nom JNDI portable ;
177/290
En [3], on configure le projet pour qu'il excute la classe [MainRemote] avec les arguments [4].
Lorsqu'on excute le projet [5], on obtient l'erreur suivante :
L'erreur suivante s'est produite : [Link]: Need to specify class name in environment or system
property, or as an applet parameter, or in an application resource file: [Link]
Pour savoir o se produit cette erreur, il faut laisser l'exception se produire sans l'intercepter. Nous modifions le code de
[MainRemote] de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
178/290
pour cette raison, ligne 1, l'exception non gre doit faire l'objet d'un [throws] ;
Exception in thread "main" [Link]: Need to specify class name in environment or system
property, or as an applet parameter, or in an application resource file: [Link]
at [Link]([Link])
at [Link]([Link])
at [Link]([Link])
at [Link]([Link])
at [Link]([Link])
2.
3.
4.
5.
6.
C'est le contexte JNDI de l'application EJB qui n'a pu tre obtenu. C'est au travers de ce contexte qu'on a accs aux EJB via leurs
noms JNDI. Pour obtenir ce contexte JNDI, il faut ajouter une dpendance ncessaire tous les clients Glassfish. Ceci est fait dans
le [[Link]] (lignes 15-19 ci-dessous) :
1.
<dependencies>
2.
<!-- projet EJB -->
3.
<dependency>
4.
<groupId>${[Link]}</groupId>
5.
<artifactId>mv-pam-ejb-metier-dao-eclipselink</artifactId>
6.
<version>${[Link]}</version>
7.
</dependency>
8.
<!-- eclipselink -->
9.
<dependency>
10.
<groupId>[Link]</groupId>
11.
<artifactId>eclipselink</artifactId>
12.
<version>2.6.3</version>
13.
</dependency>
14.
<!-- client glassfish -->
15.
<dependency>
16.
<groupId>[Link]</groupId>
17.
<artifactId>gf-client</artifactId>
18.
<version>3.1.1</version>
19.
</dependency>
20. </dependencies>
On excute le projet de nouveau. Cette fois-ci, l'ajout de la dpendance qui vient d'tre fait a cr une erreur :
1.
Failed to execute goal on project mv-pam-client-ejb-metier-dao-eclipselink: Could not resolve dependencies for project
[Link]:mv-pam-client-ejb-metier-dao-eclipselink:jar:1.0-SNAPSHOT: Failed to collect dependencies at
[Link]:gf-client:jar:3.1.1 -> [Link]:gf-client-module:jar:3.1.1 -> [Link]:ejbcontainer:jar:3.1.1 -> [Link]:security:jar:3.1.1 -> [Link]:dol:jar:3.1.1 ->
[Link]:[Link]:jar:2.0.3: Failed to read artifact descriptor for
[Link]:[Link]:jar:2.0.3: Could not transfer artifact
[Link]:[Link]:pom:2.0.3 from/to glassfish-repo-archive
([Link] hostname in certificate didn't match: <[Link]> !=
<[Link]> OR <[Link]> -> [Help 1]
De nouveau, l'erreur est peu comprhensible. Son origine est tonnante : une dpendance EclipseLink ncessite par la nouvelle
dpendance n'a pas t trouve. Elle a t cherche dans le dpt [[Link] Il est
possible de spcifier explicitement les dpts qui doivent tre explors lorsque Maven cherche des dpendances. C'est ce que nous
faisons maintenant. Le fichier [[Link]] volue de la faon suivante :
1. ...
2.
<dependencies>
3.
<!-- projet EJB -->
4.
<dependency>
5.
<groupId>${[Link]}</groupId>
6.
<artifactId>mv-pam-ejb-metier-dao-eclipselink</artifactId>
7.
<version>${[Link]}</version>
8.
</dependency>
9.
<!-- eclipselink -->
10.
<dependency>
11.
<groupId>[Link]</groupId>
12.
<artifactId>eclipselink</artifactId>
13.
<version>2.6.3</version>
14.
</dependency>
15.
<!-- client glassfish -->
16.
<dependency>
17.
<groupId>[Link]</groupId>
18.
<artifactId>gf-client</artifactId>
19.
<version>3.1.1</version>
179/290
20.
</dependency>
21.
</dependencies>
22.
23.
<!-- repositories -->
24.
<repositories>
25.
<repository>
26.
<url>[Link]
27.
<id>eclipselink</id>
28.
<layout>default</layout>
29.
<name>Repository for library Library[eclipselink]</name>
30.
</repository>
31.
</repositories>
32. ...
Aux lignes 25-30, nous avons dsign un dpt supplmentaire o Maven doit chercher les dpendances. Le dpt ajout est celui
d'EclipseLink. Cette mthode est utiliser lorsqu'on veut par exemple tlcharger des versions beta de dpendances. Celles-ci ne se
trouvent pas sur le dpt central de Maven mais dans des dpts spcialiss.
Cette fois-ci, l'excution du projet ne plante pas (mais le temps de rponse du serveur EJB peut tre particulirement long) et donne
le rsultats suivant :
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.
run:
Valeurs saisies :
N de scurit sociale de l'employ : 254104940426058
Nombre d'heures travailles : 150
Nombre de jours travaills : 20
Informations Employ :
Nom : Jouveinal
Prnom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2
Informations Cotisations :
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Scurit sociale : 9.39 %
Informations Indemnits :
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congs Pays : 15.0 %
Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnits d'entretien : 42.0 euro
Indemnits de repas : 62.0 euro
Salaire net : 368.77 euro
BUILD SUCCESSFUL (total time: 2 seconds)
Si dans les proprits, on met un n de scurit sociale incorrect, on obtient le rsultat suivant :
1.
2.
3.
run:
L'erreur suivante s'est produite : L'employ de n[254104940426058x] est introuvable
BUILD SUCCESSFUL (total time: 2 seconds)
Note : si vous rencontrez une exception de type [unmarshalling / marshalling exception] cela signifie que le serveur a voulu
envoyer au client un objet qui n'a pas pu tre srialis. Vrifiez les points suivants :
les entits changes entre le client et le serveur [Employe, Cotisation, Indemnite, FeuilleSalaire, ElementsSalaire,
PamException] doivent implmenter l'interface [Serializable] ;
l'entit JPA [Indemnite] ne doit pas voir l'annotation [@OneToMany]. Si elle est prsente, enlevez-la ainsi que le champ
qu'elle annote ;
si les points prcdents sont remplis, alors regardez les logs de Glassfish. La cause la plus probable est qu'une exception
non srialisable s'est produite ct serveur. Lorsque le serveur veut l'envoyer au client, une exception de srialisation se
produit alors.
6.1.3
Dans les versions prcdentes, l'environnement JNDI du serveur Glassfish tait configur partir d'un fichier [[Link]]
trouv dans les nombreuses dpendances .jar du projet. Son contenu par dfaut est le suivant :
180/290
1.
2.
3.
4.
5.
6.
7.
8.
Les lignes 7 et 8 dsignent la machine du service JNDI et le port d'coute de celui-ci. Ce fichier ne permet pas d'interroger un
serveur JNDI autre que localhost ou travaillant sur un port autre que le port 3700. Si on veut changer ces deux paramtres, on peut
construire son propre fichier [[Link]] ou utiliser une configuration Spring. Nous montrons cette deuxime technnique.
Nous commenons par crer un nouveau projet partir du projet [pam-client-metier-dao-jpa-eclipselink] initial.
Ce projet n'a pas la branche [Other Sources] qui sert mettre des fichiers de configuration qui doivent tre dans le Classpath du
projet. Or ici, on veut crer un fichier de configuration Spring qui doit tre dans le Classpath du projet. Pour crer la branche
[Other Sources] on peut procder de la faon suivante :
181/290
1.
2.
3.
4.
5.
6.
7.
8.
9.
10. </beans>
182/290
12.
13.
<!-- mtier -->
14.
<jee:jndi-lookup id="metier" jndi-name="java:global/mv-pam-ejb-metier-dao-eclipselink/Metier![Link]">
15.
<jee:environment>
16.
[Link]=[Link]
17.
[Link]=[Link]
18.
[Link]=[Link]
19.
[Link]=localhost
20.
[Link]=3700
21.
</jee:environment>
22.
</jee:jndi-lookup>
23.
24. </beans>
Nous utilisons ici une balise <jee> (ligne 14) apparue avec Spring 2.0. L'usage de cette balise ncessite la dfinition du schma
auquel elle appartient, lignes 4, 9 et 10.
ligne 14 : la balise <jee:jndi-lookup> permet d'obtenir la rfrence d'un objet auprs d'un service JNDI. Ici, on associe le
bean nomm " metier " la ressource JNDI associe l'EJB [Metier]. Le nom JNDI utilis ici est le nom portable (Java
EE 6) de l'EJB ;
le contenu du fichier [[Link]] devient le contenu de la balise <jee:environment> (ligne 15) qui sert dfinir les
paramtres de connexion au service JNDI ;
Pour que le fichier [[Link]] puisse tre exploit, il faut ajouter la dpendance de Spring au projet Maven :
1. ...
2.
<dependencies>
3.
<!-- projet EJB -->
4.
<dependency>
5.
<groupId>${[Link]}</groupId>
6.
<artifactId>mv-pam-ejb-metier-dao-eclipselink</artifactId>
7.
<version>${[Link]}</version>
8.
</dependency>
9.
<!-- eclipselink -->
10.
<dependency>
11.
<groupId>[Link]</groupId>
12.
<artifactId>eclipselink</artifactId>
13.
<version>2.6.3</version>
14.
</dependency>
15.
<!-- client glassfish -->
16.
<dependency>
17.
<groupId>[Link]</groupId>
18.
<artifactId>gf-client</artifactId>
19.
<version>3.1.1</version>
20.
</dependency>
21.
<!-- Spring framework -->
22.
<dependency>
23.
<groupId>[Link]</groupId>
24.
<artifactId>spring-context</artifactId>
25.
<version>[Link]</version>
26.
</dependency>
27.
</dependencies>
28. ...
lignes 22-26 : la dpendance sur [spring-context] qui ramne elle-mme d'autres dpendances qui sont automatiquement
charges ;
Lignes 6-7, la rfrence de type [IMetier] sur la couche [metier] est demande Spring. On notera que pour que notre code s'adapte
aussi bien la version locale que distante de l'EJB, nous avons, ligne 7, donn le type [IMetier] la rfrence sur la couche [metier].
183/290
L'interface [IMetier] est la classe parent des interfaces [IMetierLocal] et [IMetierRemote]. Cette solution amne de la souplesse dans
notre architecture. En effet, si l'EJB de la couche [metier] devenait local, c.a.d. excut dans la mme JVM que notre client
[MainRemote], le code de celui-ci ne changerait pas. Seul le contenu du fichier [[Link]] changerait. On retrouve l
une configuration analogue l'architecture Spring / JPA tudie au paragraphe 4.10.
Le lecteur est invit tester cette nouvelle version.
6.1.4
Le client Swing
Nous construisons maintenant le client swing de notre application client / serveur EJB.
<dependency>
<groupId>[Link]</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
Ci-dessus, la classe [PamJFrame] avait t crite initialement pour s'excuter dans un environnement Spring / JPA :
Couche
[ui]
swing
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
Maintenant cette classe doit devenir le client distant d'un EJB dploy sur le serveur Glassfish.
Couche
[ui]
swing
Jvm1 - Java SE
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
Travail faire : en suivant l'exemple du client console [[Link]] du projet, modifier la faon utilise par la mthode
[doMyInit] (cf paragraphe 4.11.4, page 134) de la classe [PamJFrame] pour acqurir une rfrence sur la couche [metier] qui est
maintenant distante.
6.1.5
Nous construisons maintenant les classes qui vont tester les couches [metier] et [DAO] distantes :
184/290
Les tests unitaires avait t crits initialement pour s'excuter dans un environnement Spring / JPA :
Couche
[tests]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
Maintenant les classes de test doivent devenir des clients distants des EJB dploys sur le serveur Glassfish.
Couche
[tests]
Jvm1 - Java SE
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
Travail faire : en suivant l'exemple du client console [[Link]] du projet, modifiez la faon utilise par la mthode
[init] des diffrents tests pour acqurir une rfrence sur les couches [DAO] et [metier] qui sont maintenant distantes, puis excutez
les tests.
Note 1 : Le fichier [[Link]] sera le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
185/290
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. </beans>
Note 2 :
A l'excution initiale de [JunitDaoRemote] tous les tests ont chou. Il en a t de mme pour les autres tests. L'erreur se passait
ct serveur. Il tait indiqu que la colonne [EMPLOYES.INDEMNITE_ID] ne pouvait avoir la valeur NULL. Aprs avoir ttonn
un bon moment la recherche de la cause de cette erreur, il s'est avr que l'erreur provenait de la dfinition suivante trouve dans
l'entit JPA [Employe] :
1.
2.
3.
// cl trangre
@Column(name = "INDEMNITE_ID", updatable = false, insertable = false, nullable = false)
private Long indemniteId;
L'erreur est ligne 2 : il ne faut pas crire [nullable=false]. Cette erreur est surprenante : en effet cet attribut n'avait pas pos
problme dans les environnements Spring / Hibernate et OpenEJB / Eclipselink. Dans l'environnement Glassfish / EclipseLink,
cet attribut cause donc une erreur. Elle est d'autant plus surprenante que dans la base de donnes on garde bien l'attribut [NOT
NULL] de la colonne [EMPLOYES.INDEMNITES_ID]. Il y a ici une incohrence entre l'attribut du champ [indemniteId]
(nullable=true par dfaut) de l'entit [Employe] et celui (NOT NULL) de la colonne correspondante
[EMPLOYES.INDEMNITES_ID] dans la base de donnes.
On retrouve de nouveau la difficult d'avoir des configurations 100% portables d'un environnement un autre. Cette erreur
corrige, tous les tests passent.
Note 3 :
On peut se demander, si lorsqu'on demande au serveur la liste des employs, ceux-ci sont envoys avec ou sans leurs indemnits.
Ct serveur, l'entit JPA [Employe] a un champ [Indemnite indemnite] avec l'attribut [LAZY]. Donc lorsqu'on demande les
employs, on ne devrait pas avoir leurs indemnits. On peut vrifier ce point, en excutant le test [JUnitInitDB] en mode dbogage :
186/290
on fixe un point d'arrt en [1] ci-dessus afin de pouvoir examiner la variable [employes] de la ligne 43 [2] ;
lorsqu'on examine dans la liste [employes], l'lment [Jouveinal] [3], on voit que son champ [Indemnite indemnite] vaut
null, ce qui montre que l'attribut [LAZY] de ce champ a t observ ;
187/290
Couche
[ui]
RMI
S
2
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
Java SE
Ci-dessus, une couche de communication [C, RMI, S] permettait une communication transparente entre le client [ui] et la couche
distante [metier]. Nous allons utiliser une architecture analogue, o la couche de communication [C, RMI, S] sera remplace par une
couche [C, HTTP / SOAP, S] :
Couche
[ui]
S
2
Java SE
HTTP /
SOAP
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
Le protocole HTTP / SOAP a l'avantage sur le protocole RMI / EJB prcdent d'tre multi-plateformes. Ainsi le service web peut
tre crit en Java et dploy sur le serveur Glassfish. Le client lui, pourrait tre un client .NET ou PHP.
Nous allons dvelopper cette architecture selon trois modes diffrents :
1.
2.
3.
Un service web peut tre implment de diverses faons au sein d'un serveur Java EE :
par une classe annote @WebService qui s'excute dans un conteneur web :
Client
du
service web tcp-ip
Conteneur web
Conteneur
Ejb3
Jpa
Donnes
serveur Java EE
Client
du
service web
Conteneur
Ejb3
Jpa
Donnes
serveur Java EE
188/290
7.1
7.1.1
Commenons par crer un nouveau projet maven copie du projet EJB [mv-pam-ejb-metier-dao-jpa-eclipselink] :
Couche
[ui]
S
2
Java SE
HTTP /
SOAP
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
la couche [metier] va tre le service web contact par la couche [ui]. Cette classe n'a pas besoin d'implmenter une interface. Ce sont
des annotations qui transforment un POJO (Plain Ordinary Java Object) en service web. La classe [Metier] qui implmente la
couche [metier] ci-dessus, est transforme de la faon suivante :
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.
package metier;
ligne 4, l'annotation @WebService fait de la classe [Metier] un service web. Un service web expose des mthodes ses
clients. Celles-ci peuvent tre annotes par l'attribut @WebMethod, mais c'est facultatif. Par dfaut, les mthodes
publiques d'une classe annote par [@WebService] deviennent automatiquement des mthodes du service web ;
lignes 16 et 22 : les deux mthodes de la classe [Metier] deviennent des mthodes du service web ;
ligne 26 : il est important que les getters et setters soient supprims sinon ils seront exposs dans le service web et cela
cause des erreurs de scurit ;
...
@WebService
@Stateless()
@TransactionAttribute([Link])
public class Metier implements IMetierLocal,IMetierRemote {
// rfrences sur les couches [DAO]
@EJB
private ICotisationDaoLocal cotisationDao = null;
@EJB
private IEmployeDaoLocal employeDao=null;
// obtenir la feuille de salaire
@WebMethod
public FeuilleSalaire calculerFeuilleSalaire(String SS,
...
}
// liste des employs
@WebMethod
public List<Employe> findAllEmployes() {
...
}
// important - pas de getters et setters pour les EJB qui deviennent des services web
}
L'ajout de ces annotations est dtect par Netbeans qui fait alors voluer la nature du projet :
189/290
En [1], une arborescence [Web Services] est apparue dans le projet. On y trouve le service web MetierService et ses deux
mthodes. L'application serveur peut tre dploye [3]. Le serveur MySQL soit tre lanc et sa base [dbpam_hibernate] exister et
tre remplie. Il peut tre ncessaire auparavant de supprimer [2] les EJB du projet client / serveur EJB tudi prcdemment pour
viter des conflits de noms. En effet, notre nouveau projet amne avec lui les mmes EJB que ceux du projet prcdent.
En [1], nous voyons notre application serveur dploye sur le serveur Glassfish. Une fois le service web dploy, il peut tre test :
190/290
en [4,5], un formulaire permettant de tester les mthodes exposes par le service web. Celles-ci sont prsentes avec leurs
paramtres que l'utilisateur peut dfinir ;
Par exemple, testons la mthode [findAllEmployes] qui n'a besoin d'aucun paramtre :
Ci-dessus, nous testons la mthode. Nous recevons alors la rponse ci-dessous (vue partielle). On remarque qu'on obtient les
employs avec leurs indemnits, ce qui montre que :
soit l'attribut [LAZY] sur le champ [[Link]] n'a pas t observ par EclipseLink. Or il avait t observ avec
le projet purement EJB ;
soit pour envoyer la rponse SOAP, le service web a accd la mthode [[Link]()], ce qui a forc
EclipseLink aller chercher l'indemnit de l'employ. On sait qu'EclipseLink sait faire cela alors qu'avec Hibernate, ce cas
d'utilisation provoque une exception ;
Le lecteur est invit tester de la mme faon la mthode [4] en lui passant les trois paramtres qu'elle attend.
191/290
Note : si vous n'obtenez pas le rsultat ci-dessus mais que vous n'avez pas d'exception, vrifiez que toutes les entits changes
entre le client et le serveur [Employe, Cotisation, Indemnite, FeuilleSalaire, ElementsSalaire, PamException] ont des setters
publics.
7.1.2
La partie cliente
Couche
[ui]
Java SE
[Link]
C
1
RMI
S
2
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
Nous crons maintenant un projet Maven de type [Java Application] pour la partie client de l'application [1]. Une fois le projet cr,
nous indiquons qu'il sera client du service web que nous venons de dployer sur le serveur Glassfish :
192/290
en [9], est affiche l'URL de dfinition du service web. Cette URL est utilise par les outils logiciels qui gnrent la couche
cliente qui va s'interfacer avec le service web ;
Couche
[ui]
3
S
2
Java SE
C
HTTP /
SOAP
Couche
[metier]
4
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
la couche cliente [C] [1] qui va tre gnre est constitue d'un ensemble de classes Java qui vont tre mises dans un mme
paquetage. Le nom de celui-ci est fix en [10] ;
une fois l'assistant de cration du client du service web termin avec le bouton [Finish], la couche [C] ci-dessus est cre ;
en [12] ci-dessus, apparat une arborescence [Generated Sources] qui contient les classes de la couche [C] qui permettent
au client [3] de communiquer avec le service web. Cette couche permet au client [3] de communiquer avec la couche
[metier] [4] comme si elle tait locale et non distante ;
en [11], apparat une arborescence [Web Service References] qui liste les services web pour lesquels une couche cliente a
t gnre ;
On notera que dans la couche [C] [12] gnre, nous retrouvons des classes qui ont t dployes ct serveur : Indemnite,
Cotisation, Employe, FeuilleSalaire, ElementsSalaire, Metier. Metier est le service web et les autres classes sont des classes
ncessaires ce service. On pourra avoir la curiosit de consulter leur code. On verra que la dfinition des classes qui, instancies,
reprsentent des objets manipuls par le service, consiste en la dfinition des champs de la classe et de leurs accesseurs ainsi qu'
l'ajout d'annotations permettant la srialisation de la classe en flux XML. La classe Metier est devenue une interface avec dedans
les deux mthodes qui ont t annotes @WebMethod. Chacune de celles-ci donne naissance deux classes, par exemple
[[Link]] et [[Link]], o l'une encapsule l'appel la mthode et l'autre son rsultat.
Enfin, la classe MetierService est la classe qui permet au client d'avoir une rfrence sur le service web Metier distant :
1.
2.
3.
4.
@WebEndpoint(name = "MetierPort")
public Metier getMetierPort() {
return [Link](new QName("[Link] "MetierPort"), [Link]);
La mthode getMetierPort de la ligne 2 permet d'obtenir une rfrence sur le service web Metier distant.
[Link]
Il ne nous reste plus qu' crire le client du service web Metier. Nous recopions la classe [MainRemote] du projet [mv-pam-clientmetier-dao-jpa-eclipselink-2] qui tait un client distant d'un serveur EJB, dans le nouveau projet.
193/290
en [1], la classe du client du service web. La classe [MainRemote] prsente des erreurs. Pour les corriger, on commencera
par supprimer toutes les instructions [import] existantes dans la classe et on les rgnerera par l'option [Fix Imports]. En
effet, certaines des classes utilises par la classe [MainRemote] font dsormais partie du package [client] gnr ;
en [2] ci-dessous, le morceau de code o la couche [metier] est instancie. Elle l'est avec du code Spring pour obtenir une
rfrence sur un EJB distant ;
en [3], il nous reste obtenir une rfrence sur la service web distant [Metier] afin de pouvoir appeler sa mthode
[calculerFeuilleSalaire] ;
194/290
en [4], avec la souris, nous tirons (drag) la mthode [calculerFeuilleSalaire] du service web [Metier] pour la dposer (drop)
en [4]. Du code est gnr [6]. Ce code gnrique peut tre ensuite adapt par le dveloppeur ;
tout d'abord, le code gnr en [5] est erron parce que les classes gnres dans une tape prcdente sont dans le
package [client] et non [metier] (lignes 56, 57) ;
ligne 63, on voit que [calculerFeuilleSalaire] est une mthode de la classe [[Link]().getMetierPort()] (lignes 5657). Maintenant que nous savons comment obtenir la couche [metier], le code prcdent peut tre rcrit de la faon
suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11. }
La ligne 4 rcupre une rfrence sur le client local du service web MetierService. Ceci fait, le code de la classe ne change pas.
Nous sommes prts pour les tests :
s'assurer que le SGBD MySQL5 est lanc, que la base dbpam_eclipselink est cre et initialise ;
s'assurer que le service web est dploy sur le serveur Glassfish ;
construire le client (Clean and Build) ;
configurer l'excution du projet client ;
excuter le client ;
L'excution du client est ici beaucoup plus rapide que ce qu'elle tait pour le client du serveur EJB. Les rsultats dans la console
sont les suivants :
195/290
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
...
Valeurs saisies :
N de scurit sociale de l'employ : 254104940426058
Nombre d'heures travailles : 150
Nombre de jours travaills : 20
Informations Employ :
Nom : Jouveinal
Prnom : Marie
Adresse : 5 rue des oiseaux
...
[Link]
L'erreur suivante s'est produite : [Link]: Client received SOAP Fault from
server: L'employ de n[254104940426058x] est introuvable Please see the server log to find more detail regarding exact
cause of the failure.
Travail faire : porter le client swing du projet [mv-pam-client-ejb-metier-dao-jpa-eclipselink-2] dans le nouveau projet afin que lui
aussi soit client du service web dploy sur le serveur Glassfish.
7.2
Client
du
service web tcp-ip
Conteneur web
Conteneur
EJB3
Jpa
Donnes
serveur Java EE
Le service web est assur par une application web excute au sein du conteneur web du serveur Glassfish. Ce service web va
s'appuyer sur l'EJB [Metier] dploy lui dans le conteneur EJB3.
7.2.1
La partie serveur
196/290
Sur le schma ci-dessous, l'application web cre va s'excuter dans le conteneur web. Elle va utiliser l'EJB [Metier] qui lui, sera
dploy dans le conteneur EJB du serveur.
Client
du
service web tcp-ip
Conteneur web
Conteneur
EJB3
Jpa
Donnes
serveur Java EE
Pour que l'application web cre ait accs aux classes associes l'EJB [Metier], nous ajoutons aux bibliothques de l'application
web [mv-pam-ws-ejb-metier-dao-eclipselink], la dpendance du serveur EJB [mv-pam-ejb-metier-dao-eclipselink] dj tudi.
197/290
package [Link];
lignes 6-9 : la classe importe des classes du module EJB [mv-pam-ejb-metier-dao-eclipselink] dont le projet Maven a t
ajout aux dpendances du projet ;
ligne 11 : la classe est un service web qui va par dfaut exposer les mthodes des lignes 18 et 23 ;
ligne 12 : elle implmente l'interface IMetier dfinie dans le module EJB ;
lignes 14-15 : l'interface locale de l'EJB [Metier] est injecte dans le champ de la ligne 15. Nous utilisons l'interface locale
car l'application web et le module EJB s'excutent dans la mme JVM ;
lignes 18 et 23 : les mthodes calculerFeuilleSalaire et findAllEmployes dlguent leur traitement aux mthodes de mme nom
de l'EJB [Metier]. La classe ne sert donc qu' exposer des clients distants les mthodes de l'EJB [Metier] comme des
mthodes d'un service web.
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@WebService
public class PamWsEjbMetier implements IMetier{
@EJB
private IMetierLocal metier;
@Override
public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravailles, int nbJoursTravaills) {
return [Link](SS, nbHeuresTravailles, nbJoursTravaills);
}
@Override
public List<Employe> findAllEmployes() {
return [Link]();
}
}
Dans Netbeans, l'application web est reconnue comme exposant un service web :
Pour dployer le service web sur le serveur Glassfish, il nous faut la fois dployer :
Pour cela, nous avons besoin de crer une application de type [Enterprise Application] qui va dployer les deux modules en mme
temps. Pour ce faire, il faut que les deux projets soient chargs dans Netbeans [2].
Ceci fait, nous crons un nouveau projet [3] :
198/290
en [6], nous configurons le projet. La version de Java EE sera Java EE 7. Un projet d'entreprise peut tre cr avec deux
modules : un module EJB et un module Web. Ici, le projet d'entreprise va encapsuler le module Web et le module EJB dj
crs et chargs dans Netbeans. Donc nous ne demandons pas la cration de nouveaux modules [7-8] ;
en [9], le projet d'entreprise [mv-pam-webapp-ear] ainsi cr. Un autre projet Maven a t cr en mme temps [mv-pamwebapp]. Nous ne nous en occuperons pas ;
en [10], nous ajoutons des dpendances au projet d'entreprise [mv-pam-webapp-ear] :
199/290
Nous construisons le projet d'entreprise par un Clean and Build. Nous sommes quasiment prts le dployer sur le serveur Glassfish.
Auparavant il peut tre ncessaire de dcharger les applications dj charges sur le serveur afin d'viter d'ventuels conflits de
noms d'EJB [14] :
Le serveur MySQL doit tre lanc et la base [dbpam_hibernate] disponible et remplie. Ceci fait, l'application d'entreprise peut tre
dploye [15]. En [16], on peut voir qu'elle a bien t dploye sur le serveur Glassfish. Comme l'application dploye est une
application web, une page par dfaut est affiche dans le navigateur par dfaut du poste :
200/290
Notes :
7.2.2
parfois l'option [Test Web Service] est inactive. Le plus souvent, c'est parce que le projet web n'a pas t associ un
serveur. Il faut alors aller dans les proprits du projet [Properties / Run] du projet web et fixer le serveur qui va excuter
le service web ;
La partie cliente
Travail faire : en suivant la dmarche dcrite au paragraphe [Link], page 192, construire un client console du service web
prcdent.
7.3
Client
du
service web tcp-ip
Conteneur web
JPA
Donnes
serveur Tomcat
Le service web est assur par une application web excute au sein du conteneur web du serveur Tomcat. L'architecture de
l'application sera la suivante :
Couche
[web]
Couche
[metier]
Couche
[DAO]
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
Nous nous appuierons sur le projet [mv-pam-spring-hibernate] construit au paragraphe 4.10, page 125 :
201/290
7.3.1
La partie serveur
Nous crons une application Maven de type web nomme [mv-pam-ws-spring-tomcat] [1]:
202/290
Nous compltons le fichier [[Link]] pour y inclure les nouvelles dpendances suivantes :
1. ...
2.
<dependencies>
3.
<!-- spring / hibernate -->
4.
<dependency>
5.
<groupId>${[Link]}</groupId>
6.
<artifactId>mv-pam-spring-hibernate</artifactId>
7.
<version>${[Link]}</version>
8.
</dependency>
9.
<!-- Apache CXF dependencies -->
10.
<dependency>
11.
<groupId>[Link]</groupId>
12.
<artifactId>cxf-rt-frontend-jaxws</artifactId>
13.
<version>3.1.7</version>
14.
</dependency>
15.
<dependency>
16.
<groupId>[Link]</groupId>
17.
<artifactId>cxf-rt-transports-http</artifactId>
18.
<version>3.1.7</version>
19.
</dependency>
20.
<!-- Spring framework -->
21.
<dependency>
22.
<groupId>[Link]</groupId>
23.
<artifactId>spring-web</artifactId>
24.
<version>[Link]</version>
25.
</dependency>
26.
27. </dependencies>
Couche
[web]
Couche
[metier]
Couche
[DAO]
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
L'application web que nous allons construire est une application web standard du monde JEE. A ce titre, elle est configure par un
fichier [WEB-INF / [Link]] que nous crons maintenant :
203/290
Les appels au service web que nous allons construire sont grs par une servlet du framework CXF. Cela se traduit dans le fichier
[WEB-INF / [Link]] de la faon suivante :
1. <?xml version="1.0" encoding="UTF-8"?>
2.
3. <web-app xmlns="[Link]
4.
xmlns:xsi="[Link]
5.
xsi:schemaLocation="[Link] [Link]
6.
version="3.1">
7.
<!-- listener Spring -->
8.
<listener>
9.
<listener-class>[Link]</listener-class>
10.
</listener>
11.
<!-- Configuration de CXF -->
12.
<servlet>
13.
<servlet-name>CXFServlet</servlet-name>
14.
<servlet-class>[Link]</servlet-class>
15.
<load-on-startup>1</load-on-startup>
16.
</servlet>
17.
<servlet-mapping>
18.
<servlet-name>CXFServlet</servlet-name>
19.
<url-pattern>/ws/*</url-pattern>
20.
</servlet-mapping>
21.
<!-- session -->
22.
<session-config>
23.
<session-timeout>
24.
30
204/290
25.
</session-timeout>
26.
</session-config>
27. </web-app>
le framework CXF a une dpendance sur Spring (les archives Spring ncessaires sont automatiquement tlcharges).
Lignes 8-10 : un listener est dclar. La classe correspondante [ContextLoaderListener] va tre charge en mme temps
que l'application web ;
lignes 12-16 : la servlet CXF qui va grer les appels au service web que nous allons crer ;
lignes 17-20 : les URL traites par la servlet CXF seront du type /ws/*. Les autres ne seront pas traites par CXF ;
Pour dfinir le service web, nous dfinissons une interface et son implmentattion :
package [Link];
ligne 7 : l'interface [IWsMetier] drive de l'interface [IMetier] de la couche [mtier] du projet [mv-pam-spring-hibernate] ;
ligne 6 : l'interface [IWsMetier] est celle d'un service web ;
import [Link];
import [Link];
@WebService
public interface IWsMetier extends IMetier{
}
package [Link];
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@WebService
public class PamWsMetier implements IWsMetier {
// couche mtier
private IMetier metier;
// constructeur
public PamWsMetier(){
}
public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillees, int nbJoursTravailles) {
return [Link](SS, nbHeuresTravaillees, nbJoursTravailles);
}
public List<Employe> findAllEmployes() {
return [Link]();
}
// getters et setters
public void setMetier(IMetier metier) {
[Link] = metier;
}
}
205/290
exploite par dfaut un fichier de configuration [WEB-INF/ [Link]]. Son contenu sera le suivant :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans xmlns="[Link]
3.
xmlns:xsi="[Link]
4.
xmlns:jaxws="[Link]
5.
xsi:schemaLocation="
6.
[Link]
7.
[Link]
8.
[Link]
9.
[Link]
10.
11.
<!-- Apache CXF -->
12.
<import resource="classpath:META-INF/cxf/[Link]" />
13.
14.
<!-- couches basses -->
15.
<import resource="classpath:[Link]" />
16.
17.
<!-- service web -->
18.
<bean id="wsMetier" class="[Link]">
19.
<property name="metier" ref="metier"/>
20.
</bean>
21.
<jaxws:endpoint id="wsmetier"
22.
implementor="#wsMetier"
23.
address="/metier">
24.
</jaxws:endpoint>
25. </beans>
ligne 12 : on importe un fichier de configuration Apache CXF. Il sera trouv dans l'une des archives jar amenes par les
dpendances Apache CXF ;
lignes 4, 8-9 : des espaces de noms spcifiques Apache CXF sont dclars ;
ligne 15 : on importe le fichier de configuration Spring du projet [mv-pam-spring-hibernate] ;
lignes 18-24 : dfinissent le bean du service web avec sa dpendance sur la couche [mtier] (ligne 19) ;
lignes 21-24 : dfinissent le service web lui-mme,
ligne 21 : le bean Spring implmentant le service web est celui dfini lignes 18-20 ;
ligne 26 : dfinit l'URL laquelle le service web sera disponible, ici /metier. Combine la forme que
doivent avoir les URL traites par Apache CXF (cf fichier [Link]), cette URL devient /ws/metier.
Notre projet est prt tre excut. Nous l'excutons (Run). Nous obtenons la page web suivante :
206/290
La page liste tous les services web dploys. Ici, il n'y en a qu'un. Nous suivons le lien WSDL :
Le texte affich [1] est celui d'un fichier XML qui dfinit les fonctionnalits du service web, comment l'appeler et quelles rponses il
envoie. On notera l'URL [2] de ce fichier WSDL. Tous les clients du service web ont besoin de la connatre.
7.3.2
La partie cliente
Travail faire : en suivant la dmarche dcrite au paragraphe [Link], page 192, construire un client console [1] du service web
prcdent ainsi qu'un test de la couche [metier] [2].
Note 1 : pour indiquer l'URL du fichier WSDL du service web, on suivra la procdure [3-5]. On mettra en [4], l'URL note
prcdemment en [2] ci-dessus.
207/290
Note 2 :
Considrons le test [JUnitMetierRemote] suivant :
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.
package metier;
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
@Test
45.
public void test02() {
46.
// employs
47.
List<Employe> employes = [Link]();
48.
[Link](employes);
49.
}
50. }
208/290
Lorsque ct client distant, on voit une erreur de type [Marshalling / Unmarshalling] [1], le plus probable est qu'il y a eu une erreur
ct serveur. Il faut donc aller voir dans les logs console du serveur, ici le serveur Tomcat [2-4] :
L'erreur [4] est une erreur Hibernate lie au mode [LAZY] du champ JPA [[Link]]. Comme il avait t remarqu au
paragraphe 7.1.1, page 189, la mthode [getAllEmployes] du service SOAP renvoie les employs avec leurs indemnits. Ceci est
surprenant, puisque le champ JPA [[Link]] a l'attribut [LAZY] qui fait que lorsqu'on demande un employ, on n'a pas
son indemnit avec. Il avait t alors indiqu que le service SOAP faisait peut-tre implicitement un appel la mthode
[[Link]]. Hibernate et EclipseLink ne se comportent alors pas de la mme faon :
Hibernate : l'excution de la mthode [[Link]] provoque l'excution d'une requte JPA pour rcuprer
l'indemnit de l'employ. Cette requte n'est possible pour Hibernate que s'il y a un contexte de persistance. Or pour
l'excution de la mthode [metier].getAllEmployes, un contexte de persistance Hibernate est cr puis ferm. Lorsque le
service web SOAP fait appel la mthode [[Link]], le contexte de persistance n'existe plus et Hibernate
lance alors une exception ;
EclipseLink : dans les mmes conditions, EclipseLink ne lance pas d'exception et ramne correctement l'indemnit ;
L'erreur [4] [no Session] indique qu'Hibernate n'a pas pu faire la requte JPA parce que le contexte de persistance, appel session
par Hibernate, n'existait pas.
Pour que le test [test02] passe, il faut donc inhiber le mode [LAZY] du champ JPA [[Link]], ce qui est fcheux.
Quoiqu'on fasse, lorsqu'on demandera les employs, on les aura avec leurs indemnits, ce qui parfois consommera de la bande
passante inutilement. De faon gnrale, lorsqu'on exploite une base de donnes, des relations de cl trangre peuvent relier
plusieurs tables entre elles. Le service web SOAP, qui suit les liens de cl trangre, ramnera des donnes dont on n'a pas
forcment besoin. Nous n'essaierons pas de rsoudre ce problme avec le protocole
7.4
Nous nous proposons maintenant de remplacer, dans le service web prcdent, Hibernate par EclipseLink. EclipseLink ne rsoud
pas le problme voqu prcdemment, savoir de ramener systmatiquement un employ avec son indemnit. Nanmoins, on n'a
pas besoin d'enlever le mode [LAZY] du champ [[Link]] pour que les tests unitaires passent.
Nous commenons par dupliquer le projet [mv-pam-ws-spring-tomcat-hibernate] dans le projet [mv-pam-ws-spring-tomcateclipselink] [1] :
209/290
en [5-9] : on cre une dpendance sur le projet [mv-pam-spring-eclipselink] du paragraphe 4.12, page 137 (le projet doit
tre charg dans Netbeans pour apparatre en [9]) ;
A ce stade, le projet ne prsente plus d'erreurs de configuration [10]. Nous modifions ensuite le fichier [META-INF / [Link]]
qui configure le contexte de l'application web :
Le fichier [META-INF / [Link]] du projet [mv-pam-sw-spring-tomcat-hibernate] avait t gnr par l'IDE et tait le suivant :
1.
2.
210/290
ligne 2 : on donne un nom l'application (attribut path du contexte). Ce nom indique comment appeler l'application depuis
un navigateur web. Les URL demandes sont de la forme [[Link] et donc ici on aura
[contexte=mv-pam-ws-spring-tomcat-hibernate]. On peut mettre n'importe quel nom. Netbeans gnre par dfaut un
contexte qui porte le nom du projet ;
On peut se demander d'o vient la page qui est affiche en [3-4]. Elle provient du fichier [[Link]] du projet [5-7] :
La page [[Link]] est la page affiche par dfaut lorsqu'on demande la racine du contexte [/mv-pam-ws-spring-tomcateclipselink]. Cela peut tre configur dans le fichier [WEB-INF / [Link]] du projet web [8].
Demandons maintenant l'URL [/mv-pam-ws-spring-tomcat-eclipselink/ws] :
211/290
Cette page reprsente la dfinition du service web dploy. Notez l'URL [4]. Vous allez en avoir besoin pour gnrer un client du
service web. Nous crons maintenant celui-ci.
Nous commenons par dupliquer le projet [pam-client-ws-spring-tomcat-hibernate] dans le projet [pam-client-ws-spring-tomcateclipselink] [1-2] :
Ensuite nous rgnrons le client du service web dans le nouveau projet. Pour cela, le service web [/mv-pam-ws-spring-tomcateclipselink] doit tre lanc :
212/290
Mettez en [8], l'URL de dfinition du service web dploy. Vous avez not cette URL prcdemment. Dans cet exemple, c'est l'URL
[[Link] Mettez en [9], le package [client]. C'est celui qui a
t utilis dans le prcdent projet [mv-pam-ws-spring-tomcat-hibernate] et l'application console a t construite pour l'utiliser.
A l'issue de l'assistant, une rfrence sur le service web distant a t ajoute au projet [9] et le code ncessaire pour communiquer
avec celui-ci a t gnr en [10].
Nous sommes prts pour excuter l'application console du client [1-8] :
213/290
package metier;
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
@Test
45.
public void test02() {
46.
// employs
47.
List<Employe> employes = [Link]();
48.
[Link](employes);
49.
}
50. }
214/290
Dans le test [test02] des lignes 44-49, le service web ramne les employs avec leurs indemnits alors que le champ JPA
[[Link]] a l'attribut [LAZY]. Ce champ est demand alors que le contexte de persistance qui avait t ouvert pour
rcuprer les employs a t ferm. Cela provoque une exception avec Hibernate mais pas avec EclipseLink.
7.5
Conclusion
Nous avons dploy des services web SOAP dans diffrents environnements :
Ils ont tous eu la particularit de ramener les employs avec leurs indemnits. Sans configuration supplmentaire, il n'est pas
possible de faire autrement. Nous ne creuserons pas davantage les services web SOAP. Dans les rfrences [ref4] [ref5], il est
montr comment crer des services web / jSON qui permettent de respecter l'ventuel mode [LAZY] prsent sur les champs
[@ManyToOne] ou [@OneToMany] des entits JPA. Ce type de services web a tendance supplanter les services web SOAP (sept
2016).
215/290
Introduction Java EE
par l'exemple
- Partie 2 -
216/290
mobile
l'URL
217/290
Architecture de l'application
Couche
[web /
JSF]
Couche
EJB3
[mtier]
Couche
EJB3
[DAO]
Objets image
de la BD
Implmentation
[EclipseLink]
Interface
[JPA]
Couche
[JDBC]
Serveur Glassfish
Dans cette version, le serveur Glassfish hbergera la totalit des couches de l'application :
les autres couches [metier, DAO, JPA] sont hberges par le conteneur EJB3 du serveur (2 ci-dessous)
Conteneur web
[web / jsf]
Navigateur
HTTP
Jpa 3
Conteneur EJB3
[mtier, DAO] 2 EclipseLink
SGBD
serveur Java EE
Les lments [metier, DAO] de l'application s'excutant dans le conteneur EJB3 ont dj t crits dans l'application client / serveur
tudie au paragraphe 6.1, page 165 et dont l'architecture tait la suivante :
client
Couche
[ui]
serveur
Couche
[DAO]
Couche
[mtier]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
Jvm1 - Java SE
Les couches [metier, DAO] s'excutaient dans le conteneur EJB3 du serveur Glassfish et la couche [ui] dans une application console
ou swing sur une autre machine :
serveur
client
Conteneur Ejb3
Client
Java SE
Jpa / EclipseLink
Donnes
serveur Java EE
Conteneur web
[web / JSF2]
Navigateur
HTTP
Jpa 3
Conteneur EJB3
[mtier, DAO] 2 EclipseLink
SGBD
serveur Java EE
218/290
Seule la couche [web / JSF2] est crire. Les autres couches [metier, DAO, JPA] sont acquises.
Dans le document [ref3], il est montr qu'une application web o la couche web est implmente avec Java Server Faces a une
architecture similaire la suivante :
Application web
couche [web]
1
Faces Servlet
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
3
Gestionnaire
d'vts
Modles
couche
[mtier, dao, jpa]
Donnes
2c
4a
Cette architecture implmente le Design Pattern MVC (Modle, Vue, Contrleur). Le traitement d'une demande d'un client se droule
de la faon suivante :
Si la demande est faite avec un GET, les deux tapes suivantes sont excutes :
1. demande - le client navigateur fait une demande au contrleur [Faces Servlet]. Celui-ci voit passer toutes les demandes des
clients. C'est la porte d'entre de l'application. C'est le C de MVC ;
2. rponse - le contrleur C demande la page JSF choisie de s'afficher. C'est la vue, le V de MVC. La page JSF utilise un modle
M pour initialiser les parties dynamiques de la rponse qu'elle doit envoyer au client. Ce modle est une classe Java qui peut faire
appel la couche [mtier] [4a] pour fournir la vue V les donnes dont elle a besoin ;
Si la demande est faite avec un POST, deux tapes supplmentaires s'insrent entre la demande et la rponse :
1. demande - le client navigateur fait une demande au contrleur [Faces Servlet] ;
2. traitement - le contrleur C traite cette demande. Une demande POST est accompagne de donnes qu'il faut traiter. Pour ce
faire, le contrleur se fait aider par des gestionnaires d'vnements spcifiques l'application crite [2a]. Ces gestionnaires
peuvent avoir besoin de la couche mtier [2b]. Le gestionnaire de l'vnement peut tre amen mettre jour certains modles
M [2c]. Une fois la demande du client traite, celle-ci peut appeler diverses rponses. Un exemple classique est :
une page d'erreurs si la demande n'a pu tre traite correctement ;
une page de confirmation sinon ;
Le gestionnaire d'vnement rend au contrleur [Faces Servlet] un rsultat de type chane de caractres appele cl de
navigation ;
3. navigation - le contrleur choisit la page JSF (= vue) envoyer au client. Ce choix se fait partir de la cl de navigation rendue
par le gestionnaire d'vnement ;
4. rponse - la page JSF choisie va envoyer la rponse au client. Elle utilise son modle M pour initialiser ses parties dynamiques.
Ce modle peut lui aussi faire appel la couche [mtier] [4a] pour fournir la page JSF les donnes dont elle a besoin ;
Dans un projet JSF :
les modles M et les gestionnaires d'vnements sont implments par des classes Java souvent appeles "backing
beans" ;
dans les version JSF 1.x la dfinition des beans ainsi que les rgles de navigation d'une page l'autre sont dfinies dans le
fichier [[Link]]. On y trouve la liste des vues et les rgles de transition de l'une l'autre. A partir de la version
JSF2, les dfinitions des beans peuvent se faire l'aide d'annotations et les transitions entre pages peuvent se faire en
" dur " dans le code des beans ;
9.2
Fonctionnement de l'application
219/290
220/290
Cette version calcule un salaire fictif. Il ne faut pas prter attention au contenu de la page mais sa mise en forme. Lorsqu'on utilise
le bouton [Raz], on revient la page [A].
Les saisies errones sont signales, comme le montre l'exemple suivant :
D
9.3
Le projet Netbeans
Nous allons construire une premire version de l'application o la couche [mtier] sera simule. Nous aurons l'architecture
suivante :
Application web
couche [web]
1
Faces Servlet
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
3
Gestionnaire
d'vts
couche
[metier]
simule
2c
Modles
4a
Lorsque les gestionnaires d'vnements ou les modles demanderont des donnes la couche [mtier] [2b, 4a], celle-ci leur donnera
des donnes fictives. Le but est d'obtenir une couche web rpondant correctement aux sollicitations de l'utilisateur. Lorsque ceci
sera atteint, il ne nous restera qu' installer la couche serveur dveloppe au paragraphe 6.1, page 165 :
Application web
couche [web]
1
Faces Servlet
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
3
Gestionnaire
d'vts
Modles
couche
[mtier, dao, jpa]
Donnes
2c
4a
221/290
222/290
L'ajout du framework JSF au projet modifie le fichier [[Link]] qui configure l'application web [10] :
223/290
lignes 7-11 : dfinissent la servlet Java qui va traiter les demandes faites aux URL de l'application web. Celle-ci sera trouve
dans la dpendance [12] ;
lignes 12-15 : dfinissent le type des URL acceptes, celles de chemin [/contexte/faces/*] o * dsigne un chemin
quelconque ;
lignes 21-23 : dfinissent la page servis lorsque l'URL [/contexte] est demande sans prcision de chemin. C'est alors la
page [/contexte/faces/[Link]] qui est alors servie. Celle-ci sera trouve en [11] ci-dessus ;
224/290
6.
<artifactId>mv-pam-jsf2-alone</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>war</packaging>
9.
10.
<name>mv-pam-jsf2-alone</name>
11.
12.
<properties>
13.
<[Link]>${[Link]}/endorsed</[Link]>
14.
<[Link]>UTF-8</[Link]>
15.
</properties>
16.
17.
<dependencies>
18.
<dependency>
19.
<groupId>javax</groupId>
20.
<artifactId>javaee-web-api</artifactId>
21.
<version>7.0</version>
22.
<scope>provided</scope>
23.
</dependency>
24.
</dependencies>
25.
26.
<build>
27.
<plugins>
28.
<plugin>
29.
<groupId>[Link]</groupId>
30.
<artifactId>maven-compiler-plugin</artifactId>
31.
<version>3.1</version>
32.
<configuration>
33.
<source>1.7</source>
34.
<target>1.7</target>
35.
<compilerArguments>
36.
<endorseddirs>${[Link]}</endorseddirs>
37.
</compilerArguments>
38.
</configuration>
39.
</plugin>
40.
<plugin>
41.
<groupId>[Link]</groupId>
42.
<artifactId>maven-war-plugin</artifactId>
43.
<version>2.3</version>
44.
<configuration>
45.
<failOnMissingWebXml>false</failOnMissingWebXml>
46.
</configuration>
47.
</plugin>
48.
<plugin>
49.
<groupId>[Link]</groupId>
50.
<artifactId>maven-dependency-plugin</artifactId>
51.
<version>2.6</version>
52.
<executions>
53.
<execution>
54.
<phase>validate</phase>
55.
<goals>
56.
<goal>copy</goal>
57.
</goals>
58.
<configuration>
59.
<outputDirectory>${[Link]}</outputDirectory>
60.
<silent>true</silent>
61.
<artifactItems>
62.
<artifactItem>
63.
<groupId>javax</groupId>
64.
<artifactId>javaee-endorsed-api</artifactId>
65.
<version>7.0</version>
66.
<type>jar</type>
67.
</artifactItem>
68.
</artifactItems>
69.
</configuration>
70.
</execution>
71.
</executions>
72.
</plugin>
73.
</plugins>
74.
</build>
75.
76. </project>
ligne 22, on voit que l'unique dpendance a l'attribut [provided] : elle est ncessaire pour la compilation du projet mais ne
fera pas partie de l'archive produite pour le projet. A l'excution, cette dpendance sera trouve dans les bibliothques du
serveur Glassfish ;
225/290
9.3.1
Le fichier [[Link]] est celui gnr par dfaut par Netbeans avec de plus la configuration d'une page d'exception :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
226/290
20.
<context-param>
21.
<param-name>[Link].FACELETS_SKIP_COMMENTS</param-name>
22.
<param-value>true</param-value>
23.
</context-param>
24.
<!-- dure session -->
25.
<session-config>
26.
<session-timeout>
27.
30
28.
</session-timeout>
29.
</session-config>
30.
<!-- page d'accueil -->
31.
<welcome-file-list>
32.
<welcome-file>faces/[Link]</welcome-file>
33.
</welcome-file-list>
34.
<!-- page d'exception -->
35.
<error-page>
36.
<error-code>500</error-code>
37.
<location>/faces/[Link]</location>
38.
</error-page>
39.
<error-page>
40.
<exception-type>[Link]</exception-type>
41.
<location>/faces/[Link]</location>
42.
</error-page>
43.
44. </web-app>
Toute exception non explicitement gre par le code de l'application web provoquera l'affichage d'une page analogue celle cidessous :
227/290
4.
5.
6.
xmlns="[Link]
xmlns:xsi="[Link]
xsi:schemaLocation="[Link] [Link]
7.
8.
<application>
9.
<locale-config>
10.
<default-locale>fr</default-locale>
11.
</locale-config>
12.
<resource-bundle>
13.
<base-name>
14.
messages
15.
</base-name>
16.
<var>msg</var>
17.
</resource-bundle>
18.
<message-bundle>messages</message-bundle>
19.
</application>
20. </faces-config>
9.3.2
lignes 12-17 : le fichier [[Link]] sera utilis pour l'internationalisation des pages. Il sera accessible dans les
pages XHTML via la cl msg ;
ligne 18 : dfinit le fichier [[Link]] comme devant tre explor en priorit pour les messages d'erreur affichs
par les balises <h:messages> et <h:message>. Cela permet de redfinir certains messages d'erreur par dfaut de JSF ;
La feuille de style
.libelle{
background-color: #ccffff;
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
body{
background-color: #ffccff
}
.error{
color: #ff3333
}
.info{
background-color: #99cc00
}
.titreInfos{
background-color: #ffcc00
}
9.3.3
[Link]=Feuille de salaire
[Link]\u00e9=Employ\u00e9
[Link]\[Link]\u00e9=Heures travaill\u00e9es
[Link]\[Link]\u00e9=Jours travaill\u00e9s
[Link]\[Link]=Indiquez le nombre d'heures travaill\u00e9es
228/290
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.
[Link]\[Link]=Donn\u00e9e incorrecte
[Link]\[Link]=Indiquez le nombre de jours travaill\u00e9s
[Link]\[Link]=Donn\u00e9e incorrecte
[Link]\u00e9=Salaire
[Link]\u00e9=Raz
[Link]=L'exception suivante s'est produite
[Link]=Code HTTP de l'erreur
[Link]=Message de l'exception
[Link]=Url demand\u00e9e lors de l'erreur
[Link]=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
[Link]\u00e9=Informations Employ\u00e9
[Link]=Nom
[Link]\u00e9nom=Pr\u00e9nom
[Link]=Adresse
[Link]=Ville
[Link]=Code postal
[Link]=Indice
[Link]=Informations Cotisations sociales
[Link]=CSGRDS
[Link]=CSGD
[Link]=Retraite
[Link]=S\u00e9curit\u00e9 sociale
[Link]=Informations Indemnit\u00e9s
[Link]=Salaire horaire
[Link]=Entretien / Jour
[Link]=Repas / Jour
[Link]\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
[Link]=Informations Salaire
[Link]=Salaire de base
[Link]=Cotisations sociales
[Link]=Indemnit\u00e9s d'entretien
[Link]=Indemnit\u00e9s de repas
[Link]=Salaire net
Ces messages sont tous utiliss dans la page [[Link]] l'exception de ceux des lignes 11-15 utiliss dans la page
[[Link]].
9.3.4
import [Link];
import [Link];
import [Link];
@ManagedBean
@RequestScoped
public class Form implements Serializable {
package [Link];
import [Link];
import [Link];
import [Link];
@ManagedBean
@ApplicationScoped
public class ChangeLocale implements Serializable{
// la locale des pages
private String locale="fr-FR";
public ChangeLocale() {
}
public String setFrenchLocale(){
locale="fr-FR";
return null;
}
public String setEnglishLocale(){
locale="en-US";
return null;
}
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
[Link] = locale;
}
229/290
33.
34.
35. }
9.3.5
La couche [mtier]
package metier;
import [Link];
@Local
public interface IMetierLocal extends IMetier{
}
Cette interface est celle utilise dans la partie serveur de l'application client / serveur dcrite au paragraphe 6.1, page 165. Ligne 6,
l'interface [IMetier] est la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
package metier;
import [Link];
import [Link];
public interface IMetier {
// obtenir la feuille de salaire
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravailles, int nbJoursTravaills );
// liste des employs
List<Employe> findAllEmployes();
}
La classe Metier que nous allons utiliser pour tester la couche [web] implmente cette interface de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
package metier;
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
// Indemnite(Long id, int version, int indice, double baseHeure, double entretienJour, double repasJour, double
indemnitesCp)
20.
// Employe(Long id, int version, String prenom, String nom, String ss, String adresse, String ville, String cp, Indemnite
indemnite)
21.
// Cotisation(Long id, int version, double secu, double retraite, double csgd, double csgrds)
22.
23.
// obtenir la feuille de salaire
24.
@Override
25.
public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravailles, int nbJoursTravaills) {
26.
// on rcupre l'employ de n SS
27.
Employe e = [Link](SS);
28.
// on rend une exception si l'employ n'existe pas
29.
if(e==null){
30.
throw new PamException([Link]("L'employ de n SS [%s] n'existe pas",SS),1);
31.
}
32.
// on rend une feuille de salaire fictive
33.
return new FeuilleSalaire(e, new Cotisation(null, 1, 9.39, 7.88, 6.15, 3.49), new ElementsSalaire(100, 100, 100, 100,
100));
34.
}
35.
36.
// liste des employs
37.
@Override
38.
public List<Employe> findAllEmployes() {
39.
if (listEmployes == null) {
40.
// on cre une liste de deux employs
41.
listEmployes = new ArrayList<>();
42.
[Link](new Employe(null, 1, "Marie", "Jouveinal", "254104940426058", "5 rue des oiseaux", "St Corentin",
"49203", new Indemnite(null, 1, 2, 2.1, 2.1, 3.1, 15)));
43.
[Link](new Employe(null, 1, "Justine", "Laverti", "260124402111742", "La brlerie", "St Marcel", "49014",
new Indemnite(null, 1, 1, 1.93, 2, 3, 12)));
44.
// dictionnaire des employes
230/290
45.
for (Employe e : listEmployes) {
46.
[Link]([Link](), e);
47.
}
48.
}
49.
// on rend la liste des employs
50.
return listEmployes;
51.
}
52. }
Nous laissons au lecteur le soin de dcrypter ce code. On notera la mthode utilise : afin de ne pas avoir mettre en place la partie
EJB de l'application, nous simulons la couche [mtier]. Lorsque la couche [web] sera dclare correcte, nous pourrons alors la
remplacer par la vritable couche [mtier].
9.4
Nous construisons maintenant la page XHTML du formulaire ainsi que son modle.
Lectures conseilles dans [ref3] :
9.4.1
tape 1
Travail faire : Construire le formulaire [[Link]] et son modle [[Link]] ncessaires pour obtenir la page suivante :
id
type JSF
modle
comboEmployes <h:selectOneMenu>
String comboEmployesValue
rle
contient la liste des employs sous la forme
"prnom nom".
heuresTravaillees <h:inputText>
List<Employe> getEmployes()
String heuresTravailles
joursTravailles
<h:inputText>
String joursTravaills
btnSalaire
<h:commandButton>
btnRaz
<h:commandButton>
la mthode getEmployes rendra une liste d'employs qu'elle obtiendra auprs de la couche [mtier]. Les objets affichs par le
combo auront pour attribut itemValue, le n SS de l'employ et pour attribut itemLabel, une chane constitue du
prnom et du nom de l'employ ;
les boutons [Salaire] et [Raz] ne seront pour l'instant pas connects des gestionnaires d'vnement ;
la validit des saisies sera vrifie ;
231/290
Testez cette version. Vrifiez notamment que les erreurs de saisie sont bien signales.
Note : il est important que les attributs id des composants de la page ne comportent pas de caractres accentus. Cela peut
planter l'application.
9.4.2
tape 2
Travail faire : complter le formulaire [[Link]] et son modle [[Link]] pour obtenir la page suivante une fois que le
bouton [Salaire] a t cliqu :
Le bouton [Salaire] sera connect au gestionnaire d'vnement calculerSalaire du modle. Cette mthode utilisera la mthode
calculerFeuilleSalaire de la couche [mtier]. Cette feuille de salaire sera faite pour l'employ slectionn en [1].
Dans le modle, la feuille de salaire sera reprsente par le champ priv suivant :
private FeuilleSalaire feuilleSalaire;
9.4.3
tape 3
Travail faire : complter le formulaire [[Link]] et son modle [[Link]] pour obtenir les informations supplmentaires
suivantes :
232/290
On suivra la mme dmarche que prcdemment. Il y a une difficult pour le signe montaire euro que l'on a en [1] par exemple.
Dans le cadre d'une application internationalise, il serait prfrable d'avoir le format d'affichage et le signe montaire de la locale
utilise (en, de, fr, ...). Cela peut s'obtenir de la faon suivante :
1.
2.
3.
<h:outputFormat value="{0,number,currency}">
<f:param value="#{[Link]}"/>
</h:outputFormat>
On aurait pu crire :
<h:outputText value="#{[Link]} ">
mais avec la locale en_GB (Anglais GB) on continuerait avoir un affichage en euros alors qu'il faudrait utiliser la livre . La balise
<h:outputFormat> permet d'afficher des informations en fonction de la locale de la page JSF affiche :
9.4.4
ligne 1 : affiche le paramtre {0} qui est un nombre (number) reprsentant une somme d'argent (currency) ;
ligne 2 : la balise <f:param> donne une valeur au paramtre {0}. Une deuxime balise <f:param> donnerait une valeur au
paramtre not {1} et ainsi de suite ;
tape 4
233/290
Ce rsultat peut tre obtenu avec une balise <f:subview> utilise de la faon suivante :
1.
2.
3.
La balise <f:subview> encadre toute la partie du formulaire qui est susceptible d'tre affiche ou cache. Tout composant peut tre
affich ou cach grce l'attribut rendered. Si rendered="true", le composant est affich, si rendered="false", il ne l'est pas. Si l'attribut
rendered prend sa valeur dans le modle, alors l'affichage du composant peut tre contrl par programme.
Ci-dessus, on contrlera l'affichage de la vue viewInfos avec le champ suivant :
private boolean viewInfosIsRendered;
accompagn de ses mthodes get et set. Les mthodes grant les clics sur les bouton [Salaire] [Raz] mettront jour ce boolen selon
que la vue viewInfos doit tre affiche ou non.
234/290
Architecture de l'application
Application web
couche [web]
1
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
Faces Servlet
3
Gestionnaire
d'vts
couche
[metier]
simule
2c
Modles
4a
Nous remplaons la couche [mtier] simule, par les couches [mtier, DAO, JPA] implmentes par des EJB au paragraphe 6.1, page
165 :
Application web
couche [web]
1
Navigateur
4b
JSF1
JSF2
JSFn
10.2
2b
2a
Faces Servlet
3
Gestionnaire
d'vts
Modles
couches
[metier, DAO, JPA]
Donnes
2c
4a
Le projet Netbeans de la version web n 2 est obtenue par copie du projet prcdent :
235/290
Nous avons peu de modifications faire pour adapter cette couche web son nouvel environnement : la couche [metier] [2] simule
doit tre remplace par la couche [metier, DAO, JPA] du serveur construit au paragraphe 6.1, page 165. Pour cela, nous faisons
deux choses :
nous supprimons les paquetages [exception, metier, JPA] qui taient prsents dans le prcdent projet ;
pour compenser cette suppression, nous ajoutons aux dpendances du projet web, le projet du serveur EJB construit au
paragraphe 6.1, page 165 ;
@ManagedBean
236/290
2. @RequestScoped
3. public class Form {
4.
5.
public Form() {
6.
}
7.
8.
// couche mtier
9.
private IMetierLocal metier=new Metier();
10.
11.
// champs du formulaire
12. ...
La ligne 9 instanciait la couche [mtier] simule. Dsormais elle doit rfrencer la couche [mtier] relle. Le code prcdent devient
le suivant :
1. @ManagedBean
2. @RequestScoped
3. public class Form {
4.
5.
public Form() {
6.
}
7.
8.
// couche mtier
9.
@EJB
10.
private IMetierLocal metier;
11.
12.
// champs du formulaire
Ligne 9, l'annotation @EJB indique au conteneur de servlets qui va excuter la couche web, d'injecter dans le champ metier de la
couche 8, l'EJB qui implmente l'interface locale IMetierLocal.
Pourquoi l'interface locale IMetierLocal plutt que l'interface IMetierRemote ? Parce que la couche web et la couche EJB s'excutent
dans la mme JVM :
Application web
couche [web]
1
Faces Servlet
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
3
Gestionnaire
d'vts
Modles
Conteneur de servlets
couche
[metier, dao, jpa]
Donnes
2c
4a
Conteneur Ejb
Les classes du conteneur de servlets peuvent rfrencer directement les classes EJB du conteneur EJB.
C'est tout. Notre couche web est prte. La transformation a t simple parce qu'on avait pris soin de simuler la couche [mtier] par
une classe qui respectait l'interface IMetierLocal implmente par la couche [mtier] relle.
10.3
Une application d'entreprise permet le dploiement simultan sur un serveur d'applications, de la couche [web] et de la couche EJB
d'une application, respectivement dans le conteneur de servlets et dans le conteneur EJB.
Nous procdons de la faon suivante :
237/290
un module EJB ;
un module web ;
On peut demander en mme temps que la cration du projet d'entreprise, la cration de ces deux modules qui seront vides
au dpart. Un projet d'entreprise ne sert qu'au dploiement des modules qui en font partie. En-dehors de a, c'est une
coquille vide. Ici, nous voulons dployer :
un module web existant [mv-pam-jsf2-ejb]. Il est donc inutile de crer un nouveau module web ;
un module EJB existant [mv-pam-ejb-metier-dao-eclipselink]. L galement, il est inutile d'en crer un nouveau ;
En [7], nous crons un projet d'entreprise sans modules. Nous allons lui ajouter ses modules web et ejb ultrieurement ;
en [8], deux projets Maven ont t crs. Le projet d'entreprise est celui qui a le suffixe ear. L'autre projet est un projet
Maven parent du prcdent. Nous ne nous en occuperons pas.
238/290
Avant le dploiement de l'application d'entreprise [mv-pam-webapp-ear], on s'assurera que la base MySQL [dbpam_hibernate]
existe et est remplie. Ceci fait, nous pouvons dployer l'application d'entreprise [mv-pam-webapp-ear] :
faire un [Clean and Build] sur les projets EJB [mv-pam-ejb-metier-dao-eclipselink], Web [mv-pam-jsf2-ejb] et EAR [mvpam-webapp-ear] ;
excuter le projet d'entreprise ;
239/290
Le lecteur est invit refaire les tests de la version web n 1. Voici un exemple d'excution :
240/290
Application web
couche [web]
1
Faces Servlet
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
3
Gestionnaires
d'vts
Modles
couche
[metier]
simule
2c
4a
la servlet [Faces Servlet] est le contrleur gnrique fourni par JSF. Ce contrleur est tendu par les gestionnaires
d'vnements spcifiques l'application. Les gestionnaires d'vnements rencontrs jusqu'ici taient des mthodes des
classes servant de modles aux pages JSF ;
les pages JSF envoient les rponses au navigateur client. Ce sont les vues de l'application ;
les pages JSF comportent des lments dynamiques qu'on appelle le modle de la page. On rappelle que pour certains
auteurs, le modle recouvre les entits manipules par l'application, telles par exemple les classes FeuilleSalaire ou Employe.
Pour distinguer ces deux modles, on pourra parler de modle de l'application et modle d'une page JSF ;
Dans l'architecture JSF, le passage d'une page JSFi une page JSFj peut tre problmatique :
la page JSFi a t affiche. A partir de cette page, l'utilisateur provoque un POST par un vnement quelconque [1] ;
en JSF, ce POST sera trait [2a,2b] en gnral par une mthode C du modle M i de la page JSFi. On peut dire que la
mthode C est un contrleur secondaire ;
si l'issue de cette mthode, la page JSFj doit tre affiche, le contrleur C doit :
1. mettre jour [2c] le modle Mj de la page JSFj ;
2. rendre [2a] au contrleur principal, la cl de navigation qui permettra l'affichage de la page JSF j ;
L'tape 1 ncessite que le modle Mi de la page JSFi ait une rfrence sur modle Mj de la page JSFj. Cela complique un
peu les choses rendant les modles Mi dpendant les uns des autres. En effet, le gestionnaire C du modle M i qui met
jour le modle Mj doit connatre celui-ci. Si on est amen changer le modle M j, on sera alors amen changer le
gestionnaire C du modle Mi.
Il existe un cas o la dpendance des modles entre-eux peut tre vite : celui o il y a un unique modle M qui sert
toutes les pages JSF. Cette architecture n'est utilisable que dans les applications n'ayant que quelques vues mais elle se
rvle alors trs simple d'usage. C'est celle que nous utilisons maintenant.
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
JSF2
JSFn
2b
[MC]
[Link]
Modle M
Gestionnaires
d'vts
couche
[metier]
simule
241/290
11.1
- la vue [VueSimulations] qui donne la liste des simulations faites par le client
242/290
- la vue [VueSimulationsVides] qui indique que le client n'a pas ou plus de simulations :
11.2
243/290
11.2.1
Les fichiers [[Link]] et [[Link]] sont identiques ceux du projet prcdent l'exception d'un dtail dans [[Link]] :
1.
2.
3.
4.
5.
6.
7.
8.
11.2.2
La feuille de style
.simulationsHeader {
text-align: center;
font-style: italic;
color: Snow;
background: Teal;
}
.simuNum {
height: 25px;
text-align: center;
background: MediumTurquoise;
}
.simuNom {
text-align: left;
background: PowderBlue;
}
.simuPrenom {
width: 6em;
text-align: left;
color: Black;
background: MediumTurquoise;
}
.simuHT {
width: 3em;
text-align: center;
color: Black;
background: PowderBlue;
}
.simuJT {
width: 3em;
text-align: center;
color: Black;
background: MediumTurquoise;
}
.simuSalaireBase {
width: 9em;
text-align: center;
color: Black;
background: PowderBlue;
}
.simuIndemnites {
width: 3em;
text-align: center;
color: Black;
background: MediumTurquoise;
}
.simuCotisationsSociales {
width: 6em;
text-align: center;
background: PowderBlue;
}
.simuSalaireNet {
width: 10em;
text-align: center;
background: MediumTurquoise;
}
.erreursHeaders {
background: Teal;
background-color: #ff6633;
color: Snow;
font-style: italic;
244/290
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
text-align: center
}
.erreurClasse {
background: MediumTurquoise;
background-color: #ffcc66;
height: 25px;
text-align: center
}
.erreurMessage {
background: PowderBlue;
background-color: #ffcc99;
text-align: left
}
Vue Erreur
<h:dataTable value="#{[Link]}" var="erreur"
headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">
11.2.3
245/290
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.
11.2.4
[Link]=CSGD
[Link]=Retraite
[Link]=S\u00e9curit\u00e9 sociale
[Link]=Informations Indemnit\u00e9s
[Link]=Salaire horaire
[Link]=Entretien / Jour
[Link]=Repas / Jour
[Link]\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
[Link]=Informations Salaire
[Link]=Salaire de base
[Link]=Cotisations sociales
[Link]=Indemnit\u00e9s d'entretien
[Link]=Indemnit\u00e9s de repas
[Link]=Salaire net
[Link]=| Faire la simulation
[Link]=| Effacer la simulation
[Link]=| Enregistrer la simulation
[Link]=| Retour au simulateur
[Link]=| Voir les simulations
[Link]=| Terminer la session
[Link]=Nom
[Link]=Nom
[Link]=Pr\u00e9nom
[Link]=Heures travaill\u00e9es
[Link]=Jours Travaill\u00e9s
[Link]=Salaire de base
[Link]=Indemnit\u00e9s
[Link]=Cotisations sociales
[Link]=SalaireNet
[Link]=N\u00b0
[Link]=Une erreur s'est produite.
[Link]=Cha\u00eene des exceptions
[Link]=Type de l'exception
[Link]=Message associ\u00e9
La couche [mtier]
package metier;
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
// Indemnite(Long id, int version, int indice, double baseHeure, double entretienJour, double repasJour, double
indemnitesCp)
20.
// Employe(Long id, int version, String prenom, String nom, String ss, String adresse, String ville, String cp, Indemnite
indemnite)
21.
// Cotisation(Long id, int version, double secu, double retraite, double csgd, double csgrds)
22.
23.
// obtenir la feuille de salaire
24.
@Override
25.
public FeuilleSalaire calculerFeuilleSalaire(String SS,
26.
double nbHeuresTravailles, int nbJoursTravaills) {
27.
// on rcupre l'employ
28.
Employe e=[Link](SS);
29.
// on rend une exception si l'employ n'existe pas
30.
if(e==null){
31.
throw new PamException([Link]("L'employ de n SS [%s] n'existe pas",SS),1);
32.
}
33.
// on rend une feuille de salaire fiictive
34.
return new FeuilleSalaire(e, new Cotisation(null, 1, 9.39, 7.88, 6.15, 3.49), new ElementsSalaire(100, 100, 100, 100,
100));
35.
}
36.
37.
// liste des employs
38.
@Override
39.
public List<Employe> findAllEmployes() {
40.
if (listEmployes == null) {
41.
// on cre une liste de trois employs
42.
listEmployes = new ArrayList<>();
246/290
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
11.3
[Link](new Employe(null, 1, "Marie", "Jouveinal", "254104940426058", "5 rue des oiseaux", "St Corentin",
"49203", new Indemnite(null, 1, 2, 2.1, 2.1, 3.1, 15)));
[Link](new Employe(null, 1, "Justine", "Laverti", "260124402111742", "La brlerie", "St Marcel", "49014",
new Indemnite(null, 1, 1, 1.93, 2, 3, 12)));
// dictionnaire des employes
for (Employe e : listEmployes) {
[Link]([Link](), e);
}
}
// on ajoute un employ qui n'existera pas dans le dictionnaire
[Link](new Employe(null, 1, "X", "Y", "Z", "", "", "", new Indemnite(null, 1, 2, 2.1, 2.1, 3.1, 15)));
// on rend la liste des employs
return listEmployes;
}
}
11.3.1
Le bean ApplicationData
package [Link];
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@Named
@ApplicationScoped
public class ApplicationData implements Serializable {
// couche mtier
private IMetierLocal metier = new Metier();
// logger
private boolean logsEnabled = true;
private final Logger logger = [Link]("pam");
public ApplicationData() {
}
@PostConstruct
public void init() {
// log
247/290
27.
if (isLogsEnabled()) {
28.
[Link]("ApplicationData");
29.
}
30.
}
31.
32.
// getters et setters
33. ...
34. }
ligne 11 : l'annotation @Named fait de la classe un bean manag. On notera qu' la diffrence du projet prcdent, on n'a
pas utilis l'annotation @ManagedBean. La raison en est que la rfrence de cette classe doit tre injecte dans une autre
classe l'aide de l'annotation @Inject et que celle-ci n'injecte que des classes annotes @Named ;
ligne 12 : l'annotation @ApplicationScoped fait de la classe, un objet de porte application. On notera que la classe de
l'annotation est [[Link]] (ligne 6) et non [[Link]] comme
dans les beans du projet prcdent ;
11.3.2
Le bean SessionData
package [Link];
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@Named
@SessionScoped
public class SessionData implements Serializable {
// application
@Inject
private ApplicationData applicationData;
// simulations
private List<Simulation> simulations = new ArrayList<Simulation>();
private int numDerniereSimulation = 0;
private Simulation simulation;
// menus
private boolean menuFaireSimulationIsRendered = true;
private boolean menuEffacerSimulationIsRendered = true;
private boolean menuEnregistrerSimulationIsRendered;
private boolean menuVoirSimulationsIsRendered;
private boolean menuRetourSimulateurIsRendered;
private boolean menuTerminerSessionIsRendered = true;
// locale
private String locale="fr_FR";
// constructeur
public SessionData() {
}
@PostConstruct
public void init() {
// log
if ([Link]()) {
[Link]().info("SessionData");
}
}
// gestion des menus
public void setMenu(boolean menuFaireSimulationIsRendered, boolean menuEnregistrerSimulationIsRendered, boolean
menuEffacerSimulationIsRendered, boolean menuVoirSimulationsIsRendered, boolean menuRetourSimulateurIsRendered, boolean
menuTerminerSessionIsRendered) {
[Link](menuFaireSimulationIsRendered);
[Link](menuEnregistrerSimulationIsRendered);
[Link](menuVoirSimulationsIsRendered);
[Link](menuEffacerSimulationIsRendered);
[Link](menuRetourSimulateurIsRendered);
[Link](menuTerminerSessionIsRendered);
}
248/290
55.
56.
// getters et setters
57.
...
58. }
ligne 13 : la classe SessionData est un bean manag (@Named) qui pourra tre inject dans d'autres beans manags,
ligne 14 : il est de porte session (@SessionScoped),
lignes 18-19 : une rfrence sur le bean ApplicationData lui est inject (@Inject),
lignes 21-32 : les donnes de l'application qui doivent tre maintenues au fil des sessions.
ligne 21 : la liste des simulations faites par l'utilisateur,
ligne 22 : le n de la dernire simulation enregistre,
ligne 23 : la dernire simulation qui a t faite,
lignes 25-30 : les options du menu,
ligne 32 : la locale de l'application.
Lignes 39-44, la mthode init est excute aprs instanciation de la classe (@PostConstruct). Ici, elle n'est utilise que pour laisser
une trace de son excution. On doit pouvoir vrifier qu'elle n'est excute qu'une fois par utilisateur puisque la classe est de porte
session. Ligne 42, la mthode utilise le logueur dfini dans la classe ApplicationData. C'est pour cette raison qu'on avait besoin
d'injecter une rfrence sur ce bean (lignes 18-19).
11.3.3
Le bean Form
package [Link];
import
import
import
import
import
import
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link].*;
[Link];
[Link];
@Named
@RequestScoped
public class Form {
public Form() {
}
// autres beans
@Inject
private ApplicationData applicationData;
@Inject
private SessionData sessionData;
// le modle des vues
private String comboEmployesValue = "";
private String heuresTravailles = "";
private String joursTravaills = "";
private Integer numSimulationToDelete;
private List<Erreur> erreurs = new ArrayList<Erreur>();
private FeuilleSalaire feuilleSalaire;
// liste des employs
public List<Employe> getEmployes(){
return [Link]().findAllEmployes();
}
// action du menu
public String faireSimulation() {
...
}
public String enregistrerSimulation() {
...
}
public String effacerSimulation() {
...
}
public String voirSimulations() {
...
249/290
57.
}
58.
59.
public String retourSimulateur() {
60.
...
61.
}
62.
63.
public String terminerSession() {
64.
...
65.
}
66.
67.
public String retirerSimulation() {
68. ...
69.
}
70.
71.
private void razFormulaire() {
72. ...
73.
}
74.
75. // getters et setters
76. ...
77. }
11.4
11.4.1
[[Link]]
250/290
27.
Gestion des assistantes maternelles
28.
</ui:insert>
29.
<ui:insert name="part2"/>
30.
</h:form>
31.
</h:body>
32.
</f:view>
33. </html>
11.4.2
L'entte [[Link]]
251/290
11.5
11.5.1
lignes 16-21 : les six liens correspondant aux six actions que peut faire l'utilisateur. Ces liens sont contrls (attribut
rendered) par des boolens du bean SessionData,
ligne 17 : un clic sur le lien [Effacer la simulation] provoque l'excution de la fonction Javascript raz. Celle-ci a t dfinie
dans le modle [[Link]],
ligne 18 : l'attribut immediate=true fait que la validit des donnes n'est pas vrifie avant excution de la mthode
[Form].enregistrerSimulation. C'est voulu. On peut vouloir enregistrer la dernire simulation faite il y a une minute, mme si
les donnes saisies depuis dans le formulaire (mais pas encore valides) ne sont pas valides. Il est fait de mme pour les
actions [Voir les simulations], [Effacer la simulation], [Retour au simulateur] et [Terminer la session].
<ui:composition template="[Link]">
<ui:define name="part1">
<ui:include src="[Link]"/>
</ui:define>
</ui:composition>
</html>
252/290
Travail faire : complter la ligne 13 du code XHTML. La liste des lments du combo des employs est fournie par une mthode
du bean [Form]. Ecrire cette mthode. Les lments affichs dans le combo auront leur proprit itemValue gale au n SS d'un
employ et la proprit itemLabel sera une chane forme du prnom et du nom de celui-ci.
Travail faire : comment doit tre initialis le bean [SessionData] pour que, lors de la requte GET initiale faite au formulaire, le
menu de l'entte soit celui montr ci-dessus ?
11.5.2
L'action [faireSimulation]
253/290
254/290
38.
<h:outputText value="#{msg['[Link]']}"/>
39.
<h:outputText value="#{[Link]} %"/>
40.
<h:outputText value="#{[Link]} %"/>
41.
<h:outputText value="#{[Link]} %"/>
42.
<h:outputText value="#{[Link]} %"/>
43.
</h:panelGrid>
44.
<br/>
45.
<h:outputText value="#{msg['[Link]']}" styleClass="titreInfos"/>
46.
<br/><br/>
47.
<h:panelGrid columns="4" rowClasses="libelle,info">
48.
<h:outputText value="#{msg['[Link]']}"/>
49.
<h:outputText value="#{msg['[Link]']}"/>
50.
<h:outputText value="#{msg['[Link]']}"/>
51.
<h:outputText value="#{msg['[Link]']}"/>
52.
<h:outputFormat value="{0,number,currency}">
53.
<f:param value="#{[Link]}"/>
54.
</h:outputFormat>
55.
<h:outputFormat value="{0,number,currency}">
56.
<f:param value="#{[Link]}"/>
57.
</h:outputFormat>
58.
<h:outputFormat value="{0,number,currency}">
59.
<f:param value="#{[Link]}"/>
60.
</h:outputFormat>
61.
<h:outputText value="#{[Link]} %"/>
62.
</h:panelGrid>
63.
<br/>
64.
<h:outputText value="#{msg['[Link]']}" styleClass="titreInfos"/>
65.
<br/><br/>
66.
<h:panelGrid columns="4" rowClasses="libelle,info">
67.
<h:outputText value="#{msg['[Link]']}"/>
68.
<h:outputText value="#{msg['[Link]']}"/>
69.
<h:outputText value="#{msg['[Link]']}"/>
70.
<h:outputText value="#{msg['[Link]']}"/>
71.
<h:outputFormat value="{0,number,currency}">
72.
<f:param value="#{[Link]}"/>
73.
</h:outputFormat>
74.
<h:outputFormat value="{0,number,currency}">
75.
<f:param value="#{[Link]}"/>
76.
</h:outputFormat>
77.
<h:outputFormat value="{0,number,currency}">
78.
<f:param value="#{[Link]}"/>
79.
</h:outputFormat>
80.
<h:outputFormat value="{0,number,currency}">
81.
<f:param value="#{[Link]}"/>
82.
</h:outputFormat>
83.
</h:panelGrid>
84.
<br/>
85.
<h:panelGrid columns="3" columnClasses="libelle,col2,info">
86.
<h:outputText value="#{msg['[Link]']}"/>
87.
<h:panelGroup></h:panelGroup>
88.
<h:outputFormat value="{0,number,currency}">
89.
<f:param value="#{[Link]}"/>
90.
</h:outputFormat>
91.
</h:panelGrid>
92.
</ui:define>
93.
</ui:composition>
94. </html>
Travail faire : crire la mthode [faireSimulation] de la classe [Form]. La simulation sera enregistre dans le bean SessionData.
11.5.3
On veut pouvoir grer proprement les exceptions qui peuvent survenir lors du calcul d'une simulation. Pour cela le code de la
mthode [faireSimulation] utilisera un try / catch :
1. // action du menu
2. public String faireSimulation(){
3.
try{
4.
// on calcule la feuille de salaire
5.
feuilleSalaire= ...
6.
// on met jour le menu
7.
...
8.
// on rend la vue simulation
9.
return "simulation";
10.
}catch(Throwable th){
11.
// on vide la liste des erreurs prcdentes
12.
...
13.
// on cre la nouvelle liste des erreurs
255/290
14.
15.
16.
17.
18.
19.
20.
21.
22.}
...
// on affiche la vue vueErreur
...
// on met jour le menu
...
// on affiche la vue erreur
return "erreurs";
}
package [Link];
public class Erreur {
public Erreur() {
}
// champ
private String classe;
private String message;
// constructeur
public Erreur(String classe, String message){
[Link](classe);
[Link]=message;
}
// getters et setters
...
}
Les erreurs seront des exceptions dont on mmorise le nom de la classe dans le champ classe et le message dans le champ
message.
La liste des erreurs construite dans la mthode [faireSimulation] est constitue de :
...
Voici un exemple de liste d'erreurs :
Ci-dessus, l'employ [Z Y] n'existe pas dans le dictionnaire des employs utilis par la couche [mtier] simule. Dans ce cas, la
couche [mtier] simule lance une exception (ligne 6 ci-dessous) :
1. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravailles, int nbJoursTravaills) {
2.
// on rcupre l'employ
3.
Employe e=[Link](SS);
4.
// on rend une exception si l'employ n'existe pas
5.
if(e==null){
6.
throw new PamException([Link]("L'employ de n SS [%s] n'existe pas",SS),1);
7.
}
8. ...
9. }
256/290
Travail faire : complter la mthode [faireSimulation] afin que lors d'une exception, elle fasse afficher la vue [vueErreur].
11.5.4
L'action [effacerSimulation]
257/290
Un clic sur le lien [EffacerSimulation] provoque d'abord l'appel de la fonction Javascript raz(). Cette mthode est dfinie dans la
page [[Link]] :
1.
2.
3.
4.
5.
6.
7.
8.
<script type="text/javascript">
function raz(){
// on change les valeurs postes
[Link]['formulaire'].elements['formulaire:comboEmployes'].value="0";
[Link]['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
[Link]['formulaire'].elements['formulaire:joursTravailles'].value="0";
}
</script>
les valeurs postes sont des valeurs valides, c.a.d. qu'elles passeront les tests de validation des champs de saisie
heuresTravaillees et joursTravailles ;
la fonction raz ne poste pas le formulaire. En effet, celui-ci va tre post par le lien cmdEffacerSimulation. Ce post se fera
aprs excution de la fonction Javascript raz ;
Au cours du post, les valeurs postes vont suivre un cheminement normal : validation puis affectation aux champs du modle. Ceuxci sont les suivants dans la classe [Form] :
// le modle des vues
private String comboEmployesValue;
private String heuresTravailles;
private String joursTravaills;
...
Ces trois champs vont recevoir les trois valeurs postes {"0","0","0"}. Une fois cette affectation opre, la mthode effacerSimulation
va tre excute.
Travail faire : crire la mthode [effacerSimulation] de la classe [Form]. On fera en sorte que :
- seule la zone des saisies soit affiche,
- le combo soit positionn sur son 1er lment,
- les zones de saisie heuresTravaillees et joursTravailles affichent des chanes vides.
11.5.5
L'action [enregistrerSimulation]
L'action [enregistrerSimulation] associe au lien permet d'enregistrer la simulation courante dans une liste de simulations maintenue
dans la classe [SessionData] :
private List<Simulation> simulations=new ArrayList<Simulation>();
258/290
25.
return [Link]().getIndemnitesEntretien()+
[Link]().getIndemnitesRepas();
26.
}
27.
28.
// getters et setters
29. ...
30. }
ligne 11 : le n de la simulation,
Le n de la simulation est un nombre incrment chaque nouvel enregistrement. Il appartient au bean SessionData :
1.
2.
3.
4.
// simulations
private List<Simulation> simulations = new ArrayList<Simulation>();
private int numDerniereSimulation = 0;
private Simulation simulation;
259/290
260/290
66.
</h:column>
67.
<h:column>
68.
<h:commandLink value="Retirer" action="#{[Link]}">
69.
<f:setPropertyActionListener target="#{[Link]}" value="#{[Link]}"/>
70.
</h:commandLink>
71.
</h:column>
72.
</h:dataTable>
73.
</ui:define>
74.
</ui:composition>
75. </html>
1. // simulations
2. private List<Simulation> simulations;
- l'attribut var="simulation" fixe le nom de la variable reprsentant la simulation courante l'intrieur de la balise
<h:datatable> ;
- l'attribut headerClass="simulationsHeaders" fixe le style des titres des colonnes du tableau ;
- l'attribut columnClasses="...." fixe le style de chacune des colonnes du tableau ;
Examinons l'une des colonnes du tableau et voyons comment elle est construite :
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['[Link]']}"/>
</f:facet>
<h:outputText value="#{[Link]}"/>
</h:column>
simulation dsigne la simulation courante de la liste des simulations : d'abord la 1re, puis la 2me, ... ;
[Link] fait rfrence au champ feuilleSalaire de la simulation courante ;
[Link] fait rfrence au champ employe du champ feuilleSalaire ;
[Link] fait rfrence au champ nom du champ employe ;
La mme technique est rpte pour toutes les colonnes du tableau. Il y a une difficult pour la colonne Indemnits qui est
gnre avec le code suivant :
1.
2.
3.
4.
5.
6.
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['[Link]']}"/>
</f:facet>
<h:outputText value="#{[Link]}"/>
</h:column>
261/290
Ligne 5, on affiche la valeur de [Link]. Or la classe Simulation n'a pas de champ indemnites. Il faut se rappeler
ici que le champ indemnites n'est pas utilis directement mais via la mthode [Link](). Il suffit donc que
cette mthode existe. Le champ indemnites peut lui ne pas exister. La mthode getIndemnites doit rendre le total des indemnits de
l'employ. Cela ncessite un calcul intermdiaire car ce total n'est pas disponible directement dans la feuille de salaire. La mthode
getIndemnites est donne page 258.
Travail faire : crire la mthode [enregistrerSimulation] de la classe [Form].
11.5.6
L'action [retourSimulateur]
L'action [retourSimulateur] associe au lien permet l'utilisateur de revenir de la vue [vueSimulations] la vue [vueSaisies] :
Le rsultat obtenu :
Travail faire : crire la mthode [retourSimulateur] de la classe [Form]. Le formulaire de saisie prsent doit tre vide comme cidessus.
11.5.7
L'action [voirSimulations]
L'action [voirSimulations] associe au lien permet l'utilisateur d'avoir le tableau des simulations, ceci quelque soit l'tat de ses
saisies :
262/290
Le rsultat obtenu :
11.5.8
L'action [retirerSimulation]
263/290
1.
2.
3.
ligne 5 : le lien [Retirer] est associ la mthode [retirerSimulation] de la classe [Form]. Cette mthode a besoin de
connatre le n de la simulation retirer. Celui-ci lui est fourni par la balise <f:setPropertyActionListener> de la ligne 8.
Cette balise a deux attributs target et value : l'attribut target dsigne un champ du modle auquel la valeur de l'attribut
value sera affecte. Ici le n de la simulation retirer #{[Link]} sera affecte au champ
numSimulationToDelete de la classe [Form] :
Lorsque la mthode [retirerSimulation] de la classe [Form] s'excutera, elle pourra utiliser la valeur qui aura t stocke
auparavant dans le champ numSimulationToDelete.
Travail faire : crire la mthode [retirerSimulation] de la classe [Form].
264/290
11.5.9
L'action [terminerSession]
L'action [terminerSession] associe au lien permet l'utilisateur d'abandonner sa session et de revenir au formulaire de saisies vide :
Si l'utilisateur avait une liste de simulations, celle-ci est vide. Par ailleurs, la numrotation des simulations repart 1.
Travail faire : crire la mthode [terminerSession] de la classe [Form].
11.6
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
JSF2
JSFn
2b
[MC]
[Link]
Modle M
Gestionnaires
d'vts
couche
[metier]
simule
Nous remplaons la couche [mtier] simule, par les couches [mtier, DAO, JPA] implmentes par des EJB au paragraphe 6.1, page
165 :
265/290
Application web
couche [web]
1
2a
Faces Servlet
JSF1
JSF2
JSFn
2b
[MC]
[Link]
Modle M
Gestionnaires
d'vts
couche
[metier, DAO, JPA]
Travail faire : raliser l'intgration des couches JSF et EJB en suivant la mthodologie du paragraphe 10, page 235.
@Named
@ApplicationScoped
public class ApplicationData implements Serializable {
// couche mtier
@EJB
private IMetierLocal metier;
...
La ligne 7 instanciait la couche [mtier] simule. Dsormais elle doit rfrencer la couche [mtier] relle. L'annotation @EJB de la
ligne 6 indique au conteneur de servlets qui va excuter la couche web, d'injecter dans le champ metier de la ligne 7, l'EJB qui
implmente l'interface locale IMetierLocal.
266/290
Nous portons maintenant l'application prcdente dans un environnement Spring / Hibernate / Tomcat :
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
JSF2
JSFn
2b
[MC]
[Link]
Modle M
Gestionnaires
d'vts
couche
[mtier, DAO, JPA]
Serveur Tomcat
Nous recopions le projet Maven [mv-pam-jsf2-alone-multipages] dans le projet [mv-pam-jsf2-multipages-spring-hibernate-tomcat]
[1] :
267/290
nous enlevons la dpendance [javaee-web-api.7.0] [7-9] qui est une dpendance pour un projet Glassfish et dont nous
n'avons pas besoin pour un projet Tomcat ;
268/290
53.
<properties>
54.
<[Link]>UTF-8</[Link]>
55.
<[Link]>1.8</[Link]>
56.
<[Link]>1.8</[Link]>
57.
</properties>
58. </project>
lignes 42-46 : la dpendance sur le projet [mv-pam-spring-hibernate] que nous venons de crer ;
lignes 22-32 : les dpendances sur la bibliothque JSF. Celles-ci taient fournies par le serveur Glassfish (provided). Ce
n'est pas le cas pour le serveur Tomcat. Il faut donc les embarquer dans l'archive qui va tre dploye sur Tomcat ;
lignes 34-39 : l'annotation [@Inject] qui permet d'injecter un bean JSF dans un autre bean JSF ncessite la bibliothque
[[Link]] de la ligne 37 ;
lignes 16-20 : la dpendance sur [spring-web] qui permet d'crire des applications web avec le support de Spring ;
lignes 49-51 : on fera attention ces lignes apparemment inutiles (que font-elles au juste ?). Si on les enlve, l'application de
marche plus. Je n'ai pas d'explications ;
package [Link];
import
import
import
import
import
import
import
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
[Link];
@Named
@Scope("application")
public class ApplicationData implements Serializable {
// couche mtier
@Autowired
private IMetier metier;
// logger
private final boolean logsEnabled = true;
private static final Logger logger = [Link]("pam");
ligne 12 : l'annotation JSF [@ApplicationScoped] est remplace par l'annotation Spring (ligne 9) [@Scope('application')]
indique que le bean JSF est de porte [Application] ;
269/290
ligne 17 : l'interface [IMetierLocal] est remplace par l'interface [IMetier] dfinie dans le projet [mv-pam-springhibernate] ;
ligne 16 : l'annotation Spring [@Autowired] va nous permettre d'injecter ligne 17, une rfrence sur le bean de type
[IMetier] dfini dans le projet [mv-pam-spring-hibernate] ;
Notre projet n'a dsormais plus d'erreurs Java. Il nous reste configurer l'intgration Spring / JSF. Cela se passe essentiellement
dans le dossier [WEB-INF] :
270/290
Le fichier [[Link]] configure l'application web. Il tait jusqu' maintenant entirement configur pour JSF. Il volue dsormais de
la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
Le fichier [WEB-INF / [Link]] est exploit au dmarrage de l'application web par le listener Spring
[ContextLoaderListener] :
271/290
15. </beans>
ligne 10 : on indique au listener Spring de chercher les beans JSF dans le package [[Link]]. Il va y trouver les classes
annotes par [@Scope('porte_du_bean')]. Ce sont les beans JSF ;
ligne 13 : on importe le fichier de configuration Spring du projet des couches basses [mv-pam-spring-hibernate]. On
dispose ainsi de tous les beans des couches basses dont celui de la couche [mtier] (lignes 6-9) ci-dessous :
1.
2.
3.
4.
5.
6.
7.
8.
9.
@Named
@Scope("application")
public class ApplicationData implements Serializable {
// couche mtier
@Autowired
private IMetier metier;
Dans le fichier [[Link]], nous avons indiqu Spring qu'il devait exploiter les beans qu'il trouverait dans le package
[[Link]]. Il va y trouver la classe [ApplicationData] dont l'annotation [@Scope] fait qu'elle est reconnue comme un bean Spring.
Ligne 6, ci-dessus, l'annotation [@Autowired] va alors tre honore par Spring qui va injecter en ligne 7 le seul bean implmentant
l'interface [IMetier], savoir celui dfini aux lignes 6-9 ci-dessus du fichier [[Link]] qui configure les couches
basses de l'application web.
Il reste un dernier dtail. Dans les pages xhtml, nous trouvons des annotations comme les suivantes :
1.
2.
3.
4.
<h:commandLink id="cmdFaireSimulation"
value="#{msg['[Link]']}"
action="#{[Link]}"
rendered="#{[Link]}"/>
lignes 2-4, les expression du type #{expression} sont des expressions qui utilisent des beans JSF. Maintenant ces
expressions utilisent des beans Spring ;
Pour que ces expressions soient reconnues, il faut modifier le fichier [[Link]] de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
<!-- le fichier des messages -->
9.
<application>
10.
<application>
11.
<!-- pour que Spring puisse grer les expression #{expression} dans les pages JSF -->
12.
<el-resolver>[Link]</el-resolver>
13.
<!-- le fichier des messages -->
14.
<resource-bundle>
15.
<base-name>
16.
messages
17.
</base-name>
18.
<var>msg</var>
19.
</resource-bundle>
20.
<message-bundle>messages</message-bundle>
21.
</application>
22.
</application>
23. </faces-config>
les expression #{expressions} sont rsolues par un programme appel rsolveur La ligne 12 permet de remplacer le
rsolveur par dfaut de JSF par un rsolveur Spring ;
Voil. On peut excuter l'application web. On modifie ses proprits pour qu'elle s'excute sur le serveur Tomcat :
272/290
12.2
Travail faire : portez le travail prcdent dans un environnement Spring / EclipseLink / Tomcat.
Notes : vous pouvez procder de la faon suivante :
273/290
12.3
Nous allons porter l'application web Spring / JSF du serveur Tomcat vers le serveur Glassfish. Nous rappelons que l'application
dveloppe au paragraphe 11, page 241, l'avait t dans un environnement JSF / EJB / Glassfish. Nous remplaons la technologie
EJB par une technologie Spring.
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
2b
[MC]
[Link]
couche
[mtier, DAO, JPA]
Modle M
Gestionnaires
d'vts
JSF2
JSFn
Serveur EE Glassfish
12.3.1
Etape 1
Nous allons implmenter les couches basses de l'application web [mtier, DAO, JPA] :
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
JSF2
JSFn
2b
[MC]
[Link]
Modle M
Gestionnaires
d'vts
couche
[mtier, DAO, JPA]
Serveur EE Glassfish
274/290
Ce projet va tre dploy sur le serveur Glassfish pour implmenter les couches basses de l'application web JSF / Spring /
Glassfish. A cause de cela, nous supprimons un certain nombre d'lments inutiles [2-4] :
Le fichier [[Link]] [7] qui configure la couche JPA est actuellement le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
Ce fichier de persistence ne peut tre dploy sur le serveur EE Glassfish car pour celui-ci, le type de transaction, ligne 3, doit tre
obligatoirement JTA. Nous rcuprons le fichier [[Link]] du projet [mv-pam-ejb-metier-dao-eclipselink] (paragraphe
[Link], page 169 :
1.
2.
275/290
12.
</properties>
13.
</persistence-unit>
14. </persistence>
Le fichier de configuration Spring [spring-config-metier-dao] [8] doit lui aussi tre modifi. Il est actuellement le suivant :
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.
Parce que la couche de persistance est dsormais gre par le serveur EE Glassfish,
les lignes 37-43 disparaissent. En effet, il n'y a pas lieu d'inclure dans l'application un pool de connexions. Celui-ci est
fourni par dfaut par le serveur Glassfish ;
Les lignes 18-43 sont remplaces par les suivantes :
1.
2.
la ligne 2 dsigne l'EntityManagerFactory qui gre la couche de persistance. Il a t associ ici l'unit de persistance [mvpam-jpa-glassfish-eclipselinkPU] qui est le nom de l'unit de persistance dans le fichier [[Link]] :
276/290
1.
2.
3.
4.
5.
6.
Dans la configuration Spring, le gestionnaire de transactions est actuellement configur par les lignes suivantes :
1.
2.
3.
4.
5.
ligne 3, la classe [JpaTransactionManager] doit tre chang au profit d'un gestionnaire de transactions JTA, le nouveau type
de transactions dfini dans [persistence;xml] ;
<beans xmlns="[Link]
xmlns:xsi="[Link]
xmlns:tx="[Link]
xmlns:jee="[Link]
xsi:schemaLocation="[Link] [Link]
[Link] [Link]
[Link] [Link]
">
<!-- dao -->
<bean id="employeDao" class="[Link]"/>
<bean id="indemniteDao" class="[Link]"/>
<bean id="cotisationDao" class="[Link]"/>
<!-- mtier -->
<bean id="metier" class="[Link]">
<property name="employeDao" ref="employeDao"/>
<property name="cotisationDao" ref="cotisationDao"/>
</bean>
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
<!-- persistence -->
21.
<jee:jndi-lookup id="entityManagerFactory" jndi-name="persistence/mv-pam-spring-glassfish-eclipselinkPU"/>
22.
23.
<!-- le gestionnaire de transactions -->
24.
<tx:jta-transaction-manager/>
25.
<tx:annotation-driven/>
26.
27.
<!-- traduction des exceptions -->
28.
<bean class="[Link]"/>
29.
<!-- persistence -->
30.
<bean class="[Link]"/>
31. </beans>
Aprs toutes ces modifications, le fichier [[Link]] peut tre allg de certaines dpendances devenues inutiles. Sa version finale est
la suivante :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="[Link] xmlns:xsi="[Link]
3.
xsi:schemaLocation="[Link] [Link]
4.
5.
<!-- artifact -->
6.
<modelVersion>4.0.0</modelVersion>
7.
<groupId>[Link]</groupId>
8.
<artifactId>mv-pam-spring-eclipselink-glassfish</artifactId>
9.
<version>1.0-SNAPSHOT</version>
10.
<packaging>jar</packaging>
11.
<name>mv-pam-spring-eclipselink-glassfish</name>
277/290
12.
13.
<dependencies>
14.
<!-- eclipselink -->
15.
<dependency>
16.
<groupId>[Link]</groupId>
17.
<artifactId>eclipselink</artifactId>
18.
<version>2.6.3</version>
19.
<scope>provided</scope>
20.
</dependency>
21.
<!-- mysql -->
22.
<dependency>
23.
<groupId>mysql</groupId>
24.
<artifactId>mysql-connector-java</artifactId>
25.
<version>5.1.39</version>
26.
</dependency>
27.
<!-- Spring framework - transactions -->
28.
<dependency>
29.
<groupId>[Link]</groupId>
30.
<artifactId>spring-tx</artifactId>
31.
<version>[Link]</version>
32.
</dependency>
33.
</dependencies>
34.
35.
<properties>
36.
<[Link]>UTF-8</[Link]>
37.
<[Link]>1.8</[Link]>
38.
<[Link]>1.8</[Link]>
39.
</properties>
40. </project>
en ligne 19, on peut se permettre de mettre l'attribut [provided] la dpendance EclipseLink car par dfaut le serveur
Glassfish la fournit ;
A ce stade, nous en avons fini avec le projet [mv-pam-spring-glassfish-eclipselink] qui va implmenter les couches basses de
l'application web.
12.3.2
Etape 2
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
JSF2
JSFn
2b
[MC]
[Link]
Modle M
Gestionnaires
d'vts
couche
[mtier, DAO, JPA]
Serveur EE Glassfish
Nous dupliquons le projet [mv-pam-jsf2-multipages-spring-eclipselink-tomcat] dans le projet [mv-pam-jsf2-multipages-springeclipselink-glassfish] [1] :
278/290
en [2-3], nous supprimons la dpendance [mv-pam-spring-eclipselink] du projet pour la remplacer par la dpendance [mvpam-spring-eclipselink-glassfish] [4-5] :
En [6], nous devons modifier le fichier [[Link]] qui configure l'application web. Pour l'instant, celui-ci est le suivant :
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.
279/290
28.
<param-value>true</param-value>
29.
</context-param>
30.
<!-- dure session -->
31.
<session-config>
32.
<session-timeout>
33.
30
34.
</session-timeout>
35.
</session-config>
36.
<!-- page d'accueil -->
37.
<welcome-file-list>
38.
<welcome-file>faces/[Link]</welcome-file>
39.
</welcome-file-list>
40.
<!-- page d'exception -->
41.
<error-page>
42.
<error-code>500</error-code>
43.
<location>/faces/[Link]</location>
44.
</error-page>
45.
<error-page>
46.
<exception-type>[Link]</exception-type>
47.
<location>/faces/[Link]</location>
48.
</error-page>
49.
50. </web-app>
Sans ces lignes, le nom JNDI [persistence] du code suivant du fichier [[Link]] n'est pas reconnu :
a)
b)
Lignes 3 et 4, nous utilisons le nom de l'unit de persistance dfinie dans le fichier [[Link]] du projet des couches basses :
<persistence-unit name="mv-pam-spring-glassfish-eclipselinkPU" transaction-type="JTA">
280/290
40.
<version>1</version>
41.
<scope>provided</scope>
42.
</dependency>
43.
44.
<!-- couches basses -->
45.
<dependency>
46.
<groupId>${[Link]}</groupId>
47.
<artifactId>mv-pam-spring-eclipselink-glassfish</artifactId>
48.
<version>${[Link]}</version>
49.
</dependency>
50.
</dependencies>
51.
52.
<properties>
53.
<[Link]>UTF-8</[Link]>
54.
<[Link]>1.8</[Link]>
55.
<[Link]>1.8</[Link]>
56.
</properties>
57. </project>
On notera aux lignes 27, 33 et 41, la balise [<scope>provided</scope>] qui fait que la dpendance ainsi annote n'est pas incluse
dans l'archive war du projet. C'est ainsi parce que ces trois dpendances peuvent tre fournies par Glassfish. Il est donc inutile de les
incorporer dans l'archive du projet dploy sur Glassfish.
12.3.3
Excution
Il nous reste configurer le projet pour qu'il s'excute sur le serveur Glassfish [1-4] :
281/290
282/290
283/290
Une simulation :
284/290
la zone [1] de la copie d'cran ci-dessus vient remplacer la ligne 23 ci-dessus, lorsqu'elle est affiche ;
<f:view>
<h:head>
<title><h:outputText value="#{msg['[Link]']}"/></title>
<link type="text/css" rel="stylesheet" href="resources/css/[Link]" />
<link type="text/css" rel="stylesheet" href="resources/css/[Link]" />
</h:head>
<body style="background-image: url('${[Link]}/resources/images/[Link]');">
<h:form id="formulaire">
<!-- contenu -->
<p:outputPanel id="contenu">
<!-- entete -->
<ui:include src="[Link]" />
<p:spacer height="20px" />
<ui:insert name="contenu" >
Gestion des assistantes maternelles
</ui:insert>
</p:outputPanel>
</h:form>
</body>
</f:view>
</html>
Les actions Ajax sont toutes contenues dans la page [[Link]] suivante :
1. <?xml version='1.0' encoding='UTF-8' ?>
2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "[Link]
3. <html xmlns="[Link]
4.
xmlns:h="[Link]
5.
xmlns:p="[Link]
6.
7.
<!-- titre -->
8.
<h2><h:outputText value="#{msg['[Link]']}"/></h2>
9.
<!-- menu -->
10.
<p:menubar style="background-color: lightblue">
11.
<p:submenu label="#{msg['[Link]']}">
12.
<p:menuitem id="cmdFaireSimulation" style="width: 200px" value="#{msg['[Link]']}"
actionListener="#{[Link]}" rendered="#{[Link]}" ajax="true" update="contenu" />
13.
<p:menuitem id="cmdEffacerSimulation" style="width: 200px" onclick="raz();" immediate="true"
value="#{msg['[Link]']}" actionListener="#{[Link]}"
rendered="#{[Link]}" ajax="true" update="contenu"/>
14.
<p:menuitem id="cmdEnregistrerSimulation" style="width: 200px" immediate="true"
value="#{msg['[Link]']}" actionListener="#{[Link]}"
rendered="#{[Link]}" ajax="true" update="contenu"/>
15.
</p:submenu>
16.
<p:submenu label="Navigation" rendered="#{[Link]}">
285/290
17.
18.
19.
20.
21.
22.
23. </html>
Conseils :
une feuille de style pour les composants Primefaces vous est fournie dans le support du cours ;
286/290
287/290
288/290
289/290
290/290