Vous êtes sur la page 1sur 257

Introduction à Java EE

avec Netbeans
et le serveur d'applications Glassfish

serge.tahe at istia.univ-angers.fr
juin 2012

1/257
INTRODUCTION

Ce document reprend un précédent document écrit en 2010 et intitulé "Introduction à Java EE avec Netbeans 6.8 et le serveur
Glassfish v3". Celui-ci amène principalement les changements suivants :

• la partie JSF (Java Server Faces) est traitée 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 caractéristiques
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 précédent. J2EE désigne les
technologies Java utilisées pour créer des applications d'entreprise avec le JDK 1.4 ou antérieur. En même temps que le JDK 1.5
amenait de nombreuses nouveautés dans le langage Java, Sun introduisait de nouvelles technologies s'appuyant sur ce langage
amélioré afin de remédier à des lacunes de ces mêmes technologies dans J2EE. Le terme Java EE 5 a alors été utilisé pour désigner
l'ensemble des technologies qui concourent à créer une application d'entreprise avec la plate-forme Java. Au moment de la mise à
jour de ce document, la dernière version de Java EE est Java EE 6.

Les livres d'Antonio Goncalves :

• Java EE 5 aux éditions Eyrolles


• Beginning Java EE 6 Platform with Glassfish 3 aux éditions Apress

sont d'excellents livres pour découvrir les technologies de Java EE 5 et Java EE 6. Toutes les technologies importantes de Java EE y
sont passées en revue dans le contexte d'études de cas réalistes. L'auteur a un site [http://www.antoniogoncalves.org] que le lecteur
est invité à visiter.

Le document présent étudie certaines des technologies de Java EE 5. Nous y créons une application basique à trois couches
[présentation, métier, accès aux données] déclinée en plusieurs versions :

Une application web avec les technologies suivantes :


• Java Server Faces : pour la couche web
• EJB3 ou Spring : pour la couche métier
• EJB3 ou Spring, JPA/Hibernate, JPA/EclipseLink : pour créer différentes couches d'accès aux données

Une application client / serveur avec les technologies suivantes :


• Swing : pour la couche graphique cliente avec un support Spring
• EJB3 ou service web : pour la couche serveur

Certaines technologies Java EE ne sont pas présentées telles les MDB (Message Driven Bean) ou les EJB3 stateful. Pour les
découvrir, on lira les livres d'Antonio Goncalves.

Il existe d'autres technologies Open Source disponibles pour créer des applications trois couches. Une tandem très populaire est
Spring (http://www.springframework.org/) / Hibernate (http://www.hibernate.org/). Afin de permettre au lecteur de comparer
les technologies EJB3 et Spring, l'application précédente a des versions où Spring remplace les EJB3.

Ce document est un TD (Travail Dirigé) utilisé en 5ième année de l'école d'ingénieurs ISTIA de l'université d'Angers
[http://www.istia.univ-angers.fr]. Ce TD décrit l'application à construire, les technologies Java à utiliser, les endroits où trouver de
l'information. La solution proposée est souvent très cadrée. Le TD pose des questions dont il ne donne pas les réponses. C'est à
l'étudiant de les trouver.

L'apprentissage Java EE proposé ici nécessite un investissement du lecteur estimé entre 50 et 100 heures. Le document contient
beaucoup de code rendant possible le copier / coller. Par ailleurs, tous les projets Netbeans sont décrits dans le détail. Globalement,
le document donne les squelettes des solutions et il est demandé à l'étudiant d'en donner certains détails. Le document peut être
utile même à quelqu'un ne pouvant ou ne voulant pas s'investir autant. On peut s'intéresser uniquement aux architectures décrites et
délaisser la partie code qui fait l'objet des questions.

Pour développer et exécuter l'application, nous utilisons l'IDE Netbeans. Netbeans est un produit assez lourd : prévoir 1 Go de
Ram pour travailler confortablement. On peut le télécharger à l'url [http://www.netbeans.org/].

Le document fait référence aux cours suivants :

2/257
1. Persistance Java 5 par la pratique : [http://tahe.developpez.com/java/JPA] - donne les outils pour construire la couche
d'accès aux données avec JPA (Java Persistence API)
2. Introduction au langage Java [http://tahe.developpez.com/java/cours] - pour les débutants
3. 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 référencés [ref1] [ref2] et [ref3].

Ce document est accompagné d'un support disponible à l'adresse [http://tahe.ftp-developpez.com/fichiers-archive/javaee2-


support.zip] contenant des scripts SQL permettant de construire les bases de données utilisées dans le document, des fichiers
[pom.xml] parfois difficiles à construire, les trois références 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 différentes tâches proposées.

Serge Tahé, juin 2012.

3/257
1 Architecture d'une application Java en couches
Une application java est souvent découpée en couches chacune ayant un rôle bien défini. Considérons une architecture courante,
celle à trois couches :

Couche interface Couche métier Couche d'accès aux Données


utilisateur
utilisateur [ui] [metier] données [DAO]
1 2 3

• la couche [1], appelée 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 rôle de fournir des données provenant de l'utilisateur à la
couche [2] ou bien de présenter à l'utilisateur des données fournies par la couche [2].
• la couche [2], appelée ici [metier] est la couche qui applique les règles dites métier, c.a.d. la logique spécifique de
l'application, sans se préoccuper de savoir d'où viennent les données qu'on lui donne, ni où vont les résultats qu'elle
produit.
• la couche [3], appelée ici [DAO] (Data Access Object) est la couche qui fournit à la couche [2] des données pré-
enregistrées (fichiers, bases de données, ...) et qui enregistre certains des résultats fournis par la couche [2].

Il existe différentes possibilités pour implémenter la couche [DAO]. Examinons-en quelques-unes :

Couche ui Couche métier Couche d'accès Couche Base de


utilisateur [JDBC]
[ui] [metier] aux données Données
1 2 [DAO]
3

La couche [JDBC] ci-dessus est la couche standard utilisée en Java pour accéder à des bases de données. Elle isole la couche [DAO]
du SGBD qui gère la base de données. On peut théoriquement changer de SGBD sans changer le code de la couche [DAO]. Malgré
cet avantage, l'API JDBC présente certains inconvénients :
• toutes les opérations sur le SGBD sont susceptibles de lancer l'exception contrôlée (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 complètement insensible au SGBD. Ceux-ci ont par exemple des méthodes propriétaires quant
à la génération automatique de valeurs de clés 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 insérer
celui-ci.
• avec SQL Server, la couche [DAO] insère l'enregistrement qui se voit donner automatiquement une valeur de clé
primaire par le SGBD, valeur rendue à la couche [DAO].

Ces différences peuvent être gommées via l'utilisation de procédures stockées. Dans l'exemple précédent, la couche [DAO]
appellera une procédure stockée dans Oracle ou SQL Server qui prendra en compte les particularités du SGBD. Celles-ci
seront cachées à la couche [DAO]. Néanmoins, si changer de SGBD n'impliquera pas de réécrire la couche [DAO], cela
implique quand même de réécrire les procédures stockées. Cela peut ne pas être considéré comme rédhibitoire.

De multiples efforts ont été faits pour isoler la couche [DAO] des aspects propriétaires des SGBD. Une solution qui a eu un vrai
succès dans ce domaine ces dernières années, est celle d'Hibernate :

4 Objets image Couche


Couche d'accès aux Couche Base de
données [DAO] de la BD [Hibernate] [JDBC] Données
3 5 6 7

4/257
La couche [Hibernate] vient se placer entre la couche [DAO] écrite par le développeur et la couche [JDBC]. Hibernate est un ORM
(Object Relational Mapper), un outil qui fait le pont entre le monde relationnel des bases de données et celui des objets manipulés
par Java. Le développeur de la couche [DAO] ne voit plus la couche [JDBC] ni les tables de la base de données dont il veut exploiter
le contenu. Il ne voit que l'image objet de la base de données, image objet fournie par la couche [Hibernate]. Le pont entre les tables
de la base de données et les objets manipulés par la couche [DAO] est fait principalement de deux façons :
• par des fichiers de configuration de type XML
• 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'idéal visé est que le développeur de
la couche [DAO] puisse ignorer totalement qu'il travaille avec une base de données. 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 délicate et
nécessite une certaine habitude.

La couche [4] des objets, image de la BD est appelée "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 exécutés par la couche JDBC. Pour les actions d'interrogation de la base (le SQL Select), Hibernate
fournit au développeur, un langage HQL (Hibernate Query Language) pour interroger le contexte de persistance [4] et non la BD
elle-même.

Hibernate est populaire mais complexe à maîtriser. La courbe d'apprentissage souvent présentée comme facile est en fait assez raide.
Dès qu'on a une base de données avec des tables ayant des relations un-à-plusieurs ou plusieurs-à-plusieurs, la configuration du
pont relationnel / objets n'est pas à la portée du premier débutant venu. Des erreurs de configuration peuvent conduire à des
applications peu performantes.

Devant le succès des produits ORM, Sun le créateur de Java, a décidé de standardiser une couche ORM via une spécification
appelée JPA (Java Persistence API) apparue en même temps que Java 5. La spécification JPA a été implémentée par divers produits :
Hibernate, Toplink, EclipseLink, OpenJpa, .... Avec JPA, l'architecture précédente devient la suivante :

4
Couche d'accès aux Objets image Interface Implémentation JPA Couche Base de
données [DAO] de la BD [JPA] [Hibernate / ...] [JDBC] Données
3 5 6 7

La couche [DAO] dialogue maintenant avec la spécification JPA, un ensemble d'interfaces. Le développeur 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 spécifique. Maintenant, il va écrire une couche [DAO] qui va dialoguer avec une couche JPA. Quelque soit
le produit qui implémente celle-ci, l'interface de la couche JPA présentée à la couche [DAO] reste la même.

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.

4
Couche Couche Objets image Interface Implémentation JPA Couche
[metier] [DAO] de la BD [JPA] [EclipseLink [JDBC]
2 / Hibernate]
3 6
5
7 Spring

Le grand intérêt de Spring est qu'il permet de lier les couches par configuration et non dans le code. Ainsi si l'implémentation JPA /
Hibernate doit être remplacée par une implémentation Hibernate sans JPA, parce que par exemple l'application s'exécute dans un
environnement JDK 1.4 qui ne supporte pas JPA, ce changement d'implémentation de la couche [DAO] n'a pas d'impact sur le
code de la couche [métier]. Seul le fichier de configuration Spring qui lie les couches entre elles doit être modifié.

Avec Java EE 5, une autre solution existe : implémenter les couches [metier] et [DAO] avec des EJB3 (Enterprise Java Bean version
3) :

5/257
4
Couche Couche Objets image Interface Implémentation JPA Couche
[metier] [DAO] de la BD [JPA] [EclipseLink [JDBC]
2 / Hibernate]
3 6
5
7 conteneur Ejb3

Nous verrons que cette solution n'est pas très différente 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 utilisés en-dehors d'un serveur d'applications. C'est le cas de JBoss EJB3 ou OpenEJB.

Dans un environnement EE5, les couches sont implémentées par des objets appelés EJB (Enterprise Java Bean). Dans les
précédentes versions d'EE, les EJB (EJB 2.x) étaient réputés 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
données et EJB2.x "session" un objet utilisé pour implémenter les couches [metier], [DAO] d'une architecture multicouche. L'un des
principaux reproches faits aux couches implémentées avec des EJB est qu'elles ne sont utilisables qu'au sein de conteneurs EJB, un
service délivré par l'environnement EE. Cet environnement, plus complexe à mettre en oeuvre qu'un environnement SE (Standard
Edition), peut décourager le développeur à faire fréquemment des tests. Néanmoins, il existe des environnements de
développement Java qui facilitent l'utilisation d'un serveur d'application en automatisant le déploiement 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 réaction à 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 données", 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'implémentation des couches d'une application par des objets Java classiques (POJO, Plain Old/Ordinary Java Object), permettant
la réutilisation de ceux-ci dans un autre contexte. Enfin, il intègre de nombreux outils tiers de façon assez transparente, notamment
des outils de persistance tels que Hibernate, EclipseLink, Ibatis, ...

Java EE5 a été conçu pour corriger les lacunes de la spécification EJB2. Les EJB 2.x sont devenus les EJB3. Ceux-ci sont des
POJOs tagués 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 bénéficier 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 ignorées.

Ci-dessus, nous avons représenté Spring et un conteneur EJB3 comme infrastructure (framework) possible de notre architecture
multicouche. C'est cette infrastructure qui délivrera les services dont nous avons besoin : un pool de connexions et un gestionnaire
de transactions.
• avec Spring, les couches seront implémentées avec des POJOs. Ceux-ci auront accès aux services de Spring (pool
de connexions, gestionnaire de transaction) par injection de dépendances dans ces POJOs : lors de la
construction de ceux-ci, Spring leur injecte des références sur les services dont il vont avoir besoin.
• avec le conteneur EJB3, les couches seront implémentées avec des EJB. Une architecture en couches
implémentées avec des EJB3 est peu différente de celles implémentées avec des POJO instanciés par Spring.
Nous trouverons beaucoup de ressemblances.

✗ pour terminer, nous présenterons un exemple d'application web multicouche :

4
Couche Couche Couche Objets image Interface Implémentation JPA Couche
[web] [metier] [DAO] de la BD [JPA] [EclipseLink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring ou Ejb3

6/257
2 Les outils du document
Les exemples du document ont été testés avec les outils suivants :

• Netbeans de la version 6.8 à la version 7.1.2. L'installation de Netbeans est décrite dans [ref3] au paragraphe 1.3.1.
• Wampserver version 2.2. L'installation de WampServer est est décrite dans [ref3] au paragraphe 1.3.3 ;
• Maven est intégré à Netbeans. Nous décrivons maintenant cet outil.

2.1 Maven

2.1.1 Introduction

Maven est disponible à l'URL [http://maven.apache.org/index.html ]. Selon ses créateurs :

Maven's primary goal is to allow a developer to comprehend the complete state of a development effort in the shortest period of time. In order to attain this
goal there are several areas of concern that Maven attempts to deal with:
• Making the build process easy
• Providing a uniform build system
• Providing quality project information
• Providing guidelines for best practices development
• Allowing transparent migration to new features
Maven est intégré dans Netbeans et nous allons l'utiliser pour une seule de ses caractéristiques : la gestion des bibliothèques d'un
projet. Celles-ci sont formées de l'ensemble des archives jars qui doivent être dans le Classpath du projet. Elles peuvent être très
nombreuses. Par exemple, nos projets futurs vont utiliser l'ORM (Object Relational Mapper) Hibernate. Cet ORM est composé de
dizaines d'archives jar. L'intérêt de Maven est qu'il nous affranchit de les connaître 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
télécharge alors également toutes les bibliothèques nécessaires à Hibernate. On appelle cela les dépendances d'Hibernate. Une
bibliothèque nécessaire à Hibernate peut elle même dépendre d'autres archives. Celles-ci seront également téléchargées. Toutes ces
bibliothèques sont placées dans un dossier appelé le dépôt local de Maven.

Un projet Maven est facilement partageable. Si on le transfère d'un poste à un autre et que les dépendances du projet ne sont pas
présentes dans le dépôt local du nouveau poste, elles seront téléchargées.

Maven peut être utilisé seul ou intégré à un EDI (Environnement de Développement Intégré) tel Netbeans ou Eclipse.

Créons un projet Maven dans Netbeans :

1 2

• en [1], créer un nouveau projet,


• en [2], choisir la catégorie [Maven] et le type de projet [Java Application],

7/257
4

3 5

• en [3], désigner le dossier parent du dossier du nouveau projet,


• en [4], donner un nom au projet,
• en [5], le projet généré.

Examinons les éléments du projet et explicitons le rôle de chacun.

• en [1] : les différentes branches du projet :


• [Source packages] : les classes Java du projet ;
• [Test packages] : les classes de test du projet ;
• [Dependencies] : les archives .jar nécessaires au projet et gérées par Maven ;
• [Test Dependencies] : les archives .jar nécessaires aux tests du projet et gérées par Maven ;
• [Java Dependencies] : les archives .jar nécessaires au projet et non gérées par Maven ;
• [Project Files] : fichiers de configuration de Maven et Netbeans,

• en [3], la branche [Source Packages],

Cette branche contient les codes source des classes Java du projet. Netbeans a généré une classe par défaut :

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 bibliothèque JUnit 3.8 nécessaire à l'exécution des tests,

Netbeans a généré une classe par défaut :

1. package istia.st.mvexemple;
2.
3. import junit.framework.Test;
4. import junit.framework.TestCase;
5. import junit.framework.TestSuite;
6.
7. /**
8. * Unit test for simple App.
9. */
10. public class AppTest extends TestCase {
11. /**
12. * Create the test case
13. *
14. * @param testName
15. * name of the test case
16. */
17. public AppTest(String testName) {
18. super(testName);
19. }
20.
21. /**
22. * @return the suite of tests being tested
23. */
24. public static Test suite() {
25. return new TestSuite(AppTest.class);
26. }
27.
28. /**
29. * Rigourous Test :-)
30. */
31. public void testApp() {
32. assertTrue(true);
33. }
34. }

C'est un test JUnit 3.8. Nous utiliserons par la suite des tests JUnit 4.x.

9/257
6

• en [6], la branche [Dependencies] est ici vide,

Cette branche affiche toute les bibliothèques nécessaires au projet et gérées par Maven. Toutes les bibliothèques listées ici sont
automatiquement téléchargées par Maven. C'est pourquoi un projet Maven a besoin d'un accès Internet. Les bibliothèques
téléchargées vont être stockées en local. Si un autre projet a besoin d'une bibliothèque déjà présente en local, celle-ci ne sera alors
pas téléchargée. Nous verrons que cette liste de bibliothèques ainsi que les dépôts où on peut les trouver sont définis dans le fichier
de configuration du projet Maven.

• en [7], les bibliothèques nécessaires au projet et non gérées par 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.

Le fichier [pom.xml] généré est le suivant :

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.

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 définissent 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 création du projet :

Un objet Maven est défini par quatre propriétés :

• [groupId] : une information qui ressemble à un nom de package. Ainsi les bibliothèques 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 : spring-
context, 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 générer un [jar] (ligne 8) dans le groupe [istia.st] (ligne 5), nommé [mv-exemple] (ligne 6) et de version [1.0-
SNAPSHOT] (ligne 7). Ces quatre informations doivent définir de façon unique un artifact Maven.

Les lignes 17-24 listent les dépendances du projet Maven, c'est à dire la liste des bibliothèques nécessaires au projet. Chaque
bibliothèque est définie 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 bibliothèque. La valeur par défaut est compile qui indique que la bibliothèque est nécessaire à la compilation et à
l'exécution. La valeur test signifie que la bibliothèque est nécessaire lors des test du projet. C'est le cas ici avec la bibliothèque JUnit
3.8.1. Si cette bibliothèque n'est pas présente dans le dépôt local du poste, elle est téléchargée.

2.1.2 Exécution du projet

Nous exécutons le projet :

11/257
1

En [1], le projet Maven est construit puis exécuté [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\mv-
exemple\src\main\resources
12.
13. [compiler:compile]
14. Compiling 1 source file to D:\data\istia-1112\netbeans\glassfish\mv-pam\00\mv-
exemple\target\classes
15.
16. [exec:exec]
17. Downloading: http://repo.maven.apache.org/maven2/org/apache/commons/commons-
exec/1.0.1/commons-exec-1.0.1.pom
18.
19. Downloaded: http://repo.maven.apache.org/maven2/org/apache/commons/commons-
exec/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/commons-
exec/1.0.1/commons-exec-1.0.1.jar
21.
22. Downloaded: http://repo.maven.apache.org/maven2/org/apache/commons/commons-
exec/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 résultat est ligne 23. On voit que même pour ce cas simple, Maven a téléchargé des éléments (lignes 17 et 20).

2.1.3 Le système de fichiers d'un projet Maven

12/257
2
4

3
5

• [1] : le système de fichiers du projet est dans l'onglet [Files],


• [2] : les sources Java sont dans le dossier [src / main / java],
• [3] : les sources Java des tests sont dans le dossier [src / test / java],
• [4] : le dossier [target] est créé par la construction (build) du projet,
• [5] : ici, la construction du projet a créé une archive [mv-exemple-1.0-SNAPSHOT.jar].

2.1.4 Le dépôt Maven local

Nous avons dit que Maven téléchargeait les dépendances nécessaires au projet et les stockait localement. On peut explorer ce dépôt
local :

• en [1], on choisit l'option [Window / Other / Maven Repository Browser],


• en [2], un onglet [Maven Repositories] s'ouvre,
• en [3], il contient deux branches, une pour le dépôt local, l'autre pour le dépôt central. Ce dernier est gigantesque. Pour
visualiser son contenu, il faut mettre à jour son index [4]. Cette mise à jour dure plusieurs dizaines de minutes.

13/257
6

• en [5], les bibliothèques du dépôt local,


• en [6], on y trouve une branche [istia.st] qui correspond au [groupId] de notre projet,
• en [7], on accède aux propriétés du dépôt local,
• en [8], on a le chemin du dépôt local. Il est utile de le connaître car parfois (rarement) Maven n'utilise plus la dernière
version du projet. On fait des modifications et on constate qu'elles ne sont pas prises en compte. On peut alors supprimer
manuellement la branche du dépôt local correspondant à notre [groupId]. Cela force Maven à recréer la branche à partir
de la dernière version du projet.

2.1.5 Chercher un artifact avec Maven

Apprenons maintenant à chercher un artifact avec Maven. Partons de la liste des dépendances 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/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>3.8.1</version>
22. <scope>test</scope>
23. </dependency>
24. </dependencies>
25. </project>

Les lignes 17-23 définissent des dépendances qu'on va modifier pour utiliser les bibliothèques dans leur version la plus récente.

14/257
1

Tout d'abord, nous supprimons les dépendances 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/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></dependencies>
18. </project>

Ligne 17, la dépendance supprimée n'apparaît plus dans [pom.xml]. Maintenant, recherchons-la dans les dépôts Maven.

1 3

5
6
2
4

• en [1], on ajoute une dépendance au projet,


• en [2], on doit préciser des informations sur l'artifact cherché (groupId, artifactId, version, packaging (Type) et scope).
Nous commençons par préciser le [groupId] [3],
• en [4], nous tapons [espace] pour faire afficher la liste des artifacts possibles. Ici [junit] et [jnit-dep]. Nous choisissons
[junit],
• en [5], en procédant de la même façon, on choisit la version la plus récente. Le type de packaging est jar,
• en [6], nous choisissons la portée test pour indiquer que la dépendance n'est nécessaire qu'aux tests.

15/257
6

En [6], les dépendances ajoutées apparaissent dans le projet. Le fichier [pom.xml] reflète 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/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. </dependencies>
26. </project>

On notera que fichier [pom.xml] ne mentionne pas la dépendance [hamcrest-core-1.1] que nous voyons en [6]. Cela parce que c'est
une dépendance de JUnit 4.10 et non du projet lui-même. Cela est signalé par une icône différente dans la branche [Dependencies].
Elle a été téléchargée automatiquement.

Supposons maintenant qu'on ne connaisse pas le [groupId] de l'artifact que l'on désire. 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 clés. Tapons hibernate et lançons la recherche.

16/257
3

• en [2], choisissons le [groupId] org.hibernate et l'[artifactId] hibernate-core,


• en [3], choisissons la version 4.1.2-Final,
• en [4], nous obtenons le code Maven à coller dans le fichier [pom.xml]. Nous le faisons.

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 téléchargement des nouvelles dépendances. Le projet évolue
comme suit :

• en [5], la dépendance [hibernate-core-4.1.2-Final]. Dans le dépôt où il a été trouvé, cet [artifactId] est lui aussi décrit par un
fichier [pom.xml]. Ce fichier a été lu et Maven a découvert que l'[artifactId] avait des dépendances. Il les télécharge
également. Il fera cela pour chaque [artifactId] téléchargé. Au final, on trouve en [6] des dépendances qu'on n'avait pas
demandées directement. Elles sont signalées par une icône différente de celle de l'[artifactId] principal.

Dans ce document, nous utilisons Maven principalement pour cette caractéristique. Cela nous évite de connaître toutes les
dépendances d'une bibliothèque que l'on veut utiliser. On laisse Maven les gérer. Par ailleurs, en partageant un fichier [pom.xml]
entre développeurs, on est assuré que chaque développeur utilise bien les mêmes bibliothèques.

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 mêmes 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 résumé

Nous nous proposons d'introduire JPA (Java Persistence API) avec quelques exemples. JPA est développé dans le cours :

• Persistance Java 5 par la pratique : [http://tahe.developpez.com/java/JPA] - donne les outils pour construire la couche
d'accès aux données avec JPA

Nous présentons tout d'abord les fondements de JPA. On attendra le paragraphe 3.4, page 31 pour créer une application
exemple.

3.1 La place de JPA dans une architecture en couches

Le lecteur est invité à relire le début de ce document (paragraphe 1, page 4) qui explique le rôle de la couche JPA dans une
architecture en couches. La couche JPA s'insère dans les couches d'accès aux données :

Couche d'accès aux Objets image Interface Implémentation JPA Couche Base de
données [DAO] de la BD [JPA] [Hibernate / ...] [JDBC] Données
5 6

La couche [DAO] dialogue avec la spécification JPA. Quelque soit le produit qui implémente celle-ci, l'interface de la couche JPA
présentée à la couche [DAO] reste la même. Nous présentons dans la suite quelques exemples tirés de [ref1] qui nous permettront
de construire notre propre couche JPA.

3.2 JPA – exemples

3.2.1 Exemple 1 - Représentation objet d'une table unique

3.2.1.1 La table [personne]

Considérons une base de données ayant une unique table [personne] dont le rôle est de mémoriser 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 modifiée, son n° de version est
incrémenté.
NOM nom de la personne

19/257
PRENOM son prénom
DATENAISSANCE sa date de naissance
MARIE entier 0 (non marié) ou 1 (marié)
NBENFANTS nombre d'enfants de la personne

3.2.1.2 L'entité [Personne]

Nous nous plaçons dans l'environnement d'exécution suivant :

4
Programme de test Objets image Interface Implémentation Couche Base de
console [main] de la BD [JPA] 6 [Toplink [JDBC] Données
3 5 / Hibernate]
7

La couche JPA [5] doit faire un pont entre le monde relationnel de la base de données [7] et le monde objet [4] manipulé par les
programmes Java [3]. Ce pont est fait par configuration et il y a deux façons de le faire :
1. avec des fichiers XML. C'était quasiment l'unique façon de faire jusqu'à l'avènement du JDK 1.5
2. avec des annotations Java depuis le JDK 1.5

Dans ce document, nous utiliserons exclusivement la seconde méthode.

L'objet [Personne] image de la table [personne] présentée précédemment 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 exploitées par le compilateur, soit
par des outils spécialisés au moment de l'exécution. En-dehors de l'annotation de la ligne 3 destinée au compilateur, toutes les
annotations sont ici destinées à l'implémentation JPA utilisée, Hibernate ou Toplink. Elles seront donc exploitées à l'exécution. En
l'absence des outils capables de les interpréter, ces annotations sont ignorées. Ainsi la classe [Personne] ci-dessus pourrait être
exploitée dans un contexte hors JPA.

Il faut distinguer deux cas d'utilisation des annotations JPA dans une classe C associée à une table T :
1. la table T existe déjà : les annotations JPA doivent alors reproduire l'existant (nom et définition des colonnes, contraintes
d'intégrité, clés étrangères, clés primaires, ...)
2. la table T n'existe pas et elle va être créée d'après les annotations trouvées dans la classe C.

Le cas 2 est le plus facile à gérer. 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 adaptée au pont relationnel / objet de JPA. Pour simplifier, nous nous plaçons dans le cas 2 où la table T associée à la
classe C va être créée d'après les annotations JPA de la classe C.

Commentons les annotations JPA de la classe [Personne] :

• ligne 4 : l'annotation @Entity est la première annotation indispensable. Elle se place avant la ligne qui déclare la classe et
indique que la classe en question doit être gérée par la couche de persistance JPA. En l'absence de cette annotation, toutes
les autres annotations JPA seraient ignorées.
• ligne 5 : l'annotation @Table désigne la table de la base de données dont la classe est une représentation. Son principal
argument est name qui désigne 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 à désigner 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 même
nom que le champ. Dans notre exemple, l'argument name n'était donc pas obligatoire. L'argument nullable=false indique
que la colonne associée au champ ne peut avoir la valeur NULL et que donc le champ doit avoir nécessairement une
valeur.
• ligne 10 : l'annotation @GeneratedValue indique comment est générée la clé primaire lorsqu'elle est générée
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 généré par le SGBD mais fixé par
l'application. Dans ce cas, l'annotation @GeneratedValue serait absente. L'argument strategy indique comment est
générée la clé primaire lorsqu'elle est générée par le SGBD. Les SGBD n'ont pas tous la même technique de génération des
valeurs de clé primaire. Par exemple :

21/257
Firebird utilise un générateur de valeurs appelée avant chaque insertion
SQL server le champ clé primaire est défini comme ayant le type Identity. On a un résultat similaire au
générateur de valeurs de Firebird, si ce n'est que la valeur de la clé n'est connue qu'après
l'insertion de la ligne.
Oracle utilise un objet appelé SEQUENCE qui là encore jouele rôle d'un générateur de valeurs

La couche JPA doit générer des ordres SQL différents selon les SGBD pour créer le générateur de valeurs. On lui indique
par configuration le type de SGBD qu'elle a à gérer. Du coup, elle peut savoir quelle est la stratégie habituelle de
génération de valeurs de clé primaire de ce SGBD. L'argument strategy = GenerationType.AUTO indique à la couche
JPA qu'elle doit utiliser cette stratégie habituelle. Cette technique a fonctionné dans tous les exemples de ce document
pour les sept SGBD utilisés.
• ligne 14 : l'annotation @Version désigne le champ qui sert à gérer les accès concurrents à une même ligne de la table.

Pour comprendre ce problème d'accès concurrents à une même 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 d’une personne P. A ce moment, le nombre d’enfants est 0. Il
passe ce nombre à 1 mais avant qu’il ne valide sa modification, un utilisateur U2 entre en modification de la même
personne P. Puisque U1 n’a pas encore validé sa modification, U2 voit sur son écran le nombre d’enfants à 0. U2
passe le nom de la personne P en majuscules. Puis U1 et U2 valident leurs modifications dans cet ordre. C’est la
modification de U2 qui va gagner : dans la base, le nom va passer en majuscules et le nombre d’enfants va rester à
zéro alors même que U1 croit l’avoir changé en 1.

La notion de version de personne nous aide à résoudre ce problème. On reprend le même cas d’usage :

Au temps T1, un utilisateur U1 entre en modification d’une personne P. A ce moment, le nombre d’enfants est 0 et
la version V1. Il passe le nombre d’enfants à 1 mais avant qu’il ne valide sa modification, un utilisateur U2 entre en
modification de la même personne P. Puisque U1 n’a pas encore validé sa modification, U2 voit le nombre d’enfants
à 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 vérifie que celui qui modifie une personne P détient la même
version que la personne P actuellement enregistrée. Ce sera le cas de l’utilisateur U1. Sa modification est donc
acceptée et on change alors la version de la personne modifiée 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 s’apercevoir que U2 détient une version V1 de
la personne P, alors qu’actuellement la version de celle-ci est V2. On va alors pouvoir dire à l’utilisateur U2 que
quelqu’un est passé avant lui et qu’il doit repartir de la nouvelle version de la personne P. Il le fera, récupèrera une
personne P de version V2 qui a maintenant un enfant, passera le nom en majuscules, validera. Sa modification sera
acceptée si la personne P enregistrée a toujours la version V2. Au final, les modifications faites par U1 et U2 seront
prises en compte alors que dans le cas d’usage sans version, l’une des modifications était perdue.

La couche [DAO] de l'application cliente peut gérer elle-même 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 incrémentée de 1 dans la table. L'annotation
@Version permet de transférer 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 présents à cause de la persistance.
On n'en aurait pas besoin si la classe [Personne] n'avait pas besoin d'être persistée. On voit donc qu'un objet n'a pas
la même représentation 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] associée
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 données
par l'ajout d'une contrainte d'unicité sur la colonne NOM de la table [personne].
• length=30 fixe à 30 le nombre de caractères 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 désigne une date seule sans heure associée. Les autres types possibles sont TemporalType.TIME
pour coder une heure et TemporalType.TIMESTAMP pour coder une date avec heure.

Commentons maintenant le reste du code de la classe [Personne] :

• ligne 6 : la classe implémente l'interface Serializable. La sérialisation d'un objet consiste à le transformer en une suite de bits.
La désérialisation est l'opération inverse. La sérialisation / désérialisation est notamment utilisée dans les applications client /

22/257
serveur où des objets sont échangés via le réseau. Les applications clientes ou serveur sont ignorantes de cette opération
qui est faite de façon transparente par les JVM. Pour qu'elle soit possible, il faut cependant que les classes des objets
échangés soit " taguées " 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 paramètres. En effet,
ces deux champs sont gérés par la couche JPA et non par l'application.
• lignes 51 et au-delà : les méthodes get et set de chacun des champs de la classe. Il est à noter que les annotations JPA
peuvent être placées sur les méthodes get des champs au lieu d'être placées sur les champs eux-mêmes. La place des
annotations indique le mode que doit utiliser JPA pour accéder aux champs :
• si les annotations sont mises au niveau champ, JPA accèdera directement aux champs pour les lire ou les écrire
• si les annotations sont mises au niveau get, JPA accèdera aux champs via les méthodes 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. Placée au niveau champ, elle
indique un accès direct aux champs et placée au niveau get, un accès aux champs via les get et set. Les autres annotations
doivent alors être placées de la même façon que l'annotation @Id.

3.2.2 Configuration de la couche JPA

Les tests de la couche JPA peuvent être faits avec l'architecture suivante :

Objets image Couche


Programme de test Interface Implémentation Base de
de la BD [JDBC]
console [main] [JPA] 6 [Hibernate] Données
3 5
4 7

• en [7] : la base de données qui sera générée à partir des annotations de l'entité [Personne] ainsi que de configurations
complémentaires faites dans un fichier appelé [persistence.xml]
• en [5, 6] : une couche JPA implémentée par Hibernate
• en [4] : l'entité [Personne]
• en [3] : un programme de test de type console

La configuration de la couche JPA est assurée par le fichier [META-INF/persistence.xml] :

1 2

A l'exécution, le fichier [META-INF/persistence.xml] est cherché dans le Classpath de l'application.

Examinons la configuration de la couche JPA faite dans le fichier [persistence.xml] de notre projet :

1. <?xml version="1.0" encoding="UTF-8"?>


2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
3. <persistence-unit name="JPA" transaction-type="RESOURCE_LOCAL">
4. <!-- provider -->
5. <provider>org.hibernate.ejb.HibernatePersistence</provider>
6. <properties>
7. <!-- Classes persistantes -->
8. <property name="hibernate.archive.autodetection" value="class, hbm" />
9. <!-- logs SQL
10. <property name="hibernate.show_sql" value="true"/>
11. <property name="hibernate.format_sql" value="true"/>

23/257
12. <property name="use_sql_comments" value="true"/>
13. -->
14. <!-- connexion JDBC -->
15. <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
16. <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/JPA" />
17. <property name="hibernate.connection.username" value="JPA" />
18. <property name="hibernate.connection.password" value="JPA" />
19. <!-- création automatique du schéma -->
20. <property name="hibernate.hbm2ddl.auto" value="create" />
21. <!-- Dialecte -->
22. <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"
/>
23. <!-- propriétés 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'accès aux données de notre application :

Programme de test Couche Pool de Couche Base de


console [main] [JPA/Hibernate] connexions [JDBC] Données
3 4 [c3p0] 5 6
7

• le fichier [persistence.xml] va configurer les couches [4, 5, 6]


• [4] : implémentation Hibernate de JPA
• [5] : Hibernate accède à la base de données via un pool de connexions. Un pool de connexions est une réserve de
connexions ouvertes avec le SGBD. Un SGBD est accédé par de multiples utilisateurs alors même que pour des raisons de
performances, il ne peut dépasser un nombre limite N de connexions ouvertes simultanément. Un code bien écrit ouvre
une connexion avec le SGBD un minimum de temps : il émet des ordres SQL et ferme la connexion. Il va faire cela de
façon répétée, à chaque fois qu'il a besoin de travailler avec la base. Le coût d'ouverture / fermeture d'une connexion n'est
pas négligeable et c'est là qu'intervient le pool de connexions. Celui-ci va au démarrage de l'application ouvrir N1
connexions avec le SGBD. C'est à lui que l'application demandera une connexion ouverte lorsqu'elle en aura besoin. Celle-
ci sera rendue au pool dès que l'application n'en aura plus besoin, de préférence le plus vite possible. La connexion n'est
pas fermée et reste disponible pour l'utilisateur suivant. Un pool de connexions est donc un système de partage de
connexions ouvertes.
• [6] : le pilote JDBC du SGBD utilisé

Maintenant voyons comment le fichier [persistence.xml] configure les couches [4, 5, 6] ci-dessus :

• ligne 2 : la balise racine du fichier XML est <persistence>.


• ligne 3 : <persistence-unit> sert à définir une unité de persistance. Il peut y avoir plusieurs unités de persistance. Chacune
d'elles a un nom (attribut name) et un type de transactions (attribut transaction-type). L'application aura accès à l'unité
de persistance via le nom de celle-ci, ici JPA. Le type de transaction RESOURCE_LOCAL indique que l'application gère
elle-même les transactions avec le SGBD. Ce sera le cas ici. Lorsque l'application s'exécute dans un conteneur EJB3, elle
peut utiliser le service de transactions de celui-ci. Dans ce cas, on mettra transaction-type=JTA (Java Transaction API).
JTA est la valeur par défaut lorsque l'attribut transaction-type est absent.
• ligne 5 : la balise <provider> sert à définir une classe implémentant l'interface [javax.persistence.spi.PersistenceProvider],
interface qui permet à l'application d'initialiser la couche de persistance. Parce qu'on utilise une implémentation JPA /
Hibernate, la classe utilisée ici est une classe d'Hibernate.
• ligne 6 : la balise <properties> introduit des propriétés propres au provider particulier choisi. Ainsi selon qu'on a choisi
Hibernate, Toplink, Kodo, ... on aura des propriétés différentes. Celles qui suivent sont propres à Hibernate.
• ligne 8 : demande à Hibernate d'explorer le classpath du projet pour y trouver les classes ayant l'annotation @Entity afin de
les gérer. Les classes @Entity peuvent également être déclarées par des balises <class>nom_de_la_classe</class>,
directement sous la balise <persistence-unit>. C'est ce que nous ferons avec le provider JPA / Toplink.

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 très 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 opérations de type [persist, merge, remove]. Il est très intéressant de savoir quels sont les ordres
SQL réellement émis sur ces opérations. En les étudiant, peu à peu on en vient à deviner les ordres SQL
qu'Hibernate va générer lorsqu'on fait telle opération sur les objets persistants et le pont relationnel / objet
commence à prendre consistance dans l'esprit.
• ligne 11 : les ordres SQL affichés sur la console peuvent être formatés joliment pour rendre leur lecture plus aisée
• ligne 12 : les ordres SQL affichés seront de plus commentés
• les lignes 15-19 définissent la couche JDBC (couche [6] dans l'architecture) :
• ligne 15 : la classe du pilote JDBC du SGBD, ici MySQL5
• ligne 16 : l'url de la base de données utilisée
• lignes 17, 18 : l'utilisateur de la connexion et son mot de passe

• ligne 22 : Hibernate a besoin de connaître le SGBD qu'il a en face de lui. En effet, les SGBD ont tous des extensions SQL
propriétaires, une façon propre de gérer la génération automatique des valeurs d'une clé primaire, ... qui font qu'Hibernate
a besoin de connaître le SGBD avec qui il travaille afin de lui envoyer les ordres SQL que celui-ci comprendra.
[MySQL5InnoDBDialect] désigne le SGBD MySQL5 avec des tables de type InnoDB qui supportent les transactions.
• les lignes 24-28 configurent le pool de connexions c3p0 (couche [5] dans l'architecture) :
• lignes 24, 25 : le nombre minimal (défaut 3) et maximal de connexions (défaut 15) dans le pool. Le nombre initial
de connexions par défaut est 3.
• ligne 26 : durée maximale en milli-secondes d'attente d'une demande de connexion de la part du client. Passé ce
délai, c3p0 lui renverra une exception.
• ligne 27 : pour accéder à la BD, Hibernate utilise des ordres SQL préparés (PreparedStatement) que c3p0 peut
mettre en cache. Cela signifie que si l'application demande une seconde fois un ordre SQL préparé déjà en cache,
celui-ci n'aura pas besoin d'être préparé (la préparation d'un ordre SQL a un coût) et celui qui est en cache sera
utilisé. Ici, on indique le nombre maximal d'ordres SQL préparés que le cache peut contenir, toutes connexions
confondues (un ordre SQL préparé appartient à une connexion).
• ligne 28 : fréquence de vérification 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 présente des " bugs ", ...).
• ligne 20 : on demande ici, qu'à l'initialisation de l'unité de persistance, la base de données image des objets @Entity soit
générée. Hibernate a désormais tous les outils pour émettre les ordres SQL de génération des tables de la base de
données :
• la configuration des objets @Entity lui permet de connaître les tables à générer
• 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 générer les tables

Ainsi le fichier [persistence.xml] utilisé ici recrée une base neuve à chaque nouvelle exécution de l'application. Les tables sont
recréées (create table) après avoir été détruites (drop table) si elles existaient. On notera que ce n'est évidemment pas à faire avec une
base en production...

3.2.3 Exemple 2 : relation un-à-plusieurs

3.2.3.1 Le schéma de la base de données

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;
1
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);

• en [1], la base de données et en [2], sa DDL (MySQL5)

Un article A(id, version, nom) appartient exactement à une catégorie C(id, version, nom). Une catégorie 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 matérialisée par la clé étrangère que possède la table [article] sur la table [categorie] (lignes 24-28 de la
DDL).

3.2.3.2 Les objets @Entity représentant la base de données

Un article est représenté par l'@Entity [Article] suivante :

1. package entites;
2.
3. ...
4. @Entity
5. @Table(name="jpa05_hb_article")
6. public class Article implements Serializable {
7.
8. // champs
9. @Id
10. @GeneratedValue(strategy = GenerationType.AUTO)
11. private Long id;
12.
13. @SuppressWarnings("unused")
14. @Version
15. private int version;
16.
17. @Column(length = 30)
18. private String nom;
19.
20. // relation principale Article (many) -> Category (one)

26/257
21. // implémentée par une clé étrangère (categorie_id) dans Article
22. // 1 Article a nécessairement 1 Categorie (nullable=false)
23. @ManyToOne(fetch=FetchType.LAZY)
24. @JoinColumn(name = "categorie_id", nullable = false)
25. private Categorie categorie;
26.
27. // constructeurs
28. public Article() {
29. }
30.
31. // getters et setters
32. ...
33. // toString
34. public String toString() {
35. return String.format("Article[%d,%d,%s,%d]", id, version, nom, categorie.getId());
36. }
37.
38. }

• lignes 9-11 : clé primaire de l'@Entity


• lignes 13-15 : son n° de version
• lignes 17-18 : nom de l'article
• lignes 20-25 : relation plusieurs-à-un qui relie l'@Entity Article à l'@Entity Categorie :
• 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 catégorie (One) peut avoir plusieurs articles (Many).
• ligne 24 : l'annotation ManyToOne définit la colonne clé étrangère 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 catégorie à laquelle appartient l'article. Lorsqu'un article sera mis dans le contexte de persistance, on
demande à ce que sa catégorie n'y soit pas mise immédiatement (fetch=FetchType.LAZY, ligne 23). On ne sait pas
si cette demande a un sens. On verra.

Une catégorie est représentée par l'@Entity [Categorie] suivante :

1. package entites;
2. ...
3. @Entity
4. @Table(name="jpa05_hb_categorie")
5. public class Categorie implements Serializable {
6.
7. // champs
8. @Id
9. @GeneratedValue(strategy = GenerationType.AUTO)
10. private Long id;
11.
12. @SuppressWarnings("unused")
13. @Version
14. private int version;
15.
16. @Column(length = 30)
17. private String nom;
18.
19. // relation inverse Categorie (one) -> Article (many) de la relation Article (many) ->
Categorie (one)
20. // cascade insertion Categorie -> insertion Articles
21. // cascade maj Categorie -> maj Articles
22. // cascade suppression Categorie -> suppression Articles
23. @OneToMany(mappedBy = "categorie", cascade = { CascadeType.ALL })
24. private Set<Article> articles = new HashSet<Article>();
25.
26. // constructeurs
27. public Categorie() {
28. }
29.

27/257
30. // getters et setters
31. ...
32. // toString
33. public String toString() {
34. return String.format("Categorie[%d,%d,%s]", id, version, nom);
35. }
36.
37. // association bidirectionnelle Categorie <--> Article
38. public void addArticle(Article article) {
39. // l'article est ajouté dans la collection des articles de la catégorie
40. articles.add(article);
41. // l'article change de catégorie
42. article.setCategorie(this);
43. }
44. }

• lignes 8-11 : la clé primaire de l'@Entity


• lignes 12-14 : sa version
• lignes 16-17 : le nom de la catégorie
• lignes 19-24 : l'ensemble (set) des articles de la catégorie
• ligne 23 : l'annotation @OneToMany désigne une relation un-à-plusieurs. Le One désigne l'@Entity
[Categorie] dans laquelle on se trouve, le Many le type [Article] de la ligne 24 : une (One) catégorie a plusieurs
(Many) articles.
• ligne 23 : l'annotation est l'inverse (mappedBy) de l'annotation ManyToOne placée sur le champ categorie de
l'@Entity Article : mappedBy=categorie. La relation ManyToOne placée sur le champ categorie de l'@Entity
Article est la relation principale. Elle est indispensable. Elle matérialise la relation de clé étrangère qui lie
l'@Entity Article à l'@Entity Categorie. La relation OneToMany placée 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
catégorie. Sans cette commodité, ces articles seraient obtenus par une requête JPQL.
• ligne 23 : cascadeType.ALL demande à que les opérations (persist, merge, remove) faites sur une @Entity
Categorie soient cascadées sur ses articles.
• ligne 24 : les articles d'une catégorie seront placés dans un objet de type Set<Article>. Le type Set n'accepte pas
les doublons. Ainsi on ne peut mettre deux fois le même article dans l'objet Set<Article>. Que veut dire "le
même article" ? Pour dire que l'article a est le même que l'article b, Java utilise l'expression a.equals(b). Dans la
classe Object, mère de toutes les classes, a.equals(b) est vraie si a==b, c.a.d. si les objets a et b ont le même
emplacement mémoire. On pourrait vouloir dire que les articles a et b sont les mêmes s'ils ont le même nom.
Dans ce csa, le développeur doit redéfinir deux méthodes dans la classe [Article] :
• equals : qui doit rendre vrai si les deux articles ont le même nom
• hashCode : doit rendre une valeur entière identique pour deux objets [Article] que la méthode equals
considère 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 utilisée dans différents 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 méthode [addArticle] nous permet d'ajouter un article à une catégorie. La méthode prend soin de mettre à jour
les deux extrémités de la relation OneToMany qui lie [Categorie] à [Article].

3.3 L'API de la couche JPA

Explicitons l'environnement d'exécution d'un client JPA :

28/257
Client JPA Interface [JPA] =
Base de
EntityManager
2 Données
1 4
Objets image de la BD =
3 Contexte de persistance

Nous savons que le couche JPA [2] crée un pont objet [3] / relationnel [4]. On appelle " contexte de persistance " l'ensemble des
objets gérés par la couche JPA dans le cadre de ce pont objet / relationnel. Pour accéder aux données du contexte de persistance,
un client JPA [1] doit passer par la couche JPA [2] :
1. il peut créer 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 référence 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 présente au client une interface appelée [EntityManager] qui, comme son nom l'indique permet de gérer les objets
@Entity du contexte de persistance. Nous présentons ci-dessous, les principales méthodes de cette interface :

void persist(Object entity) met entity dans le contexte de persistance


void remove(Object entity) enlève entity du contexte de persistance
<T> T merge(T entity) fusionne un objet entity du client non géré par le contexte de persistance avec
l'objet entity du contexte de persistance ayant la même clé primaire. Le résultat
rendu est l'objet entity du contexte de persistance.
<T> T find(Class<T> entityClass, met dans le contexte de persistance, un objet cherché dans la base de données
Object primaryKey) via sa clé primaire. Le type T de l'objet permet à la couche JPA de savoir quelle
table requêter. L'objet persistant ainsi créé est rendu au client.
Query createQuery(String queryText) crée un objet Query à partir d'une requête JPQL (Java Persistence Query
Language). Une requête JPQL est analogue à une requête SQL si ce n'est qu'on
requête des objets plutôt que des tables.
Query createNativeQuery(String méthode analogue à la précédente, si ce n'est que queryText est un ordre SQL et
queryText) non JPQL.
Query createNamedQuery(String name) méthode identique à createQuery, si ce n'est que l'ordre JPQL queryText a été
externalisé dans un fichier de configuration et associé à un nom. C'est ce nom
qui est le paramètre de la méthode.

Un objet EntityManager a un cycle de vie qui n'est pas forcément celui de l'application. Il a un début et une fin. Ainsi un client
JPA peut travailler successivement avec différents objets EntityManager. Le contexte de persistance associé à un EntityManager a le
même 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 nécessaire synchronisé avec la base de données puis il n'existe plus. Il faut créer un nouvel EntityManager pour avoir
de nouveau un contexte de persistance.

Le client JPA peut créer 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 liée à une unité de persistance précise. On se rappelle que le fichier de configuration [META-
INF/persistence.xml] permet de définir des unités de persistance et que celles-ci ont un nom :

<persistence-unit name="elections-dao-jpa-mysql-01PU" transaction-type="RESOURCE_LOCAL">

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-dao-
JPA-mysql-01PU")] crée une fabrique d'objets de type EntityManagerFactory capable de fournir des objets EntityManager
destinés à gérer des contextes de persistance liés à l'unité de persistance nommée 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 façon suivante :

EntityManager em = emf.createEntityManager();

Les méthodes suivantes de l'interface [EntityManager] permettent de gérer le cycle de vie du contexte de persistance :

void close() le contexte de persistance est fermé. Force la synchronisation du contexte de persistance avec
la base de données :
• si un objet du contexte n'est pas présent dans la base, il y est mis par une opération
SQL INSERT)
• si un objet du contexte est présent dans la base et qu'il a été modifié depuis qu'il a été
lu, une opération SQL UPDATE est faite pour persister la modification
• si un objet du contexte a été marqué comme " supprimé " à l'issue d'une opération
remove sur lui, une opération SQL DELETE est faite pour le supprimer de la base.
void clear() le contexte de persistance est vidé de tous ses objets mais pas fermé.
void flush() le contexte de persistance est synchronisé avec la base de données de la façon décrite pour
close()

Le client JPA peut forcer la synchronisation du contexte de persistance avec la base de données avec la méthode
[EntityManager].flush précédente. La synchronisation peut être explicite ou implicite. Dans le premier cas, c'est au client de faire des
opérations flush lorsqu'il veut faire des synchronisations, sinon celles-ci se font à certains moments que nous allons préciser. Le
mode de synchronisation est géré par les méthodes suivantes de l'interface [EntityManager] :

void Il y a deux valeurs possibles pour flushmode :


setFlushMode(FlushModeType FlushModeType.AUTO (défaut): la synchronisation a lieu avant chaque requête SELECT
flushMode) faite sur la base.
FlushModeType.COMMIT : la synchronisation n'a lieu qu'à la fin des transactions sur la
base.
FlushModeType rend le mode actuel de synchronisation
getFlushMode()

Résumons. En mode FlushModeType.AUTO qui est le mode par défaut, le contexte de persistance sera synchronisé avec la base
de données aux moments suivants :
1. avant chaque opération SELECT sur la base
2. à la fin d'une transaction sur la base
3. à la suite d'une opération flush ou close sur le contexte de persistance

En mode FlushModeType.COMMIT, c'est la même chose sauf pour l'opération 1 qui n'a pas lieu. Le mode normal d'interaction
avec la couche JPA est un mode transactionnel. Le client fait diverses opérations sur le contexte de persistance, à l'intérieur d'une
transaction. Dans ce cas, les moments de synchronisation du contexte de persistance avec la base de données sont les cas 1 et 2 ci-
dessus 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 données. L'interface Query est la suivante :

30/257
3

• 1 - la méthode getResultList execute un SELECT qui ramène 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 éléments de la liste L sous la forme
suivante :

1. Iterator iterator = L.iterator();


2. while (iterator.hasNext()) {
3. // exploiter l'objet iterator.next() qui représente l'élément courant de la liste
4. ...
5. }

La liste L peut être également exploitée avec un for :

1. for (Object o : L) {
2. // exploiter objet o
3. }

• 2 - la méthode getSingleResult exécute un ordre JPQL / SQL SELECT qui ramène un unique objet.
• 3 - la méthode executeUpdate exécute un ordre SQL update ou delete et rend le nombre de lignes affectées l'opération.
• 4 - la méthode setParameter(String, Object) permet de donner une valeur à un paramètre nommé d'un ordre JPQL
paramétré
• 5 - la méthode setParameter(int, Object) mais le paramètre n'est pas désigné par son nom mais par sa position dans
l'ordre JPQL.

3.4 Les requêtes JPQL

Note : le projet Netbeans et le script SQL de la base de données de ce paragraphe sont disponibles dans le support du document.

31/257
JPQL (Java Persistence Query Language) est le langage de requêtes de la couche JPA. Le langage JPQL est apparenté au langage
SQL des bases de données. 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 Couche
couche [JPA / [JDBC] SGBD BD
[DAO] Hibernate]

La base de données qu'on appellera [dbrdvmedecins2] est une base de données MySQL5 avec quatre tables :

Elle rassemble des informations permettant de gérer les rendez-vous d'un groupe de médecins.

3.4.1 La table [MEDECINS]

Elle contient des informations sur les médecins.

• ID : n° identifiant le médecin - clé primaire de la table


• VERSION : n° identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une
modification est apportée à la ligne.
• NOM : le nom du médecin
• PRENOM : son prénom
• TITRE : son titre (Melle, Mme, Mr)

3.4.2 La table [CLIENTS]

Les clients des différents médecins sont enregistrés dans la table [CLIENTS] :

• ID : n° identifiant le client - clé primaire de la table


• VERSION : n° identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une
modification est apportée à la ligne.

32/257
• NOM : le nom du client
• PRENOM : son prénom
• TITRE : son titre (Melle, Mme, Mr)

3.4.3 La table [CRENEAUX]

Elle liste les créneaux horaires où les RV sont possibles :

• ID : n° identifiant le créneau horaire - clé primaire de la table (ligne 8)


• VERSION : n° identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une
modification est apportée à la ligne.
• ID_MEDECIN : n° identifiant le médecin auquel appartient ce créneau – clé étrangère sur la colonne MEDECINS(ID).
• HDEBUT : heure début créneau
• MDEBUT : minutes début créneau
• HFIN : heure fin créneau
• MFIN : minutes fin créneau

La seconde ligne de la table [CRENEAUX] (cf [1] ci-dessus) indique, par exemple, que le créneau n° 2 commence à 8 h 20 et se
termine à 8 h 40 et appartient au médecin n° 1 (Mme Marie PELISSIER).

3.4.4 La table [RV]

Elle liste les RV pris pour chaque médecin :

• ID : n° identifiant le RV de façon unique – clé primaire


• JOUR : jour du RV
• ID_CRENEAU : créneau horaire du RV - clé étrangère sur le champ [ID] de la table [CRENEAUX] – fixe à la fois le
créneau horaire et le médecin concerné.
• ID_CLIENT : n° du client pour qui est faite la réservation – clé étrangère sur le champ [ID] de la table [CLIENTS]

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 même moment pour le même médecin. 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 créneau n° 20 et le client n° 4 le 23/08/2006. La table
[CRENEAUX] nous apprend que le créneau n° 20 correspond au créneau horaire 16 h 20 - 16 h 40 et appartient au médecin n° 1
(Mme Marie PELISSIER). La table [CLIENTS] nous apprend que le client n° 4 est Melle Brigitte BISTROU.

3.4.5 Génération de la base

Pour créer les tables et les remplir on pourra utiliser le script [dbrdvmedecins2.sql] (support du cours). Avec [WampServer], on
pourra procéder comme suit :

2
3

• en [1], on clique sur l'icône de [WampServer] et on choisit l'option [PhpMyAdmin] [2],


• en [3], dans la fenêtre qui s'est ouverte, on sélectionne le lien [Bases de données],

4 5 6

• en [2], on crée une base de données dont on a donné le nom [4] et l'encodage [5],
• en [7], la base a été créée. On clique sur son lien,

34/257
9
8

• en [8], on importe un fichier SQL,


• qu'on désigne dans le système de fichiers avec le bouton [9],

35/257
12

13

11

• en [11], on sélectionne le script SQL et en [12] on l'exécute,


• en [13], les quatre tables de la base ont été créées. On suit l'un des liens,

14

• en [14], le contenu de la table.

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]

Revenons à l'architecture de l'exemple :

Couche Couche
couche [JPA / [JDBC] SGBD BD
[DAO] Hibernate]

Nous construisons maintenant le projet Maven de la couche [JPA].

3.4.7 Le projet Netbeans

C'est le suivant :

36/257
3

• en [1], on construit un projet Maven de type [Java Application] [2],


• en [3], on donne un nom au projet,

• en [4], le projet généré.

3.4.8 Génération de la couche [JPA]

Revenons à l'architecture que nous devons construire :

Couche Couche
couche [JPA / [JDBC] SGBD BD
[DAO] Hibernate]

Avec Netbeans, il est possible de générer automatiquement la couche [JPA] . Il est intéressant de connaître ces méthodes de
génération automatique car le code généré donne de précieuses indications sur la façon d'écrire des entités JPA.

3.4.9 Création d'une connexion Netbeans à la base de données

• lancer le SGBD MySQL 5 afin que la BD soit disponible,


• créer une connexion Netbeans sur la base [dbrdvmedecins2],

37/257
5
2 1

3 7

4
8

• dans l'onglet [Services] [1], dans la branche [Databases] [2], sélectionner le pilote JDBC MySQL [3],
• puis sélectionner l'option [4] "Connect Using" permettant de créer une connexion avec une base MySQL,
• en [5], donner les informations qui sont demandées. 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

• en [10], la connexion est créée. On y voit les quatre tables de la base de données connectée.

3.4.10 Création d'une unité de persistance

Revenons à l'architecture en cours de construction :

38/257
Couche Couche
couche [JPA / [JDBC] SGBD BD
[DAO] Hibernate]

Nous sommes en train de construire la couche [JPA]. La configuration de celle-ci est faite dans un fichier [persistence.xml] dans
lequel on définit des unités de persistance. Chacune d'elles a besoin des informations suivantes :

• les caractéristiques JDBC d'accès à la base (URL, utilisateur, mot de passe),


• les classes qui seront les images des tables de la base de données,
• l'implémentation JPA utilisée. En effet, JPA est une spécification implémentée par divers produits. Ici, nous utiliserons
Hibernate.

Netbeans peut générer ce fichier de persistance via l'utilisation d'un assistant.

• cliquer droit sur le projet et choisir la création d'une unité de persistance [1],
• en [2], créer une unité de persistance,

3
6
4

5 7

• en [3], donner un nom à l'unité de persistance que l'on crée,


• en [4], choisir l'implémentation JPA Hibernate (JPA 2.0),
• en [5], indiquer que les tables de la BD sont déjà créées et que donc on ne les crée pas. On valide l'assistant,
• en [6], le nouveau projet,
• en [7], le fichier [persistence.xml] a été généré dans le dossier [META-INF],
• en [8], de nouvelles dépendances ont été ajoutées au projet Maven.

Le fichier [META-INF/persistence.xml] généré ressemble à ceci :

39/257
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" transaction-
type="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=""/>
8. <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
9. <property name="javax.persistence.jdbc.user" value="root"/>
10. <property name="hibernate.cache.provider_class"
value="org.hibernate.cache.NoCacheProvider"/>
11. </properties>
12. </persistence-unit>
13. </persistence>

Il reprend les informations données dans l'assistant :


• ligne 3 : le nom de l'unité de persistance,
• ligne 3 : le type de transactions avec la base de données. Ici, RESOURCE_LOCAL indique que l'application va gérer elle-
même ses transactions,
• lignes 6-9 : les propriétés JDBC de la source de données.

Dans l'onglet [Design], on peut avoir une vue globale du fichier [persistence.xml] :

14

Pour avoir des logs d'Hibernate, nous complétons le fichier [persistence.xml] de la façon 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" transaction-
type="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. <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
9. <property name="javax.persistence.jdbc.user" value="root"/>
10. <property name="hibernate.cache.provider_class"
value="org.hibernate.cache.NoCacheProvider"/>
11. <property name="hibernate.show_sql" value="true"/>
12. <property name="hibernate.format_sql" value="true"/>
13. </properties>
14. </persistence-unit>
15. </persistence>

• ligne 11 : on demande à voir les ordres SQL émis par Hibernate,


• ligne 12 : cette propriété permet d'avoir un affichage formaté de ceux-ci.

Des dépendances ont été ajoutées au projet. Le fichier [pom.xml] est le suivant :

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-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>junit</groupId>
20. <artifactId>junit</artifactId>
21. <version>3.8.1</version>
22. <scope>test</scope>
23. </dependency>
24. <dependency>
25. <groupId>org.hibernate</groupId>
26. <artifactId>hibernate-entitymanager</artifactId>
27. <version>4.1.2</version>
28. </dependency>
29. <dependency>
30. <groupId>org.jboss.logging</groupId>
31. <artifactId>jboss-logging</artifactId>
32. <version>3.1.0.GA</version>
33. </dependency>
34. <dependency>
35. <groupId>org.jboss.spec.javax.transaction</groupId>
36. <artifactId>jboss-transaction-api_1.1_spec</artifactId>
37. <version>1.0.0.Final</version>
38. </dependency>
39. <dependency>
40. <groupId>org.hibernate</groupId>
41. <artifactId>hibernate-core</artifactId>
42. <version>4.1.2</version>
43. </dependency>
44. <dependency>
45. <groupId>antlr</groupId>
46. <artifactId>antlr</artifactId>

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 dépendances ajoutées concernent toutes l'ORM Hibernate. On ajoutera la dépendance 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 utilisée, on peut obtenir des fichiers [pom.xml] différents.

Ce fichier contient beaucoup de redondances. La version minimale suivante peut être utilisée 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/maven-
4.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 Génération des entités JPA

Les entités JPA peuvent être générées par un assistant de Netbeans :

• en [1], on crée des entités JPA à partir d'une base de données,

• en [2], on sélectionne la connexion [dbrdvmedecins2] créée précédemment,


• en [3], on sélectionne toutes les tables de la base de données associée,

• en [4], on donne un nom aux classes Java associées 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
7

• en [7], les classes Java créées par l'assistant.

3.4.12 Les entités JPA générées

L'entité [Medecin] est l'image de la table [medecins]. La classe Java est truffée d'annotations qui rendent le code peu lisible au
premier abord. Si on ne garde que ce qui est essentiel à la compréhension du rôle 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. @Override
40. public boolean equals(Object object) {
41. ...
42. }
43.
44. @Override
45. public String toString() {
46. ...
47. }
48.
49. }

• ligne 4, l'annotation @Entity fait de la classe [Medecin], une entité JPA, c.a.d. une classe liée à une table de BD via l'API
JPA,
• ligne 5, le nom de la table de BD associée à l'entité JPA. Chaque champ de la table fait l'objet d'un champ dans la classe
Java,
• ligne 6, la classe implémente l'interface Serializable. Ceci est nécessaire dans les applications client / serveur, où les entités
sont sérialisées 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 reconnaît pas le fait que la colonne est en fait un colonne de version qui doit être incrémentée à chaque
modification de la ligne à laquelle elle appartient. Pour lui donner ce rôle, 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 précisent 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 générer la clé primaire des lignes qu'elle insèrera dans la table [Medecins]. Il y a plusieurs
stratégies possibles. Ici la stratégie 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é étrangère sur la table [medecins]. Un créneau appartient à un médecin.
Inversement, un médecin a plusieurs créneaux qui lui sont associés. On a donc une relation un (médecin) à plusieurs
(créneaux), une relation qualifiée par l'annotation @OneToMany par JPA (ligne 25). Le champ de la ligne 26 contiendra
tous les créneaux du médecin. Ceci sans programmation. Pour comprendre totalement la ligne 25, il nous faut présenter la
classe [Creneau].

Celle-ci est la suivante :

1. package rdvmedecins.jpa;
2.
3. import java.io.Serializable;
4. import java.util.List;
5. import javax.persistence.*;
6. import javax.validation.constraints.NotNull;
7.
8. @Entity
9. @Table(name = "creneaux")
10. public class Creneau implements Serializable {
11. @Id
12. @GeneratedValue(strategy = GenerationType.IDENTITY)
13. @Column(name = "ID")
14. private Long id;
15.
16. @Column(name = "MDEBUT")
17. private int mdebut;
18.
19. @Column(name = "HFIN")
20. private int hfin;
21.
22. @Column(name = "HDEBUT")
23. private int hdebut;
24.

45/257
25. @Column(name = "MFIN")
26. private int mfin;
27.
28. @Column(name = "VERSION")
29. private int version;
30.
31. @JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
32. @ManyToOne(optional = false)
33. private Medecin idMedecin;
34.
35. @OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
36. private List<Rv> rvList;
37.
38. // constructeurs
39. ...
40. // getters et setters
41. ...

42. @Override
43. public int hashCode() {
44. ...
45. }
46.
47. @Override
48. public boolean equals(Object object) {
49. ...
50. }
51.
52. @Override
53. public String toString() {
54. ...
55. }
56.
57. }

Nous ne commentons que les nouvelles annotations :

• nous avons dit que la table [creneaux] avait une clé étrangère vers la table [medecins] : un créneau est associé à un
médecin. Plusieurs créneaux peuvent être asssociés au même médecin. On a une relation de la table [creneaux] vers la table
[medecins] qui est qualifiée de plusieurs (créneaux) à un (médecin). C'est l'annotation @ManyToOne de la ligne 32 qui
sert à qualifier la clé étrangère,
• la ligne 31 avec l'annotation @JoinColumn précise la relation de clé étrangère : la colonne [ID_MEDECIN] de la table
[creneaux] est clé étrangère sur la colonne [ID] de la table [medecins],
• ligne 33 : une référence sur le médecin propriétaire du créneau. On l'obtient là encore sans programmation.

Le lien de clé étrangère entre l'entité [Creneau] et l'entité [Medecin] est donc matérialisé par deux annotations :

• dans l'entité [Creneau] :

1. @JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")


2. @ManyToOne(optional = false)
3. private Medecin idMedecin;

• dans l'entité [Medecin] :

1. @OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")


2. private List<Creneau> creneauList;

Les deux annotations reflètent la même relation : celle de la clé étrangère 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 ambiguïté la relation de
clé étrangère. La relation @OneToMany est facultative. Si elle est présente, elle se contente de référencer la relation
@ManyToOne à laquelle elle est associée. 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 spécifie la clé étrangère. Toujours dans

46/257
cette même 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 insère une nouvelle entité [Medecin] dans la base, alors les entités [Creneau] du champ de la ligne 2 doivent être
insérées elles-aussi,
• si on modifie une entité [Medecin] dans la base, alors les entités [Creneau] du champ de la ligne 2 doivent être modifiées
elles-aussi,
• si on supprime une entité [Medecin] dans la base, alors les entités [Creneau] du champ de la ligne 2 doivent être
supprimées elles-aussi.

Nous donnons le code des deux autres entités 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 reflètent la relation de clé étrangère 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é étrangère qu'a la table [rv] vers la table [creneaux],
• lignes 20-22 : qualifient la relation de clé étrangère qu'a la table [rv] vers la table [clients].

La génération automatique des entités 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 différents champs version des entités,
• il faut écrire des méthodes toString plus explicites que celles générées,
• les entités [Medecin] et [Client] sont analogues. On va les faire dériver d'une classe [Personne],
• on va supprimer les relations @OneToMany inverses des relations @ManyToOne. Elles ne sont pas indispensables et
elles amènent des complications de programmation. On supprime l'annotation et le champ annoté,

48/257
• on supprime la validation @NotNull sur les clés primaires. Lorsqu'on persiste une entité JPA avec MySQL, l'entité au
départ a une clé primaire null. Ce n'est qu'après persistance dans la base, que la clé primaire de l'élément persisté a une
valeur.

Avec ces spécifications, les différentes classes deviennent les suivantes :

La classe Personne est utilisée pour représenter les médecins et les clients :

1. package rdvmedecins.jpa;
2.
3. import java.io.Serializable;
4. import javax.persistence.*;
5.
6. @MappedSuperclass
7. public class Personne implements Serializable {
8. private static final long serialVersionUID = 1L;
9. @Id
10. @GeneratedValue(strategy = GenerationType.IDENTITY)
11. @Column(name = "ID")
12. private Long id;
13.
14. @Basic(optional = false)
15. @Column(name = "TITRE")
16. private String titre;
17.
18. @Basic(optional = false)
19. @Column(name = "NOM")
20. private String nom;
21.
22. @Basic(optional = false)
23. @Column(name = "VERSION")
24. @Version
25. private int version;
26.
27. @Basic(optional = false)
28. @Column(name = "PRENOM")
29. private String prenom;
30.
31. // constructeurs
32.
33. public Personne() {
34. }
35.
36. public Personne(Long id) {
37. this.id = id;
38. }
39.
40. public Personne(Long id, String titre, String nom, int version, String prenom) {
41. this.id = id;
42. this.titre = titre;
43. this.nom = nom;
44. this.version = version;
45. this.prenom = prenom;
46. }
47.
48. // getters et setters
49. ...
50.
51. @Override
52. public String toString() {
53. return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
54. }
55.

49/257
56. }

• ligne 6 : on notera que la classe [Personne] n'est pas elle-même une entité (@Entity). Elle va être la classe parent d'entités.
L'annotation @MappedSuperClass désigne cette situation.

L'entité [Client] encapsule les lignes de la table [clients]. Elle dérive de la classe [Personne] précédente :

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. }

• ligne 6 : la classe [Client] est une entité Jpa,


• ligne 7 : elle est associée à la table [clients],
• ligne 8 : elle dérive de la classe [Personne].

L'entité [Medecin] qui encapsule les lignes de la table [medecins] suit le même modèle :

1. package rdvmedecins.jpa;
2.
3. import java.io.Serializable;
4. import javax.persistence.*;
5.
6. @Entity
7. @Table(name = "medecins")
8. public class Medecin extends Personne implements Serializable {
9. private static final long serialVersionUID = 1L;

50/257
10.
11. // constructeurs
12. public Medecin() {
13. super();
14. }
15.
16. public Medecin(Long id) {
17. super(id);
18. }
19.
20. public Medecin(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("Médecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(),
getNom());
37. }
38.
39. }

L'entité [Creneau] encapsule les lignes de la table [creneaux] :

1. package rdvmedecins.jpa;
2.
3. import java.io.Serializable;
4. import java.util.List;
5. import javax.persistence.*;
6.
7. @Entity
8. @Table(name = "creneaux")
9. public class Creneau implements Serializable {
10.
11. private static final long serialVersionUID = 1L;
12. @Id
13. @GeneratedValue(strategy = GenerationType.IDENTITY)
14. @Basic(optional = false)
15. @Column(name = "ID")
16. private Long id;
17.
18. @Basic(optional = false)
19. @Column(name = "MDEBUT")
20. private int mdebut;
21.
22. @Basic(optional = false)
23. @Column(name = "HFIN")
24. private int hfin;
25.
26. @Basic(optional = false)
27. @NotNull
28. @Column(name = "HDEBUT")
29. private int hdebut;

51/257
30.
31. @Basic(optional = false)
32. @Column(name = "MFIN")
33. private int mfin;
34.
35. @Basic(optional = false)
36. @Column(name = "VERSION")
37. @Version
38. private int version;
39.
40. @JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
41. @ManyToOne(optional = false)
42. private Medecin medecin;
43.
44. // constructeurs
45. ...
46.
47. // getters et setters
48. ...
49.
50. @Override
51. public int hashCode() {
52. ...
53. }
54.
55. @Override
56. public boolean equals(Object object) {
57. // TODO: Warning - this method won't work in the case the id fields are not set
58. ...
59. }
60.
61. @Override
62. public String toString() {
63. return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut,
hfin, mfin, medecin);
64. }
65. }

• les lignes 40-42 modélisent la relation "plusieurs à un" qui existe entre la table [creneaux] et la table [medecins] de la base
de données : un médecin a plusieurs créneaux, un créneau appartient à un seul médecin.

L'entité [Rv] encapsule les lignes de la table [rv] :

1. package rdvmedecins.jpa;
2.
3. import java.io.Serializable;
4. import java.util.Date;
5. import javax.persistence.*;
6.
7. @Entity
8. @Table(name = "rv")
9. public class Rv implements Serializable {
10.
11. private static final long serialVersionUID = 1L;
12. @Id
13. @GeneratedValue(strategy = GenerationType.IDENTITY)
14. @Basic(optional = false)
15. @Column(name = "ID")
16. private Long id;
17.
18. @Basic(optional = false)
19. @Column(name = "JOUR")
20. @Temporal(TemporalType.DATE)

52/257
21. private Date jour;
22.
23. @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
24. @ManyToOne(optional = false)
25. private Creneau creneau;
26.
27. @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
28. @ManyToOne(optional = false)
29. private Client client;
30.
31. // constructeurs
32. ...
33.
34. // getters et setters
35. ...
36.
37. @Override
38. public int hashCode() {
39. ...
40. }
41.
42. @Override
43. public boolean equals(Object object) {
44. ...
45. }
46.
47. @Override
48. public String toString() {
49. return String.format("Rv[%s, %s, %s]", id, creneau, client);
50. }
51. }

• les lignes 27-29 modélisent la relation "plusieurs à un" qui existe entre la table [rv] et la table [clients] (un client peut
apparaître dans plusieurs Rv) de la base de données et les lignes 23-25 la relation "plusieurs à un" qui existe entre la table
[rv] et la table [creneaux] (un créneau peut apparaître dans plusieurs Rv).

3.4.13 Le code d'accès aux données

Nous allons ajouter maintenant au projet, le code d'accès aux données via la couche JPA :

Couche Couche
couche [JPA / [JDBC] SGBD BD
[console] Hibernate]

53/257
La classe [MainJpql] est la suivante :

1. package rdvmedecins.console;
2.
3. import java.util.Scanner;
4. import javax.persistence.EntityManager;
5. import javax.persistence.EntityManagerFactory;
6. import javax.persistence.Persistence;
7.
8. public class MainJpql {
9.
10. public static void main(String[] args) {
11. // EntityManagerFactory
12. EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-rdvmedecins-jpql-
hibernatePU");
13. // entityManager
14. EntityManager em = emf.createEntityManager();
15. // scanner clavier
16. Scanner clavier = new Scanner(System.in);
17. // boucle de saisie des requêtes JPQL
18. System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
19. String requete = clavier.nextLine();
20. while (!requete.trim().equals("*")) {
21. try {
22. // affichage résultat requête
23. for (Object o : em.createQuery(requete).getResultList()) {
24. System.out.println(o);
25. }
26. } catch (Exception e) {
27. System.out.println("L'exception suivante s'est produite : " + e);
28. }
29. // on vide le contexte de persistance
30. em.clear();
31. // nouvelle requête
32. System.out.println("---------------------------------------------");
33. System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
34. requete = clavier.nextLine();
35. }
36. // fermeture des ressources
37. em.close();
38. emf.close();
39. }
40. }

• ligne 12 : création de l'EntityManagerFactory associé à l'unité de persistance que nous avons créée précédemment. le
paramètre de la méthode createEntityManagerFactory est le nom de cette unité de persistance :

1. <persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-


type="RESOURCE_LOCAL">
2. ...
3. </persistence-unit>

• ligne 14 : création de l'EntityManager qui gère la couche de persistance,


• ligne 19 : saisie d'une requête JPQL select,
• lignes 23-28 : affichage du résultat de la requête,
• ligne 20 : la saisie s'arrête lorsque l'utilisateur tape *.

Question : donner les requêtes JPQL permettant d'obtenir les informations suivantes :

• liste des médecins dans l'orde décroissant de leurs noms


• liste des médecins dont titre='Mr'
• liste des créneaux horaires de Mme Pelissier
• liste des Rv pris dans l'ordre croissant des jours

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 médecins n'ayant pas de Rdv

On s'inspirera de l'exemple du paragraphe 2.7 de [ref1]. Voici un exemple d'exécution :

1. Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :


2. select c from Client c
3. Hibernate:
4. select
5. client0_.ID as ID2_,
6. client0_.NOM as NOM2_,
7. client0_.PRENOM as PRENOM2_,
8. client0_.TITRE as TITRE2_,
9. client0_.version as version2_
10. from
11. clients client0_
12. Client[1,Mr,Jules,MARTIN]
13. Client[2,Mme,Christine,GERMAN]
14. Client[3,Mr,Jules,JACQUARD]
15. Client[4,Melle,Brigitte,BISTROU]

• ligne 2 : la requête JPQL,


• lignes 3-11 : la requête SQL correspondante,
• lignes 12-15 : le résultat de la requête JPQL.

3.5 Liens entre contexte de persistance et SGBD


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. private boolean marie;
30.
31. @Column(name = "NBENFANTS", nullable = false)
32. private int nbenfants;
33.
34. // constructeurs
35.
36. public Personne() {
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. // fermeture EntityManagerFactory
32. emf.close();
33. }
34.
35. // affichage contenu table
36. private static void dump() {
37. // contexte de persistance
38. EntityManager em = emf.createEntityManager();
39. // début transaction
40. EntityTransaction tx = em.getTransaction();
41. tx.begin();
42. // affichage personnes
43. for (Object p : em.createQuery("select p from Personne p order by p.nom
asc").getResultList()) {
44. System.out.println(p);
45. }
46. // fin transaction
47. tx.commit();
48. // fin contexte
49. em.close();
50. }
51.
52. // raz BD
53. private static void clean() {
54. // contexte de persistance
55. EntityManager em = emf.createEntityManager();
56. // début transaction
57. EntityTransaction tx = em.getTransaction();
58. tx.begin();
59. // supprimer les éléments de la table PERSONNES
60. em.createNativeQuery("delete from " + TABLE_NAME).executeUpdate();
61. // fin transaction
62. tx.commit();
63. // fin contexte
64. em.close();
65. }
66.
67. // logs
68. private static void log(String message) {
69. System.out.println("main : ----------- " + message);
70. }
71.
72. // gestion d'objets persistés
73. public static void test1() throws ParseException {
74. // contexte de persistance
75. EntityManager em = emf.createEntityManager();
76. // création personnes
77. p1 = new Personne("Martin", "Paul", new
SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
78. Personne p2 = new Personne("Durant", "Sylvie", new
SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
79. // début transaction
80. EntityTransaction tx = em.getTransaction();
81. System.out.println("début transaction");
82. tx.begin();
83. // persistance des personnes
84. // les logs montrent que l'opération SQL INSERT est immédiatement générée après
l'opération persist
85. // probablement pour avoir la clé primaire
86. System.out.println(String.format("Personne p1 %s non persistée", p1));
87. System.out.println("em.persist(p1)");
88. em.persist(p1);

57/257
89. System.out.println(String.format("Personne p1 %s persistée", p1));
90. // personne p2
91. // INSERT est généré dès l'opération persist
92. System.out.println(String.format("Personne p2 %s non persistée", p2));
93. System.out.println("em.persist(p2)");
94. em.persist(p2);
95. System.out.println(String.format("Personne p2 %s persistée", p2));
96. p2.setMarie(true);
97. System.out.println(String.format("Personne p2 %s modifiée", p2));
98. // l'opération DELETE liée à l'opération remove n'est faite qu'à la fin de la
transaction
99. System.out.println("em.remove(p2)");
100. em.remove(p2);
101. System.out.println(String.format("Personne p2 %s supprimée", p2));
102. // modification p1
103. p1.setNom("P1");
104. // fin transaction
105. System.out.println("fin transaction");
106. tx.commit();
107. // fin contexte
108. em.close();
109. // on affiche la table
110. dump();
111. }
112.
113. // gestion d'objets persistés
114. public static void test2() throws ParseException {
115. // contexte de persistance
116. EntityManager em = emf.createEntityManager();
117. // début transaction
118. EntityTransaction tx = em.getTransaction();
119. System.out.println("début transaction");
120. tx.begin();
121. // on modifie la personne p1 actuellement détachée
122. System.out.println(String.format("Personne p1 %s actuelle non persistée", p1));
123. p1.setMarie(false);
124. System.out.println(String.format("Personne p1 %s nouvelle non persistée", p1));
125. // on réattache la personne P1
126. System.out.println("em.merge(p1)");
127. Personne p1b = em.merge(p1);
128. System.out.println(String.format("Personne p1b %s attachée", p1b));
129. // fin transaction
130. System.out.println("fin transaction");
131. tx.commit();
132. // fin contexte
133. em.close();
134. // on affiche la table
135. dump();
136. }
137.}

3.5.3 La configuration d'Hibernate dans [persistence.xml]

1. <?xml version="1.0" encoding="UTF-8"?>


2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
3. <persistence-unit name="JPA" transaction-type="RESOURCE_LOCAL">
4. <!-- provider -->
5. <provider>org.hibernate.ejb.HibernatePersistence</provider>
6. <properties>
7. <!-- Classes persistantes -->
8. <property name="hibernate.archive.autodetection" value="class, hbm" />
9. <property name="hibernate.show_sql" value="true"/>

58/257
10. ....
11. <!-- création automatique du schéma -->
12. <property name="hibernate.hbm2ddl.auto" value="create" />
13. ....
14. </properties>
15. </persistence-unit>
16. </persistence>

3.5.4 Les résultats

1. init:
2. deps-jar:
3. Compiling 1 source file to C:\data\travail\2008-2009\netbeans\JPA\hibernate-personnes-
entites\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. début transaction
12. Personne p1 [null,0,Martin,Paul,31/01/2000,true,2] non persistée
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] persistée
22. Personne p2 [null,0,Durant,Sylvie,05/07/2001,false,0] non persistée
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] persistée
32. Personne p2 [2,0,Durant,Sylvie,05/07/2001,true,0] modifiée
33. em.remove(p2)
34. Personne p2 [2,0,Durant,Sylvie,05/07/2001,true,0] supprimée
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
47. 17:57:26,359 DEBUG IntegerType:133 - binding '0' to parameter: 2
48. 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
49. 17:57:26,375 DEBUG IntegerType:172 - returning '1' as column: ID0_
50. 17:57:26,390 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_
51. 17:57:26,390 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_
52. 17:57:26,390 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_
53. 17:57:26,390 DEBUG StringType:172 - returning 'P1' as column: NOM0_
54. 17:57:26,390 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_
55. 17:57:26,390 DEBUG IntegerType:172 - returning '1' as column: VERSION0_
56. [1,1,P1,Paul,31/01/2000,true,2]
57. main : ----------- test2
58. début transaction
59. Personne p1 [1,1,P1,Paul,31/01/2000,true,2] actuelle non persistée
60. Personne p1 [1,1,P1,Paul,31/01/2000,false,2] nouvelle non persistée
61. em.merge(p1)
62. Hibernate: select personne0_.ID as ID0_0_, personne0_.DATENAISSANCE as DATENAIS2_0_0_,
personne0_.MARIE as MARIE0_0_, personne0_.NBENFANTS as NBENFANTS0_0_, personne0_.NOM as
NOM0_0_, personne0_.PRENOM as PRENOM0_0_, personne0_.VERSION as VERSION0_0_ from
jpa01_personne personne0_ where personne0_.ID=?
63. 17:57:26,406 DEBUG IntegerType:133 - binding '1' to parameter: 1
64. 17:57:26,406 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_0_
65. 17:57:26,406 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_0_
66. 17:57:26,406 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_0_
67. 17:57:26,406 DEBUG StringType:172 - returning 'P1' as column: NOM0_0_
68. 17:57:26,406 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_0_
69. 17:57:26,406 DEBUG IntegerType:172 - returning '1' as column: VERSION0_0_
70. Personne p1b [1,1,P1,Paul,31/01/2000,false,2] attachée
71. fin transaction
72. Hibernate: update jpa01_personne set DATENAISSANCE=?, MARIE=?, NBENFANTS=?, NOM=?,
PRENOM=?, VERSION=? where ID=? and VERSION=?
73. 17:57:26,406 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
74. 17:57:26,406 DEBUG BooleanType:133 - binding 'false' to parameter: 2
75. 17:57:26,406 DEBUG IntegerType:133 - binding '2' to parameter: 3
76. 17:57:26,421 DEBUG StringType:133 - binding 'P1' to parameter: 4
77. 17:57:26,421 DEBUG StringType:133 - binding 'Paul' to parameter: 5
78. 17:57:26,421 DEBUG IntegerType:133 - binding '2' to parameter: 6
79. 17:57:26,421 DEBUG IntegerType:133 - binding '1' to parameter: 7
80. 17:57:26,421 DEBUG IntegerType:133 - binding '1' to parameter: 8
81. 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
82. 17:57:26,453 DEBUG IntegerType:172 - returning '1' as column: ID0_
83. 17:57:26,453 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_
84. 17:57:26,453 DEBUG BooleanType:172 - returning 'false' as column: MARIE0_
85. 17:57:26,453 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_
86. 17:57:26,453 DEBUG StringType:172 - returning 'P1' as column: NOM0_
87. 17:57:26,453 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_
88. 17:57:26,453 DEBUG IntegerType:172 - returning '2' as column: VERSION0_
89. [1,2,P1,Paul,31/01/2000,false,2]
90. BUILD SUCCESSFUL (total time: 3 seconds)

Question : faites le lien entre le code Java et les résultats affichés.

60/257
4 Version 1 : Architecture Spring / JPA

Note : le script SQL de la base de données de ce paragraphe est disponible dans le support du cours (voir site du document).

On se propose d’écrire une application console ainsi qu'une application graphique permettant d’établir le bulletin de salaire des
assistantes maternelles employées par la "Maison de la petite enfance" d'une commune. Cette application aura l'architecture
suivante :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate / [JDBC] BD
EclipseLink]

7 Spring

Note : lire jusqu'au paragraphe 4.4 inclus, puis passer à la pratique.

4.1 La base de données


Les données statiques utiles pour construire la fiche de paie seront placées dans une base de données que nous désignerons par la
suite dbpam. Cette base de données pourrait avoir les tables suivantes :

Table EMPLOYES : rassemble des informations sur les différentes assistantes maternelles

Structure :

ID clé primaire
VERSION n° de version – augmente à chaque modification de la ligne
SS numéro de sécurité sociale de l'employé - unique
NOM nom de l'employé
PRENOM son prénom
ADRESSE son adresse
VILLE sa ville
CODEPOSTAL son code postal
INDEMNITE_ID clé étrangère sur le champ [ID] de la table [INDEMNITES]

Son contenu pourrait être le suivant :

Table COTISATIONS : rassemble des pourcentages nécessaires au calcul des cotisations sociales

Structure :

ID clé primaire
VERSION n° de version – augmente à chaque modification de la ligne
CSGRDS pourcentage : contribution sociale généralisée + contribution au remboursement de la dette sociale
CSGD pourcentage : contribution sociale généralisée déductible
SECU pourcentage : sécurité sociale, veuvage, vieillesse
RETRAITE pourcentage : retraite complémentaire + assurance chômage

Son contenu pourrait être le suivant :

61/257
Les taux des cotisations sociales sont indépendants du salarié. La table précédente n'a qu'une ligne.

Table INDEMNITES : rassemble les éléments permettant le calcul du salaire à payer.

ID clé primaire
VERSION n° de version – augmente à chaque modification de la ligne
INDICE indice de traitement - unique
BASEHEURE prix net en euro d’une heure de garde
ENTRETIENJOUR indemnité d’entretien en euro par jour de garde
REPASJOUR indemnité de repas en euro par jour de garde
INDEMNITESCP indemnité de congés payés. C'est un pourcentage à appliquer au salaire de
base.

Son contenu pourrait être le suivant :

On notera que les indemnités peuvent varier d'une assistante maternelle à une autre. Elles sont en effet associées à une assistante
maternelle précise 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 Mode de calcul du salaire d'une assistante maternelle


Nous présentons maintenant le mode de calcul du salaire mensuel d'une assistante maternelle. Il ne prétend pas être celui utilisé
dans la réalité. Nous prenons pour exemple, le salaire de Mme Marie Jouveinal qui a travaillé 150 h sur 20 jours pendant le mois à
payer.

Les éléments suivants sont pris en compte [TOTALHEURES]: total des heures [TOTALHEURES]=150
: travaillées dans le mois [TOTALJOURS]= 20

[TOTALJOURS]: total des jours


travaillés dans le mois
Le salaire de base de l'assistante [SALAIREBASE]=([TOTALHEURES]*[B [SALAIREBASE]=(150*[2.1])*(1+0.
maternelle est donné par la formule ASEHEURE])*(1+ 15)= 362,25
suivante : [INDEMNITESCP]/100)
Un certain nombre de cotisations sociales Contribution sociale CSGRDS : 12,64
doivent être prélevées sur ce salaire de généralisée et contribution au
base : remboursement de la dette CSGD : 22,28
sociale :
[SALAIREBASE]*[CSGRDS/100] Sécurité sociale : 34,02

Contribution sociale Retraite : 28,55


généralisée déductible :
[SALAIREBASE]*[CSGD/100]

Sécurité sociale, veuvage,


vieillesse :
[SALAIREBASE]*[SECU/100]

Retraite Complémentaire + AGPF


+ Assurance Chômage :
[SALAIREBASE]*[RETRAITE/100]

62/257
Les éléments suivants sont pris en compte [TOTALHEURES]: total des heures [TOTALHEURES]=150
: travaillées dans le mois [TOTALJOURS]= 20

[TOTALJOURS]: total des jours


travaillés dans le mois

Total des cotisations sociales : [COTISATIONSSOCIALES]=[SALAIREB [COTISATIONSSOCIALES]=97,48


ASE]*(CSGRDS+CSGD+SECU+RETRAITE
)/100
Par ailleurs, l'assistante maternelle a droit, [INDEMNITÉS]=[TOTALJOURS]*(ENTR [INDEMNITES]=104
chaque jour travaillé, à une indemnité ETIENJOUR+REPASJOUR)
d'entretien ainsi qu'à une indemnité de
repas. A ce titre elle reçoit les indemnités
suivantes :
Au final, le salaire net à payer à l'assistante [SALAIREBASE]- [salaire NET]=368,77
maternelle est le suivant : [COTISATIONSSOCIALES]+
[INDEMNITÉS]

4.3 Fonctionnement de l'application console

Voici un exemple d'exécution de l'application console dans une fenêtre Dos :

1. dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20


2.
3. Valeurs saisies :
4. N° de sécurité sociale de l'employé : 254104940426058
5. Nombre d'heures travaillées : 150
6. Nombre de jours travaillés : 20
7.
8. Informations Employé :
9. Nom : Jouveinal
10. Prénom : Marie
11. Adresse : 5 rue des Oiseaux
12. Ville : St Corentin
13. Code Postal : 49203
14. Indice : 2
15.
16. Informations Cotisations :
17. CSGRDS : 3.49 %
18. CSGD : 6.15 %
19. Retraite : 7.88 %
20. Sécurité sociale : 9.39 %
21.
22. Informations Indemnités :
23. Salaire horaire : 2.1 euro
24. Entretien/jour : 2.1 euro
25. Repas/jour : 3.1 euro
26. Congés Payés : 15.0 %
27.
28. Informations Salaire :
29. Salaire de base : 362.25 euro
30. Cotisations sociales : 97.48 euro
31. Indemnités d'entretien : 42.0 euro
32. Indemnités de repas : 62.0 euro
33. Salaire net : 368.77 euro

On écrira un programme qui recevra les informations suivantes :

63/257
1. n° de sécurité sociale de l'assistante maternelle ( 254104940426058 dans l'exemple - ligne 1)
2. nombre total d'heures travaillées (150 dans l'exemple - ligne 1)
3. nombre total de jours travaillés (20 dans l'exemple - ligne 1)

On voit que :

• lignes 9-14 : affichent les informations concernant l'employé dont on a donné le n° de sécurité sociale
• lignes 17-20 : affichent les taux des différentes cotisations
• lignes 23-26 : affichent les indemnités associées à l'indice de traitement de l'employé (ici l'indice 2)
• lignes 29-33 : affichent les éléments constitutifs du salaire à payer

L'application signale les erreurs éventuelles :

Appel sans paramètres :

dos>java -jar pam-spring-ui-metier-dao-JPA-eclipselink.jar


Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés

Appel avec des données erronées :

dos>java -jar pam-spring-ui-metier-dao-JPA-eclipselink.jar 254104940426058 150x 20x


Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné

Appel avec un n° de sécurité sociale erroné :

dos>java -jar pam-spring-ui-metier-dao-JPA-eclipselink.jar xx 150 20


L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable

4.4 Fonctionnement de l'application graphique

L'application graphique permet le calcul des salaires des assistantes maternelles au travers d'un formulaire Swing :

64/257
6

1
2 3
4

• les informations passées en paramètres 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 différents éléments du salaire jusqu'au salaire net à payer [5]

La liste déroulante [1, 6] ne présente pas les n°s SS des employés mais les noms et prénoms de ceux-ci. On fait ici l'hypothèse qu'il
n'y a pas deux employés de mêmes nom et prénom.

4.5 Création de la base de données

Nous lançons WampServer et utilisons l'outil PhpMyAdmin [1] :

• en [2], on prend l'option [Bases de données],

65/257
3 4

• en [3], on crée une base de données [dbpam_hibernate],


• en [4], la base créée. On la sélectionne,

5
6

• en [5], on veut importer un script SQL,


• en [6], on utilise le bouton [Parcourir] pour désigner le fichier,

7
8

• en [7,8], on sélectionne le script SQL,


• en [9], on l'exécute,

10

• en [10], les tables ont été créées. Leur contenu est le suivant :

66/257
table EMPLOYES

table INDEMNITES

table COTISATIONS

4.6 Implémentation JPA

4.6.1 Couche JPA / Hibernate

Nous allons configurer la couche JPA dans l'environnement suivant :

Programme Interface Implémentation Couche Base de


console [JPA] [Hibernate] [JDBC] données

Un programme console travaillera avec la base de données. Pour cela, il faut :


• avoir une base de données,
• avoir le pilote JDBC du SGBD, ici MySQL,
• implémenter la couche JPA avec Hibernate,
• écrire le programme console.

Nous créons le projet Maven [mv-pam-jpa-hibernate] [1] :

Dans l'architecture de notre application il nous faut les éléments suivants :

• la base de données,
• le pilote JDBC du SGBD MySQL,

67/257
• la couche JPA / Hibernate (entités et configuration),
• le programme console de test.

4.6.1.1 La base de données

Créons tout d'abord la base de données vide. Nous lançons WampServer et utilisons l'outil PhpMyAdmin [1] :

• en [2], on prend l'option [Bases de données],

3 4

• en [3], on crée une base de données [dbpam_hibernate],


• en [4], la base créée.

4.6.1.2 Configuration de la couche JPA

La liaison entre la couche JDBC et la base de données se fait dans le fichier [persistence.xml] qui configure la couche JPA. Ce
fichier peut être construit avec Netbeans :

68/257
1

5
2

4
7

• dans l'onglet [services] [1], on se connecte à la base de données avec le pilote JDBC de MySQL [2],
• en [3], le nom de la base de données à 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 réussi.

10

• la connexion apparaît en [8] et en [9],


• en [10], on ajoute un nouvel élément au projet,

13

14
15

16
11 12

69/257
• en [11] on choisit la catégorie [Persistence] et en [12] l'élément [Persistence Unit],
• en [13], on donne un nom à cette unité de persistance,
• en [14], on choisit une implémentation Hibernate,
• en [15], on désigne la connexion que nous venons de créer vers la base MySQL,
• en [16], on indique qu'à l'instanciation de la couche JPA, celle-ci doit construire (create) les tables correspondant aux
entités JPA du projet.

La fin de l'assistant génère le fichier [persistence.xml] :

1
3

• le fichier apparaît dans une nouvelle branche du projet, dans un dossier [META-INF] [1],
• qui correspond au dossier [src/main/resources] du projet [2,3] .

Son contenu est 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. <properties>
6. <property name="javax.persistence.jdbc.url"
value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
7. <property name="javax.persistence.jdbc.password" value=""/>
8. <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
9. <property name="javax.persistence.jdbc.user" value="root"/>
10. <property name="hibernate.cache.provider_class"
value="org.hibernate.cache.NoCacheProvider"/>
11. <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
12. </properties>
13. </persistence-unit>
14. </persistence>

• ligne 3 : le nom de l'unité de persistance et le type de transactions. RESOURCE_LOCAL indique que le projet gère lui-
même les transactions. C'est ici le programme console qui devra le faire,
• ligne 4 : l'implémentation JPA utilisée est Hibernate,
• lignes 6-9 : les caractéristiques JDBC de la connexion à la base de données,
• ligne 11 : demande la création des tables correspondant aux entités JPA. En fait, Netbeans génère ici une configuration
erronée. 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 crée les tables correspondant aux entités JPA.
L'option create-drop fait la même 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 crée les tables si elles n'existent pas mais elle ne les détruit pas si elles existent déjà.

Nous ajouterons trois autres propriétés à 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 données. 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 dépendances

Revenons à l'architecture du projet :

Programme Interface Implémentation Couche Base de


console [JPA] [Hibernate] [JDBC] données

Nous avons configuré la couche JPA via le fichier [persistence.xml]. L'implémentation choisie a été Hibernate. Cela a amené des
dépendances dans le projet :

71/257
Ces dépendances sont dues à l'inclusion d'Hibernate dans le projet. Il nous faut ajouter une autre dépendance, celle du pilote JDBC
de MySQL qui implémente la couche JDBC de l'architecture. Par ailleurs, certaines des dépendances sont inutiles. Nous faisons
évoluer le fichier [pom.xml] de la façon 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>

Les lignes 24-28 ajoutent la dépendance du pilote JDBC de MySQL.

4.6.1.4 Les entités JPA

72/257
1

Question : Ecrire les entités [Cotisation, Indemnite, Employe].

Notes :
• les entités feront partie d'un paquetage nommé [jpa],
• chaque entité aura un n° de version,
• si deux entités sont liées par une relation, seule la relation principale @ ManyToOne sera construite. La relation inverse
@OneToMany ne le sera pas.

Note : Les squelettes des entités 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. public Employe() {
16. }
17.
18. 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 Le code de la classe principale

Nous incluons dans le projet les entités JPA développées précédemment [1] :

puis nous rajoutons [2], la classe [main.Main] suivante :

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. // créer l'Entity Manager suffit à construire la couche JPA
11. EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-
hibernatePU");
12. EntityManager em=emf.createEntityManager();
13. // libération ressources
14. em.close();
15. emf.close();
16. }
17. }

• ligne 10 : on crée l'EntityManagerFactory de l'unité de persistance nommée [mv-pam-JPA-hibernatePU]. Ce nom vient du


fichier [persistence.xml] :

1. <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">


2. ...
3. </persistence-unit>

• ligne 12 : on crée l'EntityManager. Cette création crée la couche JPA. Le fichier [persistence.xml] va être exploité et donc les
tables de la base de données vont être créées,
• lignes 14-15 : on libère les ressources.

4.6.1.6 Tests

Revenons à l'architecture de notre projet :

Programme Interface Implémentation Couche Base de


console [JPA] [Hibernate] [JDBC] données

Toutes les couches ont été implémentées. On exécute le projet [2].

Les résultats console sont les suivants :

75/257
1. ------------------------------------------------------------------------
2. Building mv-pam-JPA-hibernate 1.0-SNAPSHOT
3. ------------------------------------------------------------------------
4.
5. --- exec-maven-plugin:1.2.1:exec (default-cli) @ mv-pam-JPA-hibernate ---
6. sept. 09, 2013 2:05:13 PM org.hibernate.annotations.common.Version <clinit>
7. INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
8. sept. 09, 2013 2:05:13 PM org.hibernate.Version logVersion
9. INFO: HHH000412: Hibernate Core {4.1.2}
10. sept. 09, 2013 2:05:13 PM org.hibernate.cfg.Environment <clinit>
11. INFO: HHH000206: hibernate.properties not found
12. sept. 09, 2013 2:05:13 PM org.hibernate.cfg.Environment buildBytecodeProvider
13. INFO: HHH000021: Bytecode provider name : javassist
14. sept. 09, 2013 2:05:13 PM
org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl
configure
15. INFO: HHH000402: Using Hibernate built-in connection pool (not for production use!)
16. sept. 09, 2013 2:05:13 PM
org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl
configure
17. INFO: HHH000115: Hibernate connection pool size: 20
18. sept. 09, 2013 2:05:13 PM
org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl
configure
19. INFO: HHH000006: Autocommit mode: true
20. sept. 09, 2013 2:05:13 PM
org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl
configure
21. INFO: HHH000401: using driver [com.mysql.jdbc.Driver] at URL
[jdbc:mysql://localhost:3306/dbpam_hibernate]
22. sept. 09, 2013 2:05:13 PM
org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl
configure
23. INFO: HHH000046: Connection properties: {user=root, autocommit=true, release_mode=auto}
24. sept. 09, 2013 2:05:17 PM org.hibernate.dialect.Dialect <init>
25. INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
26. sept. 09, 2013 2:05:17 PM org.hibernate.engine.jdbc.internal.LobCreatorBuilder
useContextualLobCreation
27. INFO: HHH000423: Disabling contextual LOB creation as JDBC driver reported JDBC version [3]
less than 4
28. sept. 09, 2013 2:05:18 PM
org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
29. INFO: HHH000268: Transaction strategy:
org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
30. sept. 09, 2013 2:05:18 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
31. INFO: HHH000397: Using ASTQueryTranslatorFactory
32. sept. 09, 2013 2:05:18 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
33. INFO: HHH000227: Running hbm2ddl schema export
34. Hibernate:
35. alter table EMPLOYES
36. drop
37. foreign key FK75C8D6BC73F24A67
38. sept. 09, 2013 2:05:18 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
39. ERROR: HHH000389: Unsuccessful: alter table EMPLOYES drop foreign key FK75C8D6BC73F24A67
40. sept. 09, 2013 2:05:18 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
41. ERROR: Error on rename of '.\dbpam_hibernate\employes' to '.\dbpam_hibernate\#sql2-1860-a'
(errno: 152)
42. Hibernate:
43. drop table if exists COTISATIONS
44. Hibernate:
45. drop table if exists EMPLOYES
46. Hibernate:
47. drop table if exists INDEMNITES

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 exécuté ne fait rien en-dehors d'instancier la
couche JPA. On notera les points suivants :

• ligne 35 : Hibernate essaie de supprimer la clé étrangère de la table [EMPLOYES],


• lignes 42-47 : suppression des trois tables,
• ligne 49 : création de la table [COTISATIONS],
• ligne 59 : création de la table [EMPLOYES],
• ligne 72 : création de la table [INDEMNITES],
• ligne 83 : création de la clé étrangère de la table [EMPLOYES].

Dans Netbeans, on peut voir les tables dans la connexion qui a été créée précédemment :

77/257
Les tables créées dépendent à la fois de l'implémentation de la couche JPA utilisée et du SGBD utilisé. Ainsi une implémentation
JPA / EclipseLink avec la même base de données peut générer des tables différentes. C'est ce que nous allons voir maintenant.

4.6.2 Couche JPA / EclipseLink

Nous allons construire un nouveau projet Maven dans l'environnement suivant :

Programme Interface Implémentation Couche Base de


console [JPA] [EclipseLink] [JDBC] données

On suivra la démarche du paragraphe précédent :

1. créer une base MySQL [dbpam_eclipselink]. On utilisera le script [dbpam_eclipselink.sql] pour la générer,
2. créer le fichier [persistence.xml] du projet. Prendre l'implémentation JPA 2.0 EclipseLink,
3. ajouter dans les dépendances générées la dépendance du pilote JDBC de MySQL,
4. ajouter les entités JPA et le programme console du précédent projet. Adaptez-le le programme console pour qu'il utilise la
bonne unité de persistance,
5. faire les tests.

Le fichier [persistence.xml] sera 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="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
4. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
5. <class>jpa.Cotisation</class>
6. <class>jpa.Employe</class>
7. <class>jpa.Indemnite</class>
8. <properties>
9. <property name="eclipselink.target-database" value="MySQL"/>
10. <property name="javax.persistence.jdbc.url"
value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
11. <property name="javax.persistence.jdbc.password" value=""/>
12. <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>

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>

• les propriétés 9-13 ont été générées par l'assistant Netbeans,


• ligne 14 : cette propriété nous permet de fixer le niveau de logs d'EclipseLink. Le niveau FINE nous permet de connaître
les ordres SQL qu'EclipseLink va émettre sur la base de données,
• ligne 15 : à l'instanciation de la couche JPA / EclipseLink, les tables des entités JPA seront détruites puis créées.

Les résultats console obtenus sont les suivants :

1. ------------------------------------------------------------------------
2. Building mv-pam-JPA-eclipselink 1.0-SNAPSHOT
3. ------------------------------------------------------------------------
4.
5. --- exec-maven-plugin:1.2.1:exec (default-cli) @ mv-pam-JPA-eclipselink ---
6. [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
27. [EL Info]: 2013-09-09 14:10:16.342--ServerSession(303405156)--Thread(Thread[main,5,main])--
file:/D:/data/istia-1314/netbeans/mv-pam/05/mv-pam-JPA-eclipselink/target/classes/_pam-JPA-
eclipselinkPU login successful
28. [EL Fine]: 2013-09-09 14:10:16.362--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES DROP FOREIGN KEY FK_EMPLOYES_INDEMNITE_ID
29. [EL Fine]: 2013-09-09 14:10:18.209--ServerSession(303405156)--Thread(Thread[main,5,main])--
SELECT 1
30. [EL Warning]: 2013-09-09 14:10:18.217--ServerSession(303405156)--
Thread(Thread[main,5,main])--Exception [EclipseLink-4002] (Eclipse Persistence Services -
2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
31. Internal Exception: java.sql.SQLException: Error on rename of
'.\dbpam_eclipselink\employes' to '.\dbpam_eclipselink\#sql2-1860-b' (errno: 152)
32. Error Code: 1025
33. Call: ALTER TABLE EMPLOYES DROP FOREIGN KEY FK_EMPLOYES_INDEMNITE_ID
34. Query: DataModifyQuery(sql="ALTER TABLE EMPLOYES DROP FOREIGN KEY
FK_EMPLOYES_INDEMNITE_ID")
35. [EL Fine]: 2013-09-09 14:10:18.228--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--DROP TABLE COTISATIONS
36. [EL Fine]: 2013-09-09 14:10:18.773--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--CREATE TABLE COTISATIONS (ID BIGINT NOT NULL, CSGD DOUBLE NOT
NULL, CSGRDS DOUBLE NOT NULL, RETRAITE DOUBLE NOT NULL, SECU DOUBLE NOT NULL, VERSION
INTEGER NOT NULL, PRIMARY KEY (ID))
37. [EL Fine]: 2013-09-09 14:10:19.856--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--DROP TABLE EMPLOYES
38. [EL Fine]: 2013-09-09 14:10:20.351--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--CREATE TABLE EMPLOYES (ID BIGINT NOT NULL, SS VARCHAR(15) NOT
NULL UNIQUE, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, NOM VARCHAR(30) NOT
NULL, PRENOM VARCHAR(20) NOT NULL, VERSION INTEGER NOT NULL, VILLE VARCHAR(30) NOT NULL,
INDEMNITE_ID BIGINT NOT NULL, PRIMARY KEY (ID))
39. [EL Fine]: 2013-09-09 14:10:21.309--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--DROP TABLE INDEMNITES
40. [EL Fine]: 2013-09-09 14:10:21.838--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--CREATE TABLE INDEMNITES (ID BIGINT NOT NULL, BASE_HEURE DOUBLE
NOT NULL, ENTRETIEN_JOUR DOUBLE NOT NULL, INDEMNITES_CP DOUBLE NOT NULL, INDICE INTEGER NOT
NULL UNIQUE, REPAS_JOUR DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
41. [EL Fine]: 2013-09-09 14:10:22.807--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES ADD CONSTRAINT FK_EMPLOYES_INDEMNITE_ID
FOREIGN KEY (INDEMNITE_ID) REFERENCES INDEMNITES (ID)
42. [EL Fine]: 2013-09-09 14:10:24.969--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL,
SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
43. [EL Fine]: 2013-09-09 14:10:25.858--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--DELETE FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
44. [EL Fine]: 2013-09-09 14:10:25.863--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
45. [EL Fine]: 2013-09-09 14:10:25.871--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values (SEQ_GEN, 0)
46. [EL Config]: 2013-09-09 14:10:26.45--ServerSession(303405156)--Connection(645036541)--
Thread(Thread[main,5,main])--disconnect
47. [EL Info]: 2013-09-09 14:10:26.451--ServerSession(303405156)--Thread(Thread[main,5,main])--
file:/D:/data/istia-1314/netbeans/mv-pam/05/mv-pam-JPA-eclipselink/target/classes/_pam-JPA-
eclipselinkPU logout successful
48. [EL Config]: 2013-09-09 14:10:26.451--ServerSession(303405156)--Connection(1237989929)--
Thread(Thread[main,5,main])--disconnect
49. ------------------------------------------------------------------------
50. BUILD SUCCESS
51. ------------------------------------------------------------------------
52. Total time: 12.940s
53. Finished at: Mon Sep 09 14:10:26 CEST 2013
54. Final Memory: 4M/122M

• lignes 18-22 : connexion à la base de données MySQL,


• lignes 23-27 : confirmation que la connexion a réussi,

80/257
• lignes 28-34 : suppression de la clé étrangère de la table [EMPLOYES],
• ligne 35 : suppression de la table [COTISATIONS],
• ligne 36 : création de la table [COTISATIONS]. On notera avec intérêt, que la clé primaire ID n'a pas l'attribut MySQL
auto_increment. Cela veut dire que ce n'est pas MySQL qui génère les valeurs de la clé primaire,
• ligne 37 : suppression de la table [EMPLOYES],
• ligne 38 : création de la table [EMPLOYES]. Sa clé primaire ID n'a pas l'attribut MySQL auto_increment,
• ligne 39 : suppression de la table [INDEMNITES],
• ligne 40 : création de la table [INDEMNITES]. Sa clé primaire ID n'a pas l'attribut MySQL auto_increment,
• ligne 41 : création de la clé étrangère de la table [EMPLOYES] vers la table [INDEMNITES],
• ligne 42 : création d'une table [SEQUENCE]. Elle sera utilisée pour générer les clés primaires des trois tables précédentes,
• ligne 43 : suppression de certaines lignes de la table [SEQUENCE] si elle existait déjà,
• ligne 44 : recherche des lignes qui viennent d'être supprimées. On devrait en trouver zéro,
• ligne 45 : insertion d'une ligne dans la table. C'est le générateur de clés primaires initialisé ici à zéro,
• lignes 46-48 : déconnexion de la base.

L'existence des tables générées peut être vérifiée dans Netbeans [1] :

Donc, à partir des mêmes entités JPA, les implémentations JPA Hibernate et EclipseLink ne génèrent pas les mêmes tables. Dans la
suite du document, lorsque l'implémentation JPA utilisée est :

• Hibernate, on utilisera la base de données [dbpam_hibernate],


• EclipseLink, on utilisera la base de données [dbpam_eclipselink].

4.6.3 Travail à faire

En suivant la même démarche que précédemment,

1. créer et tester un projet [mv-pam-jpa-hibernate-oracle] utilisant une implémentation JPA Hibernate et un SGBD Oracle,
2. créer et tester un projet [mv-pam-jpa-hibernate-mssql] utilisant une implémentation JPA Hibernate et un SGBD SQL
server,
3. créer et tester un projet [mv-pam-jpa-eclipselink-oracle] utilisant une implémentation JPA EclipseLink et un SGBD
Oracle,
4. créer et tester un projet [mv-pam-jpa-eclipselink-mssql] utilisant une implémentation JPA EclipseLink et un SGBD SQL
server,

4.6.4 Lazy ou Eager ?

Revenons à une définition possible de l'entité [Employe] :

1. package jpa;

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 définissent la clé étrangère de la table [EMPLOYES] vers la table [INDEMNITES]. L'attribut fetch de la ligne 27
définit la stratégie 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 ramenée. Elle le sera lorsque
le champ [Employe].indemnite sera référencé pour la première fois.
• FetchType.EAGER : lorsqu'un employé est cherché, l'indemnité qui lui correspond est ramenée. C'est le mode par
défaut lorsqu'aucun mode n'est précisé.

Pour comprendre l'intérêt de l'option FetchType.LAZY, on peut prendre l'exemple suivant. Une liste d'employés sans les indemnités
est présentée dans une page web avec un lien [Details]. Un clic sur ce lien présente alors les indemnités de l'employé sélectionné. On
voit que :
• pour afficher la première page on n'a pas besoin des employés avec leurs indemnités. Le mode FetchType.LAZY convient
alors,
• pour afficher la seconde page avec les détails, une requête supplémentaire doit être faite à la base de données pour avoir
les indemnités de l'employé sélectionné.

Le mode FetchType.LAZY évite de ramener trop de données 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 4

• en [1], on copie le projet,


• en [2], on indique le dossier de la copie et en [3] son nom,
• en [4], le nouveau projet porte le même nom que l'ancien. Nous changeons cela :

• en [1], on renomme le projet,

83/257
• en [2], on renomme le projet et son artifactId,
• en [3], le nouveau projet.

Nous modifions le programme [Main.java] de la façon suivante :

1. package main;
2.
3. import java.util.List;
4. import javax.persistence.EntityManager;
5. import javax.persistence.EntityManagerFactory;
6. import javax.persistence.Persistence;
7. import jpa.Employe;
8.
9. public class Main {
10.
11. // la requête JPQL ci-dessous ramène un employé
12. // la clé étrangère [Employe].indemnite est en FetchType.LAZY
13. public static void main(String[] args) {
14. // créer l'Entity Manager suffit à construire la couche JPA
15. EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-
hibernatePU");
16. // premier essai
17. EntityManager em = emf.createEntityManager();
18. Employe employe = (Employe) em.createQuery("select e from Employe e where
e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
19. em.close();
20. // on affiche l'employé
21. try {
22. System.out.println(employe);
23. } catch (Exception ex) {
24. System.out.println(ex);
25. }
26. // deuxième essai
27. em = emf.createEntityManager();
28. employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite
where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
29. // libérer les ressources
30. em.close();
31. // on affiche l'employé
32. try {
33. System.out.println(employe);
34. } catch (Exception ex) {
35. System.out.println(ex);
36. }
37. // libération ressources
38. emf.close();
39. }
40. }

• ligne 15 : on crée l'EntityManagerFactory de la couche JPA,


• ligne 17 : on obtient l'EntityManager qui nous permet de dialoguer avec la couche JPA,
• ligne 18 : on demande l'employé de nom Jouveinal,
• ligne 19 : on ferme l'EntityManager. Cela a pour effet de fermer le contexte de persistence.
• ligne 22 : on affiche l'employé reçu.

La classe [Employe] est la suivante :

1. package jpa;
2.
3. ...
4.
5. @Entity
6. @Table(name="EMPLOYES")

84/257
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.
32. /**
33. * Returns a string representation of the object. This implementation constructs
34. * that representation based on the id fields.
35. * @return a string representation of the object.
36. */
37. @Override
38. public String toString() {
39. return "jpa.Employe[id=" + getId()
40. + ",version="+getVersion()
41. +",SS="+getSS()
42. + ",nom="+getNom()
43. + ",prenom="+getPrenom()
44. + ",adresse="+getAdresse()
45. +",ville="+getVille()
46. +",code postal="+getCodePostal()
47. +",indice="+getIndemnite().getIndice()
48. +"]";
49. }
50. ...
51. }

• ligne 27 : le champ indemnite est ramené en mode LAZY,


• ligne 47 : utilise le champ indemnite. Si la méthode toString est appelée alors que le champ indemnite n'a pas été encore
ramené, il le sera à ce moment là. Sauf si le contexte de persistance a été fermé comme dans l'exemple.

Revenons au code du [Main] :

• lignes 21-25 : on devrait avoir une exception. En effet, la méthode toString va être appelée. Elle va utiliser le champ
indemnite. Celui-ci va être cherché. Comme le contexte de persistance a été fermé, l'entité [Employe] ramenée n'existe plus
d'où l'exception.
• ligne 27 : on crée un nouveau EntityManager,
• ligne 28 : on demande l'employé Jouveinal en demandant explicitement dans la requête JPQL l'indemnité qui va avec. Cette
demande explicite est nécessaire parce que le mode de recherche de cette indemnité est LAZY,
• ligne 30 : on ferme l'EntityManager,
• lignes 32-36 : on réaffiche l'employé. Il ne devrait pas y avoir d'exception.

Pour exécuter le projet, on a besoin d'une base de données remplie. On la créera en suivant la démarche du paragraphe 4.5, page 65.
Par ailleurs, le fichier [persistence.xml] doit être modifié :

85/257
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. </properties>
15. </persistence-unit>
16. </persistence>

• on a enlevé l'option qui créait les tables. La base de données ici existe déjà et est remplie,
• on a enlevé les options qui faisaient qu'Hibernate loguait les ordres SQL qu'il émettait vers la base de données.

L'exécution du projet donne les deux affichages suivants dans la console :

1. org.hibernate.LazyInitializationException: could not initialize proxy - no Session


2. jpa.Employe[id=31,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des
oiseaux,ville=St Corentin,code postal=49203,indice=2]

• ligne 1 : l'exception qui s'est produite lorsqu'il a fallu chercher l'indemnité qui manquait alors que la session était fermée.
On voit que l'indemnité n'avait pas été ramenée à cause du mode LAZY,
• ligne 2 : l'employé avec son indemnité obtenue par une requête qui a contourné le mode LAZY.

4.6.5 Travail à faire


En suivant une démarche analogue à celle qui vient d'être suivie, créez un projet [mv-pam-jpa-eclipselink-lazy] qui montre le
comportement d'EclipseLink face au mode LAZY.

On obtient les résultats 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 requêtes ont ramené l'indemnité avec l'employé, ce qui était inattendu. Lorsqu'on se renseigne sur le web
sur cette bizarrerie, on découvre 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'implémenteur JPA n'est pas obligé de le suivre. On voit donc que le code devient parfois
dépendant de l'implémentation JPA utilisée. Il est possible de donner par configuration à EclipseLink le comportement attendu
pour le mode LAZY.

86/257
4.6.6 Pour la suite

L'architecture de l'application à construire est la suivante :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]

7 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] :

6 4
1

• puis on renomme le nouveau projet [4, 5, 6].

On changera les dépendances du nouveau projet. Le fichier [pom.xml] devient le suivant :

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-hibernate</artifactId>
7. <version>1.0-SNAPSHOT</version>
8. <packaging>jar</packaging>
9.
10. <name>mv-pam-spring-hibernate</name>

87/257
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. </repositories>
20. <properties>
21. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
22. </properties>
23.
24. <dependencies>
25. <dependency>
26. <groupId>junit</groupId>
27. <artifactId>junit</artifactId>
28. <version>4.10</version>
29. <scope>test</scope>
30. <type>jar</type>
31. </dependency>
32. <dependency>
33. <groupId>commons-dbcp</groupId>
34. <artifactId>commons-dbcp</artifactId>
35. <version>1.2.2</version>
36. </dependency>
37. <dependency>
38. <groupId>commons-pool</groupId>
39. <artifactId>commons-pool</artifactId>
40. <version>1.6</version>
41. </dependency>
42. <dependency>
43. <groupId>org.springframework</groupId>
44. <artifactId>spring-tx</artifactId>
45. <version>3.1.1.RELEASE</version>
46. <type>jar</type>
47. </dependency>
48. <dependency>
49. <groupId>org.springframework</groupId>
50. <artifactId>spring-beans</artifactId>
51. <version>3.1.1.RELEASE</version>
52. <type>jar</type>
53. </dependency>
54. <dependency>
55. <groupId>org.springframework</groupId>
56. <artifactId>spring-context</artifactId>
57. <version>3.1.1.RELEASE</version>
58. <type>jar</type>
59. </dependency>
60. <dependency>
61. <groupId>org.springframework</groupId>
62. <artifactId>spring-orm</artifactId>
63. <version>3.1.1.RELEASE</version>
64. <type>jar</type>
65. </dependency>
66. <dependency>
67. <groupId>org.hibernate</groupId>
68. <artifactId>hibernate-entitymanager</artifactId>
69. <version>4.1.2</version>
70. <type>jar</type>
71. </dependency>
72. <dependency>
73. <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>

• lignes 25-31 : la dépendance pour les tests JUnit,


• lignes 32-41 : les dépendances pour le pool de connexions Apache DBCP,
• lignes 42-65 : les dépendances pour le framework Spring,
• lignes 67-71 : les dépendances pour l'implémentation JPA / Hibernate,
• lignes 72-76 : la dépendance pour le pilote JDBC de MySQL,
• lignes 77-81 : la dépendance pour l'interface Swing. Celle-ci est automatiquement ajoutée par Netbeans lorsqu'on ajoute
une interface Swing au projet.

Par ailleurs, on génèrera les deux bases MySQL :


• [dbpam_hibernate] à partir du script [dbpam_hibernate.sql],
• [dbpam_eclipselink] à partir du script [dbpam_eclipselink.sql],

4.7 Les interfaces des couches [metier] et [DAO]


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 Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate / [JDBC]
EclipseLink]

7 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 première approche pour définir les interfaces des différentes couches est d'examiner les
différents 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 sécurité sociale de l'employé : 254104940426058
5. Nombre d'heures travaillées : 150
6. Nombre de jours travaillés : 20
7.
8. Informations Employé :
9. Nom : Jouveinal
10. ...
11.
12. Informations Cotisations :
13. CSGRDS : 3.49 %
14. ...
15.
16. Informations Indemnités :

89/257
17. ...
18.
19. Informations Salaire :
20. Salaire de base : 362.25 euro
21. Cotisations sociales : 97.48 euro
22. Indemnités d'entretien : 42.0 euro
23. Indemnités de repas : 62.0 euro
24. Salaire net : 368.77 euro

L'application reçoit trois informations de l'utilisateur (cf ligne 1 ci-dessus)


• le n° de sécurité sociale de l'assistante maternelle
• le nombre d'heures travaillées dans le mois
• le nombre de jours travaillés dans le mois

A partir de ces information et d'autres enregistrées dans des fichiers de configuration, l'application affiche les informations suivantes
:

• lignes 4-6 : les valeurs saisies


• lignes 8-10 : les informations liées à l'employé dont on a donné le n° de sécurité sociale
• lignes 12-14 : les taux des différentes cotisations sociales
• lignes 16-17 : les différentes indemnités versées à l'assistante maternelle
• lignes 19-24 : les éléments de la feuille de salaire de l'assistante maternelle

Un certain nombre d'informations doivent être fournies par la couche [metier] à la couche [ui] :

1. les informations liées à une assistante maternelle identifiée par son n° de sécurité sociale. On trouve ces informations dans
la table [EMPLOYES]. Cela permet d'afficher les lignes 6-8.
2. les montants des divers taux de cotisations sociales à prélever sur le salaire brut. On trouve ces informations dans la table
[COTISATIONS]. Cela permet d'afficher les lignes 10-12.
3. les montants des diverses indemnités liées à la fonction d'assistante maternelle. On trouve ces informations dans la table
[INDEMNITES]. Cela permet d'afficher les lignes 14-15.
4. les éléments constitutifs du salaire affichés lignes 18-22.

De ceci, on pourrait décider d'une première écriture de l'interface [IMetier] présentée 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 nbHeuresTravaillées, int
nbJoursTravaillés );
6. }

• ligne 1 : les éléments de la couche [metier] sont mis dans le paquetage [metier]
• ligne 5 : la méthode [ calculerFeuilleSalaire ] prend pour paramètres 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 privés
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 affichée par la couche [ui]
• ligne 10 : les différents taux de cotisation - information n° 2 affichée par la couche [ui]
• ligne 11 : les différentes indemnités liées à l'indice de l'employé - information n° 3 affichée par la couche [ui]
• ligne 12 : les éléments constitutifs de son salaire - information n° 4 affichée par la couche [ui]

Un second cas d'usage de la couche [métier] apparaît avec l'interface graphique :

On voit ci-dessus, que la liste déroulante [1, 2] présente tous les employés. Cette liste doit être demandée à la couche [métier].
L'interface de celle-ci évolue alors de la façon suivante :

1. package metier;
2.
3. import java.util.List;
4. import jpa.Employe;
5.
6. public interface IMetier {
7. // obtenir la feuille de salaire
8. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int
nbJoursTravaillés );
9. // liste des employés
10. public List<Employe> findAllEmployes();
11. }

• ligne [10] : la méthode qui va permettre à la couche [ui] de demander la liste de tous les employés à la couche [métier].

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 données. Il en est de même pour obtenir la
liste de tous les employés. On peut créer une interface [DAO] unique gérant l'accès aux trois entités [Employe, Cotisation,
Indemnite]. Nous décidons plutôt ici de créer une interface [DAO] par entité.

L'interface [DAO] pour les accès aux entités [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. // créer 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 particulière
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] gère les accès à l'entité [Cotisation] et donc à la table [COTISATIONS] de la base de
données. Notre application n'a besoin que de la méthode [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 général où toutes les opérations CRUD (Create,
Read, Update, Delete) sont effectuées sur l'entité.
• ligne 8 : la méthode [create] crée une nouvelle entité [Cotisation]
• ligne 10 : la méthode [edit] modifie une entité [Cotisation] existante
• ligne 12 : la méthode [destroy] supprime une entité [Cotisation] existante
• ligne 14 : la méthode [find] permet de retrouver une entité [Cotisation] existante via son identifiant id
• ligne 16 : la méthode [findAll] rend dans une liste toutes les entités [Cotisation] existantes

Attardons-nous sur la signature de la méthode [create] :

1. // créer une nouvelle cotisation


2.Cotisation create(Cotisation cotisation);

La méthode create a un paramètre cotisation de type Cotisation. Le paramètre cotisation doit être persisté, c.a.d. ici mis dans la table
[COTISATIONS]. Avant cette persistance, le paramètre cotisation a un identifiant id sans valeur. Après la persistance, le champ id a
une valeur qui est la clé primaire de l'enregistrement ajouté à la table [COTISATIONS]. Le paramètre cotisation est donc un
paramètre d'entrée / sortie de la méthode create. Il ne semble pas nécessaire que méthode create rende de plus le paramètre cotisation
comme résultat. La méthode appelante détenant une référence sur l'objet [Cotisation cotisation], si celui-ci est modifié, elle a accès à
l'objet modifié puisqu'elle a une référence dessus. Elle peut donc connaître la valeur que la méthode create a donné au champ id de
l'objet [Cotisation cotisation]. La signature de la méthode pourrait donc être plus simplement :

1. // créer une nouvelle cotisation


2. void create(Cotisation cotisation);

Lorsqu'on écrit une interface, il est bon de se rappeler qu'elle peut être utilisée dans deux contextes différents : local et distant. Dans
le contexte local, la méthode appelante et la méthode appelée sont exécutées dans la même JVM :

Couche interface Couche métier Couche d'accès aux Données


utilisateur
utilisateur [ui] [metier] données [DAO]

JVM

Si la couche [metier] fait appel à la méthode create de la couche [DAO], elle a bien une référence sur le paramètre [Cotisation
cotisation] qu'elle passe à la méthode.

Dans le contexte distant, la méthode appelante et la méthode appelée sont exécutées dans des JVM différentes :

Couche Couche 2 Couche d'accès aux


utilisateur 3 Données
interface métier données [DAO]
utilisateur [ui] [metier]
Réseau
JVM 1 tcp /ip JVM 2

Ci-dessus, la couche [metier] s'exécute dans la JVM 1 et la couche [DAO] dans la JVM 2 sur deux machines différentes. Les deux
couches ne communiquent pas directement. Entre-elles s'intercale une couche qu'on appellera couche de communication [1]. Celle-
ci est composée d'une couche d'émission [2] et d'une couche de réception [3]. Le développeur n'a en général pas à écrire ces
couches de communication. Elles sont générées automatiquement par des outils logiciels. La couche [metier] est écrite comme si elle
s'exécutait dans la même JVM que la couche [DAO]. Il n'y a donc aucune modification de code.

Le mécanisme de communication entre la couche [metier] et la couche [DAO] est le suivant :

• la couche [metier] fait appel à la méthode create de la couche [DAO] en lui passant le paramètre [Cotisation cotisation1]
• ce paramètre est en fait passé à la couche d'émission [2]. Celle-ci va transmettre sur le réseau, la valeur du paramètre
cotisation1 et non sa référence. La forme exacte de cette valeur dépend du protocole de communication utilisé.

92/257
• la couche de réception [3] va récupérer cette valeur et reconstruire à partir d'elle un objet [Cotisation cotisation2] image du
paramètre initial envoyé par la couche [metier]. On a maintenant deux objets identiques (au sens de contenu) dans deux
JVM différentes : cotisation1 et cotisation2.
• la couche de réception va passer l'objet cotisation2 à la méthode create de la couche [DAO] qui va le persister en base de
données. Après cette opération, 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 référence. Si on veut
que la couche [metier] ait une référence sur l'objet cotisation2, il faut le lui envoyer. Aussi est-on amenés à changer la
signature de la méthode create de la couche [DAO] :

1. // créer une nouvelle cotisation


2. Cotisation create(Cotisation cotisation);

• avec cette nouvelle signature, la méthode create va rendre comme résultat l'objet persisté cotisation2. Ce résultat est rendu à
la couche de réception [3] qui avait appelé la couche [DAO]. Celle-ci va rendre la valeur (et non la référence) de cotisation2
à la couche d'émission [2].
• la couche d'émission [2] va récupérer cette valeur et reconstruire à partir d'elle un objet [Cotisation cotisation3] image du
résultat rendu par la méthode create de la couche [DAO].
• l'objet [Cotisation cotisation3] est rendu à la méthode de la couche [metier] dont l'appel à la méthode create de la couche
[DAO] avait initié tout ce mécanisme. La couche [metier] peut donc connaître 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 précédente n'est pas la plus courante. On trouve plus fréquemment les couches [metier] et [DAO] dans la même JVM
:

Couche 2 Couche Couche d'accès


utilisateur 3 Données
interface métier aux données
utilisateur [ui] [metier] [DAO]
Réseau
JVM 1 tcp /ip JVM 2

Dans cette architecture, ce sont les méthodes de la couche [metier] qui doivent rendre des résultats et non celles de la couche
[DAO]. Néanmoins la signature suivante de la méthode create de la couche [DAO] :

1. // créer une nouvelle cotisation


2. Cotisation create(Cotisation cotisation);

nous permet de ne pas faire d'hypothèses sur l'architecture réellement mise en place. Utiliser des signatures qui fonctionneront
quelque soit l'architecture retenue, locale ou distante, implique que dans le cas où une méthode appelée modifie certains de ses
paramètres :
• ceux-ci doivent faire également partie du résultat de la méthode appelée
• la méthode appelante doit utiliser le résultat de la méthode appelée et non les références des paramètres modifiés qu'elle a
transmis à la méthode appelée.

On se laisse ainsi la possibilité de passer une couche d'une architecture locale à une architecture distante sans modification de code.
Réexaminons, à cette lumière, l'interface [ICotisationDao] :

1. package dao;
2.
3. import java.util.List;
4. import jpa.Cotisation;
5.
6. public interface ICotisationDao {
7. // créer 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 particulière
14. public Cotisation find(Long id);

93/257
15. // obtenir tous les objets Cotisation
16. public List<Cotisation> findAll();
17.
18. }

• ligne 8 : le cas de la méthode create a été traité


• ligne 10 : la méthode edit utilise son paramètre [Cotisation cotisation1] pour mettre à jour l'enregistrement de la table
[COTISATIONS] ayant la même clé primaire que l'objet cotisation. Elle rend comme résultat l'objet cotisation2 image de
l'enregistrement modifié. Le paramètre cotisation1 n'est lui pas modifié. La méthode doit rendre cotisation2 comme résultat
qu'on soit dans le cadre d'une architecture distante ou locale.
• ligne 12 : la méthode destroy supprime l'enregistrement de la table [COTISATIONS] ayant la même clé primaire que l'objet
cotisation passé en paramètre. Celui-ci n'est pas modifié. Il n'a donc pas à être rendu.
• ligne 14 : le paramètre id de la méthode find n'est pas modifié par la méthode. Il n'a pas à faire partie du résultat.
• ligne 16 : la méthode findAll n'a pas de paramètres. On n'a donc pas à l'étudier.

Au final, seule la signature de la méthode create doit être adaptée pour être utilisable dans le cadre d'une architecture distante. Les
raisonnements précédents seront valables pour les autres interfaces [DAO]. Nous ne les répèterons pas et utiliserons directement
des signatures utilisables aussi bien dans le cadre d'une architecture distante que locale.

L'interface [DAO] pour les accès aux entités [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. // créer 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 entités Indemnite
16. public List<Indemnite> findAll();
17.
18. }

• ligne 6, l'interface [IIndemniteDao] gère les accès à l'entité [Indemnite] et donc à la table [INDEMNITES] de la base de
données. Notre application n'a besoin que de la méthode [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 général où toutes les opérations CRUD (Create,
Read, Update, Delete) sont effectuées sur l'entité.
• ligne 8 : la méthode [create] crée une nouvelle entité [Indemnite]
• ligne 10 : la méthode [edit] modifie une entité [Indemnite] existante
• ligne 12 : la méthode [destroy] supprime une entité [Indemnite] existante
• ligne 14 : la méthode [find] permet de retrouver une entité [Indemnite] existante via son identifiant id
• ligne 16 : la méthode [findAll] rend dans une liste toutes les entités [Indemnite] existantes

L'interface [DAO] pour les accès aux entités [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. // créer 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. public void destroy(Employe employe);
13. // chercher une entité Employe via son identifiant id
14. public Employe find(Long id);
15. // chercher une entité Employe via son n° SS
16. public Employe find(String SS);
17. // obtenir toutes les entités Employe
18. public List<Employe> findAll();
19. }

• ligne 6, l'interface [IEmployeDao] gère les accès à l'entité [Employe] et donc à la table [EMPLOYES] de la base de
données. Notre application n'a besoin que de la méthode [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 général où toutes les opérations CRUD (Create, Read,
Update, Delete) sont effectuées sur l'entité.
• ligne 8 : la méthode [create] crée une nouvelle entité [Employe]
• ligne 10 : la méthode [edit] modifie une entité [Employe] existante
• ligne 12 : la méthode [destroy] supprime une entité [Employe] existante
• ligne 14 : la méthode [find] permet de retrouver une entité [Employe] existante via son identifiant id
• ligne 16 : la méthode [find(String SS)] permet de retrouver une entité [Employe] existante via son n° SS. Nous avons vu
que cette méthode était nécessaire à l'application console.
• ligne 18 : la méthode [findAll] rend dans une liste toutes les entités [Employe] existantes. Nous avons vu que cette
méthode était nécessaire à l'application graphique.

4.8 La classe [PamException]

La couche [DAO] va travailler avec l'API JDBC de Java. Cette API lance des exceptions contrôlées de type [SQLException] qui
présentent deux inconvénients :
• elles alourdissent le code qui doit obligatoirement gérer ces exceptions avec des try / catch.
• elles doivent être déclarées dans la signature des méthodes de l'interface [IDao] par un "throws SQLException". Ceci a
pour conséquence d'empêcher l'implémentation de cette interface par des classes qui lanceraient une exception contrôlée
d'un type différent de [SQLException].

Pour remédier à ce problème, la couche [DAO] ne "remontera" que des exceptions non contrôlées de type [PamException].

[JPA]Exception SQLException
PamException

Couche Couche Couche Implémentation Couche


[ui] [metier] [DAO] [JPA / Hibernate] [JDBC]

Spring

• la couche [JDBC] lance des exceptions de type [SQLException]


• la couche [JPA] lance des exceptions propres à l'implémentation JPA utilisée
• la couche [DAO] lance des exceptions de type [PamException] non contrôlées

Ceci a deux conséquences :


• la couche [metier] n'aura pas l'obligation de gérer les exceptions de la couche [DAO] avec des try / catch. Elle pourra
simplement les laisser remonter jusqu'à la couche [ui].
• les méthodes de l'interface [IDao] n'ont pas à mettre dans leur signature la nature de l'exception [PamException], ce qui
laisse la possiblité d'implémenter cette interface avec des classes qui lanceraient un autre type d'exception non contrôlée.

La classe [PamException] sera placée dans le paquetage [exception] du projet Netbeans :

95/257
Son code est le suivant :

1. package exception;
2.
3. @SuppressWarnings("serial")
4. public class PamException extends RuntimeException {
5.
6. // code d'erreur
7. private int code;
8.
9. public PamException(int code) {
10. super();
11. this.code = code;
12. }
13.
14. public PamException(String message, int code) {
15. super(message);
16. this.code = code;
17. }
18.
19. public PamException(Throwable cause, int code) {
20. super(cause);
21. this.code = code;
22. }
23.
24. public PamException(String message, Throwable cause, int code) {
25. super(message, cause);
26. this.code = code;
27. }
28.
29. // getter et setter
30.
31. public int getCode() {
32. return code;
33. }
34.
35. public void setCode(int code) {
36. this.code = code;
37. }
38.
39. }

• ligne 4 : [PamException] dérive de [RuntimeException]. C'est donc un type d'exceptions que le compilateur ne nous oblige
pas à gérer par un try / catch ou à mettre dans la signature des méthodes. C'est pour cette raison, que [PamException]
n'est pas dans la signature des méthodes de l'interface [IDao]. Cela permet à cette interface d'être implémentée par une
classe lançant un autre type d'exceptions, pourvu que celui-ci dérive également de [RuntimeException].
• pour différencier 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 paramètre : celui du code
d'erreur qu'on veut donner à l'exception.

Le fonctionnement de l'application, du point de vue des exceptions, sera le suivant :

96/257
• la couche [DAO] encapsulera toute exception rencontrée, dans une exception de type [PamException], et relancera cette
dernière pour la couche [métier].
• la couche [métier] laissera remonter les exceptions lancées par la couche [DAO]. Elle encapsulera toute exception
survenant dans la couche [métier], dans une exception de type [PamException] et relancera cette dernière pour la couche
[ui].
• la couche [ui] intercepte toutes les exceptions qui remontent des couches [métier] et [DAO]. Elle se contentera d'afficher
l'exception sur la console ou l'interface graphique.

Examinons maintenant successivement l'implémentation des couches [DAO] et [metier].

4.9 La couche [DAO] de l'application [PAM]

Nous nous plaçons dans le cadre de l'architecture suivante :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]

7 Spring

4.9.1 Implémentation

Lectures conseillées : paragraphe 3.1.3 de [ref1]

Question : En utilisant l'intégration Spring / JPA, écrire les classes [CotisationDao, IndemniteDao, EmployeDao] d'implémentation
des interfaces [ICotisationDao, IIndemniteDao, IEmployeDao]. Chaque méthode de classe interceptera une éventuelle exception et
l'encapsulera dans une exception de type [PamException] avec un code d'erreur propre à l'exception interceptée.

Les classes d'implémentation feront partie du paquetage [dao] :

Note : les classes du paquetage [dao] auront la forme suivante :

1. package dao;
2.
3. ...
4. import org.springframework.transaction.annotation.Transactional;
5.
6. @Transactional
7. public class CotisationDao implements ICotisationDao {
8.

97/257
9. @PersistenceContext
10. private EntityManager em;
11.
12. // constructeur
13. public CotisationDao() {
14. }
15.
16. // créer 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. }

• ligne 6 : l'annotation Spring @Transactional. Elle indique à Spring que chaque méthode de la classe soit se dérouler 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'intérieur d'une méthode de la couche [DAO], on utilise l'EntityManager de la ligne 10. Grâce à lui, on
fait des opérations de persistance (persist, merge, remove, createQuery). On relira le paragraphe 3.3, page 28, qui présente
l'API d'une couche JPA ;
• parce que la méthode se déroule dans une transaction, on est assuré qu'à la fin de la méthode, les modifications apportées
au contexte de persistance seront synchronisées avec la base de données ;
• on fera en sorte que le code de la méthode soit entouré d'un try / catch arrêtant toute éventuelle exception. On
encapsulera dans un type [PamException] (ligne 22).

4.9.2 Configuration

Lectures conseillées : paragraphe 3.1.5 de [ref1]

L'intégration DAO / JPA est configurée par le fichier Spring [spring-config-dao.xml] et le fichier JPA [persistence.xml] :

Question : écrire le contenu de ces deux fichiers. On supposera que la base de données utilisée est la base MySQL5
[dbpam_hibernate] générée par le script SQL [dbpam_hibernate.sql]. Le fichier Spring définira les trois beans suivants : employeDao
de type EmployeDao, indemniteDao de type IndemniteDao, cotisationDao de type CotisationDao. Par ailleurs, l'implémentation JPA utilisée
sera Hibernate.

Note : suivre le paragraphe 3.1.5 de [ref1]

98/257
4.9.3 Tests
Lectures conseillées : paragraphes 3.1.6 et 3.1.7 de [ref1]

Maintenant que la couche [DAO] est écrite et configurée, nous pouvons la tester. L'architecture des tests sera la suivante :

Couche Couche Objets image Interface Implémentation Couche


[tests] [DAO] de la BD [JPA] [Hibernate] [JDBC]

7 Spring

4.9.4 InitDB
Nous allons créer deux programmes de tests de la couche [DAO]. Ceux-ci seront placés dans le paquetage [dao] [2] de la branche
[Test Packages] [1] du projet Netbeans. Cette branche n'est pas incluse dans le projet généré par l'option [Build project], ce qui nous
assure que les programmes de tests que nous y plaçons ne seront pas inclus dans le .jar final du projet.

99/257
2
1

Les classes placées dans la branche [Test Packages] ont connaissance des classes présentes dans la branche [Source Packages] ainsi
que des bibliothèques de classes du projet. Si les tests ont besoin de bibliothèques autres que celles du projet, celles-ci doivent être
déclarées 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 données avec quelques enregistrements et affiche ensuite ceux-ci
sur la console.
• [JUnitDao] fait une série de tests dont elle vérifie le résultat.

Le squelette de la classe [JUnitInitDB] est le suivant :

1. package dao;
2.
3. ...
4.
5. public class JUnitInitDB {
6.
7. static private IEmployeDao employeDao = null;
8. static private ICotisationDao cotisationDao = null;
9. static private IIndemniteDao indemniteDao = null;
10.
11. @BeforeClass
12. public static void init(){
13. // configuration de l'application
14. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
15. // couches DAO
16. employeDao = (IEmployeDao) ctx.getBean("employeDao");
17. cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
18. indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
19. }
20.
21. @Test
22. public void initDB(){
23. // on remplit la base
24. ...
25. // on affiche le contenu de la base
26. ...
27. }
28.
29. @Before()
30. public void clean(){
31. // on vide la base
32. ...
33. }
34. }

100/257
• la méthode [init] est exécutée avant le début de la série des tests (annotation @BeforeClass). Elle instancie la couche
[DAO]. Elle doit être statique (ligne 12) ce qui entraîne que les champs qu'elle utilise doivent être également statiques
(lignes 7-9).
• la méthode [clean] est exécutée avant chaque test (annotation @Before). Elle vide la base de données.
• la méthode [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 méthode est donc un faux test. Elle a pour rôle de remplir la base de
données avec quelques lignes puis d'afficher le contenu de la base sur la console. Ce sont les méthodes create et findAll des
couches [DAO] qui sont ici utilisées.

Question : compléter le code de la classe [JUnitInitDB]. On s'aidera de l'exemple du paragraphe 3.1.6 de [ref1]. Le code génèrera le
contenu présenté page 61.

Note : on utilisera les méthodes [create] des classes [DAO]. Parce que l'entité [Employe] embarque l'entité [Indemnite], il faut
d'abord créer ces dernières avant de créer les employés qui vont les référencer. Par ailleurs, pour persister une entité en base, il faut
qu'elle ait son id égal à null. En effet, c'est à cette caractéristique que la méthode [persist] de l'interface [EntityManager] sait qu'elle
doit persister ou non une entité en base.

4.9.5 Mise en oeuvre des tests


Nous sommes désormais prêts pour exécuter [InitDB]. Nous décrivons la procédure avec le SGBD MySQL5 :

2 3
1

• les classes [1], les fichiers de configuration [2] et les classes de test de la couche [DAO] [3] sont mis en place,

• le projet est construit [4]

101/257
• la classe [JUnitInitDB] est exécutée [5]. Le SGBD MySQL5 est lancé avec une base [dbpam_hibernate] existante,
• la fenêtre [Test Results] [6] dit que les tests ont été réussis. 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.
Néanmoins, cela montre qu'il n'y a pas eu d'exception à l'exécution du test.

La fenêtre [Output] contient les logs de l'exécution, ceux de Spring et ceux du test lui-même. Les affichages faits par la classe
[JUnitInitDB] sont les suivants :

1. ------------- Standard Output ---------------


2. Employés ----------------------
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
brûlerie,ville=St Marcel,code postal=49014,indice=1]
5. Indemnités ----------------------
6. jpa.Indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour2.0,repas
jour=3.0,indemnités CP=12.0]
7. jpa.Indemnite[id=6,version=0,indice=2,base heure=2.1,entretien jour2.1,repas
jour=3.1,indemnités 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 vérifier avec une connexion Netbeans à la
base [dbpam_hibernate].

1 3

• en [1], dans l'onglet [services], on visualise les données de la table [employes] de la connexion [dbpam_hibernate] [2],
• en [3] le résultat.

4.9.6 JUnitDao
Note : la classe [JUnitDao] peut être trouvée dans le support de cours.

Nous nous intéressons maintenant à une seconde classe de tests [JUnitDao] :

Le squelette de la classe sera le suivant :

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. // vérification
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. // vérifications

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'élément modifié
69. Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
70. // vérifications
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'élément
77. cotisationDao.destroy(cotisation3);
78. // vérifications
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 indemnités
90. ...
91. // on ajoute une Indemnite indemnite
92. ..
93. // on va chercher indemnite en base – on récupère indemnite1
94. ..
95. // on vérifie que indemnite1 = indemnite
96. ...
97. // on modifie l'indemnité obtenue et on persiste la modification en BD. On obtient
indemnite2
98. ...
99. // on vérifie la version de indemnite2
100. ...
101. // on va chercher indemnite2 en base – on obtient indemnite3
102. ...
103. // on vérifie que indemnite3 = indemnite2
104. ...
105. // on supprime en base l'image de indemnite3
106. ...
107. // on va chercher indemnite3 en base
108. ...
109. // on vérifie qu'on a obtenu une référence null
110. ...
111. }
112.
113. @Test
114. public void test03(){
115. log("test03");
116. // on répète un test analogue aux précédents pour Employe
117. ...
118. }
119.
120. @Test
121. public void test04(){
122. log("test04");
123. // on teste la méthode [IEmployeDao].find(String SS)
124. // d'abord avec un employé existant

104/257
125. // puis avec un employé inexistant
126. ...
127. }
128.
129. @Test
130. public void test05(){
131. log("test05");
132. // on crée deux indemnités avec le même indice
133. // enfreint la contrainte d'unicité de l'indice
134. // on vérifie qu'une exception de type PamException se produit
135. // et qu'elle a le n° d'erreur attendu
136. ...
137. }
138.
139. @Test
140. public void test06(){
141. log("test06");
142. // on crée deux employés avec le même n° SS
143. // enfreint la contrainte d'unicité sur le n° SS
144. // on vérifie qu'une exception de type PamException se produit
145. // et qu'elle a le n° d'erreur attendu
146. ...
147.
148. }
149.
150. @Test
151. public void test07(){
152. log("test07");
153. // on crée deux employés avec le même n° SS, le 1er avec create, le 2ème avec edit
154. // enfreint la contrainte d'unicité sur le n° SS
155. // on vérifie qu'une exception de type PamException se produit
156. // et qu'elle a le n° d'erreur attendu
157. ...
158. }
159.
160. @Test
161. public void test08(){
162. log("test08");
163. // supprimer un employé qui n'existe pas ne provoque pas d'exception
164. // il est ajouté puis détruit – on le vérifie
165. ...
166. }
167.
168. @Test
169. public void test09(){
170. log("test09");
171. // modifier un employé sans avoir la bonne version doit provoquer une exception
172. // on le vérifie
173. ...
174. }
175.
176. @Test
177. public void test10(){
178. log("test10");
179. // supprimer un employé sans avoir la bonne version doit provoquer une exception
180. // on le vérifie
181. ...
182.
183. }
184.
185. // getters et setters
186. ...
187. }

105/257
Dans la classe de tests précédente, la base est vidée avant chaque test.

Question : écrire les méthodes suivantes :


- test02 : on s'inspirera de test01
- test03 : un employé a un champ de type Indemnite. Il faut donc créer une entité Indemnite et une entité Employe
- test04.

En procédant de la même façon que pour la classe de tests [JUnitInitDB], on obtient les résultats suivants :

• en [1], on exécute la classe de tests


• en [2], les résultats des tests dans la fenêtre [Test Results]

Provoquons une erreur pour voir comment cela est signalé dans la page des résultats :

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. // vérification
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'exécution de la classe de tests donne les
résultats suivants :

106/257
1

• la page des résultats [1] montre maintenant qu'il y a eu des tests non réussis.
• en [2], un résumé 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.

4.10 La couche [metier] de l'application [PAM]

Maintenant que la couche [DAO] a été écrite, nous passons à l'étude de la couche métier [2] :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]

7 Spring

4.10.1 L'interface Java [IMetier]

Celle-ci a été décrite au paragraphe ??, page 91. Nous la rappelons ci-dessous :

1. package metier;
2.
3. import java.util.List;
4. import jpa.Employe;
5.
6. public interface IMetier {
7. // obtenir la feuille de salaire
8. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int
nbJoursTravaillés );
9. // liste des employés
10. public List<Employe> findAllEmployes();
11. }

L'implémentation de la couche [metier] sera faite dans un paquetage [metier] :

107/257
Le paquetage [metier] comprendra, outre l'interface [IMetier] et son implémentation [Metier], deux autres classes [FeuilleSalaire] et
[ElementsSalaire]. La classe [FeuilleSalaire] a été brièvement présentée page 90. Nous revenons dessus maintenant.

4.10.2 La classe [FeuilleSalaire]


La méthode [calculerFeuilleSalaire] de l'interface [IMetier] rend un objet de type [FeuilleSalaire] qui représente les différents
éléments d'une feuille de salaire. Sa définition 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 privés
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 implémente l'interface Serializable parce que ses instances sont susceptibles d'être échangées sur le réseau.
• ligne 9 : l'employé concerné par la feuille de salaire
• ligne 10 : les différents taux de cotisation
• ligne 11 : les différentes indemnités liées à l'indice de l'employé
• ligne 12 : les éléments constitutifs de son salaire
• lignes 14-22 : les deux constructeurs de la classe
• lignes 25-27 : méthode [toString] identifiant un objet [FeuilleSalaire] particulier
• lignes 29 et au-delà : les accesseurs publics aux champs privés de la classe

La classe [ElementsSalaire] référencée ligne 11 de la classe [FeuilleSalaire] ci-dessus, rassemble les éléments constituant une fiche de
paie. Sa définition est la suivante :

1. package metier;
2.
3. import java.io.Serializable;
4.
5. public class ElementsSalaire implements Serializable{
6.
7. // champs privés
8. private double salaireBase;
9. private double cotisationsSociales;
10. private double indemnitesEntretien;

108/257
11. private double indemnitesRepas;
12. private double salaireNet;
13.
14. // constructeurs
15. public ElementsSalaire() {
16.
17. }
18.
19. public ElementsSalaire(double salaireBase, double cotisationsSociales,
20. double indemnitesEntretien, double indemnitesRepas,
21. double salaireNet) {
22. setSalaireBase(salaireBase);
23. setCotisationsSociales(cotisationsSociales);
24. setIndemnitesEntretien(indemnitesEntretien);
25. setIndemnitesRepas(indemnitesRepas);
26. setSalaireNet(salaireNet);
27. }
28.
29. // toString
30. @Override
31. public String toString() {
32. return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales
+ ",indemnités d'entretien="
33. + indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
34. + salaireNet + "]";
35. }
36.
37. // getters et setters
38. ....
39. }

• ligne 3 : la classe implémente l'interface Serializable parce qu'elle est un composant de la classe FeuilleSalaire qui doit être
sérialisable.
• ligne 6 : le salaire de base
• ligne 7 : les cotisations sociales payées sur ce salaire de base
• ligne 8 : les indemnités journalières d'entretien de l'enfant
• ligne 9 : les indemnités journalières 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 : méthode [toString] identifiant un objet [ElementsSalaire] particulier
• lignes 38 et au-delà : les accesseurs publics aux champs privés de la classe

4.10.3 La classe d'implémentation [Metier] de la couche [metier]

La classe d'implémentation [Metier] de la couche [metier] pourrait être la suivante :

1. package metier;
2.
3. ...
4.
5. @Transactional
6. public class Metier implements IMetier {
7.
8. // références sur la couche [DAO]
9. private ICotisationDao cotisationDao = null;
10. private IEmployeDao employeDao=null;
11.
12.
13. // obtenir la feuille de salaire
14. public FeuilleSalaire calculerFeuilleSalaire(String SS,
15. double nbHeuresTravaillées, int nbJoursTravaillés) {
16. ...

109/257
17. }
18.
19. // liste des employés
20. public List<Employe> findAllEmployes() {
21. ...
22. }
23.
24. // getters et setters
25. ...
26. }

• ligne 5 : l'annotation Spring @Transactional fait que chaque méthode de la classe se déroulera au sein d'une transaction.
On avait déjà mis cette annotation sur les classes de la couche [DAO]. Lorsqu'une méthode de la couche [métier] appelle
une méthode de la couche [DAO], il n'y pas de nouvelle transaction créée. La méthode de la couche [DAO] utilise celle
créée par la couche [métier]. Au final, la durée de vie du contexte de persistance JPA est celle de la transaction de la
couche [métier] et non plus celle de la transaction de la couche [DAO] ;
• lignes 9-10 : les références sur les couches [DAO] des entités [Cotisation, Employe, Indemnite] ;
• lignes 14-17 : la méthode [calculerFeuilleSalaire] ;
• lignes 20-22 : la méthode [findAllEmployes] ;
• ligne 24 et au-delà : les accesseurs publics des champs privés de la classe.

Question : écrire le code de la méthode [findAllEmployes].

Question : écrire le code de la méthode [calculerFeuilleSalaire].

On notera les points suivants :


• le mode de calcul du salaire a été expliqué au paragraphe 4.2, page 62.
• si le paramètre [SS] ne correspond à aucun employé (la couche [DAO] a renvoyé un pointeur null), la méthode lancera une
exception de type [PamException] avec un code d'erreur approprié.

4.10.4 Tests de la couche [metier]


Nous créons deux programmes de test :

2
3

Les classes de tests [3] sont créés 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. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-
dao.xml");
17. metier = (IMetier) ctx.getBean("metier");
18. // couches dao
19. IEmployeDao employeDao=(IEmployeDao) ctx.getBean("employeDao");
20. ICotisationDao cotisationDao=(ICotisationDao) ctx.getBean("cotisationDao");
21. IIndemniteDao indemniteDao=(IIndemniteDao) ctx.getBean("indemniteDao");
22. // on vide la base
23. for(Employe employe:employeDao.findAll()){
24. employeDao.destroy(employe);
25. }
26. for(Cotisation cotisation:cotisationDao.findAll()){
27. cotisationDao.destroy(cotisation);
28. }
29. for(Indemnite indemnite : indemniteDao.findAll()){
30. indemniteDao.destroy(indemnite);
31. }
32. // on la remplit
33. Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
34. Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
35. indemniteDao.create(indemnite1);
36. indemniteDao.create(indemnite2);
37. employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des
oiseaux","St Corentin","49203",indemnite2));
38. employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St
Marcel","49014",indemnite1));
39. cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
40. }
41.
42. @AfterClass
43. public static void terminate() {
44. }
45.
46. // logs
47. static private void log(String message) {
48. System.out.println("----------- " + message);
49. }
50.
51. // test
52. @Test
53. public void test01(){
54. // calcul de feuilles de salaire
55. System.out.println(metier.calculerFeuilleSalaire("260124402111742",30, 5));
56. System.out.println(metier.calculerFeuilleSalaire("254104940426058",150, 20));
57. try {
58. System.out.println(metier.calculerFeuilleSalaire("xx", 150, 20));
59. } catch (PamException ex) {
60. System.err.println(String.format("PamException[Code=%d, message=%s]",ex.getCode(),
ex.getMessage()));
61. }
62. }
63. }

Il n'y a pas d'assertion Assert.assertCondition dans la classe. On cherche simplement à calculer quelques salaires afin de les
vérifier ensuite à la main. L'affichage écran obtenu par l'exécution de la classe précédente est le suivant :

1. Testsuite: metier.JUnitMetier_1
2. ----------- init
3. ....
4. [jpa.Employe[id=22,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brûlerie,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
jour=3.0,indemnités CP=12.0],[salaire base=64.85,cotisations sociales=17.45,indemnités
d'entretien=10.0,indemnités de repas=15.0,salaire net=72.4]]
5. [jpa.Employe[id=21,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue
des oiseaux,ville=St Corentin,code
postal=49203,indice=2],JPA.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retrai
te=7.88],jpa.Indemnite[id=30,version=0,indice=2,base heure=2.1,entretien jour2.1,repas
jour=3.1,indemnités CP=15.0],[salaire base=362.25,cotisations sociales=97.48,indemnités
d'entretien=42.0,indemnités de repas=62.0,salaire net=368.77]]
6. PamException[Code=101, message=L'employé de n°[xx] est introuvable]
7. Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,234 sec

• ligne 4 : la feuille de salaire de Justine Laverti


• ligne 5 : la feuille de salaire de Marie Jouveinal
• ligne 6 : l'exception due au fait que l'employé de n° SS 'xx' n'existe pas.

Question : la ligne 17 de [JUnitMetier_1] utilise le bean Spring nommé metier. Donner la définition 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 définition du bean de la couche [métier].

La classe [JUnitMetier_2] pourrait être la suivante :

1. package metier;
2.
3. ...
4. public class JUnitMetier_2 {
5.
6. // couche métier
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é placées dans la méthode
test01.

Question : écrire la méthode test01.

Lors de l'exécution de la classe [JUnitMetier_2], on obtient les résultats suivants si tout va bien :

112/257
4.11 La couche [ui] de l'application [PAM] – version console

Maintenant que la couche [metier] a été écrite, il nous reste à écrire la couche [ui] [1] :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]

7 Spring

Nous créerons deux implémentations différentes de la couche [ui] : une version console et une version graphique swing :

4.11.1 La classe [ui.console.Main]


Nous nous intéressons tout d'abord à l'application console implémentée par la classe [ui.console.Main] ci-dessus. Son
fonctionnement a été décrit 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. // données locales
18. final String syntaxe = "pg num_securite_sociale nb_heures_travaillées
nb_jours_travaillés";
19. // on vérifie le nombre de paramètres
20. ...
21. // liste des erreurs
22. ArrayList erreurs = new ArrayList();
23. // le second paramètre doit être un nombre réel >0
24. ...
25. // erreur ?
26. if (...) {
27. erreurs.add("Le nombre d'heures travaillées [" + args[1]

113/257
28. + "] est erroné");
29. }
30. // le troisième paramètre doit être un nombre entier >0
31. ...
32. // erreur ?
33. if (...) {
34. erreurs.add("Le nombre de jours travaillés [" + 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 détaillé
60. String output = "Valeurs saisies :\n";
61. output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
62. output += ajouteInfo("Nombre d'heures travaillées", args[1]);
63. output += ajouteInfo("Nombre de jours travaillés", args[2]);
64. output += ajouteInfo("\nInformations Employé", "");
65. output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
66. output += ajouteInfo("Prénom", 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("Sécurité sociale", ""+ feuilleSalaire.getCotisation().getSecu() +
" %");
76. output += ajouteInfo("\nInformations Indemnités", "");
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("Congés Payés", ""+
feuilleSalaire.getEmploye().getIndemnite().getIndemnitesCP()+ " %");
81. output += ajouteInfo("\nInformations Salaire", "");
82. output += ajouteInfo("Salaire de base", ""+
feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");

114/257
83. output += ajouteInfo("Cotisations sociales", ""+
feuilleSalaire.getElementsSalaire().getCotisationsSociales()+ " euro");
84. output += ajouteInfo("Indemnités d'entretien", ""+
feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ " euro");
85. output += ajouteInfo("Indemnités de repas", ""+
feuilleSalaire.getElementsSalaire().getIndemnitesRepas()+ " euro");
86. output += ajouteInfo("Salaire net", ""+
feuilleSalaire.getElementsSalaire().getSalaireNet() + " euro");
87.
88. System.out.println(output);
89. }
90.
91. static String ajouteInfo(String message, String valeur) {
92. return message + " : " + valeur + "\n";
93. }
94. }

Question : compléter le code ci-dessus.

4.11.2 Exécution
Pour exécuter la classe [ui.console.Main], on procèdera de la façon suivante :

3
5
6

115/257
• en [1], sélectionner les propriétés du projet,
• en [2], sélectionner la propriété [Run] du projet,
• utiliser le bouton [3] pour désigner la classe (dite classe principale) à exécuter,
• sélectionner la classe [4],
• la classe apparaît en [5]. Celle-ci a besoin de trois arguments pour s'exécuter (n° SS, nombre d'heures travaillées, nombre
de jours travaillés). Ces arguments sont placés en [6],
• ceci fait, on peut exécuter le projet [7]. La configuration précédente fait que c'est la classe [ui.console.Main] qui va être
exécutée.

Les résultats de l'exécution sont obtenus dans la fenêtre [output] :

4.12 La couche [ui] de l'application [PAM] – version graphique

Nous implémentons maintenant la couche [ui] avec une interface graphique :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]

7 Spring

116/257
2

• en [1], la classe [PamJFrame] de l'interface graphique


• en[2] : l'interface graphique

4.12.1 Un rapide tutoriel


Pour créer l'interface graphique, on pourra procéder de la façon suivante :

• [1] : on crée un nouveau fichier avec le bouton [1] [New File...]


• [2] : on choisit la catégorie du fichier [Swing GUI Forms], c.a.d. formulaires graphiques
• [3] : on choisit le type [JFrame Form], un type de formulaire vide

5
8

6
9

117/257
• [5] : on donne un nom au formulaire qui sera aussi une classe
• [6] : on place le formulaire dans un paquetage
• [8] : le formulaire est ajouté à l'arborescence du projet
• [9] : le formulaire est accessible selon deux perspectives : [Design] [9] qui permet de dessiner les différents composants du
formulaire, [Source] [10 ci-dessous] qui donne accès au code Java du formulaire. Au final, un formulaire est une classe Java
comme une autre. La perspective [Design] est une facilité pour dessiner le formulaire. A chaque ajout de composant en
mode [Design], du code Java est ajouté dans la perspective [Source] pour le prendre en compte.

11 12

10

• [11] : la liste des composants Swing disponibles pour un formulaire est trouvée dans la fenêtre [Palette].
• [12] : la fenêtre [Inspector] présente l'arborescence des composants du formulaire. Les composants ayant une
représentation visuelle se retrouveront dans la branche [JFrame], les autres dans la branche [Other Components].

118/257
15

14
13

• en [13], nous sélectionnons un composant [JLabel] par un clic simple


• en [14], nous le déposons sur le formulaire en mode [Design]
• en [15], nous définissons les propriétés du JLabel (text, font).

119/257
17
18
16
19

• en [16], le résultat obtenu.


• en [17], on demande la prévisualisation du formulaire
• en [18], le résultat
• en [19], le label [JLabel1] a été ajouté à l'arborescence des composants dans la fenêtre [Inspector]

20 21

• en [20] et [21] : dans la perspective [Source] du formulaire, du code Java a été ajouté pour gérer le JLabel ajouté.

Un tutoriel sur la construction de formulaires avec Netbeans est disponible à l'url


[http://www.netbeans.org/kb/trails/matisse.html].

4.12.2 L'interface graphique [PamJFrame]


On construira l'interface graphique suivante :

120/257
1

• en [1], l'interface graphique


• en [2], l'arborescence de ses composants : un JLabel et six conteneurs JPanel

JLabel1

JPanel1

JPanel2

121/257
JPanel3

JPanel4

JPanel5

Travail pratique : construire l'interface graphique précédente en s'aidant du tutoriel


[http://www.netbeans.org/kb/trails/matisse.html].

4.12.3 Les événements de l'interface graphique

Lectures conseillées : chapitre [Interfaces graphiques] de [ref2].

Nous gèrerons le clic sur le bouton [jButtonSalaire]. Pour créer la méthode de gestion de cet événement, on pourra procéder
comme suit :

122/257
Le gestionnaire du clic sur le bouton [JButtonSalaire] est généré :

1. private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {


2. // TODO add your handling code here:
3. }

Le code Java qui associe la méthode précédente au clic sur le bouton [JButtonSalaire] est lui aussi généré :

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 géré par
la méthode [jButtonSalaireActionPerformed] (ligne 4).

Nous gèrerons également, l'événement [caretUpdate] (déplacement du curseur de saisie) sur le champ de saisie [jTextFieldHT]. Pour
créer le gestionnaire de cet événement, nous procédons comme précédemment :

Le gestionnaire de l'événement [caretUpdate] sur le champ de saisie [jTextFieldHT] est généré :

private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {


...
}

Le code Java qui associe la méthode précédente à l'événement [caretUpdate] sur le champ de saisie [jTextFieldHT] est lui aussi
généré :

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'événement [caretUpdate] (ligne 2) sur le bouton [jTextFieldHT] (ligne 1) doit être géré par la méthode
[ jTextFieldHTCaretUpdate] (ligne 3).

4.12.4 Initialisation de l'interface graphique

123/257
Revenons à l'architecture de notre application :

Couche Couche Couche Objets image


Interface Implémentation Couche
[ui] [metier] [DAO] de la BD
[JPA] [Hibernate] [JDBC]

7 Spring

La couche [ui] a besoin d'une référence sur la couche [metier]. Rappelons comment cette référence avait été obtenue dans
l'application console :

1. // instanciation couche [metier]


2. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-
dao.xml");
3. IMetier metier = (IMetier) ctx.getBean("metier");

La méthode est la même dans l'application graphique. Il faut que lorsque celle-ci s'initialise, la référence [IMetier metier] de la ligne
3 ci-dessus soit également initialisée. Le code généré 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. }

• lignes 29-35 : la méthode statique [main] qui lance l'application


• ligne 32 : une instance de l'interface graphique [PamJFrame] est créée et rendue visible.
• lignes 7-9 : le constructeur de l'interface graphique.
• ligne 8 : appel à la méthode [initComponents] définie ligne 17. Cette méthode est auto-générée à partir du travail fait en
mode [Design]. On ne doit pas y toucher.
• ligne 21 : la méthode qui va gérer le déplacement du curseur de saisie dans le champ [jTextFieldHT]
• ligne 25 : la méthode qui va gérer le clic sur le bouton [jButtonSalaire]

Pour ajouter au code précédent nos propres initialisations, nous pouvons procéder 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 heuresTravaillées=0;
14.
15. // initialisations propriétaires
16. public void doMyInit(){
17. // init contexte
18. try{
19. // instanciation couche [metier]
20. ...
21. // liste des employés
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 travaillés
34. jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
35. // combobox employés
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 méthode propriétaire pour faire nos propres initialisations. Celles-ci sont définies par le code des
lignes 10-42

Question : en vous aidant des commentaires, compléter le code de la procédure [doMyInit].

125/257
4.12.5 Gestionnaires d'événements

Question : écrire la méthode [jTextFieldHTCaretUpdate]. Cette méthode doit faire en sorte que si la donnée présente dans le
champ [jTextFieldHT] n'est pas un nombre réel >=0, alors le bouton [jButtonSalaire] doit être inactif.

Question : écrire la méthode [jButtonSalaireActionPerformed] qui doit afficher la feuille de salaire de l'employé sélectionné dans
[jComboBoxEmployes].

4.12.6 Exécution de l'interface graphique

Pour exécuter l'interface graphique, on modifiera la configuration [Run] du projet :

• en [1], mettre la classe de l'interface graphique

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'exécuter le projet.

4.13 Implémentation de la couche JPA avec EclipseLink

Nous nous intéressons à l'architecture suivante où la couche JPA est désormais implémentée par EclipseLink :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [EclipseLink] [JDBC]

7 Spring

4.13.1 Le projet Netbeans

Le nouveau projet Netbeans est obtenu par recopie du projet précédent :

126/257
4
3
2
5

• en [1] : après un clic droit sur le projet Hibernate, choisir Copy


• à l'aide du bouton [2], choisir le dossier parent du nouveau projet. Le nom du dossier apparaît en [3].
• en [4], donner un nom au nouveau projet
• en [5], le nom du dossier du projet

127/257
1
4

3
2

• en [1], le nouveau projet a été créé. Il porte le même nom que l'original,
• en [2] et [3], on le renomme [mv-pam-spring-eclipselink].

Le projet doit être modifié en deux points pour l'adapter à la nouvelle couche JPA / EclipseLink :

1. en [4], les fichiers de configuration de Spring doivent être modifiés. On y trouve en effet la configuration de la couche JPA.
2. en [5], les bibliothèques du projet doivent être modifiées : celles d'Hibernate doivent être remplacées par celles de
EclipseLink.

Commençons 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. <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
21. <id>eclipselink</id>
22. <layout>default</layout>
23. <name>Repository for library Library[eclipselink]</name>
24. </repository>
25. </repositories>
26.
27. <properties>
28. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
29. </properties>
30.
31. <dependencies>
32. <dependency>
33. <groupId>junit</groupId>
34. <artifactId>junit</artifactId>
35. <version>4.10</version>
36. <scope>test</scope>
37. <type>jar</type>
38. </dependency>
39. <dependency>
40. <groupId>commons-dbcp</groupId>
41. <artifactId>commons-dbcp</artifactId>
42. <version>1.2.2</version>
43. </dependency>
44. <dependency>
45. <groupId>commons-pool</groupId>
46. <artifactId>commons-pool</artifactId>
47. <version>1.6</version>
48. </dependency>
49. <dependency>
50. <groupId>org.springframework</groupId>
51. <artifactId>spring-tx</artifactId>
52. <version>3.1.1.RELEASE</version>
53. <type>jar</type>
54. </dependency>
55. <dependency>
56. <groupId>org.springframework</groupId>
57. <artifactId>spring-beans</artifactId>
58. <version>3.1.1.RELEASE</version>
59. <type>jar</type>
60. </dependency>
61. <dependency>
62. <groupId>org.springframework</groupId>
63. <artifactId>spring-context</artifactId>
64. <version>3.1.1.RELEASE</version>
65. <type>jar</type>
66. </dependency>
67. <dependency>
68. <groupId>org.springframework</groupId>
69. <artifactId>spring-orm</artifactId>
70. <version>3.1.1.RELEASE</version>
71. <type>jar</type>
72. </dependency>
73. <dependency>
74. <groupId>org.eclipse.persistence</groupId>
75. <artifactId>eclipselink</artifactId>
76. <version>2.3.0</version>
77. </dependency>
78. <dependency>
79. <groupId>org.eclipse.persistence</groupId>
80. <artifactId>javax.persistence</artifactId>
81. <version>2.0.3</version>
82. </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>

• lignes 73-82 : les dépendances pour l'implémentation JPA EclipseLink,


• lignes 19-24 : le dépôt Maven pour EclipseLink.

Les fichiers de configuration de Spring doivent être modifiés pour indiquer que l'implémentation 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/spring-
tx-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. <!-- métier -->
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. <!-- la source de donnéees DBCP -->
39. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-
method="close">
40. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
41. <property name="url" value="jdbc:mysql://localhost:3306/dbpam_hibernate" />
42. <property name="username" value="root" />
43. <!--
44. <property name="password" value="" />
45. -->
46. </bean>
47. ....
48. </beans>

Les lignes 19-36 configurent la couche JPA. L'implémentation JPA utilisée est Hibernate (ligne 22). Par ailleurs, la base de données
cible est [dbpam_hibernate] (ligne 41).

Pour passer à une implémentation JPA / EclipseLink, les lignes 19-35 ci-dessus sont remplacées par les lignes ci-dessous :

1. <!-- configuration JPA -->


2. <bean id="entityManagerFactory"
class="org.springframework.orm.JPA.LocalContainerEntityManagerFactoryBean">
3. <property name="dataSource" ref="dataSource" />
4. <property name="jpaVendorAdapter">
5. <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
6. <!--
7. <property name="showSql" value="true" />
8. -->
9. <property name="databasePlatform"
value="org.eclipse.persistence.platform.database.MySQLPlatform" />
10. <!--
11. <property name="generateDdl" value="true" />
12. -->
13. </bean>
14. </property>
15. <property name="loadTimeWeaver">
16. <bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
17. </property>
18. </bean>

• ligne 5 : l'implémentation JPA utilisée est EclipseLink


• ligne 9 : la propriété databasePlatform fixe le SGBD cible, ici MySQL
• ligne 11 : pour générer les tables de la base de données lorsque la couche JPA est instanciée. Ici, la propriété est en
commentaires.
• ligne 7 : pour visualiser sur la console les ordres SQL émis par la couche JPA. Ici, la propriété est en commentaires.

Par ailleurs, la base de données cible devient [dbpam_eclipselink] (ligne 4 ci-dessous) :

1. <!-- la source de donnéees DBCP -->


2. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-
method="close">
3. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
4. <property name="url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink" />
5. <property name="username" value="root" />
6. <!--
7. <property name="password" value="" />
8. -->
9. </bean>

4.13.2 Mise en oeuvre des tests

131/257
Avant de tester l'application entière, il est bon de vérifier si les tests JUnit passent avec la nouvelle implémentation JPA. Avant de les
faire, on commencera par supprimer les tables de la base de données. Pour cela, dans l'onglet [Runtime] de Netbeans, si besoin est,
on créera une connexion sur la base dbpam_eclipselink / MySQL5. Une fois connecté à la base dbpam_eclipselink / MySQL5, on
pourra procéder à la suppression des tables comme montré ci-dessous :

• [1] : avant la suppression


• [2] : après la suppression

Ceci fait, on peut exécuter le premier test sur la couche [DAO] : InitDB qui remplit la base. Pour que les tables détruites
précédemment soient recréées par l'application, il faut s'assurer que dans la configuration JPA / EclipseLink de Spring la ligne :

<property name="generateDdl" value="true" />

existe et n'est pas mise en commentaires.

Nous construisons le projet (Build) puis nous exécutons le test [JUnitInitDB] :

• en [1], le test InitDB est exécuté.


• en [2], il échoue. L'exception est lancée par Spring et non par un test qui aurait échoué.

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class
path resource [spring-config-DAO.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Must start with
Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.

132/257
Spring indique qu'il y a un problème de configuration. Le message n'est pas clair. La raison de l'exception a été expliquée
au paragraphe 3.1.9 de [ref1]. Pour que la configuration Spring / EclipseLink fonctionne, la JVM qui exécute l'application
doit être lancée avec un paramètre particulier, un agent Java. La forme de ce paramètre est la suivante :

-javaagent:C:\...\spring-agent.jar

[spring-agent.jar] est l'agent Java dont a besoin la JVM pour gérer la configuration Spring / EclipseLink.

Lorsqu'on exécute un projet, il est possible de passer des arguments à la JVM :

2
3

• en [1], on accède aux propriétés du projet ;


• en [2], les propriétés du Run ;
• en [3], on passe le paramètre -javaagent à la JVM. On trouvera l'archive [spring-agent] dans le support du cours et on
adaptera la ligne 9 au chemin réel de cette archive. S'il y a des espaces dans ce chemin, entourez-le d'apostrophes comme
dans '-javaagent:mon chemin'.

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 réel
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 prêts pour tester de nouveau [InitDB]. Cette fois-ci les résultats obtenus sont les suivants :

1 2 3

• en [1], le test a été réussi


• en [2], dans l'onglet [Services], on rafraîchit la connexion qu'a Netbeans avec la base [dbpam_eclipselink]
• en [3], quatre tables ont été créées

• en [5], on visualise le contenu de la table [employes]


• en [6], le résultat.

4.13.4 JUnitDao
Note : la classe [JUnitDao] est disponible dans le support de cours.

L'exécution de la classe de tests [JUnitDao] peut échouer, même si avec l'implémentation JPA / Hibernate, elle avait réussi. Pour
comprendre pourquoi, analysons un exemple.

La méthode testée est la méthode IndemniteDao.create suivante :

1. package dao;
2.
3. ...
4. @Transactional(propagation=Propagation.REQUIRED)
5. public class IndemniteDao implements IIndemniteDao{
6.
7. @PersistenceContext
8. private EntityManager em;
9.
10. // constructeur
11. public IndemniteDao() {
12. }
13.
14. // créer une indemnité

134/257
15. public Indemnite create(Indemnite indemnite) {
16. try{
17. em.persist(indemnite);
18. }catch(Throwable th){
19. throw new PamException(th,31);
20. }
21. return indemnite;
22. }
23.
24. ...
25. }

• lignes 15-22 : la méthode testée

La méthode de test est la suivante :

1. package dao;
2.
3. ...
4.
5. public class JUnitDao {
6.
7. // couches DAO
8. static private IEmployeDao employeDao;
9. static private IIndemniteDao indemniteDao;
10. static private ICotisationDao cotisationDao;
11.
12. @BeforeClass
13. public static void init() {
14. // log
15. log("init");
16. // configuration de l'application
17. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
18. // couches DAO
19. employeDao = (IEmployeDao) ctx.getBean("employeDao");
20. indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
21. cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
22. }
23.
24. @Before()
25. public void clean() {
26. // on vide la base
27. for (Employe employe : employeDao.findAll()) {
28. employeDao.destroy(employe);
29. }
30. for (Cotisation cotisation : cotisationDao.findAll()) {
31. cotisationDao.destroy(cotisation);
32. }
33. for (Indemnite indemnite : indemniteDao.findAll()) {
34. indemniteDao.destroy(indemnite);
35. }
36. }
37.
38. // logs
39. private static void log(String message) {
40. System.out.println("----------- " + message);
41. }
42.
43. // tests
44. ….
45. @Test
46. public void test05() {
47. log("test05");

135/257
48. // on crée deux indemnités avec le même indice
49. // enfreint la contrainte d'unicité de l'indice
50. boolean erreur = true;
51. Indemnite indemnite1 = null;
52. Indemnite indemnite2 = null;
53. Throwable th = null;
54. try {
55. indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
56. indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
57. erreur = false;
58. } catch (PamException ex) {
59. th = ex;
60. // vérifications
61. Assert.assertEquals(31, ex.getCode());
62. } catch (Throwable th1) {
63. th = th1;
64. }
65. // vérifications
66. Assert.assertTrue(erreur);
67. // chaîne des exceptions
68. System.out.println("Chaîne des exceptions --------------------------------------");
69. System.out.println(th.getClass().getName());
70. while (th.getCause() != null) {
71. th = th.getCause();
72. System.out.println(th.getClass().getName());
73. }
74. // la 1ère indemnité a du être persistée
75. Indemnite indemnite = indemniteDao.find(indemnite1.getId());
76. // vérification
77. Assert.assertNotNull(indemnite);
78. Assert.assertEquals(1, indemnite.getIndice());
79. Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
80. Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
81. Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
82. Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
83. // la seconde indemnité n'a pas du être persistée
84. List<Indemnite> indemnites = indemniteDao.findAll();
85. int nbIndemnites = indemnites.size();
86. Assert.assertEquals(nbIndemnites, 1);
87. }
88.
89. ...
90. }

Question : expliquer ce que fait le test test05 et indiquer les résultats attendus.

Les résultats 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. Chaîne 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 vérifiées et il n'y a pas d'exception qui sort de la méthode de test.

Question : expliquer ce qui s'est passé.

136/257
Les résultats 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,indemnités CP=12.0])
8. Chaîne des exceptions --------------------------------------
9. org.springframework.transaction.TransactionSystemException
10. javax.persistence.RollbackException
11. org.eclipse.persistence.exceptions.DatabaseException
12. com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

Comme précédemment avec Hibernate, le test passe, c.a.d. que les assertions sont vérifiées et il n'y a pas d'exception qui sort de la
méthode de test.

Question : expliquer ce qui s'est passé.

Question : de ces deux exemples, que peut-on conclure de l'interchangeabilité des implémentations JPA ? Est-elle totale ici ?

4.13.5 Les autres tests

Une fois la couche [DAO] testée et considérée correcte, on pourra passer aux tests de la couche [metier] et à ceux du projet lui-
même dans sa version console ou graphique. Le changement d'implémentation 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 près : l'exemple
précédent montre en effet que les exceptions lancées par les couches [DAO] peuvent différer. 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 différents SGBD : Postgres, Oracle XE, SQL Server.

137/257
5 Version 2 : Architecture OpenEJB / JPA

5.1 Introduction aux principes du portage

Note : le cours est à lire jusqu'au paragraphe 5.2, page 143 où commence le travail pratique.

Nous présentons 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 créer les projets Maven.

5.1.1 Les nouvelles architectures

L'implémentation actuelle avec Spring / Hibernate

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]
2

71 Spring

Les deux implémentations à construire avec OpenEJB / EclipseLink

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [EclipseLink] [JDBC]
2

1 OpenEjb

Dans cette implémentation, la couche [ui] sera un client local de la couche [métier].

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [EclipseLink] [JDBC]

1 OpenEjb

Dans cette implémentation, la couche [ui] sera un client distant de la couche [métier].

5.1.2 Les bibliothèques des projets

• les couches [DAO] et [metier] ne sont plus instanciées par Spring. Elles le sont par le conteneur OpenEJB.
• les bibliothèques du conteneur Spring et sa configuration disparaissent au profit des bibliothèques du conteneur OpenEJB
et sa configuration.
• les bibliothèques de la couche JPA / Hibernate sont remplacées par celles de la couche JPA / EclipseLink

138/257
5.1.3 Configuration de la couche JPA / EclipseLink / OpenEJB

• le fichier [META-INF/persistence.xml] configurant la couche JPA devient 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="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-
type="JTA">
4. <!-- entités JPA -->
5. <class>jpa.Cotisation</class>
6. <class>jpa.Employe</class>
7. <class>jpa.Indemnite</class>
8. <!-- le fournisseur JPA est EclipseLink -->
9. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
10. <!-- propriétés provider -->
11. <properties>
12. <property name="eclipselink.ddl-generation" value="create-tables"/>
13. </properties>
14. </persistence-unit>
15. </persistence>

• 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'implémentation JPA utilisée est EclipseLink
• lignes 5-7 : les entités gérées par la couche JPA
• lignes 11-13 : propriétés du provider EclipseLink
• ligne 12 : à chaque exécution, les tables seront créées

Les caractéristiques JDBC de la source de données JTA utilisée par le conteneur OpenEJB seront précisées 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>

• ligne 3 : on utilise l'id Default JDBC Database lorsqu'on travaille avec un conteneur OpenEJB embarqué (embedded)
dans l'application elle-même.
• ligne 5 : nous utilisons une base MySQL [dbpam_eclipselink]

5.1.4 Implémentation de la couche [DAO] par des EJB

• Les classes implémentant la couche [DAO] deviennent des EJB. Prenons l'exemple de la classe [CotisationDao] :

L'interface [ICotisationDao] dans la version Spring était la suivante :

1. package dao;
2.
3. import java.util.List;
4. import jpa.Cotisation;
5.
6. public interface ICotisationDao {
7. // créer une nouvelle cotisation
8. Cotisation create(Cotisation cotisation);
9. // modifier une cotisation existante

139/257
10. Cotisation edit(Cotisation cotisation);
11. // supprimer une cotisation existante
12. void destroy(Cotisation cotisation);
13. // chercher une cotisation particulière
14. Cotisation find(Long id);
15. // obtenir tous les objets Cotisation
16. List<Cotisation> findAll();
17.
18. }

L'EJB va implémenter cette même interface sous deux formes différentes : une locale et une distante. L'interface locale
peut être utilisée par un client s'exécutant dans la même JVM, l'interface distante par un client s'exécutant dans une autre
JVM.

L'interface locale :

1. package dao;
2.
3. import javax.ejb.Local;
4.
5. @Local
6. public interface ICotisationDaoLocal extends ICotisationDao{
7. }

• ligne 6 : l'interface [ICotisationDaoLocal] hérite de l'interface [ICotisationDao] pour en reprendre toutes les méthodes.
Elle n'en ajoute pas de nouvelles.
• ligne 5 : l'annotation @Local en fait une interface locale pour l'EJB qui l'implémentera.

L'interface distante :

1. package dao;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface ICotisationDaoRemote extends ICotisationDao{
7. }

• ligne 6 : l'interface [ICotisationDaoRemote] hérite de l'interface [ICotisationDao] pour en reprendre toutes les méthodes.
Elle n'en ajoute pas de nouvelles.
• ligne 5 : l'annotation @Remote en fait une interface distante pour l'EJB qui l'implémentera.

La couche [DAO] est implémentée par un EJB implémentant 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;

• ligne 1 : l'annotation @Stateless qui fait de la classe un EJB


• ligne 2 : l'annotation @TransactionAttribute qui fait que chaque méthode de la classe s'exécutera au sein d'une
transaction.
• ligne 5 : l'annotation @PersistenceContext qui injecte dans la classe [CotisationDao] l'EntityManager de la couche JPA.
Elle est identique à ce qu'on avait dans la version Spring.

Lorsque l'interface locale de la couche [DAO] est utilisée, le client de cette interface s'exécute dans la même JVM.

140/257
Couche interface Couche métier Couche d'accès aux Données
utilisateur
utilisateur [ui] [metier] données [DAO]

JVM

Ci-dessus, les couches [metier] et [DAO] échangent des objets par référence. Lorsqu'une couche change l'objet partagé,
l'autre couche voit ce changement.

Lorsque l'interface distante de la couche [DAO] est utilisée, le client de cette interface s'exécute habituellement dans une
autre JVM.

Couche Couche 2 Couche d'accès aux


utilisateur 3 Données
interface métier données [DAO]
utilisateur [ui] [metier]
Réseau
JVM 1 tcp /ip JVM 2

Ci-dessus, les couches [metier] et [DAO] échangent des objets par valeur (sérialisation 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 Implémentation de la couche [metier] par un EJB

• La classe implémentant la couche [metier] devient elle aussi un EJB implémentant une interface locale et distante.
L'interface initiale [IMetier] était la suivante :

1. package metier;
2.
3. import java.util.List;
4. import jpa.Employe;
5.
6. public interface IMetier {
7. // obtenir la feuille de salaire
8. FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int
nbJoursTravaillés );
9. // liste des employés
10. List<Employe> findAllEmployes();
11. }

On crée une interface locale et une interface distante à partir de l'interface précédente :

1. package metier;
2.
3. import javax.ejb.Local;
4.
5. @Local
6. public interface IMetierLocal extends IMetier{
7. }

1. package metier;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface IMetierRemote extends IMetier{

141/257
7. }

L'EJB de la couche [metier] implémente ces deux interfaces :

1. @Stateless()
2. @TransactionAttribute(TransactionAttributeType.REQUIRED)
3. public class Metier implements IMetierLocal, IMetierRemote {
4.
5. // référence sur les couches [DAO] locales
6. @EJB
7. private ICotisationDaoLocal cotisationDao = null;
8. @EJB
9. private IEmployeDaoLocal employeDao = null;
10. @EJB
11. private IIndemniteDaoLocal indemniteDao = null;

• lignes 1-2 : définissent un EJB dont chaque méthode s'exécute dans une transaction.
• ligne 7 : une référence sur l'interface locale de l'EJB [CotisationDao].
• ligne 6 : l'annotation @EJB demande à ce que le conteneur EJB injecte une référence sur l'interface locale de l'EJB
[CotisationDao].
• lignes 8-11 : on refait la même 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 initialisés avec des références sur les
interfaces locales des trois EJB de la couche [DAO]. On fait donc ici l'hypothèse que les couches [metier] et [DAO]
s'exécuteront dans la même JVM.

Couche 2 Couche Couche d'accès


utilisateur 3 Données
interface métier aux données
utilisateur [ui] [metier] [DAO]
Réseau
JVM 1 tcp /ip JVM 2

5.1.6 Les clients des EJB

Couche 2 Couche Couche d'accès


utilisateur 3 Données
interface métier aux données
utilisateur [ui] [metier] [DAO]
Réseau
JVM 1 tcp /ip JVM 2

Dans le schéma ci-dessus, pour communiquer avec la couche [metier], la couche [ui] doit obtenir une référence sur l'interface
distante de l'EJB de la couche [metier].

Couche interface Couche métier Couche d'accès aux Données


utilisateur
utilisateur [ui] [metier] données [DAO]

JVM

142/257
Dans le schéma ci-dessus, pour communiquer avec la couche [metier], la couche [ui] doit obtenir une référence sur l'interface locale
de l'EJB de la couche [metier]. La méthode pour obtenir ces références diffère d'un conteneur à l'autre. Pour le conteneur
OpenEJB, on pourra procéder comme suit :

Référence sur l'interface locale :

1. // on configure le conteneur Open EJB embarqué


2. Properties properties = new Properties();
3. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
4. // initialisation du contexte JNDI avec les propriétés précédentes
5. InitialContext initialContext = new InitialContext(properties);
6. // instanciation couches DAO
7. employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
8. cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
9. indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");

• lignes 2-5 : le conteneur OpenEJB est initialisé.


• ligne 5 : on a un contexte JNDI (Java Naming and Directory Interface) qui permet d'obtenir des références sur les EJB.
Chaque EJB est désigné par un nom JNDI :
• pour l'interface locale on ajoute Local au nom de l'EJB (lignes 7-9)
• pour l'interface distante on ajoute Remote au nom de l'EJB
Avec Java EE 5, ces règles changent selon le conteneur EJB. C'est une difficulté. Java EE 6 a introduit une
notation JNDI portable sur tous les serveurs d'applications.

Le code précédent récupère des références sur les interfaces locales des EJB via leurs noms JNDI. Nous avons dit précédemment
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 honorée que si elle appartient à une classe chargée 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 chargée par le conteneur EJB. On est
donc obligés d'utiliser les noms JNDI des EJB.

Ci-dessous, le code pour avoir une référence sur l'interface distante de l'EJB [Metier] :

1. // on configure le conteneur Open EJB embarqué


2. Properties properties = new Properties();
3. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
4. // initialisation du contexte JNDI du conteneur EJB
5. InitialContext initialContext = new InitialContext(properties);
6.
7. // instanciation couche métier distante
8. metier = (IMetierRemote) initialContext.lookup("MetierRemote");

5.2 Travail pratique

On se propose de porter l'application Netbeans Spring / Hibernate vers une architecture OpenEJB / EclipseLink.

L'implémentation actuelle avec Spring / Hibernate

143/257
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]
2

Spring

L'implémentation à construire avec OpenEJB / EclipseLink

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [EclipseLink] [JDBC]

OpenEjb

5.2.1 Mise en place de la base de données [dbpam_eclipselink]

Si elle n'existe pas, créez la base MySQL [dbpam_eclipselink]. Si elle existe, supprimez toutes ses tables. Créez une connexion
Netbeans vers cette base comme il a été décrit page 144.

5.2.2 Configuration initiale du projet Netbeans


• charger le projet Maven [mv-pam-spring-hibernate]
• créer un nouveau projet Maven Java [mv-pam-openejb-eclipselink] [1]

2
3
1

• dans l'onglet [Files] [2], créer 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
5 6

9
8

10

• créer le dossier [src / main/ resources/ META-INF] [9]


• y mettre le fichier [persistence.xml] [10] suivant :

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. <!-- entités Jpa -->
7. <class>jpa.Cotisation</class>
8. <class>jpa.Employe</class>
9. <class>jpa.Indemnite</class>
10. <!-- propriétés provider EclipseLink -->
11. <properties>
12. <property name="eclipselink.logging.level" value="FINE"/>
13. <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
14. </properties>
15. </persistence-unit>
16. </persistence>

• ligne 12 : on demande des logs fins à EclipseLink,


• ligne 13 : les tables seront créées à l'instanciation de la couche JPA,
• ajouter les bibliothèques OpenEJB, EclipseLink ainsi que le pilote JDBC de MySQL au fichier [pom.xml] du projet :

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>

• lignes 18-22 : la dépendance OpenEJB,

146/257
• lignes 30-39 : les dépendances EclipseLink,
• lignes 41-44 : la dépendance du pilote JDBC de MySQL

5.2.3 Portage de la couche [DAO]


Nous allons faire le portage de la couche [DAO] par copie de paquetages du projet [mv-pam-spring-hibernate] vers le projet [mv-
pam-openejb-eclipselink].

• copier les packages [dao, exception, jpa]

Les erreurs signalées ci-dessus viennent du fait que la couche [DAO] copiée utilise Spring et que les bibliothèques Spring ne font
plus partie du projet.

5.2.3.1 L'EJB [CotisationDao]

Nous créons les interfaces locale et distante du futur EJB [CotisationDao] :

L'interface locale ICotisationDaoLocal :

1. package dao;
2.
3. import javax.ejb.Local;
4.
5. @Local
6. public interface ICotisationDaoLocal extends ICotisationDao{
7. }

Pour avoir les bons import de paquetages, faire [clic droit sur le code / Fix Imports].

L'interface distante ICotisationDaoRemote :

1. package dao;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface ICotisationDaoRemote extends ICotisationDao{
7. }

Puis nous modifions la classe [CotisationDao] pour en faire un EJB :

1. ...
2. import javax.persistence.PersistenceContext;
3. import jpa.Cotisation;
4.
5. @Stateless
6. @TransactionAttribute(TransactionAttributeType.REQUIRED)
7. 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 disparaît. Faire un [Clean and Build] du projet :

En [1], il n'y a plus d'erreurs sur la classe [CotisationDao].

5.2.3.2 Les EJB [EmployeDao] et [IndemniteDao]

On répète la même démarche pour les autres éléments de la couche [DAO] :

• interfaces IEmployeDaoLocal, IEmployeDaoRemote dérivées de IEmployeDao


• EJB EmployeDao implémentant ces deux interfaces
• interfaces IIndemniteDaoLocal, IIndemniteDaoRemote dérivées de IIndemniteDao
• EJB IndemniteDao implémentant ces deux interfaces

Ceci fait, il n'y a plus d'erreurs dans le projet [2].

5.2.3.3 La classe [PamException]

La classe [PamException] reste ce qu'elle était à un détail près :

1. package exception;
2.
3. import javax.ejb.ApplicationException;
4.
5. @ApplicationException(rollback=true)
6. public class PamException extends RuntimeException implements Serializable{
7.
8. // code d'erreur
9. private int code;
10. ...

La ligne 5 est ajoutée. Pour avoir les bons import, faire [clic droit+Fix imports].

Pour comprendre l'annotation de la ligne 5, il faut se rappeler que chaque méthode des EJB de notre couche [DAO] :
• s'exécute dans une transaction démarrée et terminée par le conteneur EJB
• lance une exception de type [PamException] dès que quelque chose se passe mal

148/257
Couche Couche Proxy Couche d'accès aux Données
utilisateur
interface métier EJB données [DAO]
utilisateur [ui] [metier]

Lorsque la couche [metier] appelle une méthode M de la couche [DAO], cet appel est intercepté par le conteneur EJB. Tout se passe
comme s'il y avait une classe intermédiaire entre la couche [metier] et la couche [DAO], ici appelée [Proxy EJB], interceptant tous
les appels vers la couche [DAO]. Lorsque l'appel à la méthode M de la couche [DAO] est interceptée, le Proxy EJB démarre une
transaction puis passe la main à la méthode M de la couche [DAO] qui s'exécute alors dans cette transaction. La méthode M se
termine avec ou sans exception.

• si la méthode M se termine sans exception, l'exécution revient au proxy EJB qui termine la transaction en la validant par
un commit. Le flux d'exécution est ensuite rendu à la méthode appelante de la couche [metier]
• si la méthode M se termine avec exception, l'exécution 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'exécution est ensuite rendu à la
méthode appelante de la couche [metier] qui reçoit donc une EJBException. L'annotation de la ligne 5 ci-dessus empêche
cette encapsulation. La couche [metier] recevra donc une PamException. De plus, l'attribut rollback=true indique au
proxy EJB que lorsqu'il reçoit une PamException, il doit invalider la transaction.

5.2.3.4 Entités sérialisables

Revenons sur l'architecture où la couche [UI] est un client distant de la couche [métier] :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [EclipseLink] [JDBC]

1 OpenEjb

Les couches [ui] et [metier] vont s'échanger des objets. Dans la pratique, ces deux couches sont dans deux JVM différentes. Si la
couche [ui] veut passer un objet à la couche [métier], elle ne peut pas passer la référence de cet objet. La couche [métier] ne peut en
effet référencer des objets qui ne sont pas dans sa JVM. La couche [ui] va alors passer la valeur de l'objet et non sa référence. On
appelle cela la sérialisation de l'objet. La couche [métier] va recevoir cette valeur et va créer un nouvel objet à partir d'elle. On
appelle cela la désérialisation de l'objet. La couche [ui] et la couche [métier] ont alors deux objets identiques mais chacun dans sa
JVM.

Dans notre exemple, les types suivants peuvent être échangés entre les couches [ui] et [metier] : [Employe, Cotisation, Indemnite,
FeuilleSalaire, ElementsSalaire, PamException]. Ces classes doivent pouvoir être sérialisées. Cela est obtenu par la simple
déclaration :

1. public [Classe] extends ... implements Serializable{


2. ..
3. }

On fera donc en sorte que les classes citées implémentent l'interface [Serializable]. Il n'y a rien d'autre à faire pour qu'une classe
puisse être sérialisée.

5.2.3.5 Test de la couche [DAO]

Notre couche [DAO] implémentée par des EJB peut être testée. Nous commençons par copier le package [dao] de [Test Packages]
du projet [mv-pam-springhibernate] dans le projet en cours de construction [1] :

149/257
3

1 2

Nous ne conservons que le test [JUnitInitDB] qui initialise la base avec quelques données [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 façon 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 propriétés précédentes
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 références sur les interfaces locales des EJB de la couche [DAO]
• ligne 7 : @BeforeClass annote la méthode exécutée au démarrage du test JUnit
• lignes 10-13 : initialisation du conteneur OpenEJB. Cette initialisation est propriétaire et change avec chaque conteneur
EJB.
• ligne 13 : on a un contexte JNDI (Java Naming and Directory Interface) qui permet d'accéder aux EJB via des noms. Avec
OpenEJB, l'interface locale d'un EJB E est désignée par ELocal et l'interface distante par ERemote.
• lignes 15-17 : on demande au contexte JNDI, une référence 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 exécute le test JUnitInitDBLocal. On rappelle que le
fichier [persistence.xml] a été configuré pour recréer les tables à chaque exécution. Avant l'exécution du test, il est préférable de
supprimer les éventuelles tables de la base MySQL [dbpam_eclipselink].

4
2 3

• 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 exécuté

5 6 7

• en [5], le test a été réussi


• en [6], on rafraîchit la connexion Netbeans
• en [7], on voit les 4 tables créées par la couche JPA. Le test avait pour but de les remplir. On visualise le contenu de l'une
d'entre-elles

• en [8], le contenu de la table [EMPLOYES]

Le conteneur OpenEJB a affiché des logs dans la console :

1. Infos - PersistenceUnit(name=dbpam_eclipselinkPU,
provider=org.eclipse.persistence.JPA.PersistenceProvider) - provider time 396ms
2. Infos - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
3. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!
dao.ICotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
4. Infos - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
5. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!
dao.ICotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)

151/257
6. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao) -->
Ejb(deployment-id=CotisationDao)
7. Infos - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
8. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!
dao.IEmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
9. Infos - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
10. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!
dao.IEmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
11. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao) -->
Ejb(deployment-id=EmployeDao)
12. Infos - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
13. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!
dao.IIndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
14. Infos - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
15. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!
dao.IIndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
16. Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao) -->
Ejb(deployment-id=IndemniteDao)
17. Infos - existing thread singleton service in SystemInstance()
org.apache.openejb.cdi.ThreadSingletonServiceImpl@624a240d
18. Infos - OpenWebBeans Container is starting...
19. Infos - Adding OpenWebBeansPlugin : [CdiPlugin]
20. Infos - All injection points were validated successfully.
21. Infos - OpenWebBeans Container has started, it took [70] ms.
22. Infos - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default
Stateless Container)
23. Infos - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default
Stateless Container)
24. Infos - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default
Stateless Container)
25. Infos - Started Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default
Stateless Container)
26. Infos - Started Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default
Stateless Container)
27. Infos - Started Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default
Stateless Container)
28. Infos - Deployed Application(path=D:\data\istia-1112\netbeans\glassfish\mv-pam\tmp\mv-pam-
openejb-eclipselink\classpath.ear)

• lignes 2-3 : les deux noms JNDI de l'EJB [CotisationDaoLocal],


• lignes 4-5 : les deux noms JNDI de l'EJB [CotisationDaoRemote],
• lignes 7-8 :les deux noms JNDI de l'EJB [EmployeDaoLocal],
• lignes 9-10 :les deux noms JNDI de l'EJB [EmployeDaoRemote],
• lignes 12-13 :les deux noms JNDI de l'EJB [IndemniteDaoLocal],
• lignes 14-15 :les deux noms JNDI de l'EJB [EmployeDaoRemote].

Nous refaisons le même test, en utilisant cette fois-ci l'interface distante des EJB.

En [1], la classe [JUnitInitDBLocal] a été dupliquée (copy / paste) dans [JUnitInitDBRemote]. Dans cette classe, nous remplaçons
les interfaces locales par les interfaces distantes :

152/257
1. public class JUnitInitDBRemote {
2.
3. static private IEmployeDaoRemote employeDao = null;
4. static private ICotisationDaoRemote cotisationDao = null;
5. static private IIndemniteDaoRemote indemniteDao = null;
6.
7. @BeforeClass
8. public static void init() throws Exception {
9. // on configure le conteneur Open EJB embarqué
10. Properties properties = new Properties();
11. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
12. // initialisation du contexte JNDI avec les propriétés précédentes
13. InitialContext initialContext = new InitialContext(properties);
14. // instanciation couches DAO distantes
15. employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
16. cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
17. indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
18. }

Ceci fait, la nouvelle classe de test peut être exécutée. Auparavant, avec la connexion Netbeans [dbpam_eclipselink], supprimez les
tables de la base [dbpam_eclipselink].

Avec la connexion Netbeans [dbpam_eclipselink], vérifiez que la base a été remplie.

5.2.4 Portage de la couche [metier]


Nous allons faire le portage de la couche [metier] par copie de packages du projet [mv-pam-spring-hibernate] vers le projet [mv-
pam-openejb-eclipselink].

2
3

Les erreurs signalées ci-dessus [1] viennent du fait que la couche [metier] copiée utilise Spring et que les bibliothèques Spring ne
font plus partie du projet.

5.2.4.1 L'EJB [Metier]

Nous suivons la même démarche que celle décrite pour l'EJB [CotisationDao]. Nous créons tout d'abord en [2] les interfaces locale
et distante du futur EJB [Metier]. Elles dérivent toutes deux de l'interface initiale [IMetier].

1. package metier;

153/257
2.
3. import javax.ejb.Local;
4.
5. @Local
6. public interface IMetierLocal extends IMetier{
7.
8. }

1. package metier;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface IMetierRemote extends IMetier{
7.
8. }

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. // références 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 nbHeuresTravaillées, int nbJoursTravaillés) {
16. // on récupère les informations liées à l'employé
17. ...

• ligne 1 : l'annotation @Stateless fait de la classe un EJB


• ligne 2 : chaque méthode de la classe s'exécutera dans une transaction
• ligne 3 : l'EJB [Metier] implémente les deux interfaces locale et distante que nous venons de définir
• ligne 7 : l'EJB [Metier] va utiliser l'EJB [CotisationDao] via l'interface locale de celui-ci. Cela signifie que les couches
[metier] et [DAO] doivent s'exécuter dans la même JVM.
• ligne 6 : l'annotation @EJB fait en sorte que le conteneur EJB injecte lui-même la référence sur l'interface locale de l'EJB
[CotisationDao]. L'autre façon que nous avons rencontrée est d'utiliser un contexte JNDI.
• lignes 8-11 : le même mécanisme est utilisé pour les deux autres EJB de la couche [DAO].

5.2.4.2 Test de la couche [metier]


Notre couche [metier] implémentée par un EJB peut être testée. Nous commençons par copier le package [metier] de [Test
Packages] du projet [mv-pam-spring-hibernate] dans le projet en cours de construction [1] :

154/257
2
3

• en [1], le résultat de la copie


• en [2], on supprime le 1er test
• en [3], le test restant est renommé [JUnitMetierLocal]

La classe [JUnitMetierLocal] devient la suivante :

1. public class JUnitMetierLocal {


2.
3. // couche métier locale
4. static private IMetierLocal 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 propriétés précédentes
12. InitialContext initialContext = new InitialContext(properties);
13.
14. // instanciation couches DAO locales
15. IEmployeDaoLocal employeDao = (IEmployeDaoLocal)
initialContext.lookup("EmployeDaoLocal");
16. ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal)
initialContext.lookup("CotisationDaoLocal");
17. IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal)
initialContext.lookup("IndemniteDaoLocal");
18. // instanciation couche métier locale
19. metier = (IMetierLocal) initialContext.lookup("MetierLocal");
20.
21. // on vide la base
22. ...
23. }

• ligne 4 : une référence sur l'interface locale de l'EJB [Metier]


• lignes 8-12 : configuration du conteneur OpenEJB identique à celle faite dans le test de la couche [DAO]
• lignes 15-19 : on demande au contexte JNDI de la ligne 12, des références sur les 3 EJB de la couche [DAO] et sur l'EJB
de la couche [metier]. Les EJB de la couche [DAO] vont servir à initialiser la base, l'EJB de la couche [metier] à faire des
tests de calculs de salaire.

Note : avant d'exécuter ce test, il faut modifier le fichier [persistence.xml] pour que les tables ne soient plus détruites et recréées 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. <!-- entités Jpa -->
7. <class>jpa.Cotisation</class>
8. <class>jpa.Employe</class>
9. <class>jpa.Indemnite</class>
10. <!-- propriétés 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>

L'exécution du test [JUnitMetierLocal] donne le résultat suivant [1] :

1 2

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 métier 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 propriétés précédentes
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 métier 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. for(Indemnite indemnite : indemniteDao.findAll()){
29. indemniteDao.destroy(indemnite);
30. }
31. // on la remplit
32. Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
33. Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
34. indemnite1=indemniteDao.create(indemnite1);
35. indemnite2=indemniteDao.create(indemnite2);
36. employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des
oiseaux","St Corentin","49203",indemnite2));
37. employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St
Marcel","49014",indemnite1));
38. cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
39. }
40. }

• lignes 4 et 19 : on utilise l'interface distante de l'EJB [Metier].


• lignes 15-17 : on utilise les interfaces distantes de la couche [DAO]
• lignes 34-35 : parce qu'avec les interfaces distantes, les objets échangés entre le client et le serveur le sont par valeur, il faut
récupérer le résultat rendu par la méthode create(Indemnite i). Ce n'était pas obligatoire avec les interfaces locales où là les
objets sont passés par référence.

Ceci fait, le projet peut être construit et le test [JUnitMetierRemote] exécuté :

5.2.5 Portage de la couche [console]


Nous allons faire le portage de la couche [console] par copie de packages du projet [mv-pam-spring-hibernate] vers le projet [mv-
pam-openejb-eclipselink].

2
3

Les erreurs signalées ci-dessus [1] viennent du fait que la couche [metier] copiée utilise Spring et que les bibliothèques Spring ne
font plus partie du projet. En [2], la classe [Main] est renommée [MainLocal]. Elle utilisera l'interface locale de l'EJB [Metier].

Le code de la classe [MainLocal] évolue de la façon suivante :

1. public static void main(String[] args) {


2. // données locales
3. final String syntaxe = "pg num_securite_sociale nb_heures_travaillées
nb_jours_travaillés";
4. ...
5. // des erreurs ?
6. if (erreurs.size() != 0) {

157/257
7. for (int i = 0; i < erreurs.size(); i++) {
8. System.err.println(erreurs.get(i));
9. }
10. return;
11. }
12. // c'est bon - on peut demander la feuille de salaire à la couche [métier]
13. IMetierLocal metier = null;
14. FeuilleSalaire feuilleSalaire = null;
15. try {
16. // on configure le conteneur Open EJB embarqué
17. Properties properties = new Properties();
18. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
19. // initialisation du contexte JNDI avec les propriétés précédentes
20. InitialContext initialContext = new InitialContext(properties);
21. // instanciation couche métier locale
22. metier = (IMetierLocal) initialContext.lookup("MetierLocal");
23. // calcul de la feuille de salaire
24. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
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 détaillé
33. String output = "Valeurs saisies :\n";
34. output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
35. ....

Les modifications se situent dans les lignes 13-25. C'est la façon d'avoir une référence sur la couche [metier] qui change (lignes 17-
22). Nous n'expliquons pas le nouveau code qui a déjà été vu dans des exemples précédents. Une fois ces modifications faites, le
projet ne présente plus d'erreurs (cf [3]).

Nous configurons le projet pour qu'il soit exécuté avec des arguments [1] :

Pour que l'application console s'exécute normalement, il faut qu'il y ait des données 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. <!-- entités Jpa -->

158/257
7. <class>jpa.Cotisation</class>
8. <class>jpa.Employe</class>
9. <class>jpa.Indemnite</class>
10. <!-- propriétés 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 données étaient recréées à chaque exécution est mise en commentaires. Le projet
doit être reconstruit (Clean and Build) pour que cette modification soit prise en compte. Ceci fait, on peut exécuter 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/pam-
console-metier-dao-openejb-eclipselink-0910/build/classes/-JPA login successful
6. Valeurs saisies :
7. N° de sécurité sociale de l'employé : 254104940426058
8. Nombre d'heures travaillées : 150
9. Nombre de jours travaillés : 20
10.
11. Informations Employé :
12. Nom : Jouveinal
13. Prénom : 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. Sécurité sociale : 9.39 %
24.
25. Informations Indemnités :
26. Salaire horaire : 2.1 euro
27. Entretien/jour : 2.1 euro
28. Repas/jour : 3.1 euro
29. Congés Payés : 15.0 %
30.
31. Informations Salaire :
32. Salaire de base : 362.25 euro
33. Cotisations sociales : 97.48 euro
34. Indemnités d'entretien : 42.0 euro
35. Indemnités 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
2

En [1], la classe [MainLocal] a été dupliquée 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 métier distante
8. metier = (IMetierRemote) initialContext.lookup("MetierRemote");
9. // calcul de la feuille de salaire
10. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
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 exécuter la classe [MainRemote]. Son exécution
donne les mêmes résultats que précédemment.

5.3 Conclusion
Nous avons montré comment porter une architecture Spring / Hibernate vers une architecture OpenEJB / EclipseLink.

L'architecture Spring / Hibernate

Couche Objets image


Couche Couche Interface Implémentation Couche
[DAO] de la BD
[ui] [metier] [JPA] [Hibernate] [JDBC]

Spring

L'architecture OpenEJB / EclipseLink

Objets image
Couche Couche Couche Interface Implémentation Couche
de la BD
[ui] [metier] [DAO] [JPA] [EclipseLink] [JDBC]

OpenEjb

160/257
Le portage a pu se faire sans trop de difficultés parce que l'application initiale avait été structurée en couches. Ce point est
important à comprendre.

161/257
6 Version 3 : Portage de l'application PAM sur un serveur d'applications
Glassfish
On se propose de placer les EJB des couches [metier] et [DAO] de l'architecture OpenEJB / EclipseLink dans le conteneur d'un
serveur d'applications Glassfish.

L'implémentation actuelle avec OpenEJB / EclipseLink

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [EclipseLink] [JDBC]

OpenEjb

Ci-dessus, la couche [ui] utilise l'interface distante de la couche [metier].

Nous avons testé deux contextes d'exécution : local et distant. Dans ce dernier mode, la couche [ui] était cliente de la couche [metier],
couche implémentée par des EJB. Pour fonctionner en mode client / serveur, dans lequel le client et le serveur s'exécutent dans
deux JVM différentes, nous allons placer les couches [metier, DAO, JPA] sur le serveur Java EE Glassfish. Ce serveur est livré avec
Netbeans.

L'implémentation à construire avec le serveur Glassfish

Couche C
Couche Couche Couche Couche
[ui] [metier] [DAO] [JPA / [JDBC]
EclipseLink]

Jvm1 - Java SE Jvm2 – Java EE - serveur Glassfish v3

• la couche [ui] s'exécutera dans un environnement Java SE (Standard Edition)


• les couches [metier, DAO, JPA] s'exécuteront dans un environnement Java EE (Enterprise Edition) sur un serveur
Glassfish v3
• le client communiquera avec le serveur via un réseau tcp-ip. Les échanges réseau sont transparents pour le développeur, si
ce n'est qu'il doit être quand même conscient que le client et le serveur s'échangent des objets sérialisés pour communiquer
et non des références d'objets. Le protocole réseau utilisé pour ces échanges s'appelle RMI (Remote Method Invocation),
un protocole utilisable uniquement entre deux applications Java.
• l'implémentation JPA utilisée sur le serveur Glassfish sera EclipseLink.

6.1 La partie serveur de l'application client / serveur PAM

6.1.1 L'architecture de l'application

Nous étudions ici la partie serveur qui sera hébergée par le conteneur EJB3 du serveur Glassfish :

162/257
Conteneur
Client Jpa / Eclipselink
Ejb3 Données
Java SE
serveur Java EE

Il s'agit de faire un portage vers le serveur Glassfish de ce qui a déjà été fait et testé avec le conteneur OpenEJB. C'est là l'intérêt de
OpenEJB et en général des conteneurs EJB embarqués : ils nous permettent de tester l'application dans un environnement
d'exécution simplifié. Lorsque l'application a été testée, il ne reste plus qu'à la porter sur un serveur cible, ici le serveur Glassfish.

6.1.1.1 Le projet Netbeans


Commençons par créer un nouveau projet Netbeans :

3
2

• en [1], nouveau projet


• en [2], choisir la catégorie Maven et en [3] le type EJB Module. Il s'agit en effet de construire un projet qui sera hébergé et
exécuté par un conteneur EJB, celui du serveur Glassfish.

5 6

4b 7
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 exécuté. 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.

2 1
3

• en [1], le nouveau projet. Il diffère d'un projet Java classique par quelques points :
• une branche [Other Sources] [2] est automatiquement créée. Elle contiendra notamment le fichier
[persistence.xml] qui configure la couche JPA,
• si on construit le projet (Build), on voit apparaître [3] une dépendance [javaee-api-6.0]. Elle est de type provided car
elle est fournie à l'exécution par le conteneur EJB de Glassfish.

163/257
6.1.1.2 Configuration de la couche de persistance
Par configuration de la couche de persistance, nous entendons l'écriture du fichier [persistence.xml] qui définit :
• l'implémentation JPA à utiliser
• la définition de la source de données exploitée par la couche JPA. Celle-ci sera une source JDBC gérée par le serveur
Glassfish.

Couche C Couche Couche Couche Couche


[ui] [metier] [DAO] [JPA / [JDBC] SGBD BD
Eclipselink]

Java SE Java EE - serveur Glassfish

On pourra procéder comme suit. Tout d'abord, dans l'onglet [Runtime / Databases], on créera [1] une connexion sur la base
MySQL5 [dbpam_eclipselink] :

Ceci fait, on peut passer à la création de la ressource JDBC utilisée par le module EJB :

4
1

• en [1], créer un nouveau fichier – on s'assurera que le projet EJB est sélectionné avant de faire cette opération
• en [2], le projet EJB
• en [3], on sélectionne la catégorie [Glassfish]
• en [4], on veut créer une ressource JDBC

164/257
7

• 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 à accélérer les échanges de l'application avec la base de données.
• en [6], donner un nom JNDI à la ressource JDBC créée. Ce nom peut être quelconque mais il a souvent la forme jdbc/nom.
Ce nom JNDI sera utilisé dans le fichier [persistence.xml] pour désigner la source de données que l'implémentation JPA
doit utiliser.
• en [7], donner un nom qui peut être quelconque au pool de connexions qui va être créé
• dans la liste déroulante [8], choisir la connexion JDBC créée précédemment sur la base MySQL / dbpam_eclipselink.
• en [9], un récapitulatif des propriétés du pool de connexions - on ne touche à rien

11

10

• en [10], on peut préciser plusieurs des propriétés du pool de connexions - on laisse les valeurs par défaut
• en [11], à l'issue de l'assistant de création 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
1. <?xml version="1.0" encoding="UTF-8"?>
2. <!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource
Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
3. <resources>
4. <jdbc-resource enabled="true" jndi-name="jdbc/dbpam_eclipselink" object-type="user" pool-
name="dbpamEclipselinkConnectionPool">
5. <description/>
6. </jdbc-resource>
7. <jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false"
connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10"
connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-
validation-method="auto-commit" datasource-
classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" fail-all-connections="false"
idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-
guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false"
match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-time-
in-millis="60000" name="dbpamEclipselinkConnectionPool" non-transactional-
connections="false" pool-resize-quantity="2" res-type="javax.sql.DataSource" statement-
timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0"
wrap-jdbc-objects="false">
8. <property name="URL" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
9. <property name="User" value="root"/>
10. <property name="Password" value=""/>
11. </jdbc-connection-pool>
12. </resources>

Le fichier [glassfish-resources.xml] est un fichier XML qui reprend toutes les données collectées par l'assistant. Il va être utilisé par
Netbeans pour, lors du déploiement du module EJB sur le serveur Glassfish, demander la création de la ressource JDBC dont a
besoin ce module.

On peut désormais créer le fichier [persistence.xml] qui va configurer la couche JPA du module EJB :

4
1
3

• en [1], créer un nouveau fichier – on s'assurera que le projet EJB est sélectionné avant de faire cette opération
• en [2], le projet EJB
• en [3], on sélectionne la catégorie [Persistence]
• en [4], on veut créer une unité de persistance

5
10
6
7
8
9

• en [5], donner un nom à l'unité de persistance

166/257
• en [6], plusieurs implémentations JPA sont proposées. On choisira ici [EclipseLink]. D'autres implémentations sont
utilisables à condition de mettre les bibliothèques qui les implémentent avec celles du serveur Glassfish.
• dans la liste déroulante [7], choisir la source de données JDBC [jdbc/dbpam_eclipselink] qui vient d'être créée.
• en [8], indiquer que les transactions sont gérées par le conteneur EJB
• en [9], indiquer qu'aucune opération ne doit être faite sur la source de données lors du déploiement du module EJB sur le
serveur. En effet, le module EJB va utiliser une base [dbpam_eclipselink] déjà créée.
• à la fin de l'assistant, un fichier [persistence.xml] a été créé [10]. Son contenu est 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-ejb-metier-dao-eclipselinkPU" transaction-type="JTA">
4. <jta-data-source>jdbc/dbpam_eclipselink</jta-data-source>
5. <exclude-unlisted-classes>false</exclude-unlisted-classes>
6. <properties/>
7. </persistence-unit>
8. </persistence>

• ligne 3 : le nom de l'unité de persistance [ mv-pam-ejb-metier-dao-eclipselinkPU ] et le type de transactions (JTA pour un


conteneur EJB)
• ligne 5 : le nom JNDI de la source de données utilisée par la couche de persistance : jdbc/dbpam_eclipselink
• ligne 6 : les entités JPA ne sont pas précisées. Elles seront cherchées dans le Classpath du module EJB.
• le nom de l'implémentation JPA (Hibernate, EclipseLink, ...) utilisée n'est pas indiquée. Dans ce cas, Glassfish v3 utilise par
défaut EclipseLink.

6.1.1.3 Insertion des couches [JPA, DAO, metier]


Maintenant que le fichier [persistence.xml] a été défini, nous pouvons passer à l'insertion dans le projet des couches [metier, dao,
JPA] de l'application d'entreprise [pam] :

Couche Couche Couche Couche Couche


[ui] [metier] [DAO] [JPA / [JDBC] SGBD BD
EclipseLink]

Java SE Java EE - serveur Glassfish

Ces trois couches sont identiques à ce qu'elles étaient avec OpenEJB. On peut procéder à un simple copier / coller entre les deux
projets. C'est ce que nous faisons maintenant :

• en [1], le résultat 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]

6.1.1.4 Configuration du serveur Glassfish

Il nous reste à configurer le serveur Glassfish sur deux points :

167/257
• la couche JPA est implémentée par EclipseLink. Il faut nous assurer que le serveur Glassfish a les bibliothèques de cette
implémentation JPA.
• la source de données est une base MySQL. Il faut nous assurer que le serveur Glassfish a le pilote JDBC de ce SGBD.

On peut découvrir l'absence de ces bibliothèques lors du déploiement du module EJB. Voici une façon de procéder parmi d'autres
pour ajouter des bibliothèques manquantes au serveur Glassfish :

• en [1], visualiser les propriétés du serveur Glassfish


• en [2], noter le dossier des domaines du serveur. Nous le notons par la suite <domains>
• dans le dossier <domains>\domain1\lib, mettre les bibliothèques manquantes. Dans l'exemple, les bibliothèques
d'Hibernate (lib / hibernate-tools) et le pilote JDBC de MySQL (lib / divers) ont été rajoutés. Par défaut, le serveur
Glassfish a les bibliothèques d'EclipseLink. On ne rajoutera donc que le pilote JDBC de MySQL.

1 2

• en [1], dans l'onglet [Services], nous lançons le serveur Glassfish v3


• en [2], il est actif

6.1.1.5 Déploiement du module EJB


Nous déployons maintenant le module EJB sur le serveur Glassfish :

168/257
3

1 4

• en [1], le module EJB est déployé


• en [2], l'arborescence du serveur Glassfish est rafraîchie
• en [3], après déploiement, le module EJB apparaît dans la branche [Applications] du serveur Glassfish
• en [4], la ressource JDBC [jdbc / dbpam_eclipselink] a été créée sur le serveur Glassfish. On rappelle que nous l'avions
définie page 164.

Lors du déploiement, le serveur Glassfish logue dans la console des informations intéressantes :

1. dao.
2. ....
3. INFO: Portable JNDI names for EJB IndemniteDao : [java:global/pam-serveur-metier-dao-JPA-
eclipselink/IndemniteDao!DAO.IIndemniteDaoLocal, java:global/pam-serveur-metier-dao-JPA-
eclipselink/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-JPA-
eclipselink/CotisationDao!dao.ICotisationDaoLocal, java:global/pam-serveur-metier-dao-JPA-
eclipselink/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-JPA-
eclipselink/Metier!metier.IMetierRemote, java:global/pam-serveur-metier-dao-JPA-
eclipselink/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-JPA-
eclipselink/EmployeDao!dao.IEmployeDaoLocal, java:global/pam-serveur-metier-dao-JPA-
eclipselink/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.

On notera aux lignes


• 3, 6, 8 et 11 les noms portables JNDI des EJB déployés. Java EE 6 a introduit la notion de nom JNDI portable. Cela
dénote un nom JNDI reconnu par tous les serveurs Java EE 6. Avec Java EE 5, les noms JNDI sont spécifiques au serveur
utilisé.
• 4, 7, 9, 12 : les noms JNDI des EJB déployés sous une forme spécifique à Glassfish v3.

Ces noms seront utiles à l'application console que nous allons écrire pour utiliser le module EJB déployé.

6.2 Client console - version 1

169/257
Maintenant que nous avons déployé la partie serveur de notre application client / serveur, nous en venons à étudier la partie client
[1] :

Couche Couche Couche Couche Couche


[ui] [metier] [DAO] [JPA / [JDBC] SGBD BD
1 Toplink]

Java SE Java EE - serveur Glassfish

Nous créons un nouveau projet Maven de type [Java Application] nommé [mv-pam-client-ejb-metier-dao-eclipselink] :

1 2

• en [1], le projet du client ;


• en [2], les dépendances du projet ;
• en [3], le projet a une dépendance sur le projet précédent.

Pour construire la dépendance [3], on procède de la façon suivante :

• en [4], on ajoute une dépendance ;


• en [5], on sélectionne l'onglet [Open Projects] ;
• en [6], on désigne le projet précédent. Il faut que celui-ci soit chargé dans Netbeans pour apparaître.

Le fichier [pom.xml] est le suivant :

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-client-ejb-metier-dao-eclipselink</artifactId>
7. <version>1.0-SNAPSHOT</version>

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 dépendance sur la bibliothèque [gf-client] qui permet à un client Glassfish de communiquer avec un
serveur distant,
• lignes 36-41 : la dépendance sur le projet Maven du module EJB. Nous voulons récupérer ici les définitions des entités JPA
et celles des différentes interfaces ainsi que celle de la classe d'exception [PamException],

A partir du projet [mv-pam-openejb-eclipselink], nous copions la classe [MainRemote] :

La classe [MainRemote] doit obtenir une référence sur l'EJB de la couche [metier]. Le code de la classe [MainRemote] évolue de la
façon suivante :

171/257
1. // c'est bon - on peut demander la feuille de salaire
2. FeuilleSalaire feuilleSalaire = null;
3. IMetierRemote metier = null;
4. try {
5. // contexte JNDI du serveur Glassfish
6. InitialContext initialContext = new InitialContext();
7. // instanciation couche métier
8. metier = (IMetierRemote) initialContext.lookup("java:global/istia.st_mv-pam-ejb-
metier-dao-eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierRemote");
9. // calcul de la feuille de salaire
10. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
11. } catch (PamException ex) {
12. System.err.println("L'erreur suivante s'est produite : "
13. + ex.getMessage());
14. return;
15. } catch (Exception ex) {
16. System.err.println("L'erreur suivante s'est produite : "
17. + ex.toString());
18. return;
19. }

• ligne 6 : initialisation du contexte JNDI du serveur Glassfish.


• ligne 8 : on demande à ce contexte JNDI une référence sur l'interface distante de la couche [metier]. D'après les logs de
Glassfish, on sait que l'interface distante de la couche [metier] a deux noms possibles :

1. Infos: EJB5181:Portable JNDI names for EJB Metier: [java:global/istia.st_mv-pam-ejb-metier-


dao-eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierLocal, java:global/istia.st_mv-pam-
ejb-metier-dao-eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierRemote]
2. Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB Metier:
[metier.IMetierRemote#metier.IMetierRemote, metier.IMetierRemote]

Ligne 1, le nom JNDI utilisable avec tout serveur d'applications JAVA EE 6. Ligne 2, le nom JNDI spécifique à Glassfish.
Dans le code, ligne 9, nous utilisons le nom JNDI portable.

• le reste du code ne change pas

En [1], on configure le projet pour qu'il exécute la classe [MainRemote] avec des arguments. Si tout va bien, l'exécution du projet
donne le résultat suivant :

1. run:
2. Valeurs saisies :
3. N° de sécurité sociale de l'employé : 254104940426058
4. Nombre d'heures travaillées : 150
5. Nombre de jours travaillés : 20
6.
7. Informations Employé :
8. Nom : Jouveinal
9. Prénom : Marie
10. Adresse : 5 rue des oiseaux
11. Ville : St Corentin

172/257
12. Code Postal : 49203
13. Indice : 2
14.
15. Informations Cotisations :
16. CSGRDS : 3.49 %
17. CSGD : 6.15 %
18. Retraite : 7.88 %
19. Sécurité sociale : 9.39 %
20.
21. Informations Indemnités :
22. Salaire horaire : 2.1 euro
23. Entretien/jour : 2.1 euro
24. Repas/jour : 3.1 euro
25. Congés Payés : 15.0 %
26.
27. Informations Salaire :
28. Salaire de base : 362.25 euro
29. Cotisations sociales : 97.48 euro
30. Indemnités d'entretien : 42.0 euro
31. Indemnités de repas : 62.0 euro
32. Salaire net : 368.77 euro
33.
34. BUILD SUCCESSFUL (total time: 2 seconds)

Si dans les propriétés, on met un n° de sécurité sociale incorrect, on obtient le résultat 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 sérialisé. Vérifiez les points suivants :
• les entités échangées entre le client et le serveur [Employe, Cotisation, Indemnite, FeuilleSalaire, ElementsSalaire,
PamException] doivent implémenter l'interface [Serializable] ;
• l'entité JPA [Indemnite] ne doit pas voir l'annotation [@OneToMany]. Si elle est présente, enlevez-la ainsi que le champ
qu'elle annote ;
• si les points précédents sont remplis, alors regardez les logs de Glassfish. La cause la plus probable est qu'une exception
non sérialisable s'est produite côté serveur. Lorsque le serveur veut l'envoyer au client, une exception de sérialisation se
produit alors.

6.3 Client console - version 2


Dans les versions précédentes, 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 défaut est le suivant :

1. # accès JNDI à Sun Application Server


2. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
3. java.naming.factory.url.pkgs=com.sun.enterprise.naming
4. # Required to add a javax.naming.spi.StateFactory for CosNaming that
5. # supports dynamic RMI-IIOP.
6. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
7. org.omg.CORBA.ORBInitialHost=localhost
8. org.omg.CORBA.ORBInitialPort=3700

Les lignes 7 et 8 désignent 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 paramètres, on peut
construire son propre fichier [jndi.properties] ou utiliser une configuration Spring. Nous montrons cette deuxième technnique.

Nous commençons par créer un nouveau projet à partir du projet [pam-client-metier-dao-JPA-eclipselink] initial.

173/257
2
1

• en [1], le nouveau projet


• en [2], le fichier [spring-config-client.xml] de configuration de Spring. Son contenu est le suivant :

Le fichier de configuration de Spring est le suivant :

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. xmlns:jee="http://www.springframework.org/schema/jee"
5. xsi:schemaLocation="
6. http://www.springframework.org/schema/beans
7. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
8. http://www.springframework.org/schema/tx
9. http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
10. http://www.springframework.org/schema/jee
11. http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
12.
13. <!-- métier -->
14. <jee:jndi-lookup id="metier" jndi-name="java:global/istia.st_mv-pam-ejb-metier-dao-
eclipselink_ejb_1.0-SNAPSHOT/Metier!metier.IMetierRemote">
15. <jee:environment>
16. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
17. java.naming.factory.url.pkgs=com.sun.enterprise.naming
18. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
19. org.omg.CORBA.ORBInitialHost=localhost
20. org.omg.CORBA.ORBInitialPort=3700
21. </jee:environment>
22. </jee:jndi-lookup>
23. </beans>

Nous utilisons ici une balise <jee> (ligne 14) apparue avec Spring 2.0. L'usage de cette balise nécessite la définition du schéma
auquel elle appartient, lignes 4, 10 et 11.

• ligne 14 : la balise <jee:jndi-lookup> permet d'obtenir la référence d'un objet auprès d'un service JNDI. Ici, on associe le
bean nommé " metier " à la ressource JNDI associée à 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 à définir les
paramètres de connexion au service JNDI.

La classe principale [MainRemote] évolue de la façon suivante :

1. ...
2. // c'est bon - on peut demander la feuille de salaire
3. FeuilleSalaire feuilleSalaire = null;
4. IMetierRemote metier=null;
5. try {
6. // instanciation couche [metier]
7. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-
client.xml");
8. metier = (IMetierRemote) ctx.getBean("metier");
9. // calcul de la feuille de salaire

174/257
10. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
11. } catch (PamException ex) {
12. System.err.println("L'erreur suivante s'est produite : "
13. + ex.getMessage());
14. return;
15. } catch (Exception ex) {
16. System.err.println("L'erreur suivante s'est produite : "
17. + ex.toString());
18. return;
19. }
20. ...

Lignes 7-8, la référence de type [IMetierRemote] sur la couche [metier] est demandée à Spring. Cette solution amène de la souplesse
dans notre architecture. En effet, si l'EJB de la couche [metier] devenait local, c.a.d. exécuté dans la même 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 étudiée 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.

Le fichier [pom.xml] doit avoir la dépendance nécessaire aux applications swing :

1. <dependency>
2. <groupId>org.swinglabs</groupId>
3. <artifactId>swing-layout</artifactId>
4. <version>1.0.3</version>
5. </dependency>

Ci-dessus, la classe [PamJFrame] avait été écrite initialement pour s'exécuter dans un environnement Spring / JPA :

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]
swing

7 Spring

Maintenant cette classe doit devenir le client distant d'un EJB déployé sur le serveur Glassfish.

175/257
Couche Couche Couche Couche Couche
[ui] [metier] [DAO] [JPA / [JDBC]
swing EclipseLink]

Jvm1 - Java SE Jvm2 – Java EE - serveur Glassfish v3

Travail pratique : en suivant l'exemple du client console [ui.console.MainRemote] du projet, modifier la façon utilisée par la
méthode [doMyInit] (cf page 125) de la classe [PamJFrame] pour acquérir une référence sur la couche [metier] qui est maintenant
distante.

176/257
7 Version 4 – client / serveur dans une architecture de service web
Dans cette nouvelle version, l'application [Pam] va s'exécuter en mode client / serveur dans une architecture de service web.
Revenons sur l'architecture de l'application précédente :

Couche C RMI S Couche Couche Couche Couche


[ui] [metier] [DAO] [JPA / [JDBC] SGBD BD
1 2 EclipseLink]

Java SE Java EE - serveur Glassfish

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 remplacée par une
couche [C, HTTP / SOAP, S] :

Couche C S Couche Couche Couche Couche


[ui] [metier] [DAO] [JPA / [JDBC] SGBD BD
2 EclipseLink]
1 HTTP /
SOAP
Java SE Java EE - serveur Glassfish

Le protocole HTTP / SOAP a l'avantage sur le protocole RMI / EJB précédent d'être multi-plateformes. Ainsi le service web peut
être écrit en Java et déployé sur le serveur Glassfish alors que le client lui, pourrait être un client .NET ou PHP.

Nous allons développer cette architecture selon trois modes différents :

1. le service web sera assuré par l'EJB [Metier]


2. le service web sera assuré par une application web utilisant l'EJB [Metier]
3. le service web sera assuré par une application web utilisant Spring

Un service web peut être implémenté de diverses façons au sein d'un serveur Java EE :

• par une classe annotée @WebService qui s'exécute dans un conteneur web

Client Conteneur
Conteneur web Jpa
du Ejb3 Données
service web tcp-ip serveur Java EE

• par un EJB annoté @WebService qui s'exécute dans un conteneur EJB

Client Conteneur
Jpa
du Ejb3 Données
service web serveur Java EE

Nous commençons par cette dernière architecture.

177/257
7.1 Service web implémenté par un EJB

7.1.1 La partie serveur


Commençons par créer un nouveau projet maven copie du projet EJB [mv-pam-ejb-metier-dao-JPA-eclipselink] :

Dans l'architecture suivante :

Couche C S Couche Couche Couche Couche


[ui] [metier] [DAO] [JPA / [JDBC] SGBD BD
2 EclipseLink]
1 HTTP /
SOAP
Java SE Java EE - serveur Glassfish

la couche [metier] va être le service web contacté par la couche [ui]. Cette classe n'a pas besoin d'implémenter une interface. Ce sont
des annotations qui transforment un POJO (Plain Ordinary Java Object) en service web. La classe [Metier] qui implémente la
couche [metier] ci-dessus, est transformée de la façon suivante :

1. package metier;
2.
3. ...
4. @WebService
5. @Stateless()
6. @TransactionAttribute(TransactionAttributeType.REQUIRED)
7. public class Metier implements IMetierLocal,IMetierRemote {
8.
9. // références 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 employés
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 méthodes à ses
clients. Celles-ci doivent être annotées par l'attribut @WebMethod.
• lignes 19 et 25 : les deux méthodes de la classe [Metier] deviennent des méthodes du service web.
• ligne 29 : il est important que les getters et setters soient supprimés sinon ils seront exposés dans le service web et cela
cause des erreurs de sécurité.

L'ajout de ces annotations est détecté par Netbeans qui fait alors évoluer la nature du projet :

En [1], une arborescence [Web Services] est apparue dans le projet. On y trouve le service web Metier et ses deux méthodes.
L'application serveur peut être déployée [2]. Le serveur MySQL soit être lancé et sa base [dbpam_eclipselink] exister et être remplie.
Il peut être nécessaire auparavant de supprimer [3] les EJB du projet client / serveur EJB étudié précédemment pour éviter des
conflits de noms. En effet, notre nouveau projet amène avec lui les mêmes EJB que ceux du projet précédent.

En [1], nous voyons notre application serveur déployée sur le serveur Glassfish. Une fois le service web déployé, il peut être testé :

3
1

• en [1], dans le projet courant, nous testons le service web [Metier]


• le service web est accessible via différentes URL. L'URL [2] permet de tester le service web

179/257
• en [3], un lien sur le fichier XML définissant le service web. Les clients du service web ont besoin de connaître l'URL de ce
fichier. C'est à partir de lui qu'est générée la couche cliente (stubs) du service web.
• en [4,5], un formulaire permettant de tester les méthodes exposées par le service web. Celles-ci sont présentées avec leurs
paramètres que l'utilisateur peut définir.

Par exemple, testons la méthode [findAllEmployes] qui n'a besoin d'aucun paramètre :

Ci-dessus, nous testons la méthode. Nous recevons alors la réponse ci-dessous (vue partielle). Nous y retrouvons bien les deux
employés avec leurs indemnités. Le lecteur est invité à tester de la même façon la méthode [4] en lui passant les trois paramètres
qu'elle attend.

Note : si vous n'obtenez pas le résultat ci-dessus mais que vous n'avez pas d'exception, vérifiez que toutes les entités échangées
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 C RMI S Couche Couche Couche Couche


[ui] [metier] [DAO] [JPA / [JDBC] SGBD BD
1 2 EclipseLink]

Java SE Java EE - serveur Glassfish

7.1.2.1 Le projet Netbeans du client console

Nous créons maintenant un projet Java de type [Java Application] pour la partie client de l'application. Il n'a pas été possible (juin
2012) de créer un projet Maven pour ce client. Une erreur survient, semble connue mais reste non résolue.

Une fois le projet créé, nous indiquons qu'il sera client du service web que nous venons de déployer sur le serveur Glassfish :

2
2
3

• en [2], nous sélectionnons le nouveau projet et activons le bouton [New File]


• en [3], nous indiquons que nous voulons créer un client de service web

4 5

• avec [4], nous allons désigner le projet Netbeans du service web


• dans la fenêtre [5], sont listés tous les projets ayant une branche [Web Services], ici uniquement le projet [mv-pam-ws-
metier-dao-eclipselink].
• un projet peut déployer plusieurs services web. En [6], nous désignons le service web auquel on veut se connecter.

181/257
7
10

11

• en [7], est affichée l'URL de définition du service web. Cette URL est utilisée par les outils logiciels qui génèrent la couche
cliente qui va s'interfacer avec le service web.

Couche C S Couche Couche Couche Couche


[ui] [metier] [DAO] [JPA / [JDBC] SGBD BD
2 Toplink]
3 4
1 HTTP /
SOAP
Java SE Java EE - serveur Glassfish

• la couche cliente [C] [1] qui va être générée est constituée d'un ensemble de classes Java qui vont être mises dans un même
paquetage. Le nom de celui-ci est fixé en [8].
• une fois l'assistant de création du client du service web terminé avec le bouton [Finish], la couche [C] ci-dessus est créée.

Ceci est reflété par un certain nombre de changements dans le projet :

• en [10] ci-dessus, apparaît 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], apparaît une arborescence [Web Service References] qui liste les services web pour lesquels une couche cliente a
été générée.

On notera que dans la couche [C] [10] générée, nous retrouvons des classes qui ont été déployées côté serveur : Indemnite,
Cotisation, Employe, FeuilleSalaire, ElementsSalaire, Metier. Metier est le service web et les autres classes sont des classes
nécessaires à ce service. On pourra avoir la curiosité de consulter leur code. On verra que la définition des classes qui, instanciées,
représentent des objets manipulés par le service, consiste en la définition des champs de la classe et de leurs accesseurs ainsi qu'à
l'ajout d'annotations permettant la sérialisation de la classe en flux XML. La classe Metier est devenue une interface avec dedans
les deux méthodes qui ont été annotées @WebMethod. Chacune de celles-ci donne naissance à deux classes, par exemple
[CalculerFeuilleSalaire.java] et [CalculerFeuilleSalaireResponse.java], où l'une encapsule l'appel à la méthode et l'autre son résultat.
Enfin, la classe MetierService est la classe qui permet au client d'avoir une référence sur le service web Metier distant :

1. @WebEndpoint(name = "MetierPort")
2. public Metier getMetierPort() {
3. return super.getPort(new QName("http://metier/", "MetierPort"), Metier.class);
4. }

La méthode getMetierPort de la ligne 2 permet d'obtenir une référence sur le service web Metier distant.

182/257
7.1.2.2 Le client console du service web Metier
Il ne nous reste plus qu'à écrire le client du service web Metier. Nous recopions la classe [MainRemote] du projet [mv-pam-client-
metier-dao-JPA-eclipselink] qui était un client d'un serveur EJB, dans le nouveau projet.

2
3

• en [1], la classe du client du service web. La classe [MainRemote] présente des erreurs. Pour les corriger, on commencera
par supprimer toutes les instructions [import] existantes dans la classe et on les régènerera par l'option [Fix Imports]. En
effet, certaines des classes utilisées par la classe [MainRemote] font désormais partie du package [client] généré.
• en [3], le morceau de code où la couche [metier] est instanciée [3]. Elle l'est avec du code JNDI pour obtenir une référence
sur un EJB distant.

Nous faisons évoluer le code de la façon suivante :

• le code JNDI est supprimé


• la classe [PamException] n'existant pas côté client, nous supprimons le catch associé pour ne garder que le catch sur la classe
mère [Exception].

• en [4], il nous reste à obtenir une référence sur la service web distant [Metier] afin de pouvoir appeler sa méthode
[calculerFeuilleSalaire].
• en [5], avec la souris, nous tirons (drag) la méthode [calculerFeuilleSalaire] du service web [Metier] pour la déposer (drop)
en [4]. Du code est généré [6]. Ce code générique peut être ensuite adapté par le développeur.

183/257
• ligne 112, on voit que [calculerFeuilleSalaire] est une méthode de la classe [client.Metier] (ligne 111). Maintenant que nous
savons comment obtenir la couche [metier], le code précédent peut être réécrit de la façon 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], nbHeuresTravaillées,
nbJoursTravaillés);
10. } catch (Throwable th) {
11. // chaîne des exceptions
12. System.out.println("Chaîne 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 récupère une référence sur le service web Metier. Ceci fait, le code de la classe ne change pas, si ce n'est quand même
qu'en ligne 10, ce n'est pas l'exception de type [Exception] qui est gérée mais le type plus général Throwable, la classe parent de la
classe Exception. S'il y a exception, nous affichons toutes les causes imbriquées de celle-ci jusqu'à la cause originelle.

Nous sommes prêts pour les tests :

• s'assurer que le SGBD MySQL5 est lancé, que la base dbpam_eclipselink est créée et initialisée
• s'assurer que le service web est déployé sur le serveur Glassfish
• construire le client (Clean and Build)
• configurer l'exécution du client

• exécuter le client

Les résultats dans la console sont les suivants :

1. ...
2. Valeurs saisies :
3. N° de sécurité sociale de l'employé : 254104940426058
4. Nombre d'heures travaillées : 150
5. Nombre de jours travaillés : 20
6.
7. Informations Employé :
8. Nom : Jouveinal
9. Prénom : Marie
10. Adresse : 5 rue des oiseaux
11. ...

184/257
Avec la configuration suivante :

on obtient les résultats suivants :

1. Chaîne des exceptions --------------------------------------


2. javax.xml.ws.soap.SOAPFaultException:L'employé de n°[xx] est introuvable
3. com.sun.xml.internal.ws.developer.ServerSideException:L'employé de n°[xx] est introuvable
4. Java Result: 1

On notera qu'alors que le service web [Metier] envoie une exception de type [PamException] l'exception reçue par le client est de
type [SOAPFaultException]. Même dans la chaîne des exceptions, on ne voit pas apparaître le type [PamException].

7.1.3 Le client swing du service web Metier

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 déployé sur le serveur Glassfish.

7.2 Service web implémenté par une application web


Nous nous plaçons maintenant dans le cadre de l'architecture suivante :

Client Conteneur
Conteneur web Jpa
du EJB3 Données
service web tcp-ip serveur Java EE

Le service web est assuré par une application web exécutée au sein du conteneur web du serveur Glassfish. Ce service web va
s'appuyer sur l'EJB [Metier] déployé lui dans le conteneur EJB3.

7.2.1 La partie serveur


Nous créons une application web :

3
1
2

• en [1], nous créons un nouveau projet


• en [2], ce projet est de type [Web Application]
• en [3], nous lui donnons le nom [mv-pam-ws-ejb-metier-dao-eclipselink]

185/257
6

• en [4], nous choisissons la version Java EE 6


• en [6], le projet créé

Sur le schéma ci-dessous, l'application web créée va s'exécuter dans le conteneur web. Elle va utiliser l'EJB [Metier] qui lui, sera
déployé dans le conteneur EJB du serveur.

Client Conteneur
Conteneur web Jpa
du EJB3 Données
service web tcp-ip serveur Java EE

Pour que l'application web créée ait accès aux classes associées à l'EJB [Metier], nous ajoutons aux bibliothèques de l'application
web [mv-pam-ws-ejb-metier-dao-eclipselink], la dépendance du serveur EJB [mv-pam-ejb-metier-dao-eclipselink] déjà étudié.

• en [1], on ajoute un projet aux dépendances du projet web,


• en [2], on sélectionne le projet [mv-pam-ejb-metier-dao-eclipselink],
• en [4], la portée de la dépendance est provided, c'est à dire qu'elle sera fournie par l'environnement d'exécution,
• en [5], la dépendance a été ajoutée.

Pour créer le même service web que précédemment, il nous faut :

• créer une classe taguée @Webservice


• avec deux méthodes calculerFeuilleSalaire et findAllEmployes taguées @WebMethod

Nous créons une classe [PamWsEjbMetier] dans un package [pam.ws] :

186/257
La classe [PamWsEjbMetier] est la suivante :

1. package pam.ws;
2.
3. import java.util.List;
4. import javax.ejb.EJB;
5. import javax.jws.WebMethod;
6. import javax.jws.WebService;
7. import jpa.Employe;
8. import metier.FeuilleSalaire;
9. import metier.IMetier;
10. import metier.IMetierLocal;
11.
12. @WebService
13. public class PamWsEjbMetier implements IMetier{
14.
15. @EJB
16. private IMetierLocal metier;
17.
18. @WebMethod
19. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int
nbJoursTravaillés) {
20. return metier.calculerFeuilleSalaire(SS, nbHeuresTravaillées, nbJoursTravaillés);
21. }
22.
23. @WebMethod
24. public List<Employe> findAllEmployes() {
25. return metier.findAllEmployes();
26. }
27.
28. }

• 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 dépendances du projet.
• ligne 12 : la classe est un service web
• ligne 13 : elle implémente l'interface IMetier définie dans le module EJB
• lignes 18-19 : la méthode calculerFeuilleSalaire est exposée comme méthode du service web
• lignes 23-24 : la méthode findAllEmployes est exposée comme méthode du service web
• lignes 15-16 : l'interface locale de l'EJB [Metier] est injectée dans le champ de la ligne 16. Nous utilisons l'interface locale
car l'application web et le module EJB s'exécutent dans la même JVM.
• lignes 20 et 25 : les méthodes calculerFeuilleSalaire et findAllEmployes délèguent leur traitement aux méthodes de même nom
de l'EJB [Metier]. La classe ne sert donc qu'à exposer à des clients distants les méthodes de l'EJB [Metier] comme des
méthodes d'un service web.

Dans Netbeans, l'application web est reconnue comme exposant un service web :

1 2
3

Pour déployer le service web sur le serveur Glassfish, il nous faut à la fois déployer :

• le module web dans le conteneur web du serveur


• le module EJB dans le conteneur EJB du serveur

Pour cela, nous avons besoin de créer une application de type [Enterprise Application] qui va déployer les deux modules en même
temps. Pour ce faire, il faut que les deux projets soient chargés dans Netbeans [2].

187/257
Ceci fait, nous créons un nouveau projet [3].

• en [4], nous choisissons un projet de type [Enterprise Application].


• en [5], nous donnons un nom au projet

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 déjà
créés et chargés dans Netbeans. Donc nous ne demandons pas la création de nouveaux modules.
• en [7], le projet d'entreprise [mv-pam-webapp-ear] ainsi créé. Un autre projet Maven a été créé en même temps [mv-pam-
webapp]. Nous ne nous en occuperons pas.
• en [8], nous ajoutons des dépendances au projet d'entreprise

188/257
9 10

• en [9], on ajoute le projet web de type war,


• en [10], on ajoute le projet EJB de type ejb,

11

• en [11], le projet d'entreprise avec ses deux dépendances.

Nous construisons le projet d'entreprise par un Clean and Build. Nous sommes quasiment prêts à le déployer sur le serveur Glassfish.
Auparavant il peut être nécessaire de décharger les applications déjà chargées 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
déployée [12]. En [13], on peut voir qu'elle a bien été déployée sur le serveur Glassfish.

Nous pouvons tester le service web qui vient d'être déployé :

189/257
1

• en [1], nous demandons à tester le service web [PamWsEjbMetier]


• en [2], la page de test. Nous laissons au lecteur le soin de conduire les tests.

7.2.2 La partie cliente

Travail à faire : en suivant la démarche décrite au paragraphe 7.1.2.1, page 181, construire un client console du service web
précédent.

7.3 Service web implémenté avec Spring et Tomcat

Nous nous plaçons maintenant dans le cadre de l'architecture suivante :

Client Conteneur web JPA


du Données
service web tcp-ip serveur Tomcat

Le service web est assuré par une application web exécutée au sein du conteneur web du serveur Tomcat. L'architecture de
l'application sera la suivante :

Couche Couche Couche Interface Implémentation Couche


[web] [metier] [DAO] [JPA] [Hibernate] [JDBC]

Spring
7

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 créons une application Maven de type web nommée [mv-pam-ws-spring-tomcat] [1]:

Nous modifions le fichier [pom.xml] pour y inclure les dépendances [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>

• lignes 3-7 : la dépendance sur le projet [mv-pam-spring-hibernate],


• lignes 8-17 : les dépendances sur le framework Apache CXF [http://cxf.apache.org/]. Celui-ci facilite la création de
services web.

Ce fichier [pom.xml] amène de nombreuses dépendances [2].

Revenons à l'architecture de l'application :

191/257
Couche Couche Couche Interface Implémentation Couche
[web] [metier] [DAO] [JPA] [Hibernate] [JDBC]

7 Spring

Les appels au service web que nous allons construire sont gérés par une servlet du framework CXF. Cela se traduit dans le fichier
[WEB-INF / web.xml] de la façon 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 dépendance sur Spring. Lignes 4-6 : un listener est déclaré. La classe correspondante va être
chargée en même 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 gérer les appels au service web que nous allons créer,
• lignes 13-16 : les URL traitées par la servlet CXF seront du type /ws/*. Les autres ne seront pas traitées par CXF.

Pour définir le service web, nous définissons une interface et son implémentattion :

192/257
L'interface [IWsMetier] sera la suivante :

1. package pam.ws;
2.
3. import javax.jws.WebService;
4. import metier.IMetier;
5.
6. @WebService
7. public interface IWsMetier extends IMetier{
8.
9. }

• ligne 7 : l'interface [IWsMetier] dérive de l'interface [IMetier] de la couche [métier] du projet [mv-pam-spring-hibernate],
• ligne 6 : l'interface [IWsMetier] est celle d'un service web.

La classe d'implémentation de cette interface est la suivante :

1. package pam.ws;
2.
3. import java.util.List;
4. import javax.jws.WebMethod;
5. import javax.jws.WebService;
6. import jpa.Employe;
7. import metier.FeuilleSalaire;
8. import metier.IMetier;
9.
10. @WebService
11. public class PamWsMetier implements IWsMetier {
12.
13. // couche métier
14. private IMetier metier;
15.
16. // constructeur
17. public PamWsMetier(){
18.
19. }
20.
21. @WebMethod
22. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillees, int
nbJoursTravailles) {
23. return metier.calculerFeuilleSalaire(SS, nbHeuresTravaillees, nbJoursTravailles);
24. }
25.
26. @WebMethod
27. public List<Employe> findAllEmployes() {
28. return metier.findAllEmployes();
29. }
30.
31. // getters et setters
32.
33. public void setMetier(IMetier metier) {
34. this.metier = metier;
35. }
36.

193/257
37. }

• ligne 11 : la classe [PamWsMetier] implémente l'interface définie précédemment,


• ligne 10 : définit la classe comme un service web,
• ligne 14 : la couche [métier] sera injectée par Spring,
• lignes 21, 26 : l'annotation @WebMethod fait d'une méthode, une méthode exposée par le service web,
• lignes 23, 28 : les méthodes sont implémentées à l'aide de la couche [métier].

Il nous reste à définir le contenu du fichier de configuration de Spring [applicationContext.xml] :

Son contenu est le suivant :

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. xmlns:jaxws="http://cxf.apache.org/jaxws"
5. xsi:schemaLocation="http://www.springframework.org/schema/beans
6. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
7. http://www.springframework.org/schema/tx
8. http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
9. http://cxf.apache.org/jaxws
10. http://cxf.apache.org/schemas/jaxws.xsd">
11.
12. <!-- Apache CXF -->
13. <import resource="classpath:META-INF/cxf/cxf.xml" />
14. <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
15. <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
16.
17. <!-- couches basses -->
18. <import resource="classpath:spring-config-metier-dao.xml" />
19.
20. <!-- service web -->
21. <bean id="wsMetier" class="pam.ws.PamWsMetier">
22. <property name="metier" ref="metier"/>
23. </bean>
24. <jaxws:endpoint id="wsmetier"
25. implementor="#wsMetier"
26. address="/metier">
27. </jaxws:endpoint>
28.
29. </beans>

• lignes 13-15 : on importe des fichiers de configuration Apache CXF. Ceux-ci sont cherchés dans le Classpath du projet
(attribut classpath:),
• lignes 4, 9, 10 : des espaces de noms spécifiques à Apache CXF sont déclarés,
• ligne 18 : on importe le fichier de configuration Spring du projet [mv-pam-spring-hibernate],
• lignes 21-23 : définissent le bean du service web avec sa dépendance sur la couche [métier] (ligne 22),
• lignes 24-27 : définissent le service web lui-même,
• ligne 25 : le bean Spring implémentant le service web est celui défini ligne 21 ;
• ligne 26 : définit l'URL à laquelle le service web sera disponible, ici /metier. Combinée à la forme que
doivent avoir les URL traitées par Apache CXF (cf fichier web.xml), cette URL devient /ws/metier.

194/257
Notre projet est prêt à être exécuté. Nous l'exécutons (Run) et demandons l'URL [http://localhost:8080/mv-pam-ws-spring-
tomcat/ws] dans un navigateur :

La page liste tous les services web déployés. Ici, il n'y en a qu'un. Nous suivons le lien WSDL :

Le texte affiché [1] est celui d'un fichier XML qui définit les fonctionnalités du service web, comment l'appeler et quelles réponses il
envoie. On notera l'URL [2] de ce fichier WSDL. Tous les clients du service web ont besoin de la connaître.

7.3.2 La partie cliente

Travail à faire : en suivant la démarche décrite au paragraphe 7.1.2.1, page 181, construire un client console du service web
précédent.

Note : pour indiquer l'URL du fichier WSDL du service web, on procèdera comme suit :

195/257
3

On mettra en [3], l'URL notée précédemment en [2].

196/257
8 Introduction à Java Server Faces

On lira le document Introduction à Java Server Faces, Primefaces et Primefaces mobile à l'URL
[http://tahe.developpez.com/java/primefaces]. On suivra le tutoriel sur Java Server Faces (JSF).

197/257
9 Version 5 - Application PAM Web / JSF

9.1 Architecture de l'application

L'architecture de l'application web PAM sera la suivante :

Couche Couche Couche Objets image Interface Implémentation Couche


[web / EJB3 EJB3 de la BD [JPA] [EclipseLink] [JDBC]
JSF] [metier] [DAO]

7 Serveur Glassfish v3

Dans cette version, le serveur Glassfish hébergera la totalité des couches de l'application :
• la couche [web] est hébergée par le conteneur de servlets du serveur (1 ci-dessous)
• les autres couches [metier, DAO, JPA] sont hébergées par le conteneur EJB3 du serveur (2 ci-dessous)

Conteneur web Conteneur EJB3 Jpa 3


Navigateur [web / jsf] [metier, DAO] 2 EclipseLink SGBD
1
HTTP serveur Java EE

Les éléments [metier, DAO] de l'application s'exécutant dans le conteneur EJB3 ont déjà été écrits dans l'application client / serveur
étudiée au paragraphe 6.1, page 162 et dont l'architecture était la suivante :

client serveur
Couche Couche Couche Couche Couche
[ui] [metier] [DAO] [JPA / [JDBC]
EclipseLink]

Jvm1 - Java SE Jvm2 – Java EE - serveur Glassfish

Les couches [metier, DAO] s'exécutaient dans le conteneur EJB3 du serveur Glassfish et la couche [ui] dans une application console
ou swing sur une autre machine :

client serveur

Client Conteneur Ejb3 Jpa / EclipseLink Données


Java SE serveur Java EE

Dans l'architecture de la nouvelle application :

198/257
Conteneur web Conteneur EJB3 Jpa 3
Navigateur [web / jsf] [metier, DAO] 2 EclipseLink SGBD
1
HTTP 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 implémentée avec Java Server Faces a une
architecture similaire à la suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche Données
Navigateur JSF1 [metier, dao, jpa]
4b JSF2 2c
Modèles 4a
JSFn

Cette architecture implémente le Design Pattern MVC (Modèle, Vue, Contrôleur). Le traitement d'une demande d'un client se déroule
de la façon suivante :

Si la demande est faite avec un GET, les deux étapes suivantes sont exécutées :

1. demande - le client navigateur fait une demande au contrôleur [Faces Servlet]. Celui-ci voit passer toutes les demandes des
clients. C'est la porte d'entrée de l'application. C'est le C de MVC.
2. réponse - le contrôleur C demande à la page JSF choisie de s'afficher. C'est la vue, le V de MVC. La page JSF utilise un modèle
M pour initialiser les parties dynamiques de la réponse qu'elle doit envoyer au client. Ce modèle est une classe Java qui peut faire
appel à la couche [métier] [4a] pour fournir à la vue V les données dont elle a besoin.

Si la demande est faite avec un POST, deux étapes supplémentaires s'insèrent entre la demande et la réponse :

1. demande - le client navigateur fait une demande au contrôleur [Faces Servlet].


2. traitement - le contrôleur C traite cette demande. Une demande POST est accompagnée de données qu'il faut traiter. Pour ce
faire, le contrôleur se fait aider par des gestionnaires d'événements spécifiques à l'application écrite [2a]. Ces gestionnaires
peuvent avoir besoin de la couche métier [2b]. Le gestionnaire de l'événement peut être amené à mettre à jour certains modèles
M [2c]. Une fois la demande du client traitée, celle-ci peut appeler diverses réponses. Un exemple classique est :
• une page d'erreurs si la demande n'a pu être traitée correctement
• une page de confirmation sinon
Le gestionnaire d'événement rend au contrôleur [Faces Servlet] un résultat de type chaîne de caractères appelée clé de
navigation.
3. navigation - le contrôleur choisit la page JSF (= vue) à envoyer au client. Ce choix se fait à partir de la clé de navigation rendue
par le gestionnaire d'événement.
4. réponse - la page JSF choisie va envoyer la réponse au client. Elle utilise son modèle M pour initialiser ses parties dynamiques.
Ce modèle peut lui aussi faire appel à la couche [métier] [4a] pour fournir à la page JSF les données dont elle a besoin.

Dans un projet JSF :


• le contrôleur C est la servlet [javax.faces.webapp.FacesServlet]. On trouve celle-ci dans la bibliothèque [JSF-api.jar].
• les vues V sont implémentées par des pages JSF.
• les modèles M et les gestionnaires d'événements sont implémentés par des classes Java souvent appelées "backing
beans".
• dans les version JSF 1.x la définition des beans ainsi que les règles de navigation d'une page à l'autre sont définies dans le
fichier [faces-config.xml]. On y trouve la liste des vues et les règles de transition de l'une à l'autre. A partir de la version JSF
2, les définitions 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

Lorsque l'application est demandée la première fois, on obtient la page suivante :

On remplit alors le formulaire puis on demande le salaire :

On obtient le résultat suivant :

200/257
C

Cette version calcule un salaire fictif. Il ne faut pas prêter attention au contenu de la page mais à sa mise en forme. Lorsqu'on utilise
le bouton [Raz], on revient à la page [A].

Les saisies erronées sont signalées, comme le montre l'exemple suivant :

9.3 Le projet Netbeans

Nous allons construire une première version de l'application où la couche [métier] sera simulée. Nous aurons l'architecture
suivante :

201/257
Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche
Navigateur JSF1 [metier]
4b JSF2 2c simulée
Modèles 4a
JSFn

Lorsque les gestionnaires d'événements ou les modèles demanderont des données à la couche [métier] [2b, 4a], celle-ci leur donnera
des données fictives. Le but est d'obtenir une couche web répondant correctement aux sollicitations de l'utilisateur. Lorsque ceci
sera atteint, il ne nous restera qu'à installer la couche serveur développée au paragraphe 6.1, page 162 :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche Données
Navigateur JSF1 [metier, dao, jpa]
4b JSF2 2c
Modèles 4a
JSFn

Ce sera la version 2 de la version web de notre application PAM.

Le projet Netbeans de la version 1 est le projet Maven suivant :

5
1
4

• en [1], les fichiers de configuration


• en [2], les pages XHTML et la feuille de style
• en [3], les classes de la couche [web]
• en [4], les objets échangés entre la couche [web] et la couche [métier] et la couche [métier] elle-même
• en [5], le fichier des messages pour l'internationalisation de l'application
• en [6], les dépendances de l'application

202/257
Nous passons en revue certains de ces éléments.

9.3.1 Les fichiers de configuration


Le fichier [web.xml] est celui généré par défaut 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>

• ligne 30 : [index.html] est la page d'accueil de l'application


• lignes 32-39 : configuration de la page d'exception

La page [exception.html] est tirée de [ref3]. Son code est le suivant :

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.
4. <html xmlns="http://www.w3.org/1999/xhtml"
5. xmlns:h="http://java.sun.com/JSF/html"
6. xmlns:f="http://java.sun.com/JSF/core">

203/257
7. <f:view locale="#{changeLocale.locale}">
8. <h:head>
9. <title>JSF</title>
10. <h:outputStylesheet library="css" name="styles.css"/>
11. </h:head>
12. <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 gérée par le code de l'application web provoquera l'affichage d'une page analogue à celle ci-
dessous :

Le fichier [faces-config.xml] sera le suivant :

1. <?xml version="1.0" encoding="UTF-8"?>


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="2.0"
4. xmlns="http://java.sun.com/xml/ns/javaee"
5. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
7.
8. <application>
9. <resource-bundle>
10. <base-name>
11. messages
12. </base-name>
13. <var>msg</var>
14. </resource-bundle>
15. <message-bundle>messages</message-bundle>
16. </application>
17. </faces-config>

On notera les points suivants :

• 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
• ligne 15 : définit le fichier [messages.properties] comme devant être exploré en priorité pour les messages d'erreur affichés
par les balises <h:messages> et <h:message>. Cela permet de redéfinir certains messages d'erreur par défaut de JSF. Cette
possibilité n'est pas utilisée ici.

9.3.2 La feuille de style

Le fichier [styles.css] est le suivant :

1. .libelle{
2. background-color: #ccffff;
3. font-family: 'Times New Roman',Times,serif;
4. font-size: 14px;
5. font-weight: bold
6. }
7. body{
8. background-color: #ffccff
9. }
10.
11. .error{
12. color: #ff3333
13. }
14.
15. .info{
16. background-color: #99cc00
17. }
18.
19. .titreInfos{
20. background-color: #ffcc00
21. }

Voici des exemples de code JSF utilisant ces styles :

<h:outputText value="#{msg['form.infos.employé']}"
styleClass="titreInfos"/>

<h:panelGrid columns="3" rowClasses="libelle,info">

<h:message for="heuresTravaillées" styleClass="error"/>

9.3.3 Le fichier des messages

Le fichier des messages [messages_fr.properties] est le suivant :

1. form.titre=Feuille de salaire
2. form.comboEmployes.libell\u00e9=Employ\u00e9
3. form.heuresTravaill\u00e9es.libell\u00e9=Heures travaill\u00e9es
4. form.joursTravaill\u00e9s.libell\u00e9=Jours travaill\u00e9s
5. form.heuresTravaill\u00e9es.required=Indiquez le nombre d'heures travaill\u00e9es
6. form.heuresTravaill\u00e9es.validation=Donn\u00e9e incorrecte
7. form.joursTravaill\u00e9s.required=Indiquez le nombre de jours travaill\u00e9s
8. form.joursTravaill\u00e9s.validation=Donn\u00e9e incorrecte
9. form.btnSalaire.libell\u00e9=Salaire
10. form.btnRaz.libell\u00e9=Raz
11. exception.header=L'exception suivante s'est produite
12. exception.httpCode=Code HTTP de l'erreur

205/257
13. exception.message=Message de l'exception
14. exception.requestUri=Url demand\u00e9e lors de l'erreur
15. exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
16. form.infos.employ\u00e9=Informations Employ\u00e9
17. form.employe.nom=Nom
18. form.employe.pr\u00e9nom=Pr\u00e9nom
19. form.employe.adresse=Adresse
20. form.employe.ville=Ville
21. form.employe.codePostal=Code postal
22. form.employe.indice=Indice
23. form.infos.cotisations=Informations Cotisations sociales
24. form.cotisations.csgrds=CSGRDS
25. form.cotisations.csgd=CSGD
26. form.cotisations.retraite=Retraite
27. form.cotisations.secu=S\u00e9curit\u00e9 sociale
28. form.infos.indemnites=Informations Indemnit\u00e9s
29. form.indemnites.salaireHoraire=Salaire horaire
30. form.indemnites.entretienJour=Entretien / Jour
31. form.indemnites.repasJour=Repas / Jour
32. form.indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
33. form.infos.salaire=Informations Salaire
34. form.salaire.base=Salaire de base
35. form.salaire.cotisationsSociales=Cotisations sociales
36. form.salaire.entretien=Indemnit\u00e9s d'entretien
37. form.salaire.repas=Indemnit\u00e9s de repas
38. form.salaire.net=Salaire net

Ces messages sont tous utilisés dans la page [index.xhtml] à l'exception de ceux des lignes 11-15 utilisés dans la page
[exception.xhtml].

9.3.4 La portée des beans

Le bean [web.forms.Form] aura une portée request :

1. import java.io.Serializable;
2. import javax.faces.bean.ManagedBean;
3. import javax.faces.bean.RequestScoped;
4.
5. @ManagedBean
6. @RequestScoped
7. public class Form implements Serializable {

Le bean [web.utils.ChangeLocale] aura une portée session :

1. package web.utils;
2.
3. import java.io.Serializable;
4. import javax.faces.bean.ManagedBean;
5. import javax.faces.bean.SessionScoped;
6.
7. @ManagedBean
8. @ApplicationScoped
9. public class ChangeLocale implements Serializable{
10. // la locale des pages
11. private String locale="fr";
12.
13. public ChangeLocale() {
14. }
15.
16. public String setFrenchLocale(){
17. locale="fr";
18. return null;

206/257
19. }
20.
21. public String setEnglishLocale(){
22. locale="en";
23. return null;
24. }
25.
26. public String getLocale() {
27. return locale;
28. }
29.
30. public void setLocale(String locale) {
31. this.locale = locale;
32. }
33.
34.
35. }

9.3.5 La couche [métier]

La couche [métier] implémente l'interface IMetierLocal suivante :

1. package metier;
2.
3. import java.util.List;
4. import javax.ejb.Local;
5. import jpa.Employe;
6.
7. @Local
8. public interface IMetierLocal {
9. // obtenir la feuille de salaire
10. FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int
nbJoursTravaillés );
11. // liste des employés
12. List<Employe> findAllEmployes();
13. }

Cette interface est celle utilisée dans la partie serveur de l'application client / serveur décrite au paragraphe 6.1, page 162.

La classe Metier que nous allons utiliser pour tester la couche [web] implémente cette interface de la façon 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 employés
9. private List<Employe> listEmployes;
10.
11. // obtenir la feuille de salaire
12. public FeuilleSalaire calculerFeuilleSalaire(String SS,
13. double nbHeuresTravaillées, int nbJoursTravaillés) {
14. // on récupère 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. // liste des employés
21. public List<Employe> findAllEmployes() {
22. if(listEmployes==null){
23. // on crée une liste de deux employés
24. listEmployes=new ArrayList<Employe>();
25. listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des
oiseaux","St Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
26. listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St
Marcel","49014",new Indemnite(1,1.93,2,3,12)));
27. // dictionnaire des employes indexé par le n° SS
28. for(Employe e:listEmployes){
29. hashEmployes.put(e.getSS(),e);
30. }
31. }
32. // on rend la liste des employés
33. return listEmployes;
34. }
35. }

Nous laissons au lecteur le soin de décrypter ce code. On notera la méthode utilisée : afin de ne pas avoir à mettre en place la partie
EJB de l'application, nous simulons la couche [métier]. Lorsque la couche [web] sera déclarée correcte, nous pourrons alors la
remplacer par la véritable couche [métier].

9.4 Le formulaire [index.xhtml] et son modèle [Form.java]

Nous construisons maintenant la page XHTML du formulaire ainsi que son modèle.

Lectures conseillées dans [ref3] :


• exemple n° 3 (mv-jsf2-03) pour la listes des balises utilisables dans un formulaire
• exemple n° 4 (mv-jsf2-04) pour les listes déroulantes remplies par le modèle
• exemple n° 6 (mv-jsf2-06) pour la validation des saisies
• exemple n° 7 (mv-jsf2-07) pour la gestion du bouton [Raz]

9.4.1 étape 1

Question : Construire le formulaire [index.xhtml] et son modèle [Form.java] nécessaires pour obtenir la page suivante :

1 4 5
2 3

Les composants de saisie sont les suivants :

n° id type JSF modèle rôle


1 comboEmployes <h:selectOneMenu> String comboEmployesValue contient la liste des employés sous la forme
List<Employe> getEmployes() "prénom nom".
2 heuresTravaillees <h:inputText> String heuresTravaillées nombre d'heures travaillées - nombre réel
3 joursTravailles <h:inputText> String joursTravaillés nombre de jours travaillés - nombre entier

208/257
4 btnSalaire <h:commandButton> lance le calcul du salaire
5 btnRaz <h:commandButton> remet le formulaire dans son état premier

• la méthode getEmployes rendra une liste d'employés qu'elle obtiendra auprès de la couche [métier]. Les objets affichés par le
combo auront pour attribut itemValue, le n° SS de l'employé et pour attribut itemLabel, une chaîne constituée du
prénom et du nom de l'employé.
• les boutons [Salaire] et [Raz] ne seront pour l'instant pas connectés à des gestionnaires d'événement.
• la validité des saisies sera vérifiée.

Testez cette version. Vérifiez notamment que les erreurs de saisie sont bien signalées.

Note : il est important que les attributs id des composants de la page ne comportent pas de caractères accentués. Avec
Glassfish 3.1.2, ça plante l'application.

9.4.2 étape 2

Question : compléter le formulaire [index.xhtml] et son modèle [Form.java] pour obtenir la page suivante une fois que le bouton
[Salaire] a été cliqué :

Le bouton [Salaire] sera connecté au gestionnaire d'événement calculerSalaire du modèle. Cette méthode utilisera la méthode
calculerFeuilleSalaire de la couche [métier]. Cette feuille de salaire sera faite pour l'employé sélectionné en [1].

Dans le modèle, la feuille de salaire sera représentée par le champ privé suivant :

private FeuilleSalaire feuilleSalaire;

disposant des méthodes get et set.

Pour obtenir les informations contenues dans cet objet, on pourra écrire dans la page JSF, des expressions comme la suivante :

<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>

L'expression de l'attribut value sera évaluée comme suit :

209/257
[form].getFeuilleSalaire().getEmploye().getNom() où [form] représente une instance de la classe [Form.java]. Le lecteur pourra vérifier que
les méthodes get utilisées ici existent bien respectivement dans les classes [Form], [FeuilleSalaire] et [Employe]. Si ce n'était pas le cas,
une exception serait lancée lors de l'évaluation de l'expression.

Testez cette nouvelle version.

9.4.3 étape 3

Question : compléter le formulaire [index.xhtml] et son modèle [Form.java] pour obtenir les informations supplémentaires
suivantes :

On suivra la même démarche que précédemment. Il y a une difficulté pour le signe monétaire euro que l'on a en [1] par exemple.
Dans le cadre d'une application internationalisée, il serait préférable d'avoir le format d'affichage et le signe monétaire de la locale
utilisée (en, de, fr, ...). Cela peut s'obtenir de la façon 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 affichée :

• ligne 1 : affiche le paramètre {0} qui est un nombre (number) représentant une somme d'argent (currency)
• ligne 2 : la balise <f:param> donne une valeur au paramètre {0}. Une deuxième balise <f:param> donnerait une valeur au
paramètre noté {1} et ainsi de suite.

9.4.4 étape 4

Lectures conseillées : exemple n° 7 (mv-jsf2-07) dans [ref3].

Question : compléter le formulaire [index.xhtml] et son modèle [Form.java] pour gérer le bouton [Raz].

Le bouton [Raz] ramène le formulaire dans l'état qu'il avait lorsqu'on l'a demandé la première fois par un GET. Il y a plusieurs
difficultés ici. Certaines ont été expliquées dans [ref3].

210/257
Le formulaire rendu par le bouton [Raz] n'est pas tout le formulaire mais seulement la partie saisie de celui-ci :

Ce résultat peut être obtenu avec une balise <f:subview> utilisée de la façon 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 affichée ou cachée. Tout composant peut être
affiché ou caché grâce à 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 modèle, alors l'affichage du composant peut être contrôlé par programme.

Ci-dessus, on contrôlera l'affichage de la vue viewInfos avec le champ suivant :

private boolean viewInfosIsRendered;

accompagné de ses méthodes get et set. Les méthodes gérant les clics sur les bouton [Salaire] [Raz] mettront à jour ce booléen selon
que la vue viewInfos doit être affichée ou non.

211/257
10 Version 6 - Intégration de la couche web dans une architecture 3
couches JSF / EJB

10.1 Architecture de l'application

L'architecture de l'application web précédente était la suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche
Navigateur JSF1 [metier]
4b JSF2 2c simulée
Modèles 4a
JSFn

Nous remplaçons la couche [métier] simulée, par les couches [métier, DAO, JPA] implémentées par des EJB au paragraphe 6.1, page
162 :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couches Données
Navigateur JSF1 [metier, DAO, JPA]
4b JSF2 2c
Modèles 4a
JSFn

10.2 Le projet Netbeans de la couche web

Le projet Netbeans de la version web n° 2 est obtenue par copie du projet précédent :

212/257
2

• [1] : on copie le nouveau projet et on le colle dans l'onglet [Projects],


• [2] : on lui donne un nom et on fixe son dossier,
• [3] : le projet créé,

Le nouveau projet porte le même nom que l'ancien. Nous changeons cela :

• [4] : nous renommons le projet,


• [5] : on change son nom ainsi que celui de l'artifactID.

213/257
Nous avons peu de modifications à faire pour adapter cette couche web à son nouvel environnement : la couche [metier] simulée
doit être remplacée 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 présents dans le précédent projet.
• pour compenser cette suppression, nous ajoutons aux dépendances du projet web, le projet du serveur EJB construit au
paragraphe 6.1, page 162.

3 4

2
5

• en [1], on ajoute une dépendance au projet,


• en [2], on sélectionne le projet Maven de la couche [métier]. En [3], on précise son type et en [4] sa portée. Celle-ci est
provided pour indiquer que celui-ci sera fourni (provided) au module web par son environnement de travail. Nous verrons
prochainement qu'il lui sera fourni par une application d'entreprise,
• en [5], la dépendance a été ajoutée.

Le fichier [pom.xml] est alors le suivant :

1. <dependencies>
2. <dependency>
3. <groupId>${project.groupId}</groupId>
4. <artifactId>mv-pam-ejb-metier-dao-eclipselink</artifactId>
5. <version>${project.version}</version>
6. <scope>provided</scope>
7. <type>ejb</type>
8. </dependency>
9. <dependency>
10. <groupId>javax</groupId>
11. <artifactId>javaee-web-api</artifactId>
12. <version>6.0</version>
13. <scope>provided</scope>
14. </dependency>
15. </dependencies>

Nous pouvons maintenant supprimer les paquetages de la couche [métier] qui ne sont plus nécessaires :

214/257
Il nous faut également modifier le code du bean [Form.java] :

1.public class Form {


2.
3. public Form() {
4. }
5.
6. // couche métier
7. private IMetierLocal metier=new Metier();
8.
9. // champs du formulaire
10....

La ligne 7 instanciait la couche [métier] simulée. Désormais elle doit référencer la couche [métier] réelle. Le code précédent devient
le suivant :

1. public class Form {


2.
3. public Form() {
4. }
5.
6. // couche métier
7. @EJB
8. private IMetierLocal metier;
9.
10. // champs du formulaire

Ligne 7, l'annotation @EJB indique au conteneur de servlets qui va exécuter la couche web, d'injecter dans le champ metier de la
couche 8, l'EJB qui implémente l'interface locale IMetierLocal.

Pourquoi l'interface locale IMetierLocal plutôt que l'interface IMetierRemote ? Parce que la couche web et la couche EJB s'exécutent
dans la même JVM :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche Données
Navigateur JSF1 [metier, dao, jpa]
4b JSF2 2c
Modèles 4a
JSFn

Conteneur de servlets Conteneur Ejb

Les classes du conteneur de servlets peuvent référencer directement les classes EJB du conteneur EJB.

C'est tout. Notre couche web est prête. La transformation a été simple parce qu'on avait pris soin de simuler la couche [métier] par
une classe qui respectait l'interface IMetierLocal implémentée par la couche [métier] réelle.

10.3 Le projet Netbeans de l'application d'entreprise

Une application d'entreprise permet le déploiement 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 procédons de la façon suivante :

215/257
4
3

1
2

• en [1], on crée un nouveau projet


• en [2], on choisit la catégorie [Maven]
• en [3], on choisit le type [Enterprise Application]
• en [4], on donne un nom au projet

7
5

• en [5], nous choisissons Java EE 6


• en [6], un projet d'entreprise peut comprendre jusqu'à deux types de modules :
• un module EJB
• un module web

On peut demander en même temps que la création du projet d'entreprise, la création de ces deux modules qui seront vides
au départ. Un projet d'entreprise ne sert qu'au déploiement des modules qui en font partie. En-dehors de ça, c'est une
coquille vide. Ici, nous voulons déployer :
• un module web existant [mv-pam-jsf2-alone]. Il est donc inutile de créer un nouveau module web.
• un module EJB existant [mv-pam-ejb-metier-dao-eclipselink]. Là également, il est inutile d'en créer un nouveau.
En [6], nous créons un projet d'entreprise sans modules. Nous allons lui ajouter ses modules web et ejb ultérieurement.
• en [7], deux projets Maven ont été créés. Le projet d'entreprise est celui qui a le suffixe ear. L'autre projet est un projet
Maven parent du précédent. Nous ne nous en occuperons pas.

Nous ajoutons le module web et le module EJB au projet d'entreprise :

2 3

216/257
• en [1], ajout d'une nouvelle dépendance,
• en [2], ajout du projet EJB [mv-pam-ejb-metier-dao-eclipselink]. On notera son type ejb,
• en [3], ajout du projet web [mv-pam-jsf2-ejb]. On notera son type war.

Le fichier [pom.xml] est alors le suivant :

1. <dependencies>
2. <dependency>
3. <groupId>${project.groupId}</groupId>
4. <artifactId>mv-pam-ejb-metier-dao-eclipselink</artifactId>
5. <version>${project.version}</version>
6. <type>ejb</type>
7. </dependency>
8. <dependency>
9. <groupId>${project.groupId}</groupId>
10. <artifactId>mv-pam-jsf2-ejb</artifactId>
11. <version>${project.version}</version>
12. <type>war</type>
13. </dependency>
14. </dependencies>

Avant le déploiement 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 déployer l'application d'entreprise [mv-pam-webapp-ear] :

• en [1], l'application d'entreprise est déployée


• en [2], l'application d'entreprise [mv-pam-webapp-ear] a bien été déployée.

Dans le navigateur, on obtient la page suivante :

• en [1], l'URL demandée


• en [2], la liste des employés a été remplie avec les éléments de la table [Employes] de la base dbpam.

217/257
Le lecteur est invité à refaire les tests de la version web n° 1. Voici un exemple d'exécution :

218/257
11 Version 7 - Application web PAM multi-vues / multi-pages
Nous revenons ici à l'architecture initiale où la couche [métier] était simulée. Nous savons désormais que celle-ci peut être aisément
remplacée par la couche [métier] réelle. La couche [métier] simulée facilite les tests.

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaires
3 d'évts couche
Navigateur JSF1 [metier]
4b JSF2 2c simulée
Modèles 4a
JSFn

Une application JSF est de type MVC (Modèle Vue Contrôleur) :


• la servlet [Faces Servlet] est le contrôleur générique fourni par JSF. Ce contrôleur est étendu par les gestionnaires
d'événements spécifiques à l'application. Les gestionnaires d'événements rencontrés jusqu'ici étaient des méthodes des
classes servant de modèles aux pages JSF
• les pages JSF envoient les réponses au navigateur client. Ce sont les vues de l'application.
• les pages JSF comportent des éléments dynamiques qu'on appelle le modèle de la page. On rappelle que pour certains
auteurs, le modèle recouvre les entités manipulées par l'application, telles par exemple les classes FeuilleSalaire ou Employe.
Pour distinguer ces deux modèles, on pourra parler de modèle de l'application et modèle d'une page JSF.

Dans l'architecture JSF, le passage d'une page JSFi à une page JSFj peut être problématique.
• la page JSFi a été affichée. A partir de cette page, l'utilisateur provoque un POST par un événement quelconque [1]
• en JSF, ce POST sera traité [2a,2b] en général par une méthode C du modèle M i de la page JSFi. On peut dire que la
méthode C est un contrôleur secondaire.
• si à l'issue de cette méthode, la page JSFj doit être affichée, le contrôleur C doit :
1. mettre à jour [2c] le modèle Mj de la page JSFj
2. rendre [2a] au contrôleur principal, la clé de navigation qui permettra l'affichage de la page JSF j
L'étape 1 nécessite que le modèle Mi de la page JSFi ait une référence sur modèle Mj de la page JSFj. Cela complique un
peu les choses rendant les modèles Mi dépendant les uns des autres. En effet, le gestionnaire C du modèle M i qui met à
jour le modèle Mj doit connaître celui-ci. Si on est amené à changer le modèle M j, on sera alors amené à changer le
gestionnaire C du modèle Mi.
Il existe un cas où la dépendance des modèles entre-eux peut être évitée : celui où il y a un unique modèle M qui sert à
toutes les pages JSF. Cette architecture n'est utilisable que dans les applications n'ayant que quelques vues mais elle se
révèle alors très 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]
2a 2b
1
Faces Servlet [MC]
3 Form.java couche
JSF1 [metier]
4 Modèle M
simulée
JSF2 Gestionnaires
d'évts
JSFn

219/257
11.1 Les vues de l'application

Les différentes vues présentées à l'utilisateur seront les suivantes :

- la vue [VueSaisies] qui présente le formulaire de simulation

- la vue [VueSimulation] utilisée pour afficher le résultat détaillé de la simulation :

- 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 :

– la vue [VueErreur] qui indique une ou plusieurs erreurs :

11.2 Le projet Netbeans de la couche [web]

Le projet Netbeans de cette version sera le projet Maven suivant :

221/257
5 6
1

3
7

4
2

• en [1], les fichiers de configuration,


• en [2], les pages JSF,
• en [3], la feuille de style et l'image de fond des vues,
• en [4], les classes de la couche [web],
• en [5], les couches basses de l'application,
• en [6], le fichier des messages pour l'internationalisation de l'application,
• en [7], les dépendances du projet.

Nous passons en revue certains de ces éléments.

11.2.1 Les fichiers de configuration

Les fichiers [web.xml] et [faces-config.xml] sont identiques à ceux du projet précédent à l'exception d'un détail 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>

• lignes 4-6 : la page d'accueil est la page [saisie.xhtml]

11.2.2 La feuille de style

Le fichier [styles.css] est le suivant :

1. .simulationsHeader {
2. text-align: center;
3. font-style: italic;
4. color: Snow;
5. background: Teal;
6. }

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. }

Voici des exemples de code JSF utilisant ces styles :

Vue Simulations

<h:dataTable value="#{form.simulations}" var="simulation"


headerClass="simulationsHeaders"
columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisat
ionsSociales,simuSalaireNet">

Le résultat obtenu est le suivant :

Vue Erreur

<h:dataTable value="#{form.erreurs}" var="erreur"


headerClass="erreursHeaders"
columnClasses="erreurClasse,erreurMessage">

11.2.3 Le fichier des messages

Le fichier des messages [messages_fr.properties] est le suivant :

1. form.titre=Simulateur de calcul de paie


2. form.comboEmployes.libell\u00e9=Employ\u00e9
3. form.heuresTravaill\u00e9es.libell\u00e9=Heures travaill\u00e9es
4. form.joursTravaill\u00e9s.libell\u00e9=Jours travaill\u00e9s
5. form.heuresTravaill\u00e9es.required=Indiquez le nombre d'heures travaill\u00e9es
6. form.heuresTravaill\u00e9es.validation=Donn\u00e9e incorrecte
7. form.joursTravaill\u00e9s.required=Indiquez le nombre de jours travaill\u00e9s
8. form.joursTravaill\u00e9s.validation=Donn\u00e9e incorrecte
9. form.btnSalaire.libell\u00e9=Salaire
10. form.btnRaz.libell\u00e9=Raz
11. exception.header=L'exception suivante s'est produite
12. exception.httpCode=Code HTTP de l'erreur
13. exception.message=Message de l'exception

224/257
14. exception.requestUri=Url demand\u00e9e lors de l'erreur
15. exception.servletName=Nom de la servlet demand\u00e9e lorsque l'erreur s'est produite
16. form.infos.employ\u00e9=Informations Employ\u00e9
17. form.employe.nom=Nom
18. form.employe.pr\u00e9nom=Pr\u00e9nom
19. form.employe.adresse=Adresse
20. form.employe.ville=Ville
21. form.employe.codePostal=Code postal
22. form.employe.indice=Indice
23. form.infos.cotisations=Informations Cotisations sociales
24. form.cotisations.csgrds=CSGRDS
25. form.cotisations.csgd=CSGD
26. form.cotisations.retraite=Retraite
27. form.cotisations.secu=S\u00e9curit\u00e9 sociale
28. form.infos.indemnites=Informations Indemnit\u00e9s
29. form.indemnites.salaireHoraire=Salaire horaire
30. form.indemnites.entretienJour=Entretien / Jour
31. form.indemnites.repasJour=Repas / Jour
32. form.indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
33. form.infos.salaire=Informations Salaire
34. form.salaire.base=Salaire de base
35. form.salaire.cotisationsSociales=Cotisations sociales
36. form.salaire.entretien=Indemnit\u00e9s d'entretien
37. form.salaire.repas=Indemnit\u00e9s de repas
38. form.salaire.net=Salaire net
39. form.menu.faireSimulation=| Faire la simulation
40. form.menu.effacerSimulation=| Effacer la simulation
41. form.menu.enregistrerSimulation=| Enregistrer la simulation
42. form.menu.retourSimulateur=| Retour au simulateur
43. form.menu.voirSimulations=| Voir les simulations
44. form.menu.terminerSession=| Terminer la session
45. simulations.headers.nom=Nom
46. simulations.headers.nom=Nom
47. simulations.headers.prenom=Pr\u00e9nom
48. simulations.headers.heuresTravaillees=Heures travaill\u00e9es
49. simulations.headers.joursTravailles=Jours Travaill\u00e9s
50. simulations.headers.salaireBase=Salaire de base
51. simulations.headers.indemnites=Indemnit\u00e9s
52. simulations.headers.cotisationsSociales=Cotisations sociales
53. simulations.headers.salaireNet=SalaireNet
54. simulations.headers.numero=N\u00b0
55. erreur.titre=Une erreur s'est produite.
56. erreur.exceptions=Cha\u00eene des exceptions
57. exception.type=Type de l'exception
58. exception.message=Message associ\u00e9

11.2.4 La couche [métier]

La couche [métier] simulée est modifiée de la façon suivante :

1. package metier;
2.
3. ...
4. public class Metier implements IMetierLocal, Serializable {
5.
6. // liste des employes
7. private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
8. private List<Employe> listEmployes;
9.
10. // obtenir la feuille de salaire
11. public FeuilleSalaire calculerFeuilleSalaire(String SS,
12. double nbHeuresTravaillées, int nbJoursTravaillés) {

225/257
13. // on récupère l'employé
14. Employe e=hashEmployes.get(SS);
15. // on rend une exception si l'employé n'existe pas
16. if(e==null){
17. throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
18. }
19. // on rend une feuille de salaire fictive
20. 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 employés
24. public List<Employe> findAllEmployes() {
25. if(listEmployes==null){
26. // on crée une liste de trois employés
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 brûlerie","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 brûlerie","St Marcel","49014",new
Indemnite(1,1.93,2,3,12)));
36. }
37. // on rend la liste des employés
38. return listEmployes;
39. }
40. }

• ligne 8 : la liste des employés


• ligne 7 : la même liste sous forme de dictionnaire indexé par le n° SS des employés
• lignes 24-39 : la méthode findAllEmployes qui rend la liste des employés. Cette méthode crée une liste en dur et la référence
par le champ employés de la ligne 8.
• lignes 27-33 : une liste et un dictionnaire de deux employés sont créés
• ligne 35 : un employé est ajouté à la liste employes (ligne 8) mais pas au dictionnaire hashEmployes (ligne 7). Ceci pour
qu'il apparaisse dans le combo des employés mais qu'ensuite il ne soit pas reconnu par la méthode calculerFeuilleSalaire
(ligne 14) afin que celle-ci lance une exception (ligne 17).
• lignes 11-21 : la méthode calculerFeuilleSalaire
• ligne 14 : l'employé est cherché dans le dictionnaire hashEmployes via son n° SS. S'il n'est pas trouvé, une exception est
lancée (lignes 16-18). Ainsi aurons-nous une exception pour l'employé de n° SS X ajouté ligne 35 dans la liste employés
mais pas dans le dictionnaire hashEmployes.
• ligne 20, une feuille de salaire fictive est créée et rendue.

11.3 Les beans de l'application

Il y aura trois beans de trois portées différentes :

226/257
11.3.1 Le bean ApplicationData

Le bean ApplicationData sera de portée application :

1. package web.beans.application;
2.
3. import java.io.Serializable;
4. import java.util.logging.Logger;
5. import javax.annotation.PostConstruct;
6. import javax.enterprise.context.ApplicationScoped;
7. import javax.inject.Named;
8. import metier.IMetierLocal;
9. import metier.Metier;
10.
11. @Named
12. @ApplicationScoped
13. public class ApplicationData implements Serializable {
14.
15. // couche métier
16. private IMetierLocal metier = new Metier();
17. // logger
18. private boolean logsEnabled = true;
19. private final Logger logger = Logger.getLogger("pam");
20.
21. public ApplicationData() {
22. }
23.
24. @PostConstruct
25. public void init() {
26. // log
27. if (isLogsEnabled()) {
28. logger.info("ApplicationData");
29. }
30. }
31.
32. // getters et setters
33. ...
34. }

• ligne 11 : l'annotation @Named fait de la classe un bean managé. On notera qu'à la différence du projet précédent, on n'a
pas utilisé l'annotation @ManagedBean. La raison en est que la référence de cette classe doit être injectée dans une autre
classe à l'aide de l'annotation @Inject et que celle-ci n'injecte que des classes annotées @Named.
• ligne 12 : l'annotation @ApplicationScoped fait de la classe, un objet de portée 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 précédent.

Le bean ApplicationData sert à deux choses :

• ligne 16 : maintenir une référence sur la couche [métier],


• lignes 18-19 : définir un logueur qui pourra être utilisé par les autres beans pour faire des logs sur la console de Glassfish.

11.3.2 Le bean SessionData

Le bean SessionData sera de portée session :

1. package web.beans.session;
2.
3. import java.io.Serializable;
4. import java.util.ArrayList;
5. import java.util.List;
6. import 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 managés,
• ligne 14 : il est de portée session (@SessionScoped),
• lignes 18-19 : une référence sur le bean ApplicationData lui est injecté (@Inject),
• lignes 21-32 : les données 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 dernière simulation enregistrée,
• ligne 23 : la dernière simulation qui a été faite,

228/257
• lignes 25-30 : les options du menu,
• ligne 32 : la locale de l'application.

Lignes 39-44, la méthode init est exécutée après instanciation de la classe (@PostConstruct). Ici, elle n'est utilisée que pour laisser
une trace de son exécution. On doit pouvoir vérifier qu'elle n'est exécutée qu'une fois par utilisateur puisque la classe est de portée
session. Ligne 42, la méthode utilise le logueur défini dans la classe ApplicationData. C'est pour cette raison qu'on avait besoin
d'injecter une référence sur ce bean (lignes 18-19).

11.3.3 Le bean Form

Le bean Form est de portée requête :

1. package web.beans.request;
2.
3. import java.util.ArrayList;
4. import java.util.List;
5. import javax.enterprise.context.RequestScoped;
6. import javax.faces.context.FacesContext;
7. import javax.inject.Inject;
8. import javax.inject.Named;
9. import javax.servlet.http.HttpServletRequest;
10. import jpa.Employe;
11. import metier.FeuilleSalaire;
12. import web.beans.application.ApplicationData;
13. import web.beans.session.*;
14. import web.entities.Erreur;
15. import web.entities.Simulation;
16.
17. @Named
18. @RequestScoped
19. public class Form {
20.
21. public Form() {
22. }
23. // autres beans
24. @Inject
25. private ApplicationData applicationData;
26. @Inject
27. private SessionData sessionData;
28. // le modèle des vues
29. private String comboEmployesValue = "";
30. private String heuresTravaillées = "";
31. private String joursTravaillés = "";
32. private Integer numSimulationToDelete;
33. private List<Erreur> erreurs = new ArrayList<Erreur>();
34. private FeuilleSalaire feuilleSalaire;
35.
36.
37. // liste des employés
38. public List<Employe> getEmployes(){
39. return applicationData.getMetier().findAllEmployes();
40. }
41.
42. // action du menu
43. public String faireSimulation() {
44. ...
45. }
46.
47. public String enregistrerSimulation() {
48. ...
49. }
50.

229/257
51. public String effacerSimulation() {
52. ...
53. }
54.
55. public String voirSimulations() {
56. ...
57. }
58.
59. public String retourSimulateur() {
60. ...
61. }
62.
63. public String terminerSession() {
64. ...
65. }
66.
67. public String retirerSimulation() {
68. ...
69. }
70.
71. private void razFormulaire() {
72. ...
73. }
74.
75. // getters et setters
76. ...
77. }

• ligne 17, la classe est un bean managé (@Named),


• ligne 18, de portée requête (@RequestScoped),
• lignes 24-25 : injection d'une référence sur le bean de portée application ApplicationData,
• lignes 26-27 : injection d'une référence sur le bean de portée session SessionData.

11.4 Les pages de l'application

11.4.1 [layout.xhtml]

La page [layout.xhtml] assure la mise en page de toutes les vues :

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"

230/257
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. <h:head>
10. <title><h:outputText value="#{msg['form.titre']}"/></title>
11. <h:outputStylesheet library="css" name="styles.css"/>
12. </h:head>
13. <script type="text/javascript">
14. function raz(){
15. // on change les valeurs postées
16. document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
17. document.forms['formulaire'].elements['formulaire:heuresTravaillees'].value="0";
18. document.forms['formulaire'].elements['formulaire:joursTravailles'].value="0";
19. }
20. </script>
21. <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>

Toute vue est constituée des éléments suivants :

• un entête affiché par le fragment [entete.xhtml] (ligne 24),


• un corps formé de deux fragments appelés part1 (lignes 26-28) et part2 (ligne 29),
• ligne 8 : l'attribut locale assure l'internationalisation des pages. Ici il n'y en aura qu'une : fr_FR,
• lignes 14-19 : un code Javascript.

L'affichage de la page [layout.xhtml] est le suivant :

• en [1], l'entête [entete.xhtml],


• en [2], le fragment part1.

231/257
11.4.2 L'entête [entete.xhtml]

Le code de la page [entete.xhtml] est le suivant :

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. <ui:composition>
9. <!-- entete -->
10. <h:panelGrid columns="2">
11. <h:panelGroup>
12. <h2><h:outputText value="#{msg['form.titre']}"/></h2>
13. </h:panelGroup>
14. <h:panelGroup>
15. <h:panelGrid columns="1">
16. <h:commandLink id="cmdFaireSimulation"
value="#{msg['form.menu.faireSimulation']}" action="#{form.faireSimulation}"
rendered="#{sessionData.menuFaireSimulationIsRendered}"/>
17. <h:commandLink id="cmdEffacerSimulation" onclick="raz()"
value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}"
rendered="#{sessionData.menuEffacerSimulationIsRendered}"/>
18. <h:commandLink id="cmdEnregistrerSimulation" immediate="true"
value="#{msg['form.menu.enregistrerSimulation']}" action="#{form.enregistrerSimulation}"
rendered="#{sessionData.menuEnregistrerSimulationIsRendered}"/>
19. <h:commandLink id="cmdVoirSimulations" immediate="true"
value="#{msg['form.menu.voirSimulations']}" action="#{form.voirSimulations}"
rendered="#{sessionData.menuVoirSimulationsIsRendered}"/>
20. <h:commandLink id="cmdRetourSimulateur" immediate="true"
value="#{msg['form.menu.retourSimulateur']}" action="#{form.retourSimulateur}"
rendered="#{sessionData.menuRetourSimulateurIsRendered}"/>
21. <h:commandLink id="cmdTerminerSession" immediate="true"
value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}"
rendered="#{sessionData.menuTerminerSessionIsRendered}"/>
22. </h:panelGrid>
23. </h:panelGroup>
24. </h:panelGrid>
25. <hr/>
26. </ui:composition>
27. </html>

• ligne 12 : le titre de l'application,


• lignes 16-21 : les six liens correspondant aux six actions que peut faire l'utilisateur. Ces liens sont contrôlés (attribut
rendered) par des booléens du bean SessionData,
• ligne 17 : un clic sur le lien [Effacer la simulation] provoque l'exécution de la fonction Javascript raz. Celle-ci a été définie
dans le modèle [layout.xhtml],
• ligne 18 : l'attribut immediate=true fait que la validité des données n'est pas vérifiée avant exécution de la méthode
[Form].enregistrerSimulation. C'est voulu. On peut vouloir enregistrer la dernière simulation faite il y a une minute même si les
données saisies depuis dans le formulaire (mais pas encore validées) ne sont pas valides. Il est fait de même pour les
actions [Voir les simulations], [Effacer la simulation], [Retour au simulateur] et [Terminer la session].

11.5 Les cas d'utilisation de l'application

11.5.1 Affichage de la page d'accueil

232/257
La page d'accueil est la page [saisie.xhtml] 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:f="http://java.sun.com/JSF/core"
6. xmlns:ui="http://java.sun.com/JSF/facelets">
7.
8. <ui:composition template="layout.xhtml">
9. <ui:define name="part1">
10. <ui:include src="saisie2.xhtml"/>
11. </ui:define>
12. </ui:composition>
13. </html>

• ligne 8 : elle s'affiche à l'intérieur de la page [layout.xhtml],


• ligne 9 : à la place du fragment nommé part1. Dans ce fragment, on affiche la page [saisie2.xhtml] :

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. <h:panelGrid columns="3">
9. <h:outputText value="#{msg['form.comboEmployes.libellé']}"/>
10. <h:outputText value="#{msg['form.heuresTravaillées.libellé']}"/>
11. <h:outputText value="#{msg['form.joursTravaillés.libellé']}"/>
12. <h:selectOneMenu id="comboEmployes" value="#{form.comboEmployesValue}">
13. <f:selectItems .../>
14. </h:selectOneMenu>
15. <h:inputText id="heuresTravaillees" value="#{form.heuresTravaillées}" required="true"
requiredMessage="#{msg['form.heuresTravaillées.required']}"
validatorMessage="#{msg['form.heuresTravaillées.validation']}">
16. <f:validateDoubleRange minimum="0" maximum="300"/>
17. </h:inputText>
18. <h:inputText id="joursTravailles" value="#{form.joursTravaillés}" required="true"
requiredMessage="#{msg['form.joursTravaillés.required']}"
validatorMessage="#{msg['form.joursTravaillés.validation']}">
19. <f:validateLongRange minimum="0" maximum="31"/>
20. </h:inputText>
21. <h:panelGroup></h:panelGroup>
22. <h:message for="heuresTravaillees" styleClass="error"/>
23. <h:message for="joursTravailles" styleClass="error"/>
24. </h:panelGrid>
25. <hr/>
26. </html>

Ce code affiche la vue suivante :

233/257
Question : compléter la ligne 13 du code XHTML. La liste des éléments du combo des employés est fournie par une méthode du
bean [Form]. Ecrire cette méthode. Les éléments affichés dans le combo auront leur propriété itemValue égale au n° SS d'un
employé et la propriété itemLabel sera une chaîne formée du prénom et du nom de celui-ci.

Question : comment doit être initialisé le bean [SessionData] pour que, lors de la requête GET initiale faite au formulaire, le menu
de l'entête soit celui montré ci-dessus ?

11.5.2 L'action [faireSimulation]

Le code JSF de l'action [faireSimulation] est le suivant :

<h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}"


action="#{form.faireSimulation}" rendered="#{sessionData.menuFaireSimulationIsRendered}"/>

L'action [faireSimulation] calcule une feuille de salaire :

A partir de la page précédente, on obtient le résultat suivant :

234/257
La simulation est affichée avec la page [simulation.xhtml] 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:f="http://java.sun.com/JSF/core"
6. xmlns:ui="http://java.sun.com/JSF/facelets">
7.
8. <ui:composition template="layout.xhtml">
9. <ui:define name="part1">
10. <ui:include src="saisie2.xhtml"/>
11. </ui:define>
12. <ui:define name="part2">
13. <h:outputText value="#{msg['form.infos.employé']}" styleClass="titreInfos"/>
14. <br/><br/>
15. <h:panelGrid columns="3" rowClasses="libelle,info">
16. <h:outputText value="#{msg['form.employe.nom']}"/>
17. <h:outputText value="#{msg['form.employe.prénom']}"/>
18. <h:outputText value="#{msg['form.employe.adresse']}"/>
19. <h:outputText value="#{form.feuilleSalaire.employe.nom}"/>
20. <h:outputText value="#{form.feuilleSalaire.employe.prenom}"/>
21. <h:outputText value="#{form.feuilleSalaire.employe.adresse}"/>
22. </h:panelGrid>
23. <h:panelGrid columns="3" rowClasses="libelle,info">
24. <h:outputText value="#{msg['form.employe.ville']}"/>
25. <h:outputText value="#{msg['form.employe.codePostal']}"/>
26. <h:outputText value="#{msg['form.employe.indice']}"/>

235/257
27. <h:outputText value="#{form.feuilleSalaire.employe.ville}"/>
28. <h:outputText value="#{form.feuilleSalaire.employe.codePostal}"/>
29. <h:outputText value="#{form.feuilleSalaire.employe.indemnite.indice}"/>
30. </h:panelGrid>
31. <br/>
32. <h:outputText value="#{msg['form.infos.cotisations']}" styleClass="titreInfos"/>
33. <br/><br/>
34. <h:panelGrid columns="4" rowClasses="libelle,info">
35. <h:outputText value="#{msg['form.cotisations.csgrds']}"/>
36. <h:outputText value="#{msg['form.cotisations.csgd']}"/>
37. <h:outputText value="#{msg['form.cotisations.retraite']}"/>
38. <h:outputText value="#{msg['form.cotisations.secu']}"/>
39. <h:outputText value="#{form.feuilleSalaire.cotisation.csgrds} %"/>
40. <h:outputText value="#{form.feuilleSalaire.cotisation.csgd} %"/>
41. <h:outputText value="#{form.feuilleSalaire.cotisation.retraite} %"/>
42. <h:outputText value="#{form.feuilleSalaire.cotisation.secu} %"/>
43. </h:panelGrid>
44. <br/>
45. <h:outputText value="#{msg['form.infos.indemnites']}" styleClass="titreInfos"/>
46. <br/><br/>
47. <h:panelGrid columns="4" rowClasses="libelle,info">
48. <h:outputText value="#{msg['form.indemnites.salaireHoraire']}"/>
49. <h:outputText value="#{msg['form.indemnites.entretienJour']}"/>
50. <h:outputText value="#{msg['form.indemnites.repasJour']}"/>
51. <h:outputText value="#{msg['form.indemnites.congésPayés']}"/>
52. <h:outputFormat value="{0,number,currency}">
53. <f:param value="#{form.feuilleSalaire.employe.indemnite.baseHeure}"/>
54. </h:outputFormat>
55. <h:outputFormat value="{0,number,currency}">
56. <f:param value="#{form.feuilleSalaire.employe.indemnite.entretienJour}"/>
57. </h:outputFormat>
58. <h:outputFormat value="{0,number,currency}">
59. <f:param value="#{form.feuilleSalaire.employe.indemnite.repasJour}"/>
60. </h:outputFormat>
61. <h:outputText value="#{form.feuilleSalaire.employe.indemnite.indemnitesCP} %"/>
62. </h:panelGrid>
63. <br/>
64. <h:outputText value="#{msg['form.infos.salaire']}" styleClass="titreInfos"/>
65. <br/><br/>
66. <h:panelGrid columns="4" rowClasses="libelle,info">
67. <h:outputText value="#{msg['form.salaire.base']}"/>
68. <h:outputText value="#{msg['form.salaire.cotisationsSociales']}"/>
69. <h:outputText value="#{msg['form.salaire.entretien']}"/>
70. <h:outputText value="#{msg['form.salaire.repas']}"/>
71. <h:outputFormat value="{0,number,currency}">
72. <f:param value="#{form.feuilleSalaire.elementsSalaire.salaireBase}"/>
73. </h:outputFormat>
74. <h:outputFormat value="{0,number,currency}">
75. <f:param value="#{form.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
76. </h:outputFormat>
77. <h:outputFormat value="{0,number,currency}">
78. <f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesEntretien}"/>
79. </h:outputFormat>
80. <h:outputFormat value="{0,number,currency}">
81. <f:param value="#{form.feuilleSalaire.elementsSalaire.indemnitesRepas}"/>
82. </h:outputFormat>
83. </h:panelGrid>
84. <br/>
85. <h:panelGrid columns="3" columnClasses="libelle,col2,info">
86. <h:outputText value="#{msg['form.salaire.net']}"/>
87. <h:panelGroup></h:panelGroup>
88. <h:outputFormat value="{0,number,currency}">
89. <f:param value="#{form.feuilleSalaire.elementsSalaire.salaireNet}"/>

236/257
90. </h:outputFormat>
91. </h:panelGrid>
92. </ui:define>
93. </ui:composition>
94. </html>

• ligne 8, la page [simulation.xhtml] s'insère dans la page [layout.xhtml],


• lignes 9-11 : la zone des saisies est affichée dans le fragment part1 de la page layout,
• lignes 12-91 : la simulation est affichée dans le fragment part2 de la page layout.

Question : écrire la méthode [faireSimulation] de la classe [Form]. La simulation sera enregistrée dans le bean SessionData.

11.5.3 La gestion des erreurs

On veut pouvoir gérer proprement les exceptions qui peuvent survenir lors du calcul d'une simulation. Pour cela le code de la
méthode [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 précédentes
14. ...
15. // on crée 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.}

La liste des erreurs créée ligne 15 est la suivante :

1. // le modèle des vues


2. ...
3. private List<Erreur> erreurs=new ArrayList<Erreur>();
4....

La classe Erreur est définie comme suit :

1. package web.entities;
2.
3. public class Erreur {
4.
5. public Erreur() {
6. }
7.
8. // champ
9. private String classe;
10. private String message;

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 mémorise le nom de la classe dans le champ classe et le message dans le champ
message.

La liste des erreurs construite dans la méthode [faireSimulation] est constituée de :


• l'exception initiale th de type Throwable qui s'est produite,
• de sa cause th.getCause() si elle en a une,
• de la cause de la cause h.getCause().getCause() si elle existe.
• ...

Voici un exemple de liste d'erreurs :

Ci-dessus, l'employé [Z Y] n'existe pas dans le dictionnaire des employés utilisé par la couche [métier] simulée. Dans ce cas, la
couche [métier] simulée lance une exception (ligne 6 ci-dessous) :

1. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int


nbJoursTravaillés) {
2. // on récupère 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.}

Le résultat obtenu est le suivant :

Cette vue est affichée par la page [erreurs.xhtml] suivante :

238/257
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. <ui:composition template="layout.xhtml">
9. <ui:define name="part1">
10. <h3><h:outputText value="#{msg['erreur.titre']}"/></h3>
11. <h:dataTable value="#{form.erreurs}" var="erreur"
12. headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">
13. <f:facet name="header">
14. <h:outputText value="#{msg['erreur.exceptions']}"/>
15. </f:facet>
16. <h:column>
17. <f:facet name="header">
18. <h:outputText value="#{msg['exception.type']}"/>
19. </f:facet>
20. <h:outputText value="#{erreur.classe}"/>
21. </h:column>
22. <h:column>
23. <f:facet name="header">
24. <h:outputText value="#{msg['exception.message']}"/>
25. </f:facet>
26. <h:outputText value="#{erreur.message}"/>
27. </h:column>
28. </h:dataTable>
29. </ui:define>
30. </ui:composition>
31. </html>

Question : compléter la méthode [faireSimulation] afin que lors d'une exception, elle fasse afficher la vue [vueErreur].

11.5.4 L'action [effacerSimulation]

L'action [effacerSimulation] permet à l'utilisateur de retrouver un formulaire vide :

Le code JSF du lien [effacerSimulation] est le suivant :

<h:commandLink id="cmdEffacerSimulation" onclick="raz()"


value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}"
rendered="#{sessionData.menuEffacerSimulationIsRendered}"/>

Un clic sur le lien [EffacerSimulation] provoque d'abord l'appel de la fonction Javascript raz(). Cette méthode est définie dans la
page [layout.xhtml] :

239/257
1. <script type="text/javascript">
2. function raz(){
3. // on change les valeurs postées
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 lignes 4-6 changent les valeurs postées. On notera que


• les valeurs postées 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
après exécution de la fonction Javascript raz.

Au cours du post, les valeurs postées vont suivre un cheminement normal : validation puis affectation aux champs du modèle. Ceux-
ci sont les suivants dans la classe [Form] :

// le modèle des vues


private String comboEmployesValue;
private String heuresTravaillées;
private String joursTravaillés;
...

Ces trois champs vont recevoir les trois valeurs postées {"0","0","0"}. Une fois cette affectation opérée, la méthode effacerSimulation
va être exécutée.

Question : écrire la méthode [effacerSimulation] de la classe [Form]. On fera en sorte que :


- seule la zone des saisies soit affichée,
- le combo soit positionné sur son 1er élément,
- les zones de saisie heuresTravaillees et joursTravailles affichent des chaînes vides.

11.5.5 L'action [enregistrerSimulation]

Le code JSF du lien [enregistrerSimulation] est le suivant :

<h:commandLink id="cmdEnregistrerSimulation" immediate="true"


value="#{msg['form.menu.enregistrerSimulation']}" action="#{form.enregistrerSimulation}"
rendered="#{sessionData.menuEnregistrerSimulationIsRendered}"/>

L'action [enregistrerSimulation] associée 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>();

La classe Simulation est la suivante :

1. package web.entities;
2.
3. import metier.FeuilleSalaire;
4.
5. public class Simulation {
6.
7. public Simulation() {
8. }
9.
10. // champs d'une simulation
11. private Integer num;
12. private FeuilleSalaire feuilleSalaire;
13. private String heuresTravaillées;
14. private String joursTravaillés;

240/257
15.
16. // constructeur
17. public Simulation(Integer num,String heuresTravaillées, String joursTravaillés,
FeuilleSalaire feuilleSalaire){
18. this.setNum(num);
19. this.setFeuilleSalaire(feuilleSalaire);
20. this.setHeuresTravaillées(heuresTravaillées);
21. this.setJoursTravaillés(joursTravaillés);
22. }
23.
24. public double getIndemnites(){
25. return feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+
feuilleSalaire.getElementsSalaire().getIndemnitesRepas();
26. }
27.
28. // getters et setters
29. ...
30. }

Cette classe permet de mémoriser une simulation faite par l'utilisateur :


• ligne 11 : le n° de la simulation,
• ligne 12 : la feuille de salaire qui a été calculée,
• ligne 13 : le nombre d'heures travaillées,
• ligne 14 : le nombre de jours travaillés.

Voici un exemple d'enregistrement :

A partir de la page précédente, on obtient le résultat qui suit :

Le n° de la simulation est un nombre incrémenté à 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
• ligne 2 : le n° de la dernière simulation faite.

La méthode [enregistrerSimulation] peut procéder ainsi :

• récupérer le n° de la dernière simulation dans le bean [SessionData] et l'incrémenter,


• ajouter la nouvelle simulation à la liste des simulations maintenue par la classe [SessionData],
• faire afficher le tableau des simulations :

Le tableau des simulations est affiché par la page [simulations.xhtml] :

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. <ui:composition template="layout.xhtml">
9. <ui:define name="part1">
10. <!-- tableau des simulations -->
11. <h:dataTable value="#{sessionData.simulations}" var="simulation"
12. headerClass="simulationsHeaders"
columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simu
CotisationsSociales,simuSalaireNet">
13. <h:column>
14. <f:facet name="header">
15. <h:outputText value="#{msg['simulations.headers.numero']}"/>
16. </f:facet>
17. <h:outputText value="#{simulation.num}"/>
18. </h:column>
19. <h:column>
20. <f:facet name="header">
21. <h:outputText value="#{msg['simulations.headers.nom']}"/>
22. </f:facet>
23. <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
24. </h:column>
25. <h:column>
26. <f:facet name="header">
27. <h:outputText value="#{msg['simulations.headers.prenom']}"/>
28. </f:facet>
29. <h:outputText value="#{simulation.feuilleSalaire.employe.prenom}"/>
30. </h:column>
31. <h:column>
32. <f:facet name="header">
33. <h:outputText value="#{msg['simulations.headers.heuresTravaillees']}"/>
34. </f:facet>
35. <h:outputText value="#{simulation.heuresTravaillées}"/>
36. </h:column>
37. <h:column>

242/257
38. <f:facet name="header">
39. <h:outputText value="#{msg['simulations.headers.joursTravailles']}"/>
40. </f:facet>
41. <h:outputText value="#{simulation.joursTravaillés}"/>
42. </h:column>
43. <h:column>
44. <f:facet name="header">
45. <h:outputText value="#{msg['simulations.headers.salaireBase']}"/>
46. </f:facet>
47. <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireBase}"/>
48. </h:column>
49. <h:column>
50. <f:facet name="header">
51. <h:outputText value="#{msg['simulations.headers.indemnites']}"/>
52. </f:facet>
53. <h:outputText value="#{simulation.indemnites}"/>
54. </h:column>
55. <h:column>
56. <f:facet name="header">
57. <h:outputText value="#{msg['simulations.headers.cotisationsSociales']}"/>
58. </f:facet>
59. <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>

• ligne 8, la page [simulations.xhtml] s'insère à l'intérieur de la page [layout.xhtml],


• ligne 9, à la place du fragment nommé part1,
• ligne 11, la balise <h:dataTable> utilise le champ #{sessionData.simulations} comme source de données, c.a.d. le champ
suivant :

1. // simulations
2.private List<Simulation> simulations;

- l'attribut var="simulation" fixe le nom de la variable représentant la simulation courante à l'intérieur 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
Le code JSF de la colonne Nom est le suivant :

1. <h:column>
2. <f:facet name="header">
3. <h:outputText value="#{msg['simulations.headers.nom']}"/>
4. </f:facet>
5. <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
6. </h:column>

• lignes 2-4 : la balise <f:facet name="header"> définit le titre de la colonne


• ligne 5 : le nom de l'employé est écrit :
• simulation fait référence à l'attribut var de la balise <h:dataTable ...> :

<h:dataTable value="#{sessionData.simulations}" var="simulation" ...>

simulation désigne la simulation courante de la liste des simulations : d'abord la 1ère, puis la 2ème, ...
• simulation.feuilleSalaire fait référence au champ feuilleSalaire de la simulation courante
• simulation.feuilleSalaire.employe fait référence au champ employe du champ feuilleSalaire
• simulation.feuilleSalaire.employe.nom fait référence au champ nom du champ employe

La même technique est répétée pour toutes les colonnes du tableau. Il y a une difficulté pour la colonne Indemnités qui est
générée 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 méthode simulation.getIndemnites(). Il suffit donc que
cette méthode existe. Le champ indemnites peut lui ne pas exister. La méthode getIndemnites doit rendre le total des indemnités de
l'employé. Cela nécessite un calcul intermédiaire car ce total n'est pas disponible directement dans la feuille de salaire. La méthode
getIndemnites est donnée page 240.

Question : écrire la méthode [enregistrerSimulation] de la classe [Form].

11.5.6 L'action [retourSimulateur]

Le code JSF du lien [retourSimulateur] est le suivant :

<h:commandLink id="cmdRetourSimulateur" immediate="true"


value="#{msg['form.menu.retourSimulateur']}" action="#{form.retourSimulateur}"
rendered="#{sessionData.menuRetourSimulateurIsRendered}"/>

L'action [retourSimulateur] associée au lien permet à l'utilisateur de revenir de la vue [vueSimulations] à la vue [vueSaisies] :

244/257
Le résultat obtenu :

Question : écrire la méthode [retourSimulateur] de la classe [Form]. Le formulaire de saisie présenté doit être vide comme ci-
dessus.

11.5.7 L'action [voirSimulations]

Le code JSF du lien [voirSimulations] est le suivant :

<h:commandLink id="cmdVoirSimulations" immediate="true"


value="#{msg['form.menu.voirSimulations']}" action="#{form.voirSimulations}"
rendered="#{sessionData.menuVoirSimulationsIsRendered}"/>

L'action [voirSimulations] associée au lien permet à l'utilisateur d'avoir le tableau des simulations, ceci quelque soit l'état de ses
saisies :

245/257
Le résultat obtenu :

Question : écrire la méthode [voirSimulations] de la classe [Form].

On fera en sorte que si la liste des simulations est vide, la vue affichée soit [vueSimulationsVides] :

Pour afficher la vue ci-dessus, on utilisera la page [simulationsVides.xhtml] 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:f="http://java.sun.com/JSF/core"
6. xmlns:ui="http://java.sun.com/JSF/facelets">
7.
8. <ui:composition template="layout.xhtml">
9. <ui:define name="part1">
10. <h2>Votre liste de simulations est vide.</h2>
11. </ui:define>
12. </ui:composition>
13. </html>

11.5.8 L'action [retirerSimulation]

L'utilisateur peut retirer des simulations de sa liste :

246/257
Le résultat obtenu est le suivant :

Si ci-dessus, on retire la dernière simulation, on obtiendra le résultat suivant :

Le code JSF de la colonne [Retirer] du tableau des simulations est le suivant :

1. <h:dataTable value="#{form.simulations}" var="simulation"


2. headerClass="simulationsHeaders"
columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simu
CotisationsSociales,simuSalaireNet">
3. ...
4. <h:column>
5. <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
6. <f:setPropertyActionListener target="#{form.numSimulationToDelete}"
value="#{simulation.num}"/>
7. </h:commandLink>
8. </h:column>
9. </h:dataTable>

• ligne 5 : le lien [Retirer] est associé à la méthode [retirerSimulation] de la classe [Form]. Cette méthode a besoin de
connaître 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 désigne un champ du modèle auquel la valeur de l'attribut
value sera affectée. Ici le n° de la simulation à retirer #{simulation.num} sera affectée au champ
numSimulationToDelete de la classe [Form] :

1. // le modèle des vues


2. ...
3. private Integer numSimulationToDelete;

Lorsque la méthode [retirerSimulation] de la classe [Form] s'exécutera, elle pourra utiliser la valeur qui aura été stockée
auparavant dans le champ numSimulationToDelete.

Question : écrire la méthode [retirerSimulation] de la classe [Form].

11.5.9 L'action [terminerSession]

Le code JSF du lien [Terminer la session] est le suivant :

247/257
<h:commandLink id="cmdTerminerSession" immediate="true"
value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}"
rendered="#{sessionData.menuTerminerSessionIsRendered}"/>

L'action [terminerSession] associée 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 vidée. Par ailleurs, la numérotation des simulations repart à 1.

Question : écrire la méthode [terminerSession] de la classe [Form].

11.6 Intégration de la couche web dans une architecture 3 couches JSF / EJB

L'architecture de l'application web précédente était la suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java couche
JSF1 [metier]
4 Modèle M
simulée
JSF2 Gestionnaires
d'évts
JSFn

Nous remplaçons la couche [métier] simulée, par les couches [métier, DAO, JPA] implémentées par des EJB au paragraphe 6.1, page
162 :

248/257
Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java
couche
JSF1 [metier, DAO, JPA]
4 Modèle M
JSF2 Gestionnaires
d'évts
JSFn

Travail pratique : réaliser l'intégration des couches JSF et EJB en suivant la méthodologie du paragraphe 10, page 212.

249/257
12 Version 8 : Portage de l'application dans un environnement Spring /
Tomcat

Question : En suivant l'exemple " Application exemple – 02 : rdvmedecins-jsf2-spring " de [ref3], portez l'application précédente
dans un environnement Spring / Tomcat / Hibernate.

La nouvelle application web est [mv-pam-jsf2-spring-multipages]. L'interface web ne change pas :

Voici une démarche possible :

• créer un projet Maven de type [Java Application] nommé [mv-pam-spring-metier],


• ajouter les dépendances nécessaires,
• copier les éléments du projet EJB précédent dans le nouveau projet Spring,
• corriger les erreurs qui apparaissent dans le projet Spring,
• créer le fichier [persistence.xml] qui configure la couche JPA,
• créer le fichier de configuration de Spring. A ce stade, le projet [mv-pam-spring-metier] doit être correct. Imaginez un test
JUnit qui en apporte la preuve,
• créer un projet Maven de type [Web Application],
• ajouter les dépendances nécessaires (JSF et la couche [métier]),
• copier les éléments du projet JSF / EJB de la version précédente dans le nouveau projet JSF / Spring,
• corriger les erreurs qui apparaissent,
• copier le fichier de configuration de Spring du projet [mv-pam-spring-metier] dans le projet web,
• ajouter au bean [ApplicationData] le code permettant d'instancier les beans Spring,
• compléter le fichier de configuration [faces-config.xml],
• tester l'application web.

250/257
13 Version 9 : Implémentation de la couche web avec Primefaces

Pré-requis : on lira " Introduction à Primefaces " dans [ref3].

Question 1 : construire un nouveau projet Maven de type [Web Application] où les pages XHTML de l'exemple précédent seraient
construites avec des composants Primefaces. On ne changera rien aux beans.

La page d'accueil utilisera les composants <p:panel>, <p:inputText>, <p:selectOneMenu>, <p:message> :

Une saisie erronée :

Une simulation :

251/257
La liste des simulations utilise les composants <p:dataTable>, <p:commandLink> :

Les méthodes 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>

• ligne 15 : une barre de menu Primefaces,


• ligne 16 : une option de menu dans cette barre,
• ligne 17 : un élément de cette option de menu.

Le modèle n'est pas affecté par ce changement. La nouvelle vue est la suivante :

253/257
254/257
Table des matières
1 ARCHITECTURE D'UNE APPLICATION JAVA EN COUCHES......................................................................................4
2 LES OUTILS DU DOCUMENT..............................................................................................................................................7
2.1 MAVEN.......................................................................................................................................................................................7
2.1.1 INTRODUCTION.........................................................................................................................................................................7
2.1.2 EXÉCUTION DU PROJET...........................................................................................................................................................11
2.1.3 LE SYSTÈME DE FICHIERS D'UN PROJET MAVEN........................................................................................................................12
2.1.4 LE DÉPÔT MAVEN LOCAL........................................................................................................................................................13
2.1.5 CHERCHER UN ARTIFACT AVEC MAVEN....................................................................................................................................14
3 JPA EN RÉSUMÉ....................................................................................................................................................................19
3.1 LA PLACE DE JPA DANS UNE ARCHITECTURE EN COUCHES......................................................................................................19
3.2 JPA – EXEMPLES.....................................................................................................................................................................19
3.2.1 EXEMPLE 1 - REPRÉSENTATION OBJET D'UNE TABLE UNIQUE.....................................................................................................19
3.2.1.1 La table [personne]...........................................................................................................................................................19
3.2.1.2 L'entité [Personne]............................................................................................................................................................20
3.2.2 CONFIGURATION DE LA COUCHE JPA.......................................................................................................................................23
3.2.3 EXEMPLE 2 : RELATION UN-À-PLUSIEURS.................................................................................................................................26
3.2.3.1 Le schéma de la base de données......................................................................................................................................26
3.2.3.2 Les objets @Entity représentant la base de données........................................................................................................26
3.3 L'API DE LA COUCHE JPA......................................................................................................................................................29
3.4 LES REQUÊTES JPQL..............................................................................................................................................................31
3.4.1 LA TABLE [MEDECINS].......................................................................................................................................................32
3.4.2 LA TABLE [CLIENTS]...........................................................................................................................................................32
3.4.3 LA TABLE [CRENEAUX]......................................................................................................................................................33
3.4.4 LA TABLE [RV]......................................................................................................................................................................33
3.4.5 GÉNÉRATION DE LA BASE........................................................................................................................................................34
3.4.6 LA COUCHE [JPA]..................................................................................................................................................................35
3.4.7 LE PROJET NETBEANS.............................................................................................................................................................36
3.4.8 GÉNÉRATION DE LA COUCHE [JPA].........................................................................................................................................36
3.4.9 CRÉATION D'UNE CONNEXION NETBEANS À LA BASE DE DONNÉES............................................................................................36
3.4.10 CRÉATION D'UNE UNITÉ DE PERSISTANCE...............................................................................................................................37
3.4.11 GÉNÉRATION DES ENTITÉS JPA.............................................................................................................................................42
3.4.12 LES ENTITÉS JPA GÉNÉRÉES.................................................................................................................................................43
3.4.13 LE CODE D'ACCÈS AUX DONNÉES...........................................................................................................................................52
3.5 LIENS ENTRE CONTEXTE DE PERSISTANCE ET SGBD...............................................................................................................54
3.5.1 LA CLASSE PERSONNE............................................................................................................................................................54
3.5.2 LE PROGRAMME DE TEST........................................................................................................................................................55
3.5.3 LA CONFIGURATION D'HIBERNATE DANS [PERSISTENCE.XML]....................................................................................................57
3.5.4 LES RÉSULTATS.......................................................................................................................................................................58
4 VERSION 1 : ARCHITECTURE SPRING / JPA.................................................................................................................60
4.1 LA BASE DE DONNÉES...............................................................................................................................................................60
4.2 MODE DE CALCUL DU SALAIRE D'UNE ASSISTANTE MATERNELLE.............................................................................................61
4.3 FONCTIONNEMENT DE L'APPLICATION CONSOLE.....................................................................................................................62
4.4 FONCTIONNEMENT DE L'APPLICATION GRAPHIQUE.................................................................................................................63
4.5 CRÉATION DE LA BASE DE DONNÉES.........................................................................................................................................64
4.6 IMPLÉMENTATION JPA............................................................................................................................................................66
4.6.1 COUCHE JPA / HIBERNATE.....................................................................................................................................................66
4.6.1.1 La base de données...........................................................................................................................................................67
4.6.1.2 Configuration de la couche JPA........................................................................................................................................67
4.6.1.3 Les dépendances...............................................................................................................................................................70
4.6.1.4 Les entités JPA..................................................................................................................................................................71
4.6.1.5 Le code de la classe principale.........................................................................................................................................73
4.6.1.6 Tests..................................................................................................................................................................................74
4.6.2 COUCHE JPA / ECLIPSELINK...................................................................................................................................................77
4.6.3 TRAVAIL À FAIRE.....................................................................................................................................................................80
4.6.4 LAZY OU EAGER ?..................................................................................................................................................................80
4.6.5 TRAVAIL À FAIRE.....................................................................................................................................................................85
4.6.6 POUR LA SUITE.......................................................................................................................................................................86
4.7 LES INTERFACES DES COUCHES [METIER] ET [DAO]...............................................................................................................88
4.8 LA CLASSE [PAMEXCEPTION]..................................................................................................................................................94
4.9 LA COUCHE [DAO] DE L'APPLICATION [PAM].......................................................................................................................96
4.9.1 IMPLÉMENTATION...................................................................................................................................................................96

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'IMPLÉMENTATION [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 EXÉCUTION........................................................................................................................................................................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 ÉVÉNEMENTS DE L'INTERFACE GRAPHIQUE.....................................................................................................................119
4.12.4 INITIALISATION DE L'INTERFACE GRAPHIQUE.........................................................................................................................121
4.12.5 GESTIONNAIRES D'ÉVÉNEMENTS..........................................................................................................................................123
4.12.6 EXÉCUTION DE L'INTERFACE GRAPHIQUE..............................................................................................................................123
4.13 IMPLÉMENTATION 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 BIBLIOTHÈQUES DES PROJETS..........................................................................................................................................135
5.1.3 CONFIGURATION DE LA COUCHE JPA / ECLIPSELINK / OPENEJB............................................................................................136
5.1.4 IMPLÉMENTATION DE LA COUCHE [DAO] PAR DES EJB.........................................................................................................136
5.1.5 IMPLÉMENTATION 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 DONNÉES [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 Entités sérialisables.........................................................................................................................................................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 Déploiement 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 IMPLÉMENTÉ PAR UN EJB...............................................................................................................................175
7.1.1 LA PARTIE SERVEUR..............................................................................................................................................................175

256/257
7.1.2 LA PARTIE CLIENTE...............................................................................................................................................................178
7.1.2.1 Le projet Netbeans du client console..............................................................................................................................178
7.1.2.2 Le client console du service web Metier.........................................................................................................................180
7.1.3 LE CLIENT SWING DU SERVICE WEB METIER...........................................................................................................................182
7.2 SERVICE WEB IMPLÉMENTÉ PAR UNE APPLICATION WEB........................................................................................................182
7.2.1 LA PARTIE SERVEUR..............................................................................................................................................................182
7.2.2 LA PARTIE CLIENTE...............................................................................................................................................................187
7.3 SERVICE WEB IMPLÉMENTÉ AVEC SPRING ET TOMCAT..........................................................................................................187
7.3.1 LA PARTIE SERVEUR..............................................................................................................................................................188
7.3.2 LA PARTIE CLIENTE...............................................................................................................................................................192
8 INTRODUCTION À JAVA SERVER FACES.....................................................................................................................194
9 VERSION 5 - APPLICATION PAM WEB / JSF................................................................................................................195
9.1 ARCHITECTURE DE L'APPLICATION........................................................................................................................................195
9.2 FONCTIONNEMENT DE L'APPLICATION...................................................................................................................................197
9.3 LE PROJET NETBEANS...........................................................................................................................................................198
9.3.1 LES FICHIERS DE CONFIGURATION..........................................................................................................................................200
9.3.2 LA FEUILLE DE STYLE...........................................................................................................................................................202
9.3.3 LE FICHIER DES MESSAGES....................................................................................................................................................202
9.3.4 LA PORTÉE DES BEANS..........................................................................................................................................................203
9.3.5 LA COUCHE [MÉTIER]...........................................................................................................................................................204
9.4 LE FORMULAIRE [INDEX.XHTML] ET SON MODÈLE [FORM.JAVA]...........................................................................................205
9.4.1 ÉTAPE 1...............................................................................................................................................................................205
9.4.2 ÉTAPE 2...............................................................................................................................................................................206
9.4.3 ÉTAPE 3...............................................................................................................................................................................207
9.4.4 ÉTAPE 4...............................................................................................................................................................................207
10 VERSION 6 - INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES JSF / EJB. 209
10.1 ARCHITECTURE DE L'APPLICATION......................................................................................................................................209
10.2 LE PROJET NETBEANS DE LA COUCHE WEB.........................................................................................................................209
10.3 LE PROJET NETBEANS DE L'APPLICATION D'ENTREPRISE....................................................................................................212
11 VERSION 7 - APPLICATION WEB PAM MULTI-VUES / MULTI-PAGES...............................................................216
11.1 LES VUES DE L'APPLICATION................................................................................................................................................217
11.2 LE PROJET NETBEANS DE LA COUCHE [WEB].......................................................................................................................218
11.2.1 LES FICHIERS DE CONFIGURATION........................................................................................................................................219
11.2.2 LA FEUILLE DE STYLE.........................................................................................................................................................219
11.2.3 LE FICHIER DES MESSAGES..................................................................................................................................................221
11.2.4 LA COUCHE [MÉTIER].........................................................................................................................................................222
11.3 LES BEANS DE L'APPLICATION..............................................................................................................................................223
11.3.1 LE BEAN APPLICATIONDATA................................................................................................................................................223
11.3.2 LE BEAN SESSIONDATA......................................................................................................................................................224
11.3.3 LE BEAN FORM..................................................................................................................................................................225
11.4 LES PAGES DE L'APPLICATION..............................................................................................................................................227
11.4.1 [LAYOUT.XHTML]................................................................................................................................................................227
11.4.2 L'ENTÊTE [ENTETE.XHTML].................................................................................................................................................228
11.5 LES CAS D'UTILISATION DE L'APPLICATION..........................................................................................................................229
11.5.1 AFFICHAGE DE LA PAGE D'ACCUEIL......................................................................................................................................229
11.5.2 L'ACTION [FAIRESIMULATION].............................................................................................................................................230
11.5.3 LA GESTION DES ERREURS...................................................................................................................................................233
11.5.4 L'ACTION [EFFACERSIMULATION].........................................................................................................................................235
11.5.5 L'ACTION [ENREGISTRERSIMULATION].................................................................................................................................236
11.5.6 L'ACTION [RETOURSIMULATEUR].........................................................................................................................................241
11.5.7 L'ACTION [VOIRSIMULATIONS]............................................................................................................................................241
11.5.8 L'ACTION [RETIRERSIMULATION].........................................................................................................................................243
11.5.9 L'ACTION [TERMINERSESSION]............................................................................................................................................244
11.6 INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES JSF / EJB.........................................................245
12 VERSION 8 : PORTAGE DE L'APPLICATION DANS UN ENVIRONNEMENT SPRING / TOMCAT................247
13 VERSION 9 : IMPLÉMENTATION DE LA COUCHE WEB AVEC PRIMEFACES.................................................248

257/257

Vous aimerez peut-être aussi