Académique Documents
Professionnel Documents
Culture Documents
Javaee 2
Javaee 2
avec Netbeans
et le serveur d'applications Glassfish
serge.tahe at istia.univ-angers.fr
juin 2012
1/257
INTRODUCTION
Ce document reprend un 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.
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 :
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/].
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].
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 :
• 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].
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/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.
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'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.
1 2
7/257
4
3 5
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,
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
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], le fichier [pom.xml] de configuration du projet Maven. POM signifie Project Object Model. On sera amené à
intervenir directement sur ce fichier.
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 :
• [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.
11/257
1
En [1], le projet Maven est construit puis exécuté [1]. Les logs dans la console Netbeans sont les suivants :
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).
12/257
2
4
3
5
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 :
13/257
6
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
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
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.
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.
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 :
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
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
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.
• 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.
• 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.
Les tests de la couche JPA peuvent être faits avec l'architecture suivante :
• 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
1 2
Examinons la configuration de la couche JPA faite dans le fichier [persistence.xml] de notre projet :
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 :
Maintenant voyons comment le fichier [persistence.xml] configure les couches [4, 5, 6] ci-dessus :
24/257
• les lignes 10-12, ici mises en commentaires configurent les logs console d'Hibernate :
• ligne 10 : pour afficher ou non les ordres SQL émis par Hibernate sur le SGBD. Ceci est 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...
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);
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).
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. }
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. }
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].
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 :
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 :
• 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 :
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] :
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. 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.
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.
Les clients des différents médecins sont enregistrés dans la table [CLIENTS] :
32/257
• NOM : le nom du client
• PRENOM : son prénom
• TITRE : son titre (Melle, Mme, Mr)
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).
33/257
Cette table a une contrainte d'unicité sur les valeurs des colonnes jointes (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.
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
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
35/257
12
13
11
14
Par la suite, nous ne reviendrons plus sur cette base. Mais le lecteur est invité à suivre son évolution au fil des programmes surtout
lorsque ça ne marche pas.
Couche Couche
couche [JPA / [JDBC] SGBD BD
[DAO] Hibernate]
C'est le suivant :
36/257
3
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.
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.
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 :
• 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
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>
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 :
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>
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>
• 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
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].
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 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 :
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.
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. }
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. }
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.
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).
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 :
Question : donner les requêtes JPQL permettant d'obtenir les informations suivantes :
54/257
• liste des clients (nom) ayant pris RV avec Mme PELISSIER le 24/08/2006
• nombre de clients de Mme PELISSIER le 24/08/2006
• les clients n'ayant pas pris de Rdv
• les médecins n'ayant pas de Rdv
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. }
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.}
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>
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)
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 :
7 Spring
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]
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
61/257
Les taux des cotisations sociales sont indépendants du salarié. La table précédente n'a qu'une ligne.
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.
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).
Les éléments suivants sont pris en compte [TOTALHEURES]: total des heures [TOTALHEURES]=150
: travaillées dans le mois [TOTALJOURS]= 20
62/257
Les éléments suivants sont pris en compte [TOTALHEURES]: total des heures [TOTALHEURES]=150
: travaillées dans le mois [TOTALJOURS]= 20
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 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.
65/257
3 4
5
6
7
8
10
• en [10], les tables ont été créées. Leur contenu est le suivant :
66/257
table EMPLOYES
table INDEMNITES
table COTISATIONS
• 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.
Créons tout d'abord la base de données vide. Nous lançons WampServer et utilisons l'outil PhpMyAdmin [1] :
3 4
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
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.
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] .
• 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 :
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 :
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à.
Elles demandent à Hibernate d'afficher les ordres SQL qu'il envoie à la base de données. Le fichier complet est donc le suivant :
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>
72/257
1
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. }
Nous incluons dans le projet les entités JPA développées précédemment [1] :
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 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
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 :
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.
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.
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>
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
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 :
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,
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.
82/257
3
2 4
83/257
• en [2], on renomme le projet et son artifactId,
• en [3], le nouveau projet.
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. }
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. }
• 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.
• 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.
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
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
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>
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.
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
A partir de ces information et d'autres enregistrées dans des fichiers de configuration, l'application affiche les informations suivantes
:
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]
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
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 :
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 :
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 :
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.
• 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] :
• 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
:
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] :
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. }
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.
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
Spring
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.
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.
7 Spring
4.9.1 Implémentation
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.
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
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.
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 :
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].
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.
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,
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 :
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.
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.
En procédant de la même façon que pour la classe de tests [JUnitInitDB], on obtient les résultats suivants :
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.
Maintenant que la couche [DAO] a été écrite, nous passons à l'étude de la couche métier [2] :
7 Spring
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. }
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.
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
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.
2
3
Les classes de tests [3] sont créés dans un paquetage [metier] [2] de la branche [Test Packages] [1] du projet.
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
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].
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.
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] :
7 Spring
Nous créerons deux implémentations différentes de la couche [ui] : une version console et une version graphique swing :
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. }
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.
7 Spring
116/257
2
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
119/257
17
18
16
19
20 21
• en [20] et [21] : dans la perspective [Source] du formulaire, du code Java a été ajouté pour gérer le JLabel ajouté.
120/257
1
JLabel1
JPanel1
JPanel2
121/257
JPanel3
JPanel4
JPanel5
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é :
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 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).
123/257
Revenons à l'architecture de notre application :
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 :
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. }
Pour ajouter au code précédent nos propres initialisations, nous pouvons procéder comme suit :
• 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
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].
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.
Nous nous intéressons à l'architecture suivante où la couche JPA est désormais implémentée par EclipseLink :
7 Spring
126/257
4
3
2
5
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>
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 :
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 :
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 :
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 :
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.
2
3
La configuration de Netbeans pour passer l'agent Java fonctionne bien pour le programme principal mais pas pour les tests (les
programmes dans la branche Tests). Pour ceux-ci, on ajoute le plugin [maven-surefire] dans le fichier [pom.xml] :
1. <build>
2. <plugins>
3. <plugin>
4. <groupId>org.apache.maven.plugins</groupId>
5. <artifactId>maven-surefire-plugin</artifactId>
6. <version>2.12.2</version>
7. <configuration>
8. <forkmode>pertest</forkmode>
9. <argLine>-javaagent:D:/Temp/12-09-10/spring-agent-2.5.6.jar</argLine>
10. </configuration>
11. </plugin>
12. </plugins>
13. </build>
133/257
Le plugin [maven-surefire] est utilisé pour les tests. Il permet de produire des rapports. Ici, nous ne l'utilisons que pour passer
l'agent Java à la JVM (ligne 9). On trouvera l'archive [spring-agent] dans le support du cours et on adaptera la ligne 9 au chemin réel
de cette archive. S'il y a des espaces dans ce chemin, entourez-le d'apostrophes comme dans 'mon chemin'.
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
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.
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. }
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.
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 : de ces deux exemples, que peut-on conclure de l'interchangeabilité des implémentations JPA ? Est-elle totale ici ?
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.
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
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.
71 Spring
1 OpenEjb
Dans cette implémentation, la couche [ui] sera un client local de la couche [métier].
1 OpenEjb
Dans cette implémentation, la couche [ui] sera un client distant de la couche [métier].
• 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
• 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]
• Les classes implémentant la couche [DAO] deviennent des EJB. Prenons l'exemple de la classe [CotisationDao] :
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;
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.
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é.
• 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. }
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.
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].
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 :
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] :
On se propose de porter l'application Netbeans Spring / Hibernate vers une architecture OpenEJB / EclipseLink.
143/257
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [DAO] de la BD [JPA] [Hibernate] [JDBC]
2
Spring
OpenEjb
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.
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
1. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
145/257
2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
3. <modelVersion>4.0.0</modelVersion>
4.
5. <groupId>istia.st</groupId>
6. <artifactId>mv-pam-openejb-eclipselink</artifactId>
7. <version>1.0-SNAPSHOT</version>
8. <packaging>jar</packaging>
9.
10. <name>mv-pam-openejb-eclipselink</name>
11. <url>http://maven.apache.org</url>
12.
13. <properties>
14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15. </properties>
16.
17. <dependencies>
18. <dependency>
19. <groupId>org.apache.openejb</groupId>
20. <artifactId>openejb-core</artifactId>
21. <version>4.0.0</version>
22. </dependency>
23. <dependency>
24. <groupId>junit</groupId>
25. <artifactId>junit</artifactId>
26. <version>4.10</version>
27. <scope>test</scope>
28. <type>jar</type>
29. </dependency>
30. <dependency>
31. <groupId>org.eclipse.persistence</groupId>
32. <artifactId>eclipselink</artifactId>
33. <version>2.3.0</version>
34. </dependency>
35. <dependency>
36. <groupId>org.eclipse.persistence</groupId>
37. <artifactId>javax.persistence</artifactId>
38. <version>2.0.3</version>
39. </dependency>
40. <dependency>
41. <groupId>mysql</groupId>
42. <artifactId>mysql-connector-java</artifactId>
43. <version>5.1.6</version>
44. </dependency>
45. <dependency>
46. <groupId>org.swinglabs</groupId>
47. <artifactId>swing-layout</artifactId>
48. <version>1.0.3</version>
49. </dependency>
50. </dependencies>
51.
52. <repositories>
53. <repository>
54. <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
55. <id>eclipselink</id>
56. <layout>default</layout>
57. <name>Repository for library Library[eclipselink]</name>
58. </repository>
59. </repositories>
60.
61. </project>
146/257
• lignes 30-39 : les dépendances EclipseLink,
• lignes 41-44 : la dépendance du pilote JDBC de MySQL
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.
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].
1. package dao;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface ICotisationDaoRemote extends ICotisationDao{
7. }
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 :
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.
Revenons sur l'architecture où la couche [UI] est un client distant de la couche [métier] :
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 :
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.
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].
• 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
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)
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].
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.
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. ...
154/257
2
3
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) :
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>
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.
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. }
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].
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] :
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] :
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.
Spring
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.
OpenEjb
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.
Couche C
Couche Couche Couche Couche
[ui] [metier] [DAO] [JPA / [JDBC]
EclipseLink]
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.
3
2
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.
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
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 :
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]
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 :
1 2
168/257
3
1 4
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.
Ces noms seront utiles à l'application console que nous allons écrire pour utiliser le module EJB déployé.
169/257
Maintenant que nous avons déployé la partie serveur de notre application client / serveur, nous en venons à étudier la partie client
[1] :
Nous créons un nouveau projet Maven de type [Java Application] nommé [mv-pam-client-ejb-metier-dao-eclipselink] :
1 2
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],
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 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.
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.
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
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.
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.
Nous construisons maintenant le client swing de notre application client / serveur EJB.
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 :
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]
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 :
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] :
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.
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
Client Conteneur
Jpa
du Ejb3 Données
service web serveur Java EE
177/257
7.1 Service web implémenté par un EJB
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
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
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
4 5
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.
• 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.
• 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.
• 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.
• 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
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 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].
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.
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.
3
1
2
185/257
6
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é.
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 :
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].
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
11
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.
189/257
1
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.
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 :
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>
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 :
• 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.
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. }
• 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.
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
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
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)
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]
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
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 :
199/257
9.2 Fonctionnement de l'application
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].
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
5
1
4
202/257
Nous passons en revue certains de ces éléments.
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 :
• 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.
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. }
<h:outputText value="#{msg['form.infos.employé']}"
styleClass="titreInfos"/>
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].
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 {
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. }
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].
Nous construisons maintenant la page XHTML du formulaire ainsi que son modèle.
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
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 :
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}"/>
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.
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 :
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
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 :
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.
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
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
Le projet Netbeans de la version web n° 2 est obtenue par copie du projet précédent :
212/257
2
Le nouveau projet porte le même nom que l'ancien. Nous changeons cela :
213/257
Nous avons peu de modifications à faire pour adapter cette couche web à son nouvel environnement : la couche [metier] 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
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] :
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 :
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
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.
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.
215/257
4
3
1
2
7
5
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.
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.
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] :
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
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.
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
- 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 :
221/257
5 6
1
3
7
4
2
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. .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. }
Vue Simulations
Vue Erreur
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
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. }
226/257
11.3.1 Le bean ApplicationData
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.
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).
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. }
11.4.1 [layout.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>
231/257
11.4.2 L'entête [entete.xhtml]
232/257
La page d'accueil est la page [saisie.xhtml] 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 ?
234/257
La simulation est affichée avec la page [simulation.xhtml] suivante :
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>
Question : écrire la méthode [faireSimulation] de la classe [Form]. La simulation sera enregistrée dans le bean SessionData.
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.}
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.
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) :
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].
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>
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] :
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.
L'action [enregistrerSimulation] associée au lien permet d'enregistrer la simulation courante dans une liste de simulations maintenue
dans la classe [SessionData] :
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. }
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.
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>
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>
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.
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.
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 :
On fera en sorte que si la liste des simulations est vide, la vue affichée soit [vueSimulationsVides] :
246/257
Le résultat obtenu est le suivant :
• 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] :
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.
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.
11.6 Intégration de la couche web dans une architecture 3 couches JSF / EJB
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.
250/257
13 Version 9 : Implémentation de la couche web avec Primefaces
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.
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) :
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 :
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