Javaee 2
Javaee 2
Javaee 2
avec Netbeans
et le serveur d'applications Glassfish
serge.tahe at istia.univ-angers.fr
juin 2012
1/257
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 [http://tahe.developpez.com/java/primefaces]. 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 [http://www.antoniogoncalves.org] 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/257
1.
2.
3.
Persistance Java 5 par la pratique : [http://tahe.developpez.com/java/JPA] - donne les outils pour construire la couche
d'accs aux donnes avec JPA (Java Persistence API)
Introduction au langage Java [http://tahe.developpez.com/java/cours] - pour les dbutants
Introduction par l'exemple Java Server Faces, Primefaces et Primefaces mobile
[http://tahe.developpez.com/java/primefaces]
Ces supports de cours sont par la suite rfrencs [ref1] [ref2] et [ref3].
Ce document est accompagn d'un support disponible l'adresse [http://tahe.ftp-developpez.com/fichiers-archive/javaee2support.zip] contenant des scripts SQL permettant de construire les bases de donnes utilises dans le document, des fichiers
[pom.xml] parfois difficiles construire, les trois rfrences de cours ci-dessus ainsi que les squelettes de certaines des applications
du document. Il contient galement un planning donnant une estimation de temps des diffrentes tches proposes.
Serge Tah, juin 2012.
3/257
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, ...) et qui enregistre certains des rsultats fournis par la couche [2].
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 qui a 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
4/257
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, EclipseLink, 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 2.8 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) :
5/257
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 oeuvre, tester et parfois 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'application en automatisant le dploiement des EJB sur le serveur :
Eclipse, Netbeans, JDeveloper, IntelliJ IDEA. Nous utiliserons ici Netbeans 6.8 et le serveur d'application Glassfish v3.
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
6/257
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 :
7/257
4
3
[Test Dependencies] : les archives .jar ncessaires aux tests du projet et gres par Maven ;
[Java Dependencies] : les archives .jar ncessaires au projet et non gres par Maven ;
Cette branche contient les codes source des classes Java du projet. Netbeans a gnr une classe par dfaut :
1. package istia.st.mvexemple;
2.
3. /**
4. * Hello world!
5. *
6. */
7. public class App {
8.
public static void main(String[] args) {
8/257
9.
System.out.println("Hello World!");
10.
}
11. }
en [4], la branche [Test Packages] qui contient les codes source des classes de test du projet,
en [5], la bibliothque JUnit 3.8 ncessaire l'excution des tests,
C'est un test JUnit 3.8. Nous utiliserons par la suite des tests JUnit 4.x.
9/257
Cette branche affiche toute les bibliothques ncessaires au projet et gres par Maven. Toutes les bibliothques listes ici sont
automatiquement tlcharges par Maven. C'est pourquoi un projet Maven a besoin d'un accs Internet. Les bibliothques
tlcharges vont tre stockes en local. Si un autre projet a besoin d'une bibliothque dj prsente en local, celle-ci ne sera alors
pas tlcharge. Nous verrons que cette liste de bibliothques ainsi que les dpts o on peut les trouver sont dfinis dans le fichier
de configuration du projet Maven.
en [7], le fichier [pom.xml] de configuration du projet Maven. POM signifie Project Object Model. On sera amen
intervenir directement sur ce fichier.
10/257
13. <properties>
14.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies>
18.
<dependency>
19.
<groupId>junit</groupId>
20.
<artifactId>junit</artifactId>
21.
<version>3.8.1</version>
22.
<scope>test</scope>
23.
</dependency>
24. </dependencies>
25. </project>
les lignes 5-8 dfinissent l'objet (artifact) Java qui va tre cr par le projet Maven. Ces informations proviennent de
l'assistant qui a t utilis lors de la cration du projet :
[groupId] : une information qui ressemble un nom de package. Ainsi les bibliothques du framework Spring ont
groupId=org.springframework, celles du framework JSF ont groupId=javax.faces,
[artifactId] : le nom de l'objet Maven. Dans le groupe [org.springframework] on trouve ainsi les artifactId suivants : springcontext, spring-core, spring-beans, ... Dans le groupe [javax.faces], on trouve l'artifactId JSF-api,
[version] : n de version de l'artifact Maven. Ainsi l'artifact org.springframework.spring-core a les versions suivantes : 2.5.4,
2.5.5, 2.5.6, 2.5.6.SECO1, ...
[packaging] : la forme prise par l'artifact, le plus souvent war ou jar.
Notre projet Maven va gnrer un [jar] (ligne 8) dans le groupe [istia.st] (ligne 5), nomm [mv-exemple] (ligne 6) et de version [1.0SNAPSHOT] (ligne 7). Ces quatre informations doivent dfinir de faon unique un artifact Maven.
Les lignes 17-24 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 comme ici, le packaging jar est utilis. On y ajoute 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 est ncessaire lors des test du projet. C'est le cas ici avec la bibliothque JUnit
3.8.1. Si cette bibliothque n'est pas prsente dans le dpt local du poste, elle est tlcharge.
2.1.2
Excution du projet
11/257
1
2
En [1], le projet Maven est construit puis excut [1]. Les logs dans la console Netbeans sont les suivants :
1. Scanning for projects...
2. ...
3.
4. -----------------------------------------------------------------------5. Building mv-exemple 1.0-SNAPSHOT
6. -----------------------------------------------------------------------7.
8. [resources:resources]
9. [debug] execute contextualize
10. Using 'UTF-8' encoding to copy filtered resources.
11. skip non existing resourceDirectory D:\data\istia-1112\netbeans\glassfish\mv-pam\00\mvexemple\src\main\resources
12.
13. [compiler:compile]
14. Compiling 1 source file to D:\data\istia-1112\netbeans\glassfish\mv-pam\00\mvexemple\target\classes
15.
16. [exec:exec]
17. Downloading: http://repo.maven.apache.org/maven2/org/apache/commons/commonsexec/1.0.1/commons-exec-1.0.1.pom
18.
19. Downloaded: http://repo.maven.apache.org/maven2/org/apache/commons/commonsexec/1.0.1/commons-exec-1.0.1.pom (8 KB at 113.5 KB/sec)
20. Downloading: http://repo.maven.apache.org/maven2/org/apache/commons/commonsexec/1.0.1/commons-exec-1.0.1.jar
21.
22. Downloaded: http://repo.maven.apache.org/maven2/org/apache/commons/commonsexec/1.0.1/commons-exec-1.0.1.jar (49 KB at 763.6 KB/sec)
23. Hello World!
24. -----------------------------------------------------------------------25. BUILD SUCCESS
26. -----------------------------------------------------------------------27. Total time: 4.040s
28. Finished at: Thu Jun 21 10:10:40 CEST 2012
29. Final Memory: 13M/122M
Le rsultat est ligne 23. On voit que mme pour ce cas simple, Maven a tlcharg des lments (lignes 17 et 20).
2.1.3
12/257
2
4
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 :
2
3
13/257
6
5
2.1.5
Apprenons maintenant chercher un artifact avec Maven. Partons de la liste des dpendances actuelles du fichier [pom.xml] :
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven4.0.0.xsd">
3.
<modelVersion>4.0.0</modelVersion>
4.
5.
<groupId>istia.st</groupId>
6.
<artifactId>mv-exemple</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
10. <name>mv-exemple</name>
11. <url>http://maven.apache.org</url>
12.
13. <properties>
14.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies>
18.
<dependency>
19.
<groupId>junit</groupId>
20.
<artifactId>junit</artifactId>
21.
<version>3.8.1</version>
22.
<scope>test</scope>
23.
</dependency>
24. </dependencies>
25. </project>
Les lignes 17-23 dfinissent des dpendances qu'on va modifier pour utiliser les bibliothques dans leur version la plus rcente.
14/257
Tout d'abord, nous supprimons les dpendances actuelles [1]. Le fichier [pom.xml] est alors modifi :
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven4.0.0.xsd">
3.
<modelVersion>4.0.0</modelVersion>
4.
5.
<groupId>istia.st</groupId>
6.
<artifactId>mv-exemple</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
10. <name>mv-exemple</name>
11. <url>http://maven.apache.org</url>
12.
13. <properties>
14.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies></dependencies>
18. </project>
Ligne 17, la dpendance supprime n'apparat plus dans [pom.xml]. Maintenant, recherchons-la dans les dpts Maven.
3
5
6
2
4
15/257
En [6], les dpendances ajoutes apparaissent dans le projet. Le fichier [pom.xml] reflte ces changements :
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven4.0.0.xsd">
3.
<modelVersion>4.0.0</modelVersion>
4.
5.
<groupId>istia.st</groupId>
6.
<artifactId>mv-exemple</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
10. <name>mv-exemple</name>
11. <url>http://maven.apache.org</url>
12.
13. <properties>
14.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies>
18.
<dependency>
19.
<groupId>junit</groupId>
20.
<artifactId>junit</artifactId>
21.
<version>4.10</version>
22.
<scope>test</scope>
23.
<type>jar</type>
24.
</dependency>
25. </dependencies>
26. </project>
On notera que fichier [pom.xml] ne mentionne pas la dpendance [hamcrest-core-1.1] que nous voyons en [6]. Cela parce que c'est
une dpendance de JUnit 4.10 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 [http://mvnrepository.com/] :
En [1], on peut taper des mots cls. Tapons hibernate et lanons la recherche.
16/257
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
3.
<modelVersion>4.0.0</modelVersion>
4.
5.
<groupId>istia.st</groupId>
6.
<artifactId>mv-exemple</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
10. <name>mv-exemple</name>
11. <url>http://maven.apache.org</url>
12.
13. <properties>
14.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies>
18.
<dependency>
19.
<groupId>junit</groupId>
20.
<artifactId>junit</artifactId>
21.
<version>4.10</version>
22.
<scope>test</scope>
23.
<type>jar</type>
24.
</dependency>
25.
<dependency>
26.
<groupId>org.hibernate</groupId>
27.
<artifactId>hibernate-core</artifactId>
28.
<version>4.1.2.Final</version>
17/257
29.
</dependency>
30. </dependencies>
31. </project>
Nous sauvegardons le fichier [pom.xml]. Maven entreprend alors le tlchargement des nouvelles dpendances. Le projet volue
comme suit :
en [5], la dpendance [hibernate-core-4.1.2-Final]. Dans le dpt o il a t trouv, cet [artifactId] est lui aussi dcrit par un
fichier [pom.xml]. 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 [6] 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 [pom.xml]
entre dveloppeurs, on est assur que chaque dveloppeur utilise bien les mmes bibliothques.
Dans les exemples qui suivront, nous nous conterons de donner le fichier [pom.xml] 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.
18/257
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 : [http://tahe.developpez.com/java/JPA] - 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 31 pour crer une application
exemple.
3.1
Le lecteur est invit relire le dbut de ce document (paragraphe 1, page 4) 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 / ...]
5
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
3.2.1.1
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
19/257
PRENOM
son prnom
DATENAISSANCE
sa date de naissance
MARIE
NBENFANTS
3.2.1.2
L'entit [Personne]
Programme de test
console [main]
3
4
Objets image
de la BD
Interface
[JPA]
5
Implmentation
6 [Toplink
/ 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. @SuppressWarnings("unused")
4. @Entity
5. @Table(name="Personne")
6. public class Personne implements Serializable{
7.
8. @Id
9. @Column(name = "ID", nullable = false)
10. @GeneratedValue(strategy = GenerationType.AUTO)
11. private Integer id;
12.
13. @Column(name = "VERSION", nullable = false)
14. @Version
15. private int version;
16.
17. @Column(name = "NOM", length = 30, nullable = false, unique = true)
18. private String nom;
19.
20. @Column(name = "PRENOM", length = 30, nullable = false)
21. private String prenom;
22.
23. @Column(name = "DATENAISSANCE", nullable = false)
24. @Temporal(TemporalType.DATE)
25. private Date datenaissance;
26.
27. @Column(name = "MARIE", nullable = false)
28. private boolean marie;
29.
30. @Column(name = "NBENFANTS", nullable = false)
31. private int nbenfants;
32.
33. // constructeurs
34. public Personne() {
20/257
35. }
36.
37. public Personne(String nom, String prenom, Date datenaissance, boolean marie,
38.
int nbenfants) {
39.
setNom(nom);
40.
setPrenom(prenom);
41.
setDatenaissance(datenaissance);
42.
setMarie(marie);
43.
setNbenfants(nbenfants);
44. }
45.
46. // toString
47. public String toString() {
48. ...
49. }
50.
51. // getters and setters
52. ...
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 Toplink. 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. 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, ...)
2. la table T n'existe pas et elle va 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 :
21/257
Firebird
SQL server
le champ cl primaire est dfini comme ayant le type Identity. On a un rsultat similaire au
gnrateur de valeurs de Firebird, si ce n'est que la valeur de la cl n'est connue qu'aprs
l'insertion de la ligne.
utilise un objet appel SEQUENCE qui l encore jouele rle d'un gnrateur de valeurs
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 = GenerationType.AUTO indique la couche
JPA qu'elle doit utiliser cette stratgie habituelle. Cette technique a fonctionn dans tous les exemples de ce document
pour les sept SGBD utiliss.
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 :
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 enregistre. Ce sera le cas de lutilisateur U1. Sa modification est donc
accepte et on change alors la version de la personne modifie 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 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 TemporalType.DATE dsigne une date seule sans heure associe. Les autres types possibles sont TemporalType.TIME
pour coder une heure et TemporalType.TIMESTAMP 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 /
22/257
3.2.2
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.
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 [persistence.xml]
en [5, 6] : une couche JPA implmente par Hibernate
en [4] : l'entit [Personne]
en [3] : un programme de test de type console
23/257
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
/>
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="hibernate.c3p0.timeout" 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 :
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 [persistence.xml] configure les couches [4, 5, 6] ci-dessus :
24/257
les lignes 10-12, ici mises en commentaires configurent les logs console d'Hibernate :
ligne 10 : pour afficher ou non les ordres SQL mis par Hibernate sur le SGBD. Ceci est trs 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.
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
Ainsi le fichier [persistence.xml] 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
3.2.3.1
25/257
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)
2 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).
3.2.3.2
26/257
ligne 23 : l'annotation ManyToOne. Le Many se rapport 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=FetchType.LAZY, ligne 23). On ne sait pas
si cette demande a un sens. On verra.
27/257
3.3
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 : cascadeType.ALL 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 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 a.equals(b). Dans la
classe Object, mre de toutes les classes, a.equals(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 csa, 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 donc 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. Nous n'aborderons pas ces cas dans ce document. Le lecteur les trouvera dans [ref1].
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].
28/257
Interface [JPA] =
EntityManager
Client JPA
1
Base de
Donnes
4
Objets image de la BD =
Contexte de persistance
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)
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 = Persistence.createEntityManagerFactory("nom d'une unit de
persistance");
javax.persistence.Persistence 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/persistence.xml] 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 [Persistence.createEntityManagerFactory("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.
29/257
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 = emf.createEntityManager();
Les mthodes suivantes de l'interface [EntityManager] permettent de grer le cycle de vie du contexte de persistance :
void close()
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 opration SQL DELETE est faite pour le supprimer de la base.
le contexte de persistance est vid de tous ses objets mais pas ferm.
void clear()
void flush()
le contexte de persistance est synchronis avec la base de donnes de la faon dcrite pour
close()
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 FlushModeType.AUTO 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 FlushModeType.COMMIT, 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 :
30/257
3
1
2
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.
Iterator iterator = L.iterator();
2.
while (iterator.hasNext()) {
3.
// exploiter l'objet iterator.next() qui reprsente l'lment courant de la liste
4. ...
5. }
3.4
for (Object o : L) {
// exploiter objet o
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.
31/257
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]
3.4.2
La table [CLIENTS]
Les clients des diffrents mdecins sont enregistrs dans la table [CLIENTS] :
32/257
3.4.3
La table [CRENEAUX]
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]
33/257
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 [dbrdvmedecins2.sql] (support du cours). Avec [WampServer], on
pourra procder comme suit :
7
4
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,
34/257
9
8
35/257
12
13
11
14
Par la suite, nous ne reviendrons plus sur cette base. Mais le lecteur est invit suivre son volution au fil des programmes surtout
lorsque a ne marche pas.
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 :
36/257
3
2
1
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.
3.4.9
37/257
6
3
7
4
8
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,
10
3.4.10
en [10], la connexion est cre. On y voit les quatre tables de la base de donnes connecte.
38/257
couche
[DAO]
Couche
[JDBC]
Couche
[JPA /
Hibernate]
SGBD
BD
Nous sommes en train de construire la couche [JPA]. La configuration de celle-ci est faite dans un fichier [persistence.xml] 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],
en [2], crer une unit de persistance,
4
7
5
8
39/257
ligne 3 : le type de transactions avec la base de donnes. Ici, RESOURCE_LOCAL indique que l'application va grer ellemme ses transactions,
14
Pour avoir des logs d'Hibernate, nous compltons le fichier [persistence.xml] de la faon suivante :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
3.
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transactiontype="RESOURCE_LOCAL">
4.
<provider>org.hibernate.ejb.HibernatePersistence</provider>
5.
<properties>
6.
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
7.
<property name="javax.persistence.jdbc.password" value=""/>
40/257
8.
9.
10.
41/257
47.
<version>2.7.7</version>
48.
</dependency>
49.
<dependency>
50.
<groupId>dom4j</groupId>
51.
<artifactId>dom4j</artifactId>
52.
<version>1.6.1</version>
53.
</dependency>
54.
<dependency>
55.
<groupId>org.hibernate.javax.persistence</groupId>
56.
<artifactId>hibernate-JPA-2.0-api</artifactId>
57.
<version>1.0.1.Final</version>
58.
</dependency>
59.
<dependency>
60.
<groupId>org.javassist</groupId>
61.
<artifactId>javassist</artifactId>
62.
<version>3.15.0-GA</version>
63.
</dependency>
64.
<dependency>
65.
<groupId>org.hibernate.common</groupId>
66.
<artifactId>hibernate-commons-annotations</artifactId>
67.
<version>4.0.1.Final</version>
68.
</dependency>
69. </dependencies>
70. </project>
Les dpendances ajoutes concernent toutes l'ORM Hibernate. On ajoutera la dpendance du pilote JDBC de MySQL :
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
Note : selon la version de Netbeans utilise, on peut obtenir des fichiers [pom.xml] diffrents.
Ce fichier contient beaucoup de redondances. La version minimale suivante peut tre utilise dans la suite :
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven4.0.0.xsd">
3.
<modelVersion>4.0.0</modelVersion>
4.
5.
<groupId>istia.st</groupId>
6.
<artifactId>mv-rdvmedecins-jpql-hibernate</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
10. <name>mv-rdvmedecins-jpql-hibernate</name>
11. <url>http://maven.apache.org</url>
12.
13. <properties>
14.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies>
18.
<dependency>
19.
<groupId>mysql</groupId>
20.
<artifactId>mysql-connector-java</artifactId>
21.
<version>5.1.6</version>
22.
</dependency>
23.
<dependency>
24.
<groupId>org.hibernate</groupId>
42/257
25.
<artifactId>hibernate-entitymanager</artifactId>
26.
<version>4.1.2</version>
27.
</dependency>
28. </dependencies>
29. </project>
3.4.11
en [4], on donne un nom aux classes Java associes aux quatre tables. Ici on a enlev le pluriel des classes,
ainsi qu'un nom de paquetage [5],
en [6], JPA rassemble des lignes de tables de BD dans des collections. Nous choisissons la liste comme collection,
43/257
3.4.12
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. package rdvmedecins.jpa;
2.
3. ...
4. @Entity
5. @Table(name = "medecins")
6. public class Medecin implements Serializable {
7.
8. @Id
9.
@GeneratedValue(strategy = GenerationType.IDENTITY)
10. @Column(name = "ID")
11. private Long id;
12.
13. @Column(name = "TITRE")
14. private String titre;
15.
16. @Column(name = "NOM")
17. private String nom;
18.
19. @Column(name = "VERSION")
20. private int version;
21.
22. @Column(name = "PRENOM")
23. private String prenom;
24.
25. @OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
26. private List<Creneau> creneauList;
27.
28. // constructeurs
29. ....
30.
31. // getters et setters
32. ....
33.
34. @Override
35. public int hashCode() {
36. ...
37. }
38.
44/257
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49. }
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
ligne 4, 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 5, 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 6, 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 10-11 : le champ id de la classe [Medecin] correspond au champ [ID] (ligne 10) de la table [medecins],
lignes 13-14 : le champ titre de la classe [Medecin] correspond au champ [TITRE] (ligne 13) de la table [medecins],
lignes 16-17 : le champ nom de la classe [Medecin] correspond au champ [NOM] (ligne 16) de la table [medecins],
lignes 19-20 : 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 22-23 : le champ prenom de la classe [Medecin] correspond au champ [PRENOM] de la table [medecins],
lignes 10-11 : le champ id correspond la cl primaire [ID] de la table. Les annotations des lignes 8-9 prcisent ce point,
ligne 8 : l'annotation @Id indique que le champ annot est associ la cl primaire de la table,
ligne 9 : 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 GenerationType.IDENTITY indique que la couche JPA va utiliser le mode auto_increment
de la table MySQL,
lignes 25-26 : 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 25). Le champ de la ligne 26 contiendra
tous les crneaux du mdecin. Ceci sans programmation. Pour comprendre totalement la ligne 25, il nous faut prsenter la
classe [Creneau].
45/257
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
nous avons dit que la table [creneaux] avait une cl trangre vers la table [medecins] : un crneau est associ un
mdecin. Plusieurs crneaux peuvent tre asssocis au mme mdecin. On a une relation de la table [creneaux] vers la table
[medecins] qui est qualifie de plusieurs (crneaux) un (mdecin). C'est l'annotation @ManyToOne de la ligne 32 qui
sert qualifier la cl trangre,
la ligne 31 avec l'annotation @JoinColumn prcise la relation de cl trangre : la colonne [ID_MEDECIN] de la table
[creneaux] est cl trangre sur la colonne [ID] de la table [medecins],
ligne 33 : une rfrence sur le mdecin propritaire du crneau. On l'obtient l encore sans programmation.
Le lien de cl trangre entre l'entit [Creneau] et l'entit [Medecin] est donc matrialis par deux annotations :
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
46/257
cette mme ligne 1 de l'entit [Medecin], l'attribut cascade=CascadeType.ALL fixe le comportement de l'entit [Medecin] vis
vis de l'entit [Creneau] :
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. package rdvmedecins.jpa;
2.
3. ...
4. @Entity
5. @Table(name = "clients")
6. public class Client implements Serializable {
7.
@Id
8.
@GeneratedValue(strategy = GenerationType.IDENTITY)
9.
@Column(name = "ID")
10. private Long id;
11.
12. @Column(name = "TITRE")
13. private String titre;
14.
15. @Column(name = "NOM")
16. private String nom;
17.
18. @Column(name = "VERSION")
19. private int version;
20.
21. @Column(name = "PRENOM")
22. private String prenom;
23.
24. @OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
25. private List<Rv> rvList;
26.
27. // constructeurs
28. ...
29. // getters et setters
30. ...
31.
32. @Override
33. public int hashCode() {
34.
...
35. }
36.
37. @Override
38. public boolean equals(Object object) {
39.
...
40. }
41.
42. @Override
43. public String toString() {
44.
...
45. }
46.
47. }
les lignes 24-25 refltent la relation de cl trangre entre la table [rv] et la table [clients].
47/257
L'entit [Rv] :
1. package rdvmedecins.jpa;
2.
3. ...
4. @Entity
5. @Table(name = "rv")
6. public class Rv implements Serializable {
7.
@Id
8.
@GeneratedValue(strategy = GenerationType.IDENTITY)
9.
@Column(name = "ID")
10. private Long id;
11.
12. @Column(name = "JOUR")
13. @Temporal(TemporalType.DATE)
14. private Date jour;
15.
16. @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
17. @ManyToOne(optional = false)
18. private Creneau idCreneau;
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 des complications de programmation. On supprime l'annotation et le champ annot,
48/257
on supprime la validation @NotNull sur les cls primaires. Lorsqu'on persiste une entit JPA avec MySQL, l'entit au
dpart a une cl primaire null. Ce n'est qu'aprs persistance dans la base, que la cl primaire de l'lment persist a une
valeur.
49/257
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. package rdvmedecins.jpa;
2.
3. import java.io.Serializable;
4. import javax.persistence.*;
5.
6. @Entity
7. @Table(name = "clients")
8. public class Client extends Personne implements Serializable {
9.
private static final long serialVersionUID = 1L;
10.
11. // constructeurs
12. public Client() {
13.
super();
14. }
15.
16. public Client(Long id) {
17.
super(id);
18. }
19.
20. public Client(Long id, String titre, String nom, int version, String prenom) {
21.
super(id, titre, nom, version, prenom);
22. }
23.
24. @Override
25. public int hashCode() {
26. ...
27. }
28.
29. @Override
30. public boolean equals(Object object) {
31. ...
32. }
33.
34. @Override
35. public String toString() {
36.
return String.format("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(),
getNom());
37. }
38.
39. }
L'entit [Medecin] qui encapsule les lignes de la table [medecins] suit le mme modle :
1.
2.
3.
4.
5.
6.
7.
8.
9.
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
50/257
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.
// constructeurs
public Medecin() {
super();
}
public Medecin(Long id) {
super(id);
}
public Medecin(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 String.format("Mdecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(),
getNom());
37. }
38.
39. }
51/257
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.
@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() {
...
}
@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 String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut,
hfin, mfin, medecin);
64. }
65. }
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.
52/257
3.4.13
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).
Nous allons ajouter maintenant au projet, le code d'accs aux donnes via la couche JPA :
couche
[console]
Couche
[JPA /
Hibernate]
Couche
[JDBC]
SGBD
BD
53/257
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.
Question : donner les requtes JPQL permettant d'obtenir les informations suivantes :
54/257
liste des clients (nom) ayant pris RV avec Mme PELISSIER le 24/08/2006
nombre de clients de Mme PELISSIER le 24/08/2006
les clients n'ayant pas pris de Rdv
les mdecins n'ayant pas de Rdv
3.5
Note : Jusqu' la fin du chapitre 3, il n'y a pas de projets construire. Il faut simplement lire le cours.
3.5.1
La classe Personne
1. package entites;
2.
3. ...
4.
5. @Entity
6. @Table(name = "jpa01_personne")
7. public class Personne {
8.
9.
@Id
10. @Column(name = "ID", nullable = false)
11. @GeneratedValue(strategy = GenerationType.AUTO)
12. private Integer id;
13.
14. @Column(name = "VERSION", nullable = false)
15. @Version
16. private int version;
17.
18. @Column(name = "NOM", length = 30, nullable = false, unique = true)
19. private String nom;
20.
21. @Column(name = "PRENOM", length = 30, nullable = false)
22. private String prenom;
23.
24. @Column(name = "DATENAISSANCE", nullable = false)
25. @Temporal(TemporalType.DATE)
26. private Date datenaissance;
27.
28. @Column(name = "MARIE", nullable = false)
55/257
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
public Personne(String nom, String prenom, Date datenaissance, boolean marie, int
nbenfants) {
40.
setNom(nom);
41.
setPrenom(prenom);
42.
setDatenaissance(datenaissance);
43.
setMarie(marie);
44.
setNbenfants(nbenfants);
45. }
46.
47. // toString
48.
49. public String toString() {
50.
return String.format("[%d,%d,%s,%s,%s,%s,%d]", getId(), getVersion(), getNom(),
getPrenom(),
51.
new SimpleDateFormat("dd/MM/yyyy").format(getDatenaissance()),
isMarie(), getNbenfants());
52. }
53.
54. // getters and setters
55. ...
56. }
3.5.2
Le programme de test
1. package tests;
2.
3. ....
4. import entites.Personne;
5.
6. @SuppressWarnings("unchecked")
7. public class Test1 {
8.
9.
// constantes
10. private final static String TABLE_NAME = "jpa01_personne"; // Contexte de persistance
11. private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPA");
12. private static Personne p1;
13.
14. public static void main(String[] args) throws Exception {
15.
// nettoyage base
16.
log("clean");
17.
clean();
18.
19.
// dump
20.
log("dump");
21.
dump();
22.
23.
// test1
24.
log("test1");
25.
test1();
26.
27.
// test2
28.
log("test2");
29.
test2();
56/257
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
// fermeture EntityManagerFactory
emf.close();
}
57/257
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
3.5.3
58/257
10. ....
11.
<!-- cration automatique du schma -->
12.
<property name="hibernate.hbm2ddl.auto" value="create" />
13. ....
14.
</properties>
15. </persistence-unit>
16. </persistence>
3.5.4
Les rsultats
1. init:
2. deps-jar:
3. Compiling 1 source file to C:\data\travail\2008-2009\netbeans\JPA\hibernate-personnesentites\build\classes
4. compile-single:
5. run-single:
6. main : ----------- clean
7. Hibernate: delete from jpa01_personne
8. main : ----------- dump
9. Hibernate: select personne0_.ID as ID0_, personne0_.DATENAISSANCE as DATENAIS2_0_,
personne0_.MARIE as MARIE0_, personne0_.NBENFANTS as NBENFANTS0_, personne0_.NOM as NOM0_,
personne0_.PRENOM as PRENOM0_, personne0_.VERSION as VERSION0_ from jpa01_personne
personne0_ order by personne0_.NOM asc
10. main : ----------- test1
11. dbut transaction
12. Personne p1 [null,0,Martin,Paul,31/01/2000,true,2] non persiste
13. em.persist(p1)
14. Hibernate: insert into jpa01_personne (DATENAISSANCE, MARIE, NBENFANTS, NOM, PRENOM,
VERSION) values (?, ?, ?, ?, ?, ?)
15. 17:57:26,312 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
16. 17:57:26,312 DEBUG BooleanType:133 - binding 'true' to parameter: 2
17. 17:57:26,312 DEBUG IntegerType:133 - binding '2' to parameter: 3
18. 17:57:26,312 DEBUG StringType:133 - binding 'Martin' to parameter: 4
19. 17:57:26,312 DEBUG StringType:133 - binding 'Paul' to parameter: 5
20. 17:57:26,312 DEBUG IntegerType:133 - binding '0' to parameter: 6
21. Personne p1 [1,0,Martin,Paul,31/01/2000,true,2] persiste
22. Personne p2 [null,0,Durant,Sylvie,05/07/2001,false,0] non persiste
23. em.persist(p2)
24. Hibernate: insert into jpa01_personne (DATENAISSANCE, MARIE, NBENFANTS, NOM, PRENOM,
VERSION) values (?, ?, ?, ?, ?, ?)
25. 17:57:26,328 DEBUG DateType:133 - binding '05 juillet 2001' to parameter: 1
26. 17:57:26,328 DEBUG BooleanType:133 - binding 'false' to parameter: 2
27. 17:57:26,328 DEBUG IntegerType:133 - binding '0' to parameter: 3
28. 17:57:26,328 DEBUG StringType:133 - binding 'Durant' to parameter: 4
29. 17:57:26,328 DEBUG StringType:133 - binding 'Sylvie' to parameter: 5
30. 17:57:26,328 DEBUG IntegerType:133 - binding '0' to parameter: 6
31. Personne p2 [2,0,Durant,Sylvie,05/07/2001,false,0] persiste
32. Personne p2 [2,0,Durant,Sylvie,05/07/2001,true,0] modifie
33. em.remove(p2)
34. Personne p2 [2,0,Durant,Sylvie,05/07/2001,true,0] supprime
35. fin transaction
36. Hibernate: update jpa01_personne set DATENAISSANCE=?, MARIE=?, NBENFANTS=?, NOM=?,
PRENOM=?, VERSION=? where ID=? and VERSION=?
37. 17:57:26,343 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
38. 17:57:26,343 DEBUG BooleanType:133 - binding 'true' to parameter: 2
39. 17:57:26,343 DEBUG IntegerType:133 - binding '2' to parameter: 3
40. 17:57:26,343 DEBUG StringType:133 - binding 'P1' to parameter: 4
41. 17:57:26,359 DEBUG StringType:133 - binding 'Paul' to parameter: 5
42. 17:57:26,359 DEBUG IntegerType:133 - binding '1' to parameter: 6
43. 17:57:26,359 DEBUG IntegerType:133 - binding '1' to parameter: 7
44. 17:57:26,359 DEBUG IntegerType:133 - binding '0' to parameter: 8
45. Hibernate: delete from jpa01_personne where ID=? and VERSION=?
46. 17:57:26,359 DEBUG IntegerType:133 - binding '2' to parameter: 1
59/257
60/257
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
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 :
cl primaire
ID
VERSION n de version augmente chaque modification de la ligne
CSGRDS pourcentage : contribution sociale gnralise + contribution au remboursement de la dette sociale
pourcentage : contribution sociale gnralise dductible
CSGD
pourcentage : scurit sociale, veuvage, vieillesse
SECU
RETRAITE pourcentage : retraite complmentaire + assurance chmage
Son contenu pourrait tre le suivant :
61/257
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]: total des heures
travailles dans le mois
:
[TOTALHEURES]=150
[TOTALJOURS]= 20
[SALAIREBASE]=([TOTALHEURES]*[B
ASEHEURE])*(1+
[INDEMNITESCP]/100)
[SALAIREBASE]=(150*[2.1])*(1+0.
15)= 362,25
CSGRDS : 12,64
CSGD : 22,28
Scurit sociale : 34,02
Retraite : 28,55
62/257
Les lments suivants sont pris en compte [TOTALHEURES]: total des heures
:
travailles dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
[COTISATIONSSOCIALES]=[SALAIREB
ASE]*(CSGRDS+CSGD+SECU+RETRAITE
)/100
[COTISATIONSSOCIALES]=97,48
[INDEMNITS]=[TOTALJOURS]*(ENTR
ETIENJOUR+REPASJOUR)
[INDEMNITES]=104
[salaire NET]=368,77
[INDEMNITS]
4.3
63/257
1.
2.
3.
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
4.4
L'application graphique permet le calcul des salaires des assistantes maternelles au travers d'un formulaire Swing :
64/257
6
1
3
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
1
2
65/257
5
6
7
8
10
66/257
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
la base de donnes,
le pilote JDBC du SGBD MySQL,
67/257
4.6.1.1
La base de donnes
Crons tout d'abord la base de donnes vide. Nous lanons WampServer et utilisons l'outil PhpMyAdmin [1] :
1
2
4.6.1.2
La liaison entre la couche JDBC et la base de donnes se fait dans le fichier [persistence.xml] qui configure la couche JPA. Ce
fichier peut tre construit avec Netbeans :
68/257
1
3
5
2
6
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], on peut tester la connexion,
en [7], la connexion a russi.
10
13
14
15
16
11
12
69/257
2
1
le fichier apparat dans une nouvelle branche du projet, dans un dossier [META-INF] [1],
qui correspond au dossier [src/main/resources] du projet [2,3] .
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. En fait, Netbeans gnre ici une configuration
errone. La configuration doit tre la suivante :
<property name="hibernate.hbm2ddl.auto" value="create"/>
Avec l'option create, Hibernate, l'instanciation de la couche JPA, supprime puis cre les tables correspondant aux entits JPA.
L'option create-drop fait la mme chose mais la fin de vie de la couche JPA, elle supprime toutes les tables. Il existe une autre
option :
<property name="hibernate.hbm2ddl.auto" value="update"/>
70/257
Cette option cre les tables si elles n'existent pas mais elle ne les dtruit pas si elles existent dj.
Nous ajouterons trois autres proprits la configuration d'Hibernate :
1.
<property name="hibernate.show_sql" value="true"/>
2.
<property name="hibernate.format_sql" value="true"/>
3. <property name="use_sql_comments" value="true"/>
Elles demandent Hibernate d'afficher les ordres SQL qu'il envoie la base de donnes. Le fichier complet est donc le suivant :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
3.
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
4.
<provider>org.hibernate.ejb.HibernatePersistence</provider>
5.
<class>jpa.Cotisation</class>
6.
<class>jpa.Employe</class>
7.
<class>jpa.Indemnite</class>
8.
<properties>
9.
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
10.
<property name="javax.persistence.jdbc.password" value=""/>
11.
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
12.
<property name="javax.persistence.jdbc.user" value="root"/>
13.
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.NoCacheProvider"/>
14.
<property name="hibernate.hbm2ddl.auto" value="create"/>
15.
<property name="hibernate.show_sql" value="true"/>
16.
<property name="hibernate.format_sql" value="true"/>
17.
<property name="use_sql_comments" value="true"/>
18.
</properties>
19. </persistence-unit>
20. </persistence>
4.6.1.3
Les dpendances
Programme
console
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Base de
donnes
Nous avons configur la couche JPA via le fichier [persistence.xml]. L'implmentation choisie a t Hibernate. Cela a amen des
dpendances dans le projet :
71/257
Ces dpendances sont dues l'inclusion d'Hibernate dans le projet. 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 [pom.xml] de la faon suivante :
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
3.
<modelVersion>4.0.0</modelVersion>
4.
5.
<groupId>istia.st</groupId>
6.
<artifactId>mv-pam-jpa-hibernate</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
10. <name>mv-pam-jpa-hibernate</name>
11. <url>http://maven.apache.org</url>
12.
13. <properties>
14.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies>
18.
<dependency>
19.
<groupId>org.hibernate</groupId>
20.
<artifactId>hibernate-entitymanager</artifactId>
21.
<version>4.1.2</version>
22.
<type>jar</type>
23.
</dependency>
24.
<dependency>
25.
<groupId>mysql</groupId>
26.
<artifactId>mysql-connector-java</artifactId>
27.
<version>5.1.6</version>
28.
</dependency>
29. </dependencies>
30. </project>
4.6.1.4
72/257
Note : Les squelettes des entits sans les annotations sont disponibles dans le support de cours.
1. package jpa;
2.
3. public class Cotisation implements Serializable {
4.
5.
private Long id;
6.
private int version;
7.
private double csgrds;
8.
private double csgd;
9.
private double secu;
10. private double retraite;
11.
12. public Cotisation() {
13. }
14.
15. public Cotisation(double csgrds, double csgd, double secu, double retraite){
16.
setCsgrds(csgrds);
17.
setCsgd(csgd);
18.
setSecu(secu);
19.
setRetraite(retraite);
20. }
21. }
1. package jpa;
2.
3. public class Employe implements Serializable {
4.
5.
private Long id;
6.
private int version;
7.
private String SS;
8.
private String nom;
9.
private String prenom;
10. private String adresse;
11. private String ville;
12. private String codePostal;
13. private Indemnite indemnite;
14.
73/257
15.
16.
17.
18.
public Employe() {
}
public Employe(String SS, String nom, String prenom, String adresse, String ville, String
codePostal, Indemnite indemnite){
19.
setSS(SS);
20.
setNom(nom);
21.
setPrenom(prenom);
22.
setAdresse(adresse);
23.
setVille(ville);
24.
setCodePostal(codePostal);
25.
setIndemnite(indemnite);
26. }
27. }
1. package jpa;
2.
3. public class Indemnite implements Serializable {
4.
5.
private Long id;
6.
private int version;
7.
private int indice;
8.
private double baseHeure;
9.
private double entretienJour;
10. private double repasJour;
11. private double indemnitesCP;
12.
13. public Indemnite() {
14. }
15.
16. public Indemnite(int indice, double baseHeure, double entretienJour, double repasJour,
double indemnitesCP){
17.
setIndice(indice);
18.
setBaseHeure(baseHeure);
19.
setEntretienJour(entretienJour);
20.
setRepasJour(repasJour);
21.
setIndemnitesCP(indemnitesCP);
22. }
23. }
4.6.1.5
Nous incluons dans le projet les entits JPA dveloppes prcdemment [1] :
1
2
74/257
1. package main;
2.
3. import javax.persistence.EntityManager;
4. import javax.persistence.EntityManagerFactory;
5. import javax.persistence.Persistence;
6.
7. public class Main {
8.
9.
public static void main(String[] args) {
10.
// crer l'Entity Manager suffit construire la couche JPA
11.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpahibernatePU");
12.
EntityManager em=emf.createEntityManager();
13.
// libration ressources
14.
em.close();
15.
emf.close();
16. }
17. }
1.
2.
3.
4.6.1.6
ligne 12 : on cre l'EntityManager. Cette cration cre la couche JPA. Le fichier [persistence.xml] va tre exploit et donc les
tables de la base de donnes vont tre cres,
lignes 14-15 : on libre les ressources.
Tests
Programme
console
Interface
[JPA]
Couche
[JDBC]
Implmentation
[Hibernate]
Base de
donnes
75/257
76/257
48. Hibernate:
49.
create table COTISATIONS (
50.
id bigint not null auto_increment,
51.
CSGD double precision not null,
52.
CSGRDS double precision not null,
53.
RETRAITE double precision not null,
54.
SECU double precision not null,
55.
VERSION integer not null,
56.
primary key (id)
57.
)
58. Hibernate:
59.
create table EMPLOYES (
60.
id bigint not null auto_increment,
61.
SS varchar(15) not null unique,
62.
ADRESSE varchar(50) not null,
63.
CP varchar(5) not null,
64.
NOM varchar(30) not null,
65.
PRENOM varchar(20) not null,
66.
VERSION integer not null,
67.
VILLE varchar(30) not null,
68.
INDEMNITE_ID bigint not null,
69.
primary key (id)
70.
)
71. Hibernate:
72.
create table INDEMNITES (
73.
id bigint not null auto_increment,
74.
BASE_HEURE double precision not null,
75.
ENTRETIEN_JOUR double precision not null,
76.
INDEMNITES_CP double precision not null,
77.
INDICE integer not null unique,
78.
REPAS_JOUR double precision not null,
79.
VERSION integer not null,
80.
primary key (id)
81.
)
82. Hibernate:
83.
alter table EMPLOYES
84.
add index FK75C8D6BC73F24A67 (INDEMNITE_ID),
85.
add constraint FK75C8D6BC73F24A67
86.
foreign key (INDEMNITE_ID)
87.
references INDEMNITES (id)
88. sept. 09, 2013 2:05:19 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
89. INFO: HHH000230: Schema export complete
90. sept. 09, 2013 2:05:20 PM
org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
91. INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306/dbpam_hibernate]
92. -----------------------------------------------------------------------93. BUILD SUCCESS
94. -----------------------------------------------------------------------95. Total time: 11.724s
96. Finished at: Mon Sep 09 14:05:20 CEST 2013
97. Final Memory: 5M/122M
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 :
77/257
Les tables cres dpendent la fois de l'implmentation de la couche JPA utilise et du SGBD utilis. Ainsi une implmentation
JPA / EclipseLink avec la mme base de donnes peut gnrer des tables diffrentes. C'est ce que nous allons voir maintenant.
4.6.2
Programme
console
Interface
[JPA]
Implmentation
[EclipseLink]
Couche
[JDBC]
Base de
donnes
crer une base MySQL [dbpam_eclipselink]. On utilisera le script [dbpam_eclipselink.sql] pour la gnrer,
crer le fichier [persistence.xml] du projet. Prendre l'implmentation JPA 2.0 EclipseLink,
ajouter dans les dpendances gnres la dpendance du pilote JDBC de MySQL,
ajouter les entits JPA et le programme console du prcdent projet. Adaptez-le le programme console pour qu'il utilise la
bonne unit de persistance,
faire les tests.
78/257
13.
<property name="javax.persistence.jdbc.user" value="root"/>
14.
<property name="eclipselink.logging.level" value="FINE"/>
15.
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
16.
</properties>
17. </persistence-unit>
18. </persistence>
--- exec-maven-plugin:1.2.1:exec (default-cli) @ mv-pam-JPA-eclipselink --[EL Config]: 2013-09-09 14:10:15.181--ServerSession(303405156)-Thread(Thread[main,5,main])--The access type for the persistent class [class
JPA.Cotisation] is set to [FIELD].
7. [EL Config]: 2013-09-09 14:10:15.219--ServerSession(303405156)-Thread(Thread[main,5,main])--The access type for the persistent class [class JPA.Employe]
is set to [FIELD].
8. [EL Config]: 2013-09-09 14:10:15.229--ServerSession(303405156)-Thread(Thread[main,5,main])--The target entity (reference) class for the many to one
mapping element [field indemnite] is being defaulted to: class JPA.Indemnite.
9. [EL Config]: 2013-09-09 14:10:15.23--ServerSession(303405156)-Thread(Thread[main,5,main])--The access type for the persistent class [class JPA.Indemnite]
is set to [FIELD].
10. [EL Config]: 2013-09-09 14:10:15.231--ServerSession(303405156)-Thread(Thread[main,5,main])--The alias name for the entity class [class JPA.Cotisation] is
being defaulted to: Cotisation.
11. [EL Config]: 2013-09-09 14:10:15.257--ServerSession(303405156)-Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
12. [EL Config]: 2013-09-09 14:10:15.261--ServerSession(303405156)-Thread(Thread[main,5,main])--The alias name for the entity class [class JPA.Employe] is
being defaulted to: Employe.
13. [EL Config]: 2013-09-09 14:10:15.262--ServerSession(303405156)-Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
14. [EL Config]: 2013-09-09 14:10:15.262--ServerSession(303405156)-Thread(Thread[main,5,main])--The alias name for the entity class [class JPA.Indemnite] is
being defaulted to: Indemnite.
15. [EL Config]: 2013-09-09 14:10:15.263--ServerSession(303405156)-Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
16. [EL Config]: 2013-09-09 14:10:15.289--ServerSession(303405156)-Thread(Thread[main,5,main])--The primary key column name for the mapping element [field
indemnite] is being defaulted to: ID.
17. [EL Info]: 2013-09-09 14:10:15.824--ServerSession(303405156)--Thread(Thread[main,5,main])-EclipseLink, version: Eclipse Persistence Services - 2.3.0.v20110604-r9504
18. [EL Config]: 2013-09-09 14:10:15.834--ServerSession(303405156)--Connection(1237989929)-Thread(Thread[main,5,main])--connecting(DatabaseLogin(
19.
platform=>MySQLPlatform
20.
user name=> "root"
21.
datasource URL=> "jdbc:mysql://localhost:3306/dbpam_eclipselink"
22. ))
23. [EL Config]: 2013-09-09 14:10:16.276--ServerSession(303405156)--Connection(645036541)-Thread(Thread[main,5,main])--Connected: jdbc:mysql://localhost:3306/dbpam_eclipselink
24.
User: root@localhost
25.
Database: MySQL Version: 5.5.24-log
26.
Driver: MySQL-AB JDBC Driver Version: mysql-connector-java-5.1.6 ( Revision: $
{svn.Revision} )
79/257
80/257
L'existence des tables gnres peut tre vrifie dans Netbeans [1] :
Donc, partir des mmes entits JPA, les implmentations JPA Hibernate et EclipseLink ne gnrent pas les mmes tables. Dans la
suite du document, lorsque l'implmentation JPA utilise est :
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 ?
81/257
2.
3. ...
4.
5. @Entity
6. @Table(name="EMPLOYES")
7. public class Employe implements Serializable {
8.
9.
@Id
10. @GeneratedValue(strategy = GenerationType.AUTO)
11. private Long id;
12. @Version
13. @Column(name="VERSION",nullable=false)
14. private int version;
15. @Column(name="SS", nullable=false, unique=true, length=15)
16. private String SS;
17. @Column(name="NOM", nullable=false, length=30)
18. private String nom;
19. @Column(name="PRENOM", nullable=false, length=20)
20. private String prenom;
21. @Column(name="ADRESSE", nullable=false, length=50)
22. private String adresse;
23. @Column(name="VILLE", nullable=false, length=30)
24. private String ville;
25. @Column(name="CP", nullable=false, length=5)
26. private String codePostal;
27. @ManyToOne(fetch= FetchType.LAZY)
28. @JoinColumn(name="INDEMNITE_ID",nullable=false)
29. private Indemnite indemnite;
30. ...
31. }
Les lignes 27-29 dfinissent la cl trangre de la table [EMPLOYES] vers la table [INDEMNITES]. L'attribut fetch de la ligne 27
dfinit la stratgie de recherche du champ indemnite de la ligne 29. Il y a deux modes :
FetchType.LAZY : lorsqu'un employ est cherch, l'indemnit qui lui correspond n'est pas ramene. Elle le sera lorsque
le champ [Employe].indemnite sera rfrenc pour la premire fois.
FetchType.EAGER : 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 FetchType.LAZY, 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 FetchType.LAZY 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 FetchType.LAZY vite de ramener trop de donnes dont l'application n'a pas besoin tout de suite. Voyons un exemple.
Le projet [mv-pam-jpa-hibernate] est dupliqu :
82/257
3
2
3
2
83/257
package jpa;
...
@Entity
@Table(name="EMPLOYES")
84/257
lignes 21-25 : on devrait avoir une exception. En effet, la mthode toString va tre appele. Elle va utiliser le champ
indemnite. Celui-ci va tre cherch. Comme le contexte de persistance a t ferm, l'entit [Employe] ramene n'existe plus
d'o l'exception.
ligne 27 : on cre un nouveau EntityManager,
ligne 28 : 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 30 : on ferme l'EntityManager,
lignes 32-36 : 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 65.
Par ailleurs, le fichier [persistence.xml] doit tre modifi :
85/257
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.
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.
On obtient les rsultats suivants :
1. jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue
des oiseaux,ville=St Corentin,code postal=49203,indice=2]
2. jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue
des oiseaux,ville=St Corentin,code postal=49203,indice=2]
En mode LAZY, les deux requtes ont ramen l'indemnit avec l'employ, ce qui tait inattendu. Lorsqu'on se renseigne sur le web
sur cette bizarrerie, on dcouvre que l'annotation [FetchType.LAZY] (ligne 1) :
1.
@ManyToOne(fetch= FetchType.LAZY)
2.
@JoinColumn(name="INDEMNITE_ID",nullable=false)
3. private Indemnite indemnite;
n'est pas un ordre mais un souhait. L'implmenteur JPA n'est pas oblig de le suivre. On voit donc que le code devient parfois
dpendant de l'implmentation JPA utilise. Il est possible de donner par configuration EclipseLink le comportement attendu
pour le mode LAZY.
86/257
4.6.6
Pour la suite
Couche
[DAO]
Couche
[metier]
Couche
[ui]
Objets image
de la BD
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
Pour la suite du document, on dupliquera le projet Maven [mv-pam-jpa-hibernate] dans le projet [mv-pam-spring-hibernate] [1, 2,
3] :
87/257
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.
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>mysql</groupId>
88/257
74.
<artifactId>mysql-connector-java</artifactId>
75.
<version>5.1.6</version>
76.
</dependency>
77.
<dependency>
78.
<groupId>org.swinglabs</groupId>
79.
<artifactId>swing-layout</artifactId>
80.
<version>1.0.3</version>
81.
</dependency>
82. </dependencies>
83. </project>
4.7
Note : le prochain travail pratique est au paragraphe 4.9, page 97. D'ici l, il faut lire le cours.
Revenons l'architecture de l'application :
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
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. dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20
2.
3. Valeurs saisies :
4. N de scurit sociale de l'employ : 254104940426058
5. Nombre d'heures travailles : 150
6. Nombre de jours travaills : 20
7.
8. Informations Employ :
9. Nom : Jouveinal
10. ...
11.
12. Informations Cotisations :
13. CSGRDS : 3.49 %
14. ...
15.
16. Informations Indemnits :
89/257
17. ...
18.
19. Informations Salaire :
20. Salaire de base : 362.25 euro
21. Cotisations sociales : 97.48 euro
22. Indemnits d'entretien : 42.0 euro
23. Indemnits de repas : 62.0 euro
24. Salaire net : 368.77 euro
Un certain nombre d'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. package metier;
2.
3. public interface IMetier {
4.
// obtenir la feuille de salaire
5.
public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravailles, int
nbJoursTravaills );
6. }
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. La classe
[FeuilleSalaire] pourrait tre la suivante :
1. package metier;
2.
3. import jpa.Cotisation;
4. import jpa.Employe;
5. import jpa.Indemnite;
6.
7. public class FeuilleSalaire {
8.
// champs privs
9.
private Employe employe;
10. private Cotisation cotisation;
11. private ElementsSalaire elementsSalaire;
12.
13. ...
14. }
90/257
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]
2
1
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.
3.
4.
5.
6.
7.
8.
package metier;
ligne [10] : la mthode qui va permettre la couche [ui] de demander la liste de tous les employs la couche [mtier].
import java.util.List;
import jpa.Employe;
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. package dao;
2.
3. import java.util.List;
4. import jpa.Cotisation;
5.
6. public interface ICotisationDao {
7.
// crer une nouvelle cotisation
8.
public Cotisation create(Cotisation cotisation);
9.
// modifier une cotisation existante
10. public Cotisation edit(Cotisation cotisation);
11.
// supprimer une cotisation existante
12. public void destroy(Cotisation cotisation);
13.
// chercher une cotisation particulire
14. public Cotisation find(Long id);
15.
// obtenir tous les objets Cotisation
16. public List<Cotisation> findAll();
17.
18. }
91/257
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
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 de plus 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.
// crer une nouvelle cotisation
2. void create(Cotisation cotisation);
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 :
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.
92/257
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.
// crer une nouvelle cotisation
2. Cotisation create(Cotisation cotisation);
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
:
1
utilisateur
2
Couche
interface
utilisateur [ui]
JVM 1
Couche
mtier
[metier]
Rseau
tcp /ip
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.
// crer une nouvelle cotisation
2. Cotisation create(Cotisation cotisation);
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. package dao;
2.
3. import java.util.List;
4. import jpa.Cotisation;
5.
6. public interface ICotisationDao {
7.
// crer une nouvelle cotisation
8.
public Cotisation create(Cotisation cotisation);
9.
// modifier une cotisation existante
10. public Cotisation edit(Cotisation cotisation);
11.
// supprimer une cotisation existante
12. public void destroy(Cotisation cotisation);
13.
// chercher une cotisation particulire
14. public Cotisation find(Long id);
93/257
15.
// obtenir tous les objets Cotisation
16. public List<Cotisation> findAll();
17.
18. }
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. package dao;
2.
3. import java.util.List;
4. import jpa.Indemnite;
5.
6. public interface IIndemniteDao {
7.
// crer une entit Indemnite
8.
public Indemnite create(Indemnite indemnite);
9.
// modifier une entit Indemnite
10. public Indemnite edit(Indemnite indemnite);
11.
// supprimer une entit Indemnite
12. public void destroy(Indemnite indemnite);
13.
// rechercher une entit Indemnite via son identifiant
14. public Indemnite find(Long id);
15.
// obtenir toutes les entits Indemnite
16. public List<Indemnite> findAll();
17.
18. }
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. package dao;
2.
3. import java.util.List;
4. import jpa.Employe;
5.
6. public interface IEmployeDao {
7.
// crer une nouvelle entit Employe
8.
public Employe create(Employe employe);
9.
// modifier une entit Employe existante
10. public Employe edit(Employe employe);
11.
// supprimer une entit Employe
94/257
12.
13.
14.
15.
16.
17.
18.
19. }
4.8
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 16 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 8 : la mthode [create] cre une nouvelle entit [Employe]
ligne 10 : la mthode [edit] modifie une entit [Employe] existante
ligne 12 : la mthode [destroy] supprime une entit [Employe] existante
ligne 14 : la mthode [find] permet de retrouver une entit [Employe] existante via son identifiant id
ligne 16 : 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 18 : la mthode [findAll] rend dans une liste toutes les entits [Employe] existantes. Nous avons vu que cette
mthode tait ncessaire l'application graphique.
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.
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 :
95/257
ligne 4 : [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 7. Les trois constructeurs des
lignes 14, 19 et 24 sont ceux de la classe parente [RuntimeException] auxquels on a rajout un paramtre : celui du code
d'erreur qu'on veut donner l'exception.
96/257
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.9
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
4.9.1
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
Implmentation
package dao;
...
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class CotisationDao implements ICotisationDao {
97/257
9.
@PersistenceContext
10. private EntityManager em;
11.
12. // constructeur
13. public CotisationDao() {
14. }
15.
16. // crer une cotisation
17. public Cotisation create(Cotisation cotisation) {
18.
try{
19.
em.persist(cotisation);
20.
return cotisation;
21.
}catch (Throwable th){
22.
throw new PamException(th,11);
23.
}
24. }
25. ....
26.
27. }
4.9.2
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 28, 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 dans un type [PamException] (ligne 22).
Configuration
Question : 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 [dbpam_hibernate.sql]. 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 : suivre le paragraphe 3.1.5 de [ref1]
98/257
4.9.3
Tests
Couche
[tests]
Couche
[DAO]
Objets image
de la BD
4.9.4
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
InitDB
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.
99/257
2
1
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.
100/257
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 12) ce qui entrane que les champs qu'elle utilise doivent tre galement statiques
(lignes 7-9).
la mthode [clean] est excute avant chaque test (annotation @Before). Elle vide la base de donnes.
la mthode [initDB] est un test (annotation @Test). C'est le seul. Un test doit contenir des instructions d'assertion
Assert.assertCondition. 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.
Question : 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 61.
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.
4.9.5
Nous sommes dsormais prts pour excuter [InitDB]. Nous dcrivons la procdure avec le SGBD MySQL5 :
les classes [1], les fichiers de configuration [2] et les classes de test de la couche [DAO] [3] sont mis en place,
4
6
101/257
la classe [JUnitInitDB] est excute [5]. Le SGBD MySQL5 est lanc avec une base [dbpam_hibernate] existante,
la fentre [Test Results] [6] dit que les tests ont t russis. Ce message n'est pas significatif ici, car le programme
[JUnitInitDB] ne contient aucune instruction d'assertion Assert.assertCondition qui pourrait provoquer l'chec du test.
Nanmoins, cela montre qu'il n'y a pas eu d'exception l'excution du test.
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. ------------- Standard Output --------------2. Employs ---------------------3. jpa.Employe[id=5,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des
oiseaux,ville=St Corentin,code postal=49203,indice=2]
4. jpa.Employe[id=6,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brlerie,ville=St Marcel,code postal=49014,indice=1]
5. Indemnits ---------------------6. jpa.Indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour2.0,repas
jour=3.0,indemnits CP=12.0]
7. jpa.Indemnite[id=6,version=0,indice=2,base heure=2.1,entretien jour2.1,repas
jour=3.1,indemnits CP=15.0]
8. Cotisations ---------------------9. jpa.Cotisation[id=3,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
10. ------------- ---------------- ---------------
Les tables [EMPLOYES, INDEMNITES, COTISATIONS] ont t remplies. On peut le vrifier avec une connexion Netbeans la
base [dbpam_hibernate].
2
4.9.6
en [1], dans l'onglet [services], on visualise les donnes de la table [employes] de la connexion [dbpam_hibernate] [2],
en [3] le rsultat.
JUnitDao
102/257
1. package dao;
2.
3. import exception.PamException;
4. ...
5.
6. public class JUnitDao {
7.
8. // couches DAO
9.
static private IEmployeDao employeDao;
10. static private IIndemniteDao indemniteDao;
11. static private ICotisationDao cotisationDao;
12.
13. @BeforeClass
14. public static void init() {
15.
// log
16.
log("init");
17.
// configuration de l'application
18.
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
19.
// couches DAO
20.
employeDao = (IEmployeDao) ctx.getBean("employeDao");
21.
indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
22.
cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
23. }
24.
25. @AfterClass
26. public static void terminate() {
27. }
28.
29. @Before()
30. public void clean() {
31. ...
32. }
33.
34. // logs
35. private static void log(String message) {
36.
System.out.println("----------- " + message);
37. }
38.
39. // tests
40. @Test
41. public void test01() {
42.
log("test01");
43.
// liste des cotisations
44.
List<Cotisation> cotisations = cotisationDao.findAll();
45.
int nbCotisations = cotisations.size();
46.
// on ajoute une cotisation
47.
Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
48.
// on la demande
49.
cotisation = cotisationDao.find(cotisation.getId());
50.
// vrification
51.
Assert.assertNotNull(cotisation);
52.
Assert.assertEquals(3.49, cotisation.getCsgrds(), 1e-6);
53.
Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
54.
Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
55.
Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
56.
// on la modifie
57.
cotisation.setCsgrds(-1);
58.
cotisation.setCsgd(-1);
59.
cotisation.setRetraite(-1);
60.
cotisation.setSecu(-1);
61.
Cotisation cotisation2 = cotisationDao.edit(cotisation);
62.
// vrifications
103/257
63.
Assert.assertEquals(cotisation.getVersion() + 1, cotisation2.getVersion());
64.
Assert.assertEquals(-1, cotisation2.getCsgrds(), 1e-6);
65.
Assert.assertEquals(-1, cotisation2.getCsgd(), 1e-6);
66.
Assert.assertEquals(-1, cotisation2.getRetraite(), 1e-6);
67.
Assert.assertEquals(-1, cotisation2.getSecu(), 1e-6);
68.
// on demande l'lment modifi
69.
Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
70.
// vrifications
71.
Assert.assertEquals(cotisation3.getVersion(), cotisation2.getVersion());
72.
Assert.assertEquals(-1, cotisation3.getCsgrds(), 1e-6);
73.
Assert.assertEquals(-1, cotisation3.getCsgd(), 1e-6);
74.
Assert.assertEquals(-1, cotisation3.getRetraite(), 1e-6);
75.
Assert.assertEquals(-1, cotisation3.getSecu(), 1e-6);
76.
// on supprime l'lment
77.
cotisationDao.destroy(cotisation3);
78.
// vrifications
79.
Cotisation cotisation4 = cotisationDao.find(cotisation3.getId());
80.
Assert.assertNull(cotisation4);
81.
cotisations = cotisationDao.findAll();
82.
Assert.assertEquals(nbCotisations, cotisations.size());
83. }
84.
85.
86. @Test
87. public void test02(){
88.
log("test02");
89.
// on demande la liste des indemnits
90. ...
91.
// on ajoute une Indemnite indemnite
92. ..
93.
// on va chercher indemnite en base on rcupre indemnite1
94. ..
95.
// on vrifie que indemnite1 = indemnite
96. ...
97.
// on modifie l'indemnit obtenue et on persiste la modification en BD. On obtient
indemnite2
98. ...
99.
// on vrifie la version de indemnite2
100.
...
101.
// on va chercher indemnite2 en base on obtient indemnite3
102.
...
103.
// on vrifie que indemnite3 = indemnite2
104.
...
105.
// on supprime en base l'image de indemnite3
106.
...
107.
// on va chercher indemnite3 en base
108.
...
109.
// on vrifie qu'on a obtenu une rfrence null
110.
...
111.
}
112.
113.
@Test
114.
public void test03(){
115.
log("test03");
116.
// on rpte un test analogue aux prcdents pour Employe
117.
...
118.
}
119.
120.
@Test
121.
public void test04(){
122.
log("test04");
123.
// on teste la mthode [IEmployeDao].find(String SS)
124.
// d'abord avec un employ existant
104/257
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.
// getters et setters
...
105/257
Dans la classe de tests prcdente, la base est vide avant chaque test.
Question : 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 = cotisationDao.findAll();
6.
int nbCotisations = cotisations.size();
7.
// on ajoute une cotisation
8.
Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
9.
// on la demande
10.
cotisation = cotisationDao.find(cotisation.getId());
11.
// vrification
12.
Assert.assertNotNull(cotisation);
13.
Assert.assertEquals(0, cotisation.getCsgrds(), 1e-6);
14.
Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
15.
Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
16.
Assert.assertEquals(7.88, cotisation.getRetraite(), 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 :
106/257
1
2
4.10
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
[metier]
Couche
[DAO]
Objets image
de la BD
4.10.1
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
package metier;
import java.util.List;
import jpa.Employe;
107/257
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 90. Nous revenons dessus maintenant.
4.10.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. package metier;
2.
3. import jpa.Cotisation;
4. import jpa.Employe;
5. import jpa.Indemnite;
6.
7. public class FeuilleSalaire implements Serializable{
8.
// champs privs
9.
private Employe employe;
10. private Cotisation cotisation;
11. private ElementsSalaire elementsSalaire;
12.
13. // constructeurs
14. public FeuilleSalaire() {
15.
16. }
17.
18. public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire
elementsSalaire) {
19.
setEmploye(employe);
20.
setCotisation(cotisation);
21.
setElementsSalaire(elementsSalaire);
22. }
23.
24. // toString
25. public String toString() {
26.
return "[" + employe + "," + cotisation + "," + elementsSalaire + "]";
27. }
28.
29. // accesseurs
30. ...
31. }
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 29 et au-del : les accesseurs publics aux champs privs de la classe
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 java.io.Serializable;
4.
5. public class ElementsSalaire implements Serializable{
6.
7.
// champs privs
8.
private double salaireBase;
9.
private double cotisationsSociales;
10. private double indemnitesEntretien;
108/257
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
// toString
@Override
public String toString() {
return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales
+ ",indemnits d'entretien="
33.
+ indemnitesEntretien + ",indemnits de repas=" + indemnitesRepas + ",salaire net="
34.
+ salaireNet + "]";
35. }
36.
37. // getters et setters
38. ....
39. }
4.10.3
ligne 3 : la classe implmente l'interface Serializable parce qu'elle est un composant de la classe FeuilleSalaire qui doit tre
srialisable.
ligne 6 : le salaire de base
ligne 7 : les cotisations sociales payes sur ce salaire de base
ligne 8 : les indemnits journalires d'entretien de l'enfant
ligne 9 : les indemnits journalires de repas de l'enfant
ligne 10 : le salaire net payer l'assistante maternelle
lignes 12-27 : les constructeurs de la classe
lignes 30-35 : mthode [toString] identifiant un objet [ElementsSalaire] particulier
lignes 38 et au-del : les accesseurs publics aux champs privs de la classe
109/257
17. }
18.
19. // liste des employs
20.
public List<Employe> findAllEmployes() {
21.
...
22. }
23.
24. // getters et setters
25. ...
26. }
ligne 5 : 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 9-10 : les rfrences sur les couches [DAO] des entits [Cotisation, Employe, Indemnite] ;
lignes 14-17 : la mthode [calculerFeuilleSalaire] ;
lignes 20-22 : la mthode [findAllEmployes] ;
ligne 24 et au-del : les accesseurs publics des champs privs de la classe.
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.10.4
2
3
Les classes de tests [3] sont crs dans un paquetage [metier] [2] de la branche [Test Packages] [1] du projet.
La classe [JUnitMetier_1] pourrait tre la suivante :
1. package metier;
2.
3. ...
4.
5. public class JUnitMetier_1 {
6.
7. // couches dao
8.
static private IMetier metier;
9.
10. @BeforeClass
11. public static void init(){
12.
// log
13.
log("init");
14.
// configuration de l'application
15.
// instanciation couche [metier]
110/257
16.
Il n'y a pas d'assertion Assert.assertCondition 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.
2.
3.
4.
Testsuite: metier.JUnitMetier_1
----------- init
....
[jpa.Employe[id=22,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brlerie,ville=St Marcel,code
postal=49014,indice=1],JPA.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retrai
te=7.88],JPA.Indemnite[id=29,version=0,indice=1,base heure=1.93,entretien jour2.0,repas
111/257
Question : la ligne 17 de [JUnitMetier_1] utilise le bean Spring nomm metier. Donner la dfinition de ce bean dans le fichier
[spring-config-metier-dao.xml]. 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. package metier;
2.
3. ...
4. public class JUnitMetier_2 {
5.
6. // couche mtier
7.
static private IMetier metier;
8.
9.
@BeforeClass
10. public static void init(){
11. ...
12. }
13.
14. // logs
15. static private void log(String message) {
16.
System.out.println("----------- " + message);
17. }
18.
19. // test
20. @Test
21. public void test01(){
22. ...
23. }
24. }
La classe [JUnitMetier_2] est une copie de la classe [JUnitMetier_1] o cette fois, des assertions ont t places dans la mthode
test01.
Question : crire la mthode test01.
Lors de l'excution de la classe [JUnitMetier_2], on obtient les rsultats suivants si tout va bien :
112/257
4.11
Maintenant que la couche [metier] a t crite, il nous reste crire la couche [ui] [1] :
Couche
[ui]
Couche
[metier]
Objets image
de la BD
Couche
[DAO]
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.11.1
La classe [ui.console.Main]
Nous nous intressons tout d'abord l'application console implmente par la classe [ui.console.Main] ci-dessus. Son
fonctionnement a t dcrit au paragraphe 4.3, page 63. Le squelette de la classe [Main] pourrait tre le suivant :
1. package ui.console;
2.
3. import exception.PamException;
4. import metier.FeuilleSalaire;
5. import metier.IMetier;
6.
7. import java.util.ArrayList;
8. import org.springframework.context.ApplicationContext;
9. import org.springframework.context.support.ClassPathXmlApplicationContext;
10.
11. public class Main {
12.
13. /**
14.
* @param args
15.
*/
16. public static void main(String[] args) {
17.
// donnes locales
18.
final String syntaxe = "pg num_securite_sociale nb_heures_travailles
nb_jours_travaills";
19.
// on vrifie le nombre de paramtres
20. ...
21.
// liste des erreurs
22.
ArrayList erreurs = new ArrayList();
23.
// le second paramtre doit tre un nombre rel >0
24. ...
25.
// erreur ?
26.
if (...) {
27.
erreurs.add("Le nombre d'heures travailles [" + args[1]
113/257
28.
+ "] est erron");
29.
}
30.
// le troisime paramtre doit tre un nombre entier >0
31. ...
32.
// erreur ?
33.
if (...) {
34.
erreurs.add("Le nombre de jours travaills [" + args[2]
35.
+ "] est erron");
36.
}
37.
// des erreurs ?
38.
if (erreurs.size() != 0) {
39.
for (int i = 0; i < erreurs.size(); i++) {
40.
System.err.println(erreurs.get(i));
41.
}
42.
return;
43.
}
44.
// c'est bon - on peut demander la feuille de salaire
45.
FeuilleSalaire feuilleSalaire = null;
46.
try {
47.
// instanciation couche [metier]
48.
...
49.
// calcul de la feuille de salaire
50.
...
51.
} catch (PamException ex) {
52.
System.err.println("L'erreur suivante s'est produite : "+ ex.getMessage());
53.
return;
54.
} catch (Exception ex) {
55.
System.err.println("L'erreur suivante s'est produite : "+ ex.toString());
56.
return;
57.
}
58.
59.
// affichage dtaill
60.
String output = "Valeurs saisies :\n";
61.
output += ajouteInfo("N de scurit sociale de l'employ", args[0]);
62.
output += ajouteInfo("Nombre d'heures travailles", args[1]);
63.
output += ajouteInfo("Nombre de jours travaills", args[2]);
64.
output += ajouteInfo("\nInformations Employ", "");
65.
output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
66.
output += ajouteInfo("Prnom", feuilleSalaire.getEmploye().getPrenom());
67.
output += ajouteInfo("Adresse", feuilleSalaire.getEmploye().getAdresse());
68.
output += ajouteInfo("Ville", feuilleSalaire.getEmploye().getVille());
69.
output += ajouteInfo("Code Postal", feuilleSalaire.getEmploye().getCodePostal());
70.
output += ajouteInfo("Indice", ""+
feuilleSalaire.getEmploye().getIndemnite().getIndice());
71.
output += ajouteInfo("\nInformations Cotisations", "");
72.
output += ajouteInfo("CSGRDS", ""+ feuilleSalaire.getCotisation().getCsgrds() + " %");
73.
output += ajouteInfo("CSGD", ""+ feuilleSalaire.getCotisation().getCsgd() + " %");
74.
output += ajouteInfo("Retraite", ""+ feuilleSalaire.getCotisation().getRetraite() + "
%");
75.
output += ajouteInfo("Scurit sociale", ""+ feuilleSalaire.getCotisation().getSecu() +
" %");
76.
output += ajouteInfo("\nInformations Indemnits", "");
77.
output += ajouteInfo("Salaire horaire", ""+
feuilleSalaire.getEmploye().getIndemnite().getBaseHeure() + " euro");
78.
output += ajouteInfo("Entretien/jour", ""+
feuilleSalaire.getEmploye().getIndemnite().getEntretienJour() + " euro");
79.
output += ajouteInfo("Repas/jour", ""+
feuilleSalaire.getEmploye().getIndemnite().getRepasJour() + " euro");
80.
output += ajouteInfo("Congs Pays", ""+
feuilleSalaire.getEmploye().getIndemnite().getIndemnitesCP()+ " %");
81.
output += ajouteInfo("\nInformations Salaire", "");
82.
output += ajouteInfo("Salaire de base", ""+
feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");
114/257
83.
4.11.2
Excution
3
5
6
2
115/257
4.12
Couche
[ui]
Couche
[metier]
Objets image
de la BD
Couche
[DAO]
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
116/257
4.12.1
Un rapide tutoriel
1
3
2
117/257
12
10
[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].
118/257
15
14
13
119/257
17
18
16
19
20
21
en [20] et [21] : dans la perspective [Source] du formulaire, du code Java a t ajout pour grer le JLabel ajout.
4.12.2
120/257
1
2
JLabel1
JPanel1
JPanel2
121/257
JPanel3
JPanel4
JPanel5
4.12.3
122/257
Le code Java qui associe la mthode prcdente au clic sur le bouton [JButtonSalaire] est lui aussi gnr :
1.
jButtonSalaire.setText("Salaire");
2.
jButtonSalaire.addActionListener(new java.awt.event.ActionListener() {
3.
public void actionPerformed(java.awt.event.ActionEvent evt) {
4.
jButtonSalaireActionPerformed(evt);
5.
}
6. });
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.
jTextFieldHT.addCaretListener(new javax.swing.event.CaretListener() {
2.
public void caretUpdate(javax.swing.event.CaretEvent evt) {
3.
jTextFieldHTCaretUpdate(evt);
4.
}
5. });
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.12.4
123/257
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.
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. package ui.swing;
2.
3. ...
4. public class PamJFrame extends javax.swing.JFrame {
5.
6.
/** Creates new form PamJFrame */
7.
public PamJFrame() {
8.
initComponents();
9.
}
10.
11. /** This method is called from within the constructor to
12.
* initialize the form.
13.
* WARNING: Do NOT modify this code. The content of this method is
14.
* always regenerated by the Form Editor.
15.
*/
16. // <editor-fold defaultstate="collapsed" desc=" Generated Code ">
17. private void initComponents() {
18. ...
19. }// </editor-fold>
20.
21. private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {
22. ...
23. }
24.
25. private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {
26. ...
27. }
28.
29. public static void main(String args[]) {
30.
java.awt.EventQueue.invokeLater(new Runnable() {
31.
public void run() {
32.
new PamJFrame().setVisible(true);
33.
}
34.
});
35. }
36.
37. // Variables declaration - do not modify
38. private javax.swing.JButton jButtonSalaire;
39. ...
40. // End of variables declaration
124/257
41.
42. }
Pour ajouter au code prcdent nos propres initialisations, nous pouvons procder comme suit :
1. /** Creates new form PamJFrame */
2.
public PamJFrame() {
3.
initComponents();
4.
doMyInit();
5.
}
6.
7. ...
8.
9.
// variables d'instance
10. private IMetier metier=null;
11. private List<Employe> employes=null;
12. private String[] employesCombo=null;
13. private double heuresTravailles=0;
14.
15. // initialisations propritaires
16. public void doMyInit(){
17.
// init contexte
18.
try{
19.
// instanciation couche [metier]
20. ...
21. // liste des employs
22. ...
23.
}catch (PamException ex){
24. // le message de l'exception est plac dans [jTextAreaStatus]
25. ...
26. // retour
27.
return;
28.
}
29.
// bouton salaire inhib
30. ...
31.
// jScrollPane1 cach
32. ...
33.
// spinner jours travaills
34.
jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
35.
// combobox employs
36.
employesCombo=new String[employes.size()];
37.
int i=0;
38.
for(Employe employe : employes){
39.
employesCombo[i++]=employe.getPrenom()+" "+employe.getNom();
40.
}
41.
jComboBoxEmployes.setModel(new DefaultComboBoxModel(employesCombo));
42. }
ligne 4 : on appelle une mthode propritaire pour faire nos propres initialisations. Celles-ci sont dfinies par le code des
lignes 10-42
125/257
4.12.5
Gestionnaires d'vnements
Question : 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.
Question : crire la mthode [jButtonSalaireActionPerformed] qui doit afficher la feuille de salaire de l'employ slectionn dans
[jComboBoxEmployes].
4.12.6
Le projet doit tre complet avec ses fichiers de configuration (persistence.xml, spring-config-metier-dao.xml) et la classe de
l'interface graphique. On lancera Le SGBD cible avant d'excuter le projet.
4.13
Nous nous intressons l'architecture suivante o la couche JPA est dsormais implmente par EclipseLink :
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Objets image
de la BD
4.13.1
Interface
[JPA]
Implmentation
[EclipseLink]
Couche
[JDBC]
Spring
Le projet Netbeans
126/257
4
3
2
5
127/257
1
4
5
3
2
Le projet doit tre modifi en deux points pour l'adapter la nouvelle couche JPA / EclipseLink :
1.
2.
en [4], les fichiers de configuration de Spring doivent tre modifis. On y trouve en effet la configuration de la couche JPA.
en [5], les bibliothques du projet doivent tre modifies : celles d'Hibernate doivent tre remplaces par celles de
EclipseLink.
Commenons par ce dernier point. Le fichier [pom.xml] pour le nouveau projet sera celui-ci :
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
3.
<modelVersion>4.0.0</modelVersion>
4.
5.
<groupId>istia.st</groupId>
6.
<artifactId>mv-pam-spring-eclipselink</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
10. <name>mv-pam-spring-eclipselink</name>
11. <url>http://maven.apache.org</url>
12. <repositories>
13.
<repository>
14.
<url>http://repo1.maven.org/maven2/</url>
15.
<id>swing-layout</id>
16.
<layout>default</layout>
17.
<name>Repository for library Library[swing-layout]</name>
18.
</repository>
19.
<repository>
128/257
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.
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library Library[eclipselink]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
</dependency>
129/257
83.
<dependency>
84.
<groupId>mysql</groupId>
85.
<artifactId>mysql-connector-java</artifactId>
86.
<version>5.1.6</version>
87.
</dependency>
88.
<dependency>
89.
<groupId>org.swinglabs</groupId>
90.
<artifactId>swing-layout</artifactId>
91.
<version>1.0.3</version>
92.
</dependency>
93. </dependencies>
94. </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 [spring-config-metier-dao.xml] on a :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3.
xmlns:tx="http://www.springframework.org/schema/tx"
4.
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/springtx-2.0.xsd">
5.
6.
<!-- couches applicatives -->
7.
<!-- DAO -->
8.
<bean id="employeDao" class="dao.EmployeDao" />
9.
<bean id="indemniteDao" class="dao.IndemniteDao" />
10. <bean id="cotisationDao" class="dao.CotisationDao" />
11. <!-- mtier -->
12. <bean id="metier" class="metier.Metier">
13.
<property name="employeDao" ref="employeDao"/>
14.
<property name="indemniteDao" ref="indemniteDao"/>
15.
<property name="cotisationDao" ref="cotisationDao"/>
16. </bean>
17.
18. <!-- configuration JPA -->
19. <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
20.
<property name="dataSource" ref="dataSource" />
21.
<property name="jpaVendorAdapter">
22.
<bean class="org.springframework.orm.JPA.vendor.HibernateJpaVendorAdapter">
23.
<!-24.
<property name="showSql" value="true" />
25.
-->
26.
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect"
/>
27.
<property name="generateDdl" value="true" />
28.
<!-29.
<property name="generateDdl" value="true" />
30.
-->
31.
</bean>
32.
</property>
33.
<property name="loadTimeWeaver">
34.
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
35.
</property>
36. </bean>
37.
130/257
38.
39.
Les lignes 19-36 configurent la couche JPA. L'implmentation JPA utilise est Hibernate (ligne 22). Par ailleurs, la base de donnes
cible est [dbpam_hibernate] (ligne 41).
Pour passer une implmentation JPA / EclipseLink, les lignes 19-35 ci-dessus sont remplaces par les lignes ci-dessous :
1.
2.
4.13.2
131/257
Avant de tester l'application entire, il est bon de vrifier si les tests JUnit passent avec la nouvelle implmentation JPA. Avant de les
faire, on commencera par supprimer les tables de la base de donnes. Pour cela, dans l'onglet [Runtime] de Netbeans, si besoin est,
on crera une connexion sur la base dbpam_eclipselink / MySQL5. Une fois connect la base dbpam_eclipselink / MySQL5, on
pourra procder la suppression des tables comme montr ci-dessous :
Ceci fait, on peut excuter le premier test sur la couche [DAO] : InitDB qui remplit la base. Pour que les tables dtruites
prcdemment soient recres par l'application, il faut s'assurer que dans la configuration JPA / EclipseLink de Spring la ligne :
<property name="generateDdl" value="true" />
132/257
Spring indique qu'il y a un problme de configuration. Le message n'est pas clair. La raison de l'exception a t explique
au paragraphe 3.1.9 de [ref1]. Pour que la configuration Spring / EclipseLink fonctionne, la JVM qui excute l'application
doit tre lance avec un paramtre particulier, un agent Java. La forme de ce paramtre est la suivante :
-javaagent:C:\...\spring-agent.jar
[spring-agent.jar] est l'agent Java dont a besoin la JVM pour grer la configuration Spring / EclipseLink.
Lorsqu'on excute un projet, il est possible de passer des arguments la JVM :
2
3
La configuration de Netbeans pour passer l'agent Java fonctionne bien pour le programme principal mais pas pour les tests (les
programmes dans la branche Tests). Pour ceux-ci, on ajoute le plugin [maven-surefire] dans le fichier [pom.xml] :
1. <build>
2.
<plugins>
3.
<plugin>
4.
<groupId>org.apache.maven.plugins</groupId>
5.
<artifactId>maven-surefire-plugin</artifactId>
6.
<version>2.12.2</version>
7.
<configuration>
8.
<forkmode>pertest</forkmode>
9.
<argLine>-javaagent:D:/Temp/12-09-10/spring-agent-2.5.6.jar</argLine>
10.
</configuration>
11.
</plugin>
12.
</plugins>
13. </build>
133/257
Le plugin [maven-surefire] est utilis pour les tests. Il permet de produire des rapports. Ici, nous ne l'utilisons que pour passer
l'agent Java la JVM (ligne 9). On trouvera l'archive [spring-agent] dans le support du cours et on adaptera la ligne 9 au chemin rel
de cette archive. S'il y a des espaces dans ce chemin, entourez-le d'apostrophes comme dans 'mon chemin'.
Note : on trouvera le [pom.xml] complet dans le support de cours.
4.13.3
InitDB
Maintenant, nous sommes prts pour tester de nouveau [InitDB]. Cette fois-ci les rsultats obtenus sont les suivants :
5
6
4.13.4
JUnitDao
134/257
135/257
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. }
Question : expliquer ce que fait le test test05 et indiquer les rsultats attendus.
Les rsultats obtenus avec une couche JPA / Hibernate sont les suivants :
1. ----------- test05
2. 4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
3. ATTENTION: SQL Error: 1062, SQLState: 23000
4. 4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
5. GRAVE: Duplicate entry '1' for key 2
6. Chane des exceptions -------------------------------------7. exception.PamException
8. javax.persistence.EntityExistsException
9. org.hibernate.exception.ConstraintViolationException
10. com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException
Le test passe, c.a.d. que les assertions sont vrifies et il n'y a pas d'exception qui sort de la mthode de test.
Question : expliquer ce qui s'est pass.
136/257
Les rsultats obtenus avec une couche JPA / EclipseLink sont les suivants :
1. ----------- test05
2. [EL Warning]: 2010-06-04 16:48:26.421--UnitOfWork(749304)--Exception [EclipseLink-4002]
(Eclipse Persistence Services - 2.0.0.v20091127-r5931):
org.eclipse.persistence.exceptions.DatabaseException
3. Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException:
Duplicate entry '1' for key 2
4. Error Code: 1062
5. Call: INSERT INTO INDEMNITES (ID, ENTRETIEN_JOUR, REPAS_JOUR, INDICE, INDEMNITES_CP,
BASE_HEURE, VERSION) VALUES (?, ?, ?, ?, ?, ?, ?)
6.
bind => [108, 2.0, 3.0, 1, 12.0, 1.93, 1]
7. Query: InsertObjectQuery(JPA.Indemnite[id=108,version=1,indice=1,base heure=1.93,entretien
jour2.0,repas jour=3.0,indemnits CP=12.0])
8. Chane des exceptions -------------------------------------9. org.springframework.transaction.TransactionSystemException
10. javax.persistence.RollbackException
11. org.eclipse.persistence.exceptions.DatabaseException
12. com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException
Comme prcdemment avec Hibernate, le test passe, c.a.d. que les assertions sont vrifies et il n'y a pas d'exception qui sort de la
mthode de test.
Question : expliquer ce qui s'est pass.
Question : de ces deux exemples, que peut-on conclure de l'interchangeabilit des implmentations JPA ? Est-elle totale ici ?
4.13.5
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. 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 exceptions prs : l'exemple
prcdent montre en effet que les exceptions lances par les couches [DAO] peuvent diffrer. Ainsi dans le cas d'utilisation du test,
Spring / JPA / Hibernate lance une exception de type [PamException], une exception propre l'application [pam] alors que
Spring / JPA / EclipseLink lui, lance une exception de type [TransactionSystemException], une exception du framework Spring. Si
dans le cas d'usage du test, la couche [ui] attend une exception de type [PamException] parce qu'elle a t construite avec Hibernate,
elle ne fonctionnera plus lorsqu'on passera EclipseLink.
4.13.6
Travail faire
Travail pratique : refaire les tests des applications console et swing avec diffrents SGBD : Postgres, Oracle XE, SQL Server.
137/257
Note : le cours est lire jusqu'au paragraphe 5.2, page 143 o commence le travail pratique.
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 143 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]
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
138/257
5.1.3
ligne 3 : les transactions dans un conteneur EJB sont de type JTA (Java Transaction API). Elles taient de type
RESOURCE_LOCAL avec Spring.
ligne 9 : l'implmentation JPA utilise est EclipseLink
lignes 5-7 : les entits gres par la couche JPA
lignes 11-13 : 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/openejb.conf] suivant :
1. <?xml version="1.0"?>
2. <openejb>
3.
<Resource id="Default JDBC Database">
4.
JdbcDriver com.mysql.jdbc.Driver
5.
JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
6.
UserName root
7.
</Resource>
8. </openejb>
5.1.4
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 une base MySQL [dbpam_eclipselink]
1.
2.
3.
4.
5.
6.
7.
8.
9.
package dao;
import java.util.List;
import jpa.Cotisation;
public interface ICotisationDao {
// crer une nouvelle cotisation
Cotisation create(Cotisation cotisation);
// modifier une cotisation existante
139/257
10.
11.
12.
13.
14.
15.
16.
17.
18. }
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 :
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 javax.ejb.Local;
@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 javax.ejb.Remote;
@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
La couche [DAO] est implmente par un EJB implmentant les deux interfaces (ce n'est pas obligatoire) :
1. @Stateless()
2. @TransactionAttribute(TransactionAttributeType.REQUIRED)
3. public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {
4.
5.
@PersistenceContext
6.
private EntityManager em;
140/257
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]
Rseau
tcp /ip
Donnes
JVM 2
Ci-dessus, les couches [metier] et [DAO] changent des objets par valeur (srialisation 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.
package metier;
import java.util.List;
import jpa.Employe;
On cre une interface locale et une interface distante partir de l'interface prcdente :
1.
2.
3.
4.
5.
6.
7.
package metier;
1.
2.
3.
4.
5.
6.
package metier;
import javax.ejb.Local;
@Local
public interface IMetierLocal extends IMetier{
}
import javax.ejb.Remote;
@Remote
public interface IMetierRemote extends IMetier{
141/257
7. }
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-11 : on refait la mme chose pour les interfaces locales des EJB [EmployeDao] et [IndemniteDao].
Au final, lorsque l'EJB [Metier] sera instanci, les champs des lignes 7, 9 et 11 seront initialiss avec des rfrences sur les
interfaces locales des trois 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
5.1.6
Couche
mtier
[metier]
Rseau
tcp /ip
Couche d'accs
aux donnes
[DAO]
Donnes
JVM 2
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
142/257
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.
8.
9.
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 IemployeDaoLocal employeDaoLocal ;
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 obligs 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.
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
143/257
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
Si elle n'existe pas, crez la base MySQL [dbpam_eclipselink]. Si elle existe, supprimez toutes ses tables. Crez une connexion
Netbeans vers cette base comme il a t dcrit page 144.
5.2.2
dans l'onglet [Files] [2], crer un dossier [conf] [3] sous la racine du projet
placer dans ce dossier, le fichier [openejb.conf] [4-8] suivant :
1. <?xml version="1.0"?>
2. <openejb>
3.
<Resource id="Default JDBC Database">
4.
JdbcDriver com.mysql.jdbc.Driver
5.
JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
6.
UserName root
7.
</Resource>
8. </openejb>
144/257
9
8
10
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
145/257
2.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
3.
<modelVersion>4.0.0</modelVersion>
4.
5.
<groupId>istia.st</groupId>
6.
<artifactId>mv-pam-openejb-eclipselink</artifactId>
7.
<version>1.0-SNAPSHOT</version>
8.
<packaging>jar</packaging>
9.
10. <name>mv-pam-openejb-eclipselink</name>
11. <url>http://maven.apache.org</url>
12.
13. <properties>
14.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies>
18.
<dependency>
19.
<groupId>org.apache.openejb</groupId>
20.
<artifactId>openejb-core</artifactId>
21.
<version>4.0.0</version>
22.
</dependency>
23.
<dependency>
24.
<groupId>junit</groupId>
25.
<artifactId>junit</artifactId>
26.
<version>4.10</version>
27.
<scope>test</scope>
28.
<type>jar</type>
29.
</dependency>
30.
<dependency>
31.
<groupId>org.eclipse.persistence</groupId>
32.
<artifactId>eclipselink</artifactId>
33.
<version>2.3.0</version>
34.
</dependency>
35.
<dependency>
36.
<groupId>org.eclipse.persistence</groupId>
37.
<artifactId>javax.persistence</artifactId>
38.
<version>2.0.3</version>
39.
</dependency>
40.
<dependency>
41.
<groupId>mysql</groupId>
42.
<artifactId>mysql-connector-java</artifactId>
43.
<version>5.1.6</version>
44.
</dependency>
45.
<dependency>
46.
<groupId>org.swinglabs</groupId>
47.
<artifactId>swing-layout</artifactId>
48.
<version>1.0.3</version>
49.
</dependency>
50. </dependencies>
51.
52. <repositories>
53.
<repository>
54.
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
55.
<id>eclipselink</id>
56.
<layout>default</layout>
57.
<name>Repository for library Library[eclipselink]</name>
58.
</repository>
59. </repositories>
60.
61. </project>
146/257
5.2.3
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].
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.
5.2.3.1
L'EJB [CotisationDao]
package dao;
import javax.ejb.Local;
@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 javax.ejb.Remote;
@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
...
import javax.persistence.PersistenceContext;
import jpa.Cotisation;
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {
147/257
8.
9.
@PersistenceContext
10. private EntityManager em;
11. ...
L'import que faisait cette classe sur le framework Spring disparat. Faire un [Clean and Build] du projet :
1
2
5.2.3.2
5.2.3.3
La classe [PamException]
La ligne 5 est ajoute. Pour avoir les bons import, faire [clic droit+Fix imports].
Pour comprendre l'annotation de la ligne 5, 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
148/257
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], ici appele [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.
5.2.3.4
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
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.
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. public [Classe] extends ... implements Serializable{
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.
5.2.3.5
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] :
149/257
3
2
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. public class JUnitInitDBLocal {
2.
3.
static private IEmployeDaoLocal employeDao = null;
4.
static private ICotisationDaoLocal cotisationDao = null;
5.
static private IIndemniteDaoLocal 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.
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
12.
// initialisation du contexte JNDI avec les proprits prcdentes
13.
InitialContext initialContext = new InitialContext(properties);
14.
// instanciation couches DAO locales
15.
employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
16.
cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
17.
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
18. }
1.
2. ...
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].
150/257
On construit le projet (Build), on lance le serveur MySQL si besoin est, on excute le test JUnitInitDBLocal. On rappelle que le
fichier [persistence.xml] a t configur pour recrer les tables chaque excution. Avant l'excution du test, il est prfrable de
supprimer les ventuelles tables de la base MySQL [dbpam_eclipselink].
4
2
en [1], dans l'onglet [Services], on supprime les tables de la connexion Netbeans tablie au paragraphe 5.2.1.
en [2], la base [dbpam_eclipselink] n'a plus de tables
en [3], le projet est construit
en [4], le test JUnitInitDBLocal est excut
151/257
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 :
152/257
Ceci fait, la nouvelle classe de test peut tre excute. Auparavant, avec la connexion Netbeans [dbpam_eclipselink], supprimez les
tables de la base [dbpam_eclipselink].
5.2.4
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].
2
3
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.
5.2.4.1
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. package metier;
153/257
2.
3.
4.
5.
6.
7.
8.
1.
2.
3.
4.
5.
6.
7.
8.
import javax.ejb.Local;
@Local
public interface IMetierLocal extends IMetier{
}
package metier;
import javax.ejb.Remote;
@Remote
public interface IMetierRemote extends IMetier{
}
Ceci fait, en [3] nous modifions la classe [Metier] afin qu'elle devienne un EJB :
1. @Stateless
2. @TransactionAttribute(TransactionAttributeType.REQUIRED)
3. public class Metier implements IMetierLocal, IMetierRemote {
4.
5.
// rfrences sur la couche [DAO] locale
6.
@EJB
7.
private ICotisationDaoLocal cotisationDao = null;
8.
@EJB
9.
private IEmployeDaoLocal employeDao = null;
10. @EJB
11. private IIndemniteDaoLocal indemniteDao = null;
12.
13. // obtenir la feuille de salaire
14. public FeuilleSalaire calculerFeuilleSalaire(String SS,
15.
double nbHeuresTravailles, int nbJoursTravaills) {
16.
// on rcupre les informations lies l'employ
17. ...
5.2.4.2
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] :
154/257
Note : avant d'excuter ce test, il faut modifier le fichier [persistence.xml] pour que les tables ne soient plus dtruites et recres au
moment de l'instanciation de la couche [JPA] (lignes 13-15 ci-dessous) :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
155/257
3.
<persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
4.
<!-- le fournisseur JPA est EclipseLink -->
5.
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
6.
<!-- entits Jpa -->
7.
<class>jpa.Cotisation</class>
8.
<class>jpa.Employe</class>
9.
<class>jpa.Indemnite</class>
10.
<!-- proprits provider EclipseLink -->
11.
<properties>
12.
<property name="eclipselink.logging.level" value="FINE"/>
13.
<!-14.
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
15.
-->
16.
</properties>
17. </persistence-unit>
18. </persistence>
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. public class JUnitMetierRemote {
2.
3.
// couche mtier distante
4.
static private IMetierRemote metier;
5.
6.
@BeforeClass
7.
public static void init() throws NamingException {
8.
// on configure le conteneur Open EJB embarqu
9.
Properties properties = new Properties();
10.
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
11.
// initialisation du contexte JNDI avec les proprits prcdentes
12.
InitialContext initialContext = new InitialContext(properties);
13.
14.
// instanciation couches DAO distantes
15.
IEmployeDaoRemote employeDao = (IEmployeDaoRemote)
initialContext.lookup("EmployeDaoRemote");
16.
ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote)
initialContext.lookup("CotisationDaoRemote");
17.
IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote)
initialContext.lookup("IndemniteDaoRemote");
18.
// instanciation couche mtier distante
19.
metier = (IMetierRemote) initialContext.lookup("MetierRemote");
20.
21.
// on vide la base
22.
for(Employe employe:employeDao.findAll()){
23.
employeDao.destroy(employe);
24.
}
25.
for(Cotisation cotisation:cotisationDao.findAll()){
26.
cotisationDao.destroy(cotisation);
27.
}
156/257
28.
29.
30.
31.
32.
33.
34.
35.
36.
5.2.5
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].
2
3
1
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.
2.
3.
// donnes locales
final String syntaxe = "pg num_securite_sociale nb_heures_travailles
nb_jours_travaills";
4. ...
5.
// des erreurs ?
6.
if (erreurs.size() != 0) {
157/257
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
}
// c'est bon - on peut demander la feuille de salaire la couche [mtier]
IMetierLocal metier = null;
FeuilleSalaire feuilleSalaire = null;
try {
// on configure le conteneur Open EJB embarqu
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
19.
// initialisation du contexte JNDI avec les proprits prcdentes
20.
InitialContext initialContext = new InitialContext(properties);
21.
// instanciation couche mtier locale
22.
metier = (IMetierLocal) initialContext.lookup("MetierLocal");
23.
// calcul de la feuille de salaire
24.
feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravailles,
nbJoursTravaills);
25.
} catch (PamException ex) {
26.
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
27.
return;
28.
} catch (Exception ex) {
29.
System.err.println("L'erreur suivante s'est produite : " + ex.toString());
30.
return;
31.
}
32.
// affichage dtaill
33.
String output = "Valeurs saisies :\n";
34.
output += ajouteInfo("N de scurit sociale de l'employ", args[0]);
35. ....
Les modifications se situent dans les lignes 13-25. C'est la faon d'avoir une rfrence sur la couche [metier] qui change (lignes 1722). 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] :
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/persistence.xml] :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
3.
<persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
4.
<!-- le fournisseur JPA est EclipseLink -->
5.
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
6.
<!-- entits Jpa -->
158/257
7.
<class>jpa.Cotisation</class>
8.
<class>jpa.Employe</class>
9.
<class>jpa.Indemnite</class>
10.
<!-- proprits provider EclipseLink -->
11.
<properties>
12.
<property name="eclipselink.logging.level" value="FINE"/>
13.
<!-14.
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
15.
-->
16.
</properties>
17. </persistence-unit>
18. </persistence>
La ligne 14 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. Ceci fait, on peut excuter le programme.
Si tout va bien, on obtient un affichage console analogue au suivant :
1. .......
2. INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default
Stateless Container)
3. INFO - Deployed Application(path=classpath.ear)
4. [EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse
Persistence Services - 1.1.2.v20090612-r4475
5. [EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pamconsole-metier-dao-openejb-eclipselink-0910/build/classes/-JPA login successful
6. Valeurs saisies :
7. N de scurit sociale de l'employ : 254104940426058
8. Nombre d'heures travailles : 150
9. Nombre de jours travaills : 20
10.
11. Informations Employ :
12. Nom : Jouveinal
13. Prnom : Marie
14. Adresse : 5 rue des oiseaux
15. Ville : St Corentin
16. Code Postal : 49203
17. Indice : 2
18.
19. Informations Cotisations :
20. CSGRDS : 3.49 %
21. CSGD : 6.15 %
22. Retraite : 7.88 %
23. Scurit sociale : 9.39 %
24.
25. Informations Indemnits :
26. Salaire horaire : 2.1 euro
27. Entretien/jour : 2.1 euro
28. Repas/jour : 3.1 euro
29. Congs Pays : 15.0 %
30.
31. Informations Salaire :
32. Salaire de base : 362.25 euro
33. Cotisations sociales : 97.48 euro
34. Indemnits d'entretien : 42.0 euro
35. Indemnits de repas : 62.0 euro
36. Salaire net : 368.77 euro
37.
38. 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 :
159/257
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. // c'est bon - on peut demander la feuille de salaire la couche [metier]
2.
IMetierRemote metier = null;
3.
FeuilleSalaire feuilleSalaire = null;
4.
try {
5.
// on configure le conteneur Open EJB embarqu
6. ...
7.
// instanciation couche mtier distante
8.
metier = (IMetierRemote) initialContext.lookup("MetierRemote");
9.
// calcul de la feuille de salaire
10.
feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravailles,
nbJoursTravaills);
11.
} catch (PamException ex) {
12. ...
13.
} catch (Exception ex) {
14. ...
15.
}
Les modifications sont faites aux lignes 2 et 8. Le projet est configur [2] pour excuter la classe [MainRemote]. Son excution
donne les mmes rsultats que prcdemment.
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
160/257
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.
161/257
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 :
162/257
Conteneur
Ejb3
Client
Java SE
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.
6.1.1.1
Le projet Netbeans
5
4b
4a
avec le bouton [4a], choisir le dossier parent du dossier du projet ou taper son nom directement en [4b].
en [5], donner un nom au projet
en [6], choisir le serveur d'application sur lequel il sera excut. Celui choisi ici est l'un de ceux visibles dans l'onglet
[Runtime / Servers], ici Glassfish v3.
en [7], choisir la version de Java EE.
1
3
en [1], le nouveau projet. Il diffre d'un projet Java classique par quelques points :
une branche [Other Sources] [2] est automatiquement cre. Elle contiendra notamment le fichier
[persistence.xml] qui configure la couche JPA,
si on construit le projet (Build), on voit apparatre [3] une dpendance [javaee-api-6.0]. Elle est de type provided car
elle est fournie l'excution par le conteneur EJB de Glassfish.
163/257
6.1.1.2
Par configuration de la couche de persistance, nous entendons l'criture du fichier [persistence.xml] 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]
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
Eclipselink]
Couche
[JDBC]
SGBD
BD
Java SE
On pourra procder comme suit. Tout d'abord, dans l'onglet [Runtime / Databases], on crera [1] une connexion sur la base
MySQL5 [dbpam_eclipselink] :
Ceci fait, on peut passer la cration de la ressource JDBC utilise par le module EJB :
2
4
1
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
164/257
7
8
5
6
9
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 [persistence.xml] 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_eclipselink.
en [9], un rcapitulatif des proprits du pool de connexions - on ne touche rien
11
10
en [10], on peut prciser plusieurs des proprits du pool de connexions - on laisse les valeurs par dfaut
en [11], l'issue de l'assistant de cration d'une ressource JDBC pour le module EJB, un fichier [glassfish-resources.xml] a
t cr dans la branche [Other Sources]. Le contenu de ce fichier est le suivant :
165/257
Le fichier [glassfish-resources.xml] 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 [persistence.xml] qui va configurer la couche JPA du module EJB :
4
1
3
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 [Persistence]
en [4], on veut crer une unit de persistance
5
10
6
8
7
9
166/257
en [6], plusieurs implmentations JPA sont proposes. On choisira ici [EclipseLink]. D'autres implmentations sont
utilisables condition de mettre les bibliothques qui les implmentent avec celles du serveur Glassfish.
dans la liste droulante [7], choisir la source de donnes JDBC [jdbc/dbpam_eclipselink] qui vient d'tre cre.
en [8], indiquer que les transactions sont gres par le conteneur EJB
en [9], indiquer qu'aucune opration ne doit tre faite sur la source de donnes lors du dploiement du module EJB sur le
serveur. En effet, le module EJB va utiliser une base [dbpam_eclipselink] dj cre.
la fin de l'assistant, un fichier [persistence.xml] a t cr [10]. Son contenu est le suivant :
6.1.1.3
Maintenant que le fichier [persistence.xml] a t dfini, nous pouvons passer l'insertion dans le projet des couches [metier, dao,
JPA] de l'application d'entreprise [pam] :
Couche
[ui]
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
Java SE
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 :
6.1.1.4
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]
167/257
la couche JPA est implmente par EclipseLink. Il faut nous assurer que le serveur Glassfish a les bibliothques de cette
implmentation JPA.
la source de donnes est une base MySQL. Il faut nous assurer que le serveur Glassfish a le pilote JDBC de ce SGBD.
On peut dcouvrir l'absence de ces bibliothques lors du dploiement du module EJB. Voici une faon de procder parmi d'autres
pour ajouter des bibliothques manquantes au serveur Glassfish :
6.1.1.5
168/257
Lors du dploiement, le serveur Glassfish logue dans la console des informations intressantes :
1. dao.
2. ....
3. INFO: Portable JNDI names for EJB IndemniteDao : [java:global/pam-serveur-metier-dao-JPAeclipselink/IndemniteDao!DAO.IIndemniteDaoLocal, java:global/pam-serveur-metier-dao-JPAeclipselink/IndemniteDao!DAO.IIndemniteDaoRemote]
4. INFO: Glassfish-specific (Non-portable) JNDI names for EJB IndemniteDao :
[DAO.IIndemniteDaoRemote#DAO.IIndemniteDaoRemote, dao.IIndemniteDaoRemote]
5. ...
6. INFO: Portable JNDI names for EJB CotisationDao : [java:global/pam-serveur-metier-dao-JPAeclipselink/CotisationDao!dao.ICotisationDaoLocal, java:global/pam-serveur-metier-dao-JPAeclipselink/CotisationDao!dao.ICotisationDaoRemote]
7. INFO: Glassfish-specific (Non-portable) JNDI names for EJB CotisationDao :
[DAO.ICotisationDaoRemote, dao.ICotisationDaoRemote#DAO.ICotisationDaoRemote]
8. INFO: Portable JNDI names for EJB Metier : [java:global/pam-serveur-metier-dao-JPAeclipselink/Metier!metier.IMetierRemote, java:global/pam-serveur-metier-dao-JPAeclipselink/Metier!metier.IMetierLocal]
9. INFO: Glassfish-specific (Non-portable) JNDI names for EJB Metier :
[metier.IMetierRemote#metier.IMetierRemote, metier.IMetierRemote]
10. ...
11. INFO: Portable JNDI names for EJB EmployeDao : [java:global/pam-serveur-metier-dao-JPAeclipselink/EmployeDao!dao.IEmployeDaoLocal, java:global/pam-serveur-metier-dao-JPAeclipselink/EmployeDao!dao.IEmployeDaoRemote]
12. INFO: Glassfish-specific (Non-portable) JNDI names for EJB EmployeDao :
[DAO.IEmployeDaoRemote#dao.IEmployeDaoRemote, DAO.IEmployeDaoRemote]
13. INFO: pam-serveur-metier-dao-JPA-eclipselink was successfully deployed in 12 891
milliseconds.
3, 6, 8 et 11 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.
4, 7, 9, 12 : les noms JNDI des EJB dploys sous une forme spcifique Glassfish v3.
Ces noms seront utiles l'application console que nous allons crire pour utiliser le module EJB dploy.
6.2
169/257
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]
Couche
[JDBC]
Couche
[JPA /
Toplink]
SGBD
BD
Java SE
Nous crons un nouveau projet Maven de type [Java Application] nomm [mv-pam-client-ejb-metier-dao-eclipselink] :
2
3
4
5
6
170/257
8.
<packaging>jar</packaging>
9.
10. <name>mv-pam-client-ejb-metier-dao-eclipselink</name>
11. <url>http://maven.apache.org</url>
12. <repositories>
13.
<repository>
14.
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
15.
<id>eclipselink</id>
16.
<layout>default</layout>
17.
<name>Repository for library Library[eclipselink]</name>
18.
</repository>
19.
<repository>
20.
<url>http://repo1.maven.org/maven2/</url>
21.
<id>swing-layout</id>
22.
<layout>default</layout>
23.
<name>Repository for library Library[swing-layout]</name>
24.
</repository>
25. </repositories>
26. <properties>
27.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
28. </properties>
29.
30. <dependencies>
31.
<dependency>
32.
<groupId>org.glassfish.appclient</groupId>
33.
<artifactId>gf-client</artifactId>
34.
<version>3.1.1</version>
35.
</dependency>
36.
<dependency>
37.
<groupId>${project.groupId}</groupId>
38.
<artifactId>mv-pam-ejb-metier-dao-eclipselink</artifactId>
39.
<version>${project.version}</version>
40.
<type>ejb</type>
41.
</dependency>
42.
<dependency>
43.
<groupId>org.swinglabs</groupId>
44.
<artifactId>swing-layout</artifactId>
45.
<version>1.0.3</version>
46.
</dependency>
47. </dependencies>
48. </project>
lignes 31-35 : la dpendance sur la bibliothque [gf-client] qui permet un client Glassfish de communiquer avec un
serveur distant,
lignes 36-41 : 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],
La classe [MainRemote] doit obtenir une rfrence sur l'EJB de la couche [metier]. Le code de la classe [MainRemote] volue de la
faon suivante :
171/257
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 9, nous utilisons le nom JNDI portable.
En [1], on configure le projet pour qu'il excute la classe [MainRemote] avec des arguments. Si tout va bien, l'excution du projet
donne le rsultat suivant :
1. run:
2. Valeurs saisies :
3. N de scurit sociale de l'employ : 254104940426058
4. Nombre d'heures travailles : 150
5. Nombre de jours travaills : 20
6.
7. Informations Employ :
8. Nom : Jouveinal
9. Prnom : Marie
10. Adresse : 5 rue des oiseaux
11. Ville : St Corentin
172/257
Si dans les proprits, on met un n de scurit sociale incorrect, on obtient le rsultat suivant :
1. run:
2. L'erreur suivante s'est produite : L'employ de n[254104940426058x] est introuvable
3. 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.3
Dans les versions prcdentes, l'environnement JNDI du serveur Glassfish tait configur partir d'un fichier [jndi.properties]
trouv quelque part dans les archives du projet. Son contenu par dfaut est le suivant :
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 [jndi.properties] 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.
173/257
2
1
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, 10 et 11.
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 [jndi.properties] devient le contenu de la balise <jee:environment> (ligne 15) qui sert dfinir les
paramtres de connexion au service JNDI.
174/257
10.
Lignes 7-8, la rfrence de type [IMetierRemote] sur la couche [metier] est demande Spring. 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 [spring-config-client.xml] changerait. On retrouverait
alors une configuration analogue l'architecture Spring / JPA tudie au paragraphe 4.11.
Le lecteur est invit tester cette nouvelle version.
6.4
Le client Swing
Nous construisons maintenant le client swing de notre application client / serveur EJB.
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.
175/257
Couche
[ui]
swing
Jvm1 - Java SE
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
Travail pratique : en suivant l'exemple du client console [ui.console.MainRemote] du projet, modifier la faon utilise par la
mthode [doMyInit] (cf page 125) de la classe [PamJFrame] pour acqurir une rfrence sur la couche [metier] qui est maintenant
distante.
176/257
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 alors que 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
177/257
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]
2
1
Java SE
S
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. package metier;
2.
3. ...
4. @WebService
5. @Stateless()
6. @TransactionAttribute(TransactionAttributeType.REQUIRED)
7. public class Metier implements IMetierLocal,IMetierRemote {
8.
9.
// rfrences sur les couches [DAO]
10. @EJB
11. private ICotisationDaoLocal cotisationDao = null;
12. @EJB
13. private IEmployeDaoLocal employeDao=null;
14. @EJB
15. private IIndemniteDaoLocal indemniteDao=null;
16.
17.
18. // obtenir la feuille de salaire
19. @WebMethod
20. public FeuilleSalaire calculerFeuilleSalaire(String SS,
21. ...
22. }
23.
24. // liste des employs
25. @WebMethod
26.
public List<Employe> findAllEmployes() {
27. ...
28. }
29. // important - pas de getters et setters pour les EJB qui deviennent des services web
178/257
30. }
ligne 4, l'annotation @WebService fait de la classe [Metier] un service web. Un service web expose des mthodes ses
clients. Celles-ci doivent tre annotes par l'attribut @WebMethod.
lignes 19 et 25 : les deux mthodes de la classe [Metier] deviennent des mthodes du service web.
ligne 29 : 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.
L'ajout de ces annotations est dtect par Netbeans qui fait alors voluer la nature du projet :
1
3
2
En [1], une arborescence [Web Services] est apparue dans le projet. On y trouve le service web Metier et ses deux mthodes.
L'application serveur peut tre dploye [2]. Le serveur MySQL soit tre lanc et sa base [dbpam_eclipselink] exister et tre remplie.
Il peut tre ncessaire auparavant de supprimer [3] 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.
1
En [1], nous voyons notre application serveur dploye sur le serveur Glassfish. Une fois le service web dploy, il peut tre test :
2
179/257
en [3], un lien sur le fichier XML dfinissant le service web. Les clients du service web ont besoin de connatre l'URL de ce
fichier. C'est partir de lui qu'est gnre la couche cliente (stubs) du service web.
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). Nous y retrouvons bien les deux
employs avec leurs indemnits. Le lecteur est invit tester de la mme faon la mthode [4] en lui passant les trois paramtres
qu'elle attend.
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.
180/257
7.1.2
La partie cliente
Couche
[ui]
C
1
S
2
Couche
[metier]
Couche
[DAO]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
SGBD
BD
Java SE
7.1.2.1
RMI
Nous crons maintenant un projet Java de type [Java Application] pour la partie client de l'application. Il n'a pas t possible (juin
2012) de crer un projet Maven pour ce client. Une erreur survient, semble connue mais reste non rsolue.
Une fois le projet cr, nous indiquons qu'il sera client du service web que nous venons de dployer sur le serveur Glassfish :
2
3
181/257
7
10
8
11
en [7], 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]
Couche
[DAO]
Couche
[JPA /
Toplink]
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 [8].
une fois l'assistant de cration du client du service web termin avec le bouton [Finish], la couche [C] ci-dessus est cre.
en [10] 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] [10] 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
[CalculerFeuilleSalaire.java] et [CalculerFeuilleSalaireResponse.java], 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 super.getPort(new QName("http://metier/", "MetierPort"), Metier.class);
La mthode getMetierPort de la ligne 2 permet d'obtenir une rfrence sur le service web Metier distant.
182/257
7.1.2.2
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] qui tait un client d'un serveur EJB, dans le nouveau projet.
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 [3], le morceau de code o la couche [metier] est instancie [3]. Elle l'est avec du code JNDI pour obtenir une rfrence
sur un EJB distant.
en [4], il nous reste obtenir une rfrence sur la service web distant [Metier] afin de pouvoir appeler sa mthode
[calculerFeuilleSalaire].
en [5], 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.
183/257
ligne 112, on voit que [calculerFeuilleSalaire] est une mthode de la classe [client.Metier] (ligne 111). Maintenant que nous
savons comment obtenir la couche [metier], le code prcdent peut tre rcrit de la faon suivante :
1. ...
2. // c'est bon - on peut demander la feuille de salaire
3.
FeuilleSalaire feuilleSalaire = null;
4.
Metier metier = null;
5.
try {
6.
// instanciation couche [metier]
7.
metier = new MetierService().getMetierPort();
8.
// calcul de la feuille de salaire
9.
feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravailles,
nbJoursTravaills);
10.
} catch (Throwable th) {
11.
// chane des exceptions
12.
System.out.println("Chane des exceptions --------------------------------------");
13.
System.out.println(th.getClass().getName() + ":" + th.getMessage());
14.
while (th.getCause() != null) {
15.
th = th.getCause();
16.
System.out.println(th.getClass().getName() + ":" + th.getMessage());
17.
}
18.
System.exit(1);
19.
}
20.
// affichage rapide
21. ...
La ligne 7 rcupre une rfrence sur le service web Metier. Ceci fait, le code de la classe ne change pas, si ce n'est quand mme
qu'en ligne 10, ce n'est pas l'exception de type [Exception] qui est gre mais le type plus gnral Throwable, la classe parent de la
classe Exception. S'il y a exception, nous affichons toutes les causes imbriques de celle-ci jusqu' la cause originelle.
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 client
excuter le client
184/257
On notera qu'alors que le service web [Metier] envoie une exception de type [PamException] l'exception reue par le client est de
type [SOAPFaultException]. Mme dans la chane des exceptions, on ne voit pas apparatre le type [PamException].
7.1.3
Travail faire : porter le client swing du projet [mv-pam-client-ejb-metier-dao-JPA-eclipselink] 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
1
2
185/257
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.
4
5
2
186/257
lignes 7-10 : la classe importe des classes du module EJB [pam-serveurws-metier-dao-JPA-eclipselink] dont le projet
Maven a t ajout aux dpendances du projet.
ligne 12 : la classe est un service web
ligne 13 : elle implmente l'interface IMetier dfinie dans le module EJB
lignes 18-19 : la mthode calculerFeuilleSalaire est expose comme mthode du service web
lignes 23-24 : la mthode findAllEmployes est expose comme mthode du service web
lignes 15-16 : l'interface locale de l'EJB [Metier] est injecte dans le champ de la ligne 16. Nous utilisons l'interface locale
car l'application web et le module EJB s'excutent dans la mme JVM.
lignes 20 et 25 : 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.
Dans Netbeans, l'application web est reconnue comme exposant un service web :
2
3
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].
187/257
6
7
8
en [6], nous configurons le projet. La version de Java EE sera Java EE 6. 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.
en [7], 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 [8], nous ajoutons des dpendances au projet d'entreprise
188/257
10
11
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 [11] :
11
13
12
Le serveur MySQL doit tre lanc et la base [dbpam_eclipselink] disponible et remplie. Ceci fait, l'application d'entreprise peut tre
dploye [12]. En [13], on peut voir qu'elle a bien t dploye sur le serveur Glassfish.
Nous pouvons tester le service web qui vient d'tre dploy :
189/257
1
2
7.2.2
La partie cliente
Travail faire : en suivant la dmarche dcrite au paragraphe 7.1.2.1, page 181, 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.11, page 113 :
190/257
7.3.1
La partie serveur
Nous crons une application Maven de type web nomme [mv-pam-ws-spring-tomcat] [1]:
Nous modifions le fichier [pom.xml] pour y inclure les dpendances [2] suivantes :
1.
<dependencies>
2.
<dependency>
3.
<groupId>${project.groupId}</groupId>
4.
<artifactId>mv-pam-spring-hibernate</artifactId>
5.
<version>${project.version}</version>
6.
</dependency>
7.
<!-- Apache CXF dependencies -->
8.
<dependency>
9.
<groupId>org.apache.cxf</groupId>
10.
<artifactId>cxf-rt-frontend-jaxws</artifactId>
11.
<version>2.2.12</version>
12.
</dependency>
13.
<dependency>
14.
<groupId>org.apache.cxf</groupId>
15.
<artifactId>cxf-rt-transports-http</artifactId>
16.
<version>2.2.12</version>
17.
</dependency>
18. </dependencies>
191/257
Couche
[web]
Couche
[metier]
Couche
[DAO]
Interface
[JPA]
Implmentation
[Hibernate]
Couche
[JDBC]
Spring
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 / web.xml] de la faon suivante :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
3.
<display-name>mv-pam-ws-spring-tomcat</display-name>
4.
<listener>
5.
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
6.
</listener>
7. <!-- Configuration de CXF -->
8.
<servlet>
9.
<servlet-name>CXFServlet</servlet-name>
10.
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
11.
<load-on-startup>1</load-on-startup>
12. </servlet>
13. <servlet-mapping>
14.
<servlet-name>CXFServlet</servlet-name>
15.
<url-pattern>/ws/*</url-pattern>
16. </servlet-mapping>
17. <session-config>
18.
<session-timeout>
19.
30
20.
</session-timeout>
21. </session-config>
22. <welcome-file-list>
23.
<welcome-file>index.jsp</welcome-file>
24. </welcome-file-list>
25. </web-app>
le framework CXF a une dpendance sur Spring. Lignes 4-6 : un listener est dclar. La classe correspondante va tre
charge en mme temps que l'application web. Elle va exploiter le fichier de configuration de Spring [WEB-INF /
applicationContext.xml] :
lignes 8-12 : la servlet CXF qui va grer les appels au service web que nous allons crer,
lignes 13-16 : 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 :
192/257
package pam.ws;
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 javax.jws.WebService;
import metier.IMetier;
@WebService
public interface IWsMetier extends IMetier{
}
193/257
37. }
lignes 13-15 : on importe des fichiers de configuration Apache CXF. Ceux-ci sont cherchs dans le Classpath du projet
(attribut classpath:),
lignes 4, 9, 10 : des espaces de noms spcifiques Apache CXF sont dclars,
ligne 18 : on importe le fichier de configuration Spring du projet [mv-pam-spring-hibernate],
lignes 21-23 : dfinissent le bean du service web avec sa dpendance sur la couche [mtier] (ligne 22),
lignes 24-27 : dfinissent le service web lui-mme,
ligne 25 : le bean Spring implmentant le service web est celui dfini ligne 21 ;
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 web.xml), cette URL devient /ws/metier.
194/257
Notre projet est prt tre excut. Nous l'excutons (Run) et demandons l'URL [http://localhost:8080/mv-pam-ws-springtomcat/ws] dans un navigateur :
La page liste tous les services web dploys. Ici, il n'y en a qu'un. Nous suivons le lien WSDL :
2
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 7.1.2.1, page 181, construire un client console du service web
prcdent.
Note : pour indiquer l'URL du fichier WSDL du service web, on procdera comme suit :
195/257
196/257
mobile
l'URL
197/257
Architecture de l'application
Couche
[web /
JSF]
Couche
EJB3
[metier]
Couche
EJB3
[DAO]
Objets image
de la BD
Implmentation
[EclipseLink]
Interface
[JPA]
Couche
[JDBC]
Serveur Glassfish v3
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
[metier, 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 162 et dont l'architecture tait la suivante :
client
Couche
[ui]
Jvm1 - Java SE
serveur
Couche
[DAO]
Couche
[metier]
Couche
[JPA /
EclipseLink]
Couche
[JDBC]
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
Client
Java SE
Conteneur Ejb3
Jpa / EclipseLink
Donnes
serveur Java EE
198/257
Jpa 3
Conteneur EJB3
[metier, DAO] 2 EclipseLink
Conteneur web
[web / jsf]
Navigateur
HTTP
SGBD
serveur Java EE
seule la couche [web / JSF] 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
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
Faces Servlet
3
Gestionnaire
d'vts
Modles
couche
[metier, 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 [faces-config.xml]. On y trouve la liste des vues et les rgles de transition de l'une l'autre. A partir de la version JSF
2, 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.
199/257
9.2
Fonctionnement de l'application
200/257
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 :
201/257
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 162 :
Application web
couche [web]
1
Faces Servlet
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
3
Gestionnaire
d'vts
Modles
couche
[metier, dao, jpa]
Donnes
2c
4a
1
4
3
6
202/257
9.3.1
Le fichier [web.xml] est celui gnr par dfaut par Netbeans avec de plus la configuration d'une page d'exception :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
3.
<context-param>
4.
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
5.
<param-value>client</param-value>
6.
</context-param>
7.
<context-param>
8.
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
9.
<param-value>true</param-value>
10. </context-param>
11. <context-param>
12.
<param-name>javax.faces.PROJECT_STAGE</param-name>
13.
<param-value>Development</param-value>
14. </context-param>
15. <servlet>
16.
<servlet-name>Faces Servlet</servlet-name>
17.
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
18.
<load-on-startup>1</load-on-startup>
19. </servlet>
20. <servlet-mapping>
21.
<servlet-name>Faces Servlet</servlet-name>
22.
<url-pattern>/faces/*</url-pattern>
23. </servlet-mapping>
24. <session-config>
25.
<session-timeout>
26.
30
27.
</session-timeout>
28. </session-config>
29. <welcome-file-list>
30.
<welcome-file>faces/index.xhtml</welcome-file>
31. </welcome-file-list>
32. <error-page>
33.
<error-code>500</error-code>
34.
<location>/faces/exception.xhtml</location>
35. </error-page>
36. <error-page>
37.
<exception-type>java.lang.Exception</exception-type>
38.
<location>/faces/exception.xhtml</location>
39. </error-page>
40. </web-app>
203/257
7.
8.
9.
10.
11.
12.
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('$
{request.contextPath}/resources/images/standard.jpg');">
13.
<h:form id="formulaire">
14.
<h3><h:outputText value="#{msg['exception.header']}"/></h3>
15.
<h:panelGrid columnClasses="col1,col2" columns="2" border="1">
16.
<h:outputText value="#{msg['exception.httpCode']}"/>
17.
<h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/>
18.
<h:outputText value="#{msg['exception.message']}"/>
19.
<h:outputText value="#{requestScope['javax.servlet.error.exception']}"/>
20.
<h:outputText value="#{msg['exception.requestUri']}"/>
21.
<h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
22.
<h:outputText value="#{msg['exception.servletName']}"/>
23.
<h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/>
24.
</h:panelGrid>
25.
</h:form>
26.
</h:body>
27. </f:view>
28. </html>
Toute exception non explicitement gre par le code de l'application web provoquera l'affichage d'une page analogue celle cidessous :
lignes 9-14 : le fichier [messages.properties] sera utilis pour l'internationalisation des pages. Il sera accessible dans les
pages XHTML via la cl msg.
204/257
9.3.2
ligne 15 : dfinit le fichier [messages.properties] 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. Cette
possibilit n'est pas utilise ici.
La feuille de style
9.3.3
205/257
Ces messages sont tous utiliss dans la page [index.xhtml] l'exception de ceux des lignes 11-15 utiliss dans la page
[exception.xhtml].
9.3.4
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class Form implements Serializable {
206/257
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35. }
9.3.5
}
public String setEnglishLocale(){
locale="en";
return null;
}
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
La couche [mtier]
Cette interface est celle utilise dans la partie serveur de l'application client / serveur dcrite au paragraphe 6.1, page 162.
La classe Metier que nous allons utiliser pour tester la couche [web] implmente cette interface de la faon suivante :
1. package metier;
2.
3. ...
4. public class Metier implements IMetierLocal {
5.
6.
// dictionnaire des employes index par le n SS
7.
private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
8.
// liste des employs
9.
private List<Employe> listEmployes;
10.
11. // obtenir la feuille de salaire
12. public FeuilleSalaire calculerFeuilleSalaire(String SS,
13.
double nbHeuresTravailles, int nbJoursTravaills) {
14.
// on rcupre l'employ de n SS
15.
Employe e=hashEmployes.get(SS);
16.
// on rend une feuille de salaire fiictive
17.
return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new
ElementsSalaire(100,100,100,100,100));
18. }
19.
207/257
20.
21.
22.
23.
24.
25.
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
Question : Construire le formulaire [index.xhtml] et son modle [Form.java] ncessaires pour obtenir la page suivante :
4
2
id
type JSF
modle
comboEmployes <h:selectOneMenu>
String comboEmployesValue
heuresTravaillees <h:inputText>
List<Employe> getEmployes()
String heuresTravailles
joursTravailles
String joursTravaills
<h:inputText>
rle
contient la liste des employs sous la forme
"prnom nom".
nombre d'heures travailles - nombre rel
nombre de jours travaills - nombre entier
208/257
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.
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. Avec
Glassfish 3.1.2, a plante l'application.
9.4.2
tape 2
Question : complter le formulaire [index.xhtml] et son modle [Form.java] 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;
209/257
[form].getFeuilleSalaire().getEmploye().getNom() o [form] reprsente une instance de la classe [Form.java]. Le lecteur pourra vrifier que
les mthodes get utilises ici existent bien respectivement dans les classes [Form], [FeuilleSalaire] et [Employe]. Si ce n'tait pas le cas,
une exception serait lance lors de l'valuation de l'expression.
Testez cette nouvelle version.
9.4.3
tape 3
Question : complter le formulaire [index.xhtml] et son modle [Form.java] pour obtenir les informations supplmentaires
suivantes :
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.
<h:outputFormat value="{0,number,currency}">
2.
<f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
3. </h:outputFormat>
On aurait pu crire :
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.entretienJour} ">
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
210/257
Le formulaire rendu par le bouton [Raz] n'est pas tout le formulaire mais seulement la partie saisie de celui-ci :
Ce rsultat peut tre obtenu avec une balise <f:subview> utilise de la faon suivante :
1.
<f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">
2. ... la partie du formulaire qu'on veut pouvoir ne pas afficher
3. </f:subview>
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.
211/257
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
162 :
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 :
212/257
Le nouveau projet porte le mme nom que l'ancien. Nous changeons cela :
213/257
Nous avons peu de modifications faire pour adapter cette couche web son nouvel environnement : la couche [metier] simule
doit tre remplace par la couche [metier, DAO, JPA] du serveur construit au paragraphe 6.1, page 162. 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 162.
1
3
Nous pouvons maintenant supprimer les paquetages de la couche [mtier] qui ne sont plus ncessaires :
214/257
La ligne 7 instanciait la couche [mtier] simule. Dsormais elle doit rfrencer la couche [mtier] relle. Le code prcdent devient
le suivant :
1. public class Form {
2.
3.
public Form() {
4.
}
5.
6.
// couche mtier
7.
@EJB
8.
private IMetierLocal metier;
9.
10. // champs du formulaire
Ligne 7, 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
Navigateur
4b
JSF1
JSF2
JSFn
2b
2a
Faces Servlet
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 :
215/257
4
3
1
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-alone]. 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 [6], nous crons un projet d'entreprise sans modules. Nous allons lui ajouter ses modules web et ejb ultrieurement.
en [7], 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.
1
2
216/257
Avant le dploiement de l'application d'entreprise [mv-pam-webapp-ear], on s'assurera que la base MySQL [dbpam_eclipselink]
existe et est remplie. Ceci fait, nous pouvons dployer l'application d'entreprise [mv-pam-webapp-ear] :
217/257
Le lecteur est invit refaire les tests de la version web n 1. Voici un exemple d'excution :
218/257
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.
Dans ce contexte, l'architecture de l'application est la suivante :
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
JSF2
JSFn
2b
[MC]
Form.java
Modle M
Gestionnaires
d'vts
couche
[metier]
simule
219/257
11.1
- la vue [VueSimulations] qui donne la liste des simulations faites par le client
220/257
- la vue [VueSimulationsVides] qui indique que le client n'a pas ou plus de simulations :
11.2
221/257
3
7
4
2
11.2.1
Les fichiers [web.xml] et [faces-config.xml] sont identiques ceux du projet prcdent l'exception d'un dtail dans [web.xml] :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
3.
...
4.
<welcome-file-list>
5.
<welcome-file>faces/saisie.xhtml</welcome-file>
6.
</welcome-file-list>
7.
...
8. </web-app>
11.2.2
La feuille de style
222/257
7.
8. .simuNum {
9.
height: 25px;
10.
text-align: center;
11.
background: MediumTurquoise;
12. }
13. .simuNom {
14.
text-align: left;
15.
background: PowderBlue;
16. }
17. .simuPrenom {
18.
width: 6em;
19.
text-align: left;
20.
color: Black;
21.
background: MediumTurquoise;
22. }
23. .simuHT {
24.
width: 3em;
25.
text-align: center;
26.
color: Black;
27.
background: PowderBlue;
28. }
29. .simuJT {
30.
width: 3em;
31.
text-align: center;
32.
color: Black;
33.
background: MediumTurquoise;
34. }
35. .simuSalaireBase {
36.
width: 9em;
37.
text-align: center;
38.
color: Black;
39.
background: PowderBlue;
40. }
41. .simuIndemnites {
42.
width: 3em;
43.
text-align: center;
44.
color: Black;
45.
background: MediumTurquoise;
46. }
47. .simuCotisationsSociales {
48.
width: 6em;
49.
text-align: center;
50.
background: PowderBlue;
51. }
52.
53. .simuSalaireNet {
54.
width: 10em;
55.
text-align: center;
56.
background: MediumTurquoise;
57. }
58.
59. .erreursHeaders {
60.
background: Teal;
61.
background-color: #ff6633;
62.
color: Snow;
63.
font-style: italic;
64.
text-align: center
65.
66. }
67.
68. .erreurClasse {
69.
background: MediumTurquoise;
223/257
70.
background-color: #ffcc66;
71.
height: 25px;
72.
text-align: center
73. }
74.
75. .erreurMessage {
76.
background: PowderBlue;
77.
background-color: #ffcc99;
78.
text-align: left
79. }
Vue Erreur
<h:dataTable value="#{form.erreurs}" var="erreur"
headerClass="erreursHeaders"
columnClasses="erreurClasse,erreurMessage">
11.2.3
224/257
11.2.4
La couche [mtier]
225/257
13.
14.
15.
16.
17.
18.
19.
20.
// on rcupre l'employ
Employe e=hashEmployes.get(SS);
// on rend une exception si l'employ n'existe pas
if(e==null){
throw new PamException(String.format("L'employ de n SS [%s] n'existe pas",SS),1);
}
// on rend une feuille de salaire fictive
return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new
ElementsSalaire(100,100,100,100,100));
21. }
22.
23. // liste des employs
24. public List<Employe> findAllEmployes() {
25.
if(listEmployes==null){
26.
// on cre une liste de trois employs
27.
listEmployes=new ArrayList<Employe>();
28.
listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des
oiseaux","St Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
29.
listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brlerie","St
Marcel","49014",new Indemnite(1,1.93,2,3,12)));
30.
// dictionnaire des employes
31.
for(Employe e:listEmployes){
32.
hashEmployes.put(e.getSS(),e);
33.
}
34.
// on ajoute un employ qui n'existera pas dans le dictionnaire
35.
listEmployes.add(new Employe("X","Y","Z","La brlerie","St Marcel","49014",new
Indemnite(1,1.93,2,3,12)));
36.
}
37.
// on rend la liste des employs
38.
return listEmployes;
39. }
40. }
11.3
226/257
11.3.1
Le bean ApplicationData
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 [javax.enterprise.context.ApplicationScoped] (ligne 6) et non [javax.faces.bean.ApplicationScoped] comme
dans les beans du projet prcdent.
11.3.2
Le bean SessionData
package web.beans.session;
import
import
import
import
java.io.Serializable;
java.util.ArrayList;
java.util.List;
javax.annotation.PostConstruct;
227/257
7. import javax.enterprise.context.SessionScoped;
8. import javax.inject.Inject;
9. import javax.inject.Named;
10. import web.beans.application.ApplicationData;
11. import web.entities.Simulation;
12.
13. @Named
14. @SessionScoped
15. public class SessionData implements Serializable {
16.
17. // application
18. @Inject
19. private ApplicationData applicationData;
20. // simulations
21. private List<Simulation> simulations = new ArrayList<Simulation>();
22. private int numDerniereSimulation = 0;
23. private Simulation simulation;
24. // menus
25. private boolean menuFaireSimulationIsRendered = true;
26. private boolean menuEffacerSimulationIsRendered = true;
27. private boolean menuEnregistrerSimulationIsRendered;
28. private boolean menuVoirSimulationsIsRendered;
29. private boolean menuRetourSimulateurIsRendered;
30. private boolean menuTerminerSessionIsRendered = true;
31. // locale
32. private String locale="fr_FR";
33.
34. // constructeur
35. public SessionData() {
36. }
37.
38. @PostConstruct
39. public void init() {
40.
// log
41.
if (applicationData.isLogsEnabled()) {
42.
applicationData.getLogger().info("SessionData");
43.
}
44. }
45.
46. // gestion des menus
47. public void setMenu(boolean menuFaireSimulationIsRendered, boolean
menuEnregistrerSimulationIsRendered, boolean menuEffacerSimulationIsRendered, boolean
menuVoirSimulationsIsRendered, boolean menuRetourSimulateurIsRendered, boolean
menuTerminerSessionIsRendered) {
48.
this.setMenuFaireSimulationIsRendered(menuFaireSimulationIsRendered);
49.
this.setMenuEnregistrerSimulationIsRendered(menuEnregistrerSimulationIsRendered);
50.
this.setMenuVoirSimulationsIsRendered(menuVoirSimulationsIsRendered);
51.
this.setMenuEffacerSimulationIsRendered(menuEffacerSimulationIsRendered);
52.
this.setMenuRetourSimulateurIsRendered(menuRetourSimulateurIsRendered);
53.
this.setMenuTerminerSessionIsRendered(menuTerminerSessionIsRendered);
54. }
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,
228/257
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
229/257
11.4
11.4.1
[layout.xhtml]
230/257
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
xmlns:h="http://java.sun.com/JSF/html"
xmlns:f="http://java.sun.com/JSF/core"
xmlns:ui="http://java.sun.com/JSF/facelets">
<f:view locale="#{sessionData.locale}">
<h:head>
<title><h:outputText value="#{msg['form.titre']}"/></title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<script type="text/javascript">
function raz(){
// on change les valeurs postes
document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
document.forms['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
document.forms['formulaire'].elements['formulaire:joursTravailles'].value="0";
}
</script>
<h:body style="background-image: url('$
{request.contextPath}/resources/images/standard.jpg');">
22.
<h:form id="formulaire">
23.
<!-- entete -->
24.
<ui:include src="entete.xhtml" />
25.
<!-- contenu -->
26.
<ui:insert name="part1" >
27.
Gestion des assistantes maternelles
28.
</ui:insert>
29.
<ui:insert name="part2"/>
30.
</h:form>
31.
</h:body>
32. </f:view>
33. </html>
231/257
11.4.2
L'entte [entete.xhtml]
11.5
11.5.1
232/257
233/257
Question : 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.
Question : 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]
234/257
235/257
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.
<h:outputText value="#{form.feuilleSalaire.employe.ville}"/>
<h:outputText value="#{form.feuilleSalaire.employe.codePostal}"/>
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.indice}"/>
</h:panelGrid>
<br/>
<h:outputText value="#{msg['form.infos.cotisations']}" styleClass="titreInfos"/>
<br/><br/>
<h:panelGrid columns="4" rowClasses="libelle,info">
<h:outputText value="#{msg['form.cotisations.csgrds']}"/>
<h:outputText value="#{msg['form.cotisations.csgd']}"/>
<h:outputText value="#{msg['form.cotisations.retraite']}"/>
<h:outputText value="#{msg['form.cotisations.secu']}"/>
<h:outputText value="#{form.feuilleSalaire.cotisation.csgrds} %"/>
<h:outputText value="#{form.feuilleSalaire.cotisation.csgd} %"/>
<h:outputText value="#{form.feuilleSalaire.cotisation.retraite} %"/>
<h:outputText value="#{form.feuilleSalaire.cotisation.secu} %"/>
</h:panelGrid>
<br/>
<h:outputText value="#{msg['form.infos.indemnites']}" styleClass="titreInfos"/>
<br/><br/>
<h:panelGrid columns="4" rowClasses="libelle,info">
<h:outputText value="#{msg['form.indemnites.salaireHoraire']}"/>
<h:outputText value="#{msg['form.indemnites.entretienJour']}"/>
<h:outputText value="#{msg['form.indemnites.repasJour']}"/>
<h:outputText value="#{msg['form.indemnites.congsPays']}"/>
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.baseHeure}"/>
</h:outputFormat>
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
</h:outputFormat>
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.employe.indemnite.repasJour}"/>
</h:outputFormat>
<h:outputText value="#{form.feuilleSalaire.employe.indemnite.indemnitesCP} %"/>
</h:panelGrid>
<br/>
<h:outputText value="#{msg['form.infos.salaire']}" styleClass="titreInfos"/>
<br/><br/>
<h:panelGrid columns="4" rowClasses="libelle,info">
<h:outputText value="#{msg['form.salaire.base']}"/>
<h:outputText value="#{msg['form.salaire.cotisationsSociales']}"/>
<h:outputText value="#{msg['form.salaire.entretien']}"/>
<h:outputText value="#{msg['form.salaire.repas']}"/>
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.elementsSalaire.salaireBase}"/>
</h:outputFormat>
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
</h:outputFormat>
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesEntretien}"/>
</h:outputFormat>
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesRepas}"/>
</h:outputFormat>
</h:panelGrid>
<br/>
<h:panelGrid columns="3" columnClasses="libelle,col2,info">
<h:outputText value="#{msg['form.salaire.net']}"/>
<h:panelGroup></h:panelGroup>
<h:outputFormat value="{0,number,currency}">
<f:param value="#{form.feuilleSalaire.elementsSalaire.salaireNet}"/>
236/257
90.
</h:outputFormat>
91.
</h:panelGrid>
92.
</ui:define>
93. </ui:composition>
94. </html>
Question : 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 affiche la simulation
7.
...
8.
// on met jour le menu
9.
...
10.
// on rend la vue simulation
11.
return "simulation";
12.
}catch(Throwable th){
13.
// on vide la liste des erreurs prcdentes
14.
...
15.
// on cre la nouvelle liste des erreurs
16.
...
17.
// on affiche la vue vueErreur
18.
...
19.
// on met jour le menu
20.
...
21.
// on affiche la vue erreur
22.
return "erreurs";
23.
}
24.}
237/257
11.
12. // constructeur
13. public Erreur(String classe, String message){
14.
this.setClasse(classe);
15.
this.message=message;
16. }
17.
18. // getters et setters
19. ...
20. }
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=hashEmployes.get(SS);
4.
// on rend une exception si l'employ n'existe pas
5.
if(e==null){
6.
throw new PamException(String.format("L'employ de n SS [%s] n'existe pas",SS),1);
7.
}
8....
9.}
238/257
Question : complter la mthode [faireSimulation] afin que lors d'une exception, elle fasse afficher la vue [vueErreur].
11.5.4
L'action [effacerSimulation]
Un clic sur le lien [EffacerSimulation] provoque d'abord l'appel de la fonction Javascript raz(). Cette mthode est dfinie dans la
page [layout.xhtml] :
239/257
1.
<script type="text/javascript">
2.
function raz(){
3.
// on change les valeurs postes
4.
document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
5.
document.forms['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
6.
document.forms['formulaire'].elements['formulaire:joursTravailles'].value="0";
7.
}
8. </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.
Question : 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>();
240/257
15.
16.
17.
// constructeur
public Simulation(Integer num,String heuresTravailles, String joursTravaills,
FeuilleSalaire feuilleSalaire){
18.
this.setNum(num);
19.
this.setFeuilleSalaire(feuilleSalaire);
20.
this.setHeuresTravailles(heuresTravailles);
21.
this.setJoursTravaills(joursTravaills);
22. }
23.
24. public double getIndemnites(){
25.
return feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+
feuilleSalaire.getElementsSalaire().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.
// simulations
2.
private List<Simulation> simulations = new ArrayList<Simulation>();
3.
private int numDerniereSimulation = 0;
4. private Simulation simulation;
241/257
242/257
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
<f:facet name="header">
<h:outputText value="#{msg['simulations.headers.joursTravailles']}"/>
</f:facet>
<h:outputText value="#{simulation.joursTravaills}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['simulations.headers.salaireBase']}"/>
</f:facet>
<h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireBase}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['simulations.headers.indemnites']}"/>
</f:facet>
<h:outputText value="#{simulation.indemnites}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['simulations.headers.cotisationsSociales']}"/>
</f:facet>
<h:outputText
value="#{simulation.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
60.
</h:column>
61.
<h:column>
62.
<f:facet name="header">
63.
<h:outputText value="#{msg['simulations.headers.salaireNet']}"/>
64.
</f:facet>
65.
<h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireNet}"/>
66.
</h:column>
67.
<h:column>
68.
<h:commandLink value="Retirer" action="#{form.retirerSimulation}">
69.
<f:setPropertyActionListener target="#{form.numSimulationToDelete}"
value="#{simulation.num}"/>
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 :
243/257
simulation dsigne la simulation courante de la liste des simulations : d'abord la 1re, puis la 2me, ...
simulation.feuilleSalaire fait rfrence au champ feuilleSalaire de la simulation courante
simulation.feuilleSalaire.employe fait rfrence au champ employe du champ feuilleSalaire
simulation.feuilleSalaire.employe.nom 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.
<h:column>
2.
<f:facet name="header">
3.
<h:outputText value="#{msg['simulations.headers.indemnites']}"/>
4.
</f:facet>
5.
<h:outputText value="#{simulation.indemnites}"/>
6. </h:column>
Ligne 5, on affiche la valeur de simulation.indemnites. 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 simulation.getIndemnites(). 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 240.
Question : 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] :
244/257
Le rsultat obtenu :
Question : 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 :
245/257
Le rsultat obtenu :
11.5.8
L'action [retirerSimulation]
246/257
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 #{simulation.num} 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.
Question : crire la mthode [retirerSimulation] de la classe [Form].
11.5.9
L'action [terminerSession]
247/257
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.
Question : crire la mthode [terminerSession] de la classe [Form].
11.6
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
JSF2
JSFn
2b
[MC]
Form.java
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
162 :
248/257
Application web
couche [web]
1
Faces Servlet
2a
3
JSF1
JSF2
JSFn
2b
[MC]
Form.java
Modle M
Gestionnaires
d'vts
couche
[metier, DAO, JPA]
Travail pratique : raliser l'intgration des couches JSF et EJB en suivant la mthodologie du paragraphe 10, page 212.
249/257
250/257
Une simulation :
251/257
Les mthodes AJAX mettront jour la zone d'id='formulaire' qui inclut la totalit de la page (cf ligne 11 ci-dessous) :
1. <?xml version='1.0' encoding='UTF-8' ?>
2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3. <html xmlns="http://www.w3.org/1999/xhtml"
4.
xmlns:h="http://java.sun.com/JSF/html"
5.
xmlns:f="http://java.sun.com/JSF/core"
6.
xmlns:ui="http://java.sun.com/JSF/facelets">
7.
8.
<f:view locale="#{sessionData.locale}">
9.
...
10.
<h:body style="background-image: url('$
{request.contextPath}/resources/images/standard.jpg');">
11.
<h:form id="formulaire">
12.
<!-- entete -->
13.
<ui:include src="entete.xhtml" />
252/257
14.
<!-- contenu -->
15.
<ui:insert name="part1" >
16.
Gestion des assistantes maternelles
17.
</ui:insert>
18.
<ui:insert name="part2"/>
19.
</h:form>
20.
</h:body>
21. </f:view>
22. </html>
Une fois que votre application fonctionnera, remplacez la page [entete.xhtml] par la page suivante :
1. <?xml version='1.0' encoding='UTF-8' ?>
2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3. <html xmlns="http://www.w3.org/1999/xhtml"
4.
xmlns:h="http://java.sun.com/JSF/html"
5.
xmlns:p="http://primefaces.org/ui"
6.
xmlns:f="http://java.sun.com/JSF/core"
7.
xmlns:ui="http://java.sun.com/JSF/facelets">
8.
9.
10. <!-- entete -->
11. <h:panelGroup>
12.
<h2><h:outputText value="#{msg['form.titre']}"/></h2>
13. </h:panelGroup>
14.
15. <p:menubar id="menu" header="Menu" style="width: 500px">
16.
<p:submenu label="#{msg['form.menu.simulation']}">
17.
<p:menuitem id="cmdFaireSimulation" style="width: 200px"
value="#{msg['form.menu.faireSimulation']}" actionListener="#{form.faireSimulation}"
rendered="#{sessionData.menu.faireSimulation}" update=":formulaire:contenu"/>
18.
<p:menuitem id="cmdEffacerSimulation" style="width: 200px" onclick="raz();"
immediate="true" value="#{msg['form.menu.effacerSimulation']}"
actionListener="#{form.effacerSimulation}" rendered="#{sessionData.menu.effacerSimulation}"
update=":formulaire:contenu"/>
19.
<p:menuitem id="cmdEnregistrerSimulation" style="width: 200px" immediate="true"
value="#{msg['form.menu.enregistrerSimulation']}"
actionListener="#{form.enregistrerSimulation}"
rendered="#{sessionData.menu.enregistrerSimulation}" update=":formulaire:contenu"/>
20.
</p:submenu>
21.
<p:submenu label="Navigation">
22.
<p:menuitem id="cmdRetourSimulateur" style="width: 200px" immediate="true"
value="#{msg['form.menu.retourSimulateur']}" actionListener="#{form.retourSimulateur}"
rendered="#{sessionData.menu.retourSimulateur}" update=":formulaire:contenu"/>
23.
<p:menuitem id="cmdVoirSimulations" style="width: 200px" immediate="true"
value="#{msg['form.menu.voirSimulations']}" actionListener="#{form.voirSimulations}"
rendered="#{sessionData.menu.voirSimulations}" update=":formulaire:contenu"/>
24.
</p:submenu>
25.
<p:menuitem id="cmdTerminerSession" immediate="true"
value="#{msg['form.menu.terminerSession']}" actionListener="#{form.terminerSession}"
rendered="#{sessionData.menu.terminerSession}" update=":formulaire:contenu"/>
26. </p:menubar>
27.
28. <p:spacer height="50px"/>
29.
30. </html>
Le modle n'est pas affect par ce changement. La nouvelle vue est la suivante :
253/257
254/257
255/257
4.9.2 CONFIGURATION.....................................................................................................................................................................97
4.9.3 TESTS....................................................................................................................................................................................98
4.9.4 INITDB..................................................................................................................................................................................98
4.9.5 MISE EN OEUVRE DES TESTS...................................................................................................................................................99
4.9.6 JUNITDAO...........................................................................................................................................................................101
4.10 LA COUCHE [METIER] DE L'APPLICATION [PAM]...............................................................................................................105
4.10.1 L'INTERFACE JAVA [IMETIER]..............................................................................................................................................105
4.10.2 LA CLASSE [FEUILLESALAIRE]............................................................................................................................................106
4.10.3 LA CLASSE D'IMPLMENTATION [METIER] DE LA COUCHE [METIER].......................................................................................108
4.10.4 TESTS DE LA COUCHE [METIER]...........................................................................................................................................108
4.11 LA COUCHE [UI] DE L'APPLICATION [PAM] VERSION CONSOLE........................................................................................111
4.11.1 LA CLASSE [UI.CONSOLE.MAIN]..........................................................................................................................................111
4.11.2 EXCUTION........................................................................................................................................................................113
4.12 LA COUCHE [UI] DE L'APPLICATION [PAM] VERSION GRAPHIQUE....................................................................................115
4.12.1 UN RAPIDE TUTORIEL..........................................................................................................................................................116
4.12.2 L'INTERFACE GRAPHIQUE [PAMJFRAME]..............................................................................................................................118
4.12.3 LES VNEMENTS DE L'INTERFACE GRAPHIQUE.....................................................................................................................119
4.12.4 INITIALISATION DE L'INTERFACE GRAPHIQUE.........................................................................................................................121
4.12.5 GESTIONNAIRES D'VNEMENTS..........................................................................................................................................123
4.12.6 EXCUTION DE L'INTERFACE GRAPHIQUE..............................................................................................................................123
4.13 IMPLMENTATION DE LA COUCHE JPA AVEC ECLIPSELINK.................................................................................................123
4.13.1 LE PROJET NETBEANS........................................................................................................................................................123
4.13.2 MISE EN OEUVRE DES TESTS...............................................................................................................................................128
4.13.3 INITDB..............................................................................................................................................................................131
4.13.4 JUNITDAO.........................................................................................................................................................................131
4.13.5 LES AUTRES TESTS..............................................................................................................................................................134
4.13.6 TRAVAIL FAIRE.................................................................................................................................................................134
5 VERSION 2 : ARCHITECTURE OPENEJB / JPA...........................................................................................................135
5.1 INTRODUCTION AUX PRINCIPES DU PORTAGE.........................................................................................................................135
5.1.1 LES NOUVELLES ARCHITECTURES..........................................................................................................................................135
5.1.2 LES BIBLIOTHQUES DES PROJETS..........................................................................................................................................135
5.1.3 CONFIGURATION DE LA COUCHE JPA / ECLIPSELINK / OPENEJB............................................................................................136
5.1.4 IMPLMENTATION DE LA COUCHE [DAO] PAR DES EJB.........................................................................................................136
5.1.5 IMPLMENTATION DE LA COUCHE [METIER] PAR UN EJB........................................................................................................138
5.1.6 LES CLIENTS DES EJB..........................................................................................................................................................139
5.2 TRAVAIL PRATIQUE.................................................................................................................................................................140
5.2.1 MISE EN PLACE DE LA BASE DE DONNES [DBPAM_ECLIPSELINK]............................................................................................141
5.2.2 CONFIGURATION INITIALE DU PROJET NETBEANS....................................................................................................................141
5.2.3 PORTAGE DE LA COUCHE [DAO]..........................................................................................................................................144
5.2.3.1 L'EJB [CotisationDao]....................................................................................................................................................144
5.2.3.2 Les EJB [EmployeDao] et [IndemniteDao]....................................................................................................................145
5.2.3.3 La classe [PamException]...............................................................................................................................................145
5.2.3.4 Entits srialisables.........................................................................................................................................................146
5.2.3.5 Test de la couche [DAO].................................................................................................................................................146
5.2.4 PORTAGE DE LA COUCHE [METIER]........................................................................................................................................150
5.2.4.1 L'EJB [Metier]................................................................................................................................................................150
5.2.4.2 Test de la couche [metier]...............................................................................................................................................151
5.2.5 PORTAGE DE LA COUCHE [CONSOLE].....................................................................................................................................154
5.3 CONCLUSION..........................................................................................................................................................................157
6 VERSION 3 : PORTAGE DE L'APPLICATION PAM SUR UN SERVEUR D'APPLICATIONS GLASSFISH.......159
6.1 LA PARTIE SERVEUR DE L'APPLICATION CLIENT / SERVEUR PAM.........................................................................................159
6.1.1 L'ARCHITECTURE DE L'APPLICATION.......................................................................................................................................159
6.1.1.1 Le projet Netbeans..........................................................................................................................................................160
6.1.1.2 Configuration de la couche de persistance......................................................................................................................161
6.1.1.3 Insertion des couches [JPA, DAO, metier].....................................................................................................................164
6.1.1.4 Configuration du serveur Glassfish.................................................................................................................................164
6.1.1.5 Dploiement du module EJB..........................................................................................................................................165
6.2 CLIENT CONSOLE - VERSION 1...............................................................................................................................................166
6.3 CLIENT CONSOLE - VERSION 2...............................................................................................................................................170
6.4 LE CLIENT SWING..................................................................................................................................................................172
7 VERSION 4 CLIENT / SERVEUR DANS UNE ARCHITECTURE DE SERVICE WEB......................................... 174
7.1 SERVICE WEB IMPLMENT PAR UN EJB...............................................................................................................................175
7.1.1 LA PARTIE SERVEUR..............................................................................................................................................................175
256/257
257/257