Introduction à Java EE avec Netbeans et le serveur d'applications Glassfish

Dimension: px
Commencer à balayer dès la page:

Download "Introduction à Java EE avec Netbeans et le serveur d'applications Glassfish"

Transcription

1 Introduction à Java EE avec Netbeans et le serveur d'applications Glassfish serge.tahe at istia.univ-angers.fr juin /257

2 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 [ On y utilise des caractéristiques de la version 2 de JSF, les projets sont des projets Maven. Java EE signifie Java Enterprise Edition. J2EE (Java 2 Enterprise Edition) était le terme précédent. J2EE désigne les technologies Java utilisées pour créer des applications d'entreprise avec le JDK 1.4 ou antérieur. En même temps que le JDK 1.5 amenait de nombreuses nouveautés dans le langage Java, Sun introduisait de nouvelles technologies s'appuyant sur ce langage amélioré afin de remédier à des lacunes de ces mêmes technologies dans J2EE. Le terme Java EE 5 a alors été utilisé pour désigner l'ensemble des technologies qui concourent à créer une application d'entreprise avec la plate-forme Java. Au moment de la mise à jour de ce document, la dernière version de Java EE est Java EE 6. Les livres d'antonio Goncalves : Java EE 5 aux éditions Eyrolles Beginning Java EE 6 Platform with Glassfish 3 aux éditions Apress sont d'excellents livres pour découvrir les technologies de Java EE 5 et Java EE 6. Toutes les technologies importantes de Java EE y sont passées en revue dans le contexte d'études de cas réalistes. L'auteur a un site [ que le lecteur est invité à visiter. Le document présent étudie certaines des technologies de Java EE 5. Nous y créons une application basique à trois couches [présentation, métier, accès aux données] déclinée en plusieurs versions : Une application web avec les technologies suivantes : Java Server Faces : pour la couche web EJB3 ou Spring : pour la couche métier EJB3 ou Spring, JPA/Hibernate, JPA/EclipseLink : pour créer différentes couches d'accès aux données Une application client / serveur avec les technologies suivantes : Swing : pour la couche graphique cliente avec un support Spring EJB3 ou service web : pour la couche serveur Certaines technologies Java EE ne sont pas présentées telles les MDB (Message Driven Bean) ou les EJB3 stateful. Pour les découvrir, on lira les livres d'antonio Goncalves. Il existe d'autres technologies Open Source disponibles pour créer des applications trois couches. Une tandem très populaire est Spring ( / Hibernate ( 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 [ 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 [ Le document fait référence aux cours suivants : 2/257

3 1. 3. Persistance Java 5 par la pratique : [ - donne les outils pour construire la couche d'accès aux données avec JPA (Java Persistence API) Introduction au langage Java [ - pour les débutants Introduction par l'exemple à Java Server Faces, Primefaces et Primefaces mobile [ Ces supports de cours sont par la suite référencés [ref1] [ref2] et [ref3]. Ce document est accompagné d'un support disponible à l'adresse [ contenant des scripts SQL permettant de construire les bases de données utilisées dans le document, des fichiers [pom.xml] parfois difficiles à construire, les trois références de cours ci-dessus ainsi que les squelettes de certaines des applications du document. Il contient également un planning donnant une estimation de temps des différentes tâches proposées. Serge Tahé, juin 201 3/257

4 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 : utilisateur métier [metier] interface utilisateur [ui] 1 2 d'accès aux données [DAO] Données 3 la couche [1], appelée ici [ui] (User Interface) est la couche qui dialogue avec l'utilisateur, via une interface graphique Swing, une interface console ou une interface web. Elle a pour rôle de fournir des données provenant de l'utilisateur à la couche [2] ou bien de présenter à l'utilisateur des données fournies par la couche [2]. la couche [2], appelée ici [metier] est la couche qui applique les règles dites métier, c.a.d. la logique spécifique de l'application, sans se préoccuper de savoir d'où viennent les données qu'on lui donne, ni où vont les résultats qu'elle produit. la couche [3], appelée ici [DAO] (Data Access Object) est la couche qui fournit à la couche [2] des données préenregistrées (fichiers, bases de données,...) et qui enregistre certains des résultats fournis par la couche [2]. Il existe différentes possibilités pour implémenter la couche [DAO]. Examinons-en quelques-unes : utilisateur ui [ui] 1 métier [metier] 2 [JDBC] d'accès aux données [DAO] Base de Données 3 La couche [JDBC] ci-dessus est la couche standard utilisée en Java pour accéder à des bases de données. Elle isole la couche [DAO] du SGBD qui gère la base de données. On peut théoriquement changer de SGBD sans changer le code de la couche [DAO]. Malgré cet avantage, l'api JDBC présente certains inconvénients : toutes les opérations sur le SGBD sont susceptibles de lancer l'exception contrôlée (checked) SQLException. Ceci oblige le code appelant (la couche [DAO] ici) à les entourer par des try / catch rendant ainsi le code assez lourd. la couche [DAO] n'est pas complètement insensible au SGBD. Ceux-ci ont par exemple des méthodes propriétaires quant à la génération automatique de valeurs de clés primaires que la couche [DAO] ne peut ignorer. Ainsi lors de l'insertion d'un enregistrement : avec Oracle, la couche [DAO] doit d'abord obtenir une valeur pour la clé primaire de l'enregistrement puis insérer celui-ci. avec SQL Server, la couche [DAO] insère l'enregistrement qui se voit donner automatiquement une valeur de clé primaire par le SGBD, valeur rendue à la couche [DAO]. Ces différences peuvent être gommées via l'utilisation de procédures stockées. Dans l'exemple précédent, la couche [DAO] appellera une procédure stockée dans Oracle ou SQL Server qui prendra en compte les particularités du SGBD. Celles-ci seront cachées à la couche [DAO]. Néanmoins, si changer de SGBD n'impliquera pas de réécrire la couche [DAO], cela implique quand même de réécrire les procédures stockées. Cela peut ne pas être considéré comme rédhibitoire. De multiples efforts ont été faits pour isoler la couche [DAO] des aspects propriétaires des SGBD. Une solution qui a eu un vrai succès dans ce domaine ces dernières années, est celle d'hibernate : d'accès aux données [DAO] 3 4 Objets image de la BD [Hibernate] 5 [JDBC] 6 Base de Données 7 4/257

5 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 : d'accès aux données [DAO] 3 4 Objets image de la BD Interface [JPA] Implémentation JPA [Hibernate /...] 5 [JDBC] 6 Base de Données 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 8 pour lier ces couches entre-elles. [metier] [DAO] 2 3 Objets image de la BD 4 Interface Implémentation JPA [EclipseLink [JPA] / Hibernate] 5 7 [JDBC] 6 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

6 [metier] [DAO] 2 3 Objets image de la BD 4 Interface Implémentation JPA [EclipseLink [JPA] / Hibernate] 5 7 [JDBC] 6 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 x) étaient réputés difficiles à mettre en oeuvre, à tester et parfois peu-performants. On distingue les EJBx "entity" et les EJBx "session". Pour faire court, un EJBx "entity" est l'image d'une ligne de table de base de données et EJBx "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 EJB 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 EJB Les EJB x sont devenus les EJB3. Ceux-ci sont des POJOs tagués par des annotations qui en font des objets particuliers lorsqu'ils sont au sein d'un conteneur EJB3. Dans celui-ci, l'ejb3 va pouvoir bénéficier des services du conteneur (pool de connexions, gestionnaire de transactions,...). En-dehors du conteneur EJB3, l'ejb3 devient un objet Java normal. Ses annotations EJB sont ignorées. Ci-dessus, nous avons représenté Spring et un conteneur EJB3 comme infrastructure (framework) possible de notre architecture multicouche. C'est cette infrastructure qui délivrera les services dont nous avons besoin : un pool de connexions et un gestionnaire de transactions. avec Spring, les couches seront implémentées avec des POJOs. Ceux-ci auront accès aux services de Spring (pool de connexions, gestionnaire de transaction) par injection de dépendances dans ces POJOs : lors de la construction de ceux-ci, Spring leur injecte des références sur les services dont il vont avoir besoin. avec le conteneur EJB3, les couches seront implémentées avec des EJB. Une architecture en couches implémentées avec des EJB3 est peu différente de celles implémentées avec des POJO instanciés par Spring. Nous trouverons beaucoup de ressemblances. pour terminer, nous présenterons un exemple d'application web multicouche : [web] 1 [metier] [DAO] 2 3 Objets image de la BD 4 Interface Implémentation JPA [EclipseLink [JPA] / Hibernate] 5 7 [JDBC] 6 Spring ou Ejb3 6/257

7 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. L'installation de Netbeans est décrite dans [ref3] au paragraphe Wampserver version L'installation de WampServer est est décrite dans [ref3] au paragraphe ; Maven est intégré à Netbeans. Nous décrivons maintenant cet outil Maven Introduction Maven est disponible à l'url [ ]. Selon ses créateurs : Maven's primary goal is to allow a developer to comprehend the complete state of a development effort in the shortest period of time. In order to attain this goal there are several areas of concern that Maven attempts to deal with: Making the build process easy Providing a uniform build system Providing quality project information Providing guidelines for best practices development Allowing transparent migration to new features Maven est intégré dans Netbeans et nous allons l'utiliser pour une seule de ses caractéristiques : la gestion des bibliothèques d'un projet. Celles-ci sont formées de l'ensemble des archives jars qui doivent être dans le Classpath du projet. Elles peuvent être très nombreuses. Par exemple, nos projets futurs vont utiliser l'orm (Object Relational Mapper) Hibernate. Cet ORM est composé de dizaines d'archives jar. L'intérêt de Maven est qu'il nous affranchit de les connaître toutes. Il nous suffit d'indiquer dans notre projet que nous avons besoin d'hibernate en donnant toutes les informations utiles pour trouver l'archive principale de cet ORM. Maven télécharge alors également toutes les bibliothèques nécessaires à Hibernate. On appelle cela les dépendances d'hibernate. Une bibliothèque nécessaire à Hibernate peut elle même dépendre d'autres archives. Celles-ci seront également téléchargées. Toutes ces bibliothèques sont placées dans un dossier appelé le dépôt local de Maven. Un projet Maven est facilement partageable. Si on le transfère d'un poste à un autre et que les dépendances du projet ne sont pas présentes dans le dépôt local du nouveau poste, elles seront téléchargées. Maven peut être utilisé seul ou intégré à un EDI (Environnement de Développement Intégré) tel Netbeans ou Eclipse. Créons un projet Maven dans Netbeans : 1 2 en [1], créer un nouveau projet, en [2], choisir la catégorie [Maven] et le type de projet [Java Application], 7/257

8 4 3 5 en [3], désigner le dossier parent du dossier du nouveau projet, en [4], donner un nom au projet, en [5], le projet généré. Examinons les éléments du projet et explicitons le rôle de chacun. 1 en [1] : les différentes branches du projet : [Source packages] : les classes Java du projet ; [Test packages] : les classes de test du projet ; [Dependencies] : les archives.jar nécessaires au projet et gérées par Maven ; [Test Dependencies] : les archives.jar nécessaires aux tests du projet et gérées par Maven ; [Java Dependencies] : les archives.jar nécessaires au projet et non gérées par Maven ; [Project Files] : fichiers de configuration de Maven et Netbeans, 3 en [3], la branche [Source Packages], Cette branche contient les codes source des classes Java du projet. Netbeans a généré une classe par défaut : 1. package istia.st.mvexemple; 3. /** 4. * Hello world! 5. * 6. */ 7. public class App { 8. public static void main(string[] args) { 8/257

9 9. System.out.println("Hello World!"); 10. } 11. } 4 5 en [4], la branche [Test Packages] qui contient les codes source des classes de test du projet, en [5], la bibliothèque JUnit 3.8 nécessaire à l'exécution des tests, Netbeans a généré une classe par défaut : 1. package istia.st.mvexemple; 3. import junit.framework.test; 4. import junit.framework.testcase; 5. import junit.framework.testsuite; /** 8. * Unit test for simple App. 9. */ 10. public class AppTest extends TestCase { 11. /** 1 * Create the test case 13. * 14. testname 15. * name of the test case 16. */ 17. public AppTest(String testname) { 18. super(testname); 19. } /** 2 the suite of tests being tested 23. */ 24. public static Test suite() { 25. return new TestSuite(AppTest.class); 26. } /** 29. * Rigourous Test :-) 30. */ 31. public void testapp() { 3 asserttrue(true); 33. } 34. } C'est un test JUnit 3.8. Nous utiliserons par la suite des tests JUnit 4.x. 9/257

10 6 en [6], la branche [Dependencies] est ici vide, Cette branche affiche toute les bibliothèques nécessaires au projet et gérées par Maven. Toutes les bibliothèques listées ici sont automatiquement téléchargées par Maven. C'est pourquoi un projet Maven a besoin d'un accès Internet. Les bibliothèques téléchargées vont être stockées en local. Si un autre projet a besoin d'une bibliothèque déjà présente en local, celle-ci ne sera alors pas téléchargée. Nous verrons que cette liste de bibliothèques ainsi que les dépôts où on peut les trouver sont définis dans le fichier de configuration du projet Maven. 7 en [7], les bibliothèques nécessaires au projet et non gérées par Maven, 8 en [7], le fichier [pom.xml] de configuration du projet Maven. POM signifie Project Object Model. On sera amené à intervenir directement sur ce fichier. Le fichier [pom.xml] généré est le suivant : 1. <project xmlns=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-exemple</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-exemple</name> 11. <url> 1 10/257

11 13. <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <dependencies> 18. <dependency> 19. <groupid>junit</groupid> 20. <artifactid>junit</artifactid> 21. <version>3.8.1</version> 2 <scope>test</scope> 23. </dependency> 24. </dependencies> 25. </project> les lignes 5-8 définissent l'objet (artifact) Java qui va être créé par le projet Maven. Ces informations proviennent de l'assistant qui a été utilisé lors de la création du projet : Un objet Maven est défini par quatre propriétés : [groupid] : une information qui ressemble à un nom de package. Ainsi les bibliothèques du framework Spring ont groupid=org.springframework, celles du framework JSF ont groupid=javax.faces, [artifactid] : le nom de l'objet Maven. Dans le groupe [org.springframework] on trouve ainsi les artifactid suivants : springcontext, spring-core, spring-beans,... Dans le groupe [javax.faces], on trouve l'artifactid JSF-api, [version] : n de version de l'artifact Maven. Ainsi l'artifact org.springframework.spring-core a les versions suivantes : 5.4, 5.5, 5.6, 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.0SNAPSHOT] (ligne 7). Ces quatre informations doivent définir de façon unique un artifact Maven. Les lignes 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 Si cette bibliothèque n'est pas présente dans le dépôt local du poste, elle est téléchargée. 1.2 Exécution du projet Nous exécutons le projet : 11/257

12 1 2 En [1], le projet Maven est construit puis exécuté [1]. Les logs dans la console Netbeans sont les suivants : 1. Scanning for projects Building mv-exemple 1.0-SNAPSHOT [resources:resources] 9. [debug] execute contextualize 10. Using 'UTF-8' encoding to copy filtered resources. 11. skip non existing resourcedirectory D:\data\istia-1112\netbeans\glassfish\mv-pam\00\mvexemple\src\main\resources [compiler:compile] 14. Compiling 1 source file to D:\data\istia-1112\netbeans\glassfish\mv-pam\00\mvexemple\target\classes [exec:exec] 17. Downloading: Downloaded: (8 KB at KB/sec) 20. Downloading: Downloaded: (49 KB at KB/sec) 23. Hello World! BUILD SUCCESS Total time: 4.040s 28. Finished at: Thu Jun 21 10:10:40 CEST Final Memory: 13M/122M Le résultat est ligne 23. On voit que même pour ce cas simple, Maven a téléchargé des éléments (lignes 17 et 20). 1.3 Le système de fichiers d'un projet Maven 12/257

13 [1] : le système de fichiers du projet est dans l'onglet [Files], [2] : les sources Java sont dans le dossier [src / main / java], [3] : les sources Java des tests sont dans le dossier [src / test / java], [4] : le dossier [target] est créé par la construction (build) du projet, [5] : ici, la construction du projet a créé une archive [mv-exemple-1.0-snapshot.jar]. Le dépôt Maven local Nous avons dit que Maven téléchargeait les dépendances nécessaires au projet et les stockait localement. On peut explorer ce dépôt local : en [1], on choisit l'option [Window / Other / Maven Repository Browser], en [2], un onglet [Maven Repositories] s'ouvre, en [3], il contient deux branches, une pour le dépôt local, l'autre pour le dépôt central. Ce dernier est gigantesque. Pour visualiser son contenu, il faut mettre à jour son index [4]. Cette mise à jour dure plusieurs dizaines de minutes. 13/257

14 en [5], les bibliothèques du dépôt local, en [6], on y trouve une branche [istia.st] qui correspond au [groupid] de notre projet, en [7], on accède aux propriétés du dépôt local, en [8], on a le chemin du dépôt local. Il est utile de le connaître car parfois (rarement) Maven n'utilise plus la dernière version du projet. On fait des modifications et on constate qu'elles ne sont pas prises en compte. On peut alors supprimer manuellement la branche du dépôt local correspondant à notre [groupid]. Cela force Maven à recréer la branche à partir de la dernière version du projet. Chercher un artifact avec Maven Apprenons maintenant à chercher un artifact avec Maven. Partons de la liste des dépendances actuelles du fichier [pom.xml] : 1. <project xmlns=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-exemple</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-exemple</name> 11. <url> <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <dependencies> 18. <dependency> 19. <groupid>junit</groupid> 20. <artifactid>junit</artifactid> 21. <version>3.8.1</version> 2 <scope>test</scope> 23. </dependency> 24. </dependencies> 25. </project> Les lignes définissent des dépendances qu'on va modifier pour utiliser les bibliothèques dans leur version la plus récente. 14/257

15 1 Tout d'abord, nous supprimons les dépendances actuelles [1]. Le fichier [pom.xml] est alors modifié : 1. <project xmlns=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-exemple</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-exemple</name> 11. <url> <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <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 en [1], on ajoute une dépendance au projet, en [2], on doit préciser des informations sur l'artifact cherché (groupid, artifactid, version, packaging (Type) et scope). Nous commençons par préciser le [groupid] [3], en [4], nous tapons [espace] pour faire afficher la liste des artifacts possibles. Ici [junit] et [jnit-dep]. Nous choisissons [junit], en [5], en procédant de la même façon, on choisit la version la plus récente. Le type de packaging est jar, en [6], nous choisissons la portée test pour indiquer que la dépendance n'est nécessaire qu'aux tests. 15/257

16 6 En [6], les dépendances ajoutées apparaissent dans le projet. Le fichier [pom.xml] reflète ces changements : 1. <project xmlns=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-exemple</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-exemple</name> 11. <url> <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <dependencies> 18. <dependency> 19. <groupid>junit</groupid> 20. <artifactid>junit</artifactid> 21. <version>4.10</version> 2 <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 [ : 1 En [1], on peut taper des mots clés. Tapons hibernate et lançons la recherche. 16/257

17 3 4 2 en [2], choisissons le [groupid] org.hibernate et l'[artifactid] hibernate-core, en [3], choisissons la version Final, en [4], nous obtenons le code Maven à coller dans le fichier [pom.xml]. Nous le faisons. 1. <project xmlns=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-exemple</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-exemple</name> 11. <url> <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <dependencies> 18. <dependency> 19. <groupid>junit</groupid> 20. <artifactid>junit</artifactid> 21. <version>4.10</version> 2 <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.final</version> 17/257

18 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 : 5 6 en [5], la dépendance [hibernate-core 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

19 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 : [ - donne les outils pour construire la couche d'accès aux données avec JPA Nous présentons tout d'abord les fondements de JPA. On attendra le paragraphe 3.4, page 31 pour créer une application exemple. 3.1 La place de JPA dans une architecture en couches Le lecteur est invité à relire le début de ce document (paragraphe 1, page 4) qui explique le rôle de la couche JPA dans une architecture en couches. La couche JPA s'insère dans les couches d'accès aux données : d'accès aux données [DAO] Objets image de la BD Interface [JPA] Implémentation JPA [Hibernate /...] 5 [JDBC] Base de Données 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 JPA exemples Exemple 1 - Représentation objet d'une table unique La table [personne] Considérons une base de données ayant une unique table [personne] dont le rôle est de mémoriser quelques informations sur des individus : ID clé primaire de la table VERSION version de la ligne dans la table. A chaque fois que la personne est modifiée, son n de version est incrémenté. NOM nom de la personne 19/257

20 PRENOM son prénom DATENAISSANCE sa date de naissance MARIE entier 0 (non marié) ou 1 (marié) NBENFANTS nombre d'enfants de la personne L'entité [Personne] Nous nous plaçons dans l'environnement d'exécution suivant : Programme de test console [main] 3 4 Objets image de la BD Interface [JPA] 5 Implémentation 6 [Toplink / Hibernate] [JDBC] Base de Données 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 avec des annotations Java depuis le JDK 1.5 Dans ce document, nous utiliserons exclusivement la seconde méthode. L'objet [Personne] image de la table [personne] présentée précédemment pourrait être le suivant : public class Personne implements Serializable{ 7. = "ID", nullable = false) = GenerationType.AUTO) 11. private Integer id; 1 = "VERSION", nullable = false) 15. private int version; 16. = "NOM", length = 30, nullable = false, unique = true) 18. private String nom; 19. = "PRENOM", length = 30, nullable = false) 21. private String prenom; 2 = "DATENAISSANCE", nullable = false) 25. private Date datenaissance; 26. = "MARIE", nullable = false) 28. private boolean marie; 29. = "NBENFANTS", nullable = false) 31. private int nbenfants; // constructeurs 34. public Personne() { 20/257

21 35. } public Personne(String nom, String prenom, Date datenaissance, boolean marie, 38. int nbenfants) { 39. setnom(nom); 40. setprenom(prenom); 41. setdatenaissance(datenaissance); 4 setmarie(marie); 43. setnbenfants(nbenfants); 44. } // tostring 47. public String tostring() { } // getters and setters } La configuration se fait à l'aide d'annotations 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,...) la table T n'existe pas et elle va être créée d'après les annotations trouvées dans la classe C. Le cas 2 est le plus facile à gérer. A l'aide des annotations JPA, nous indiquons la structure de la table T que nous voulons. Le cas 1 est souvent plus complexe. La table T a pu être construite, il y a longtemps, en-dehors de tout contexte JPA. Sa structure peut alors être mal adaptée au pont relationnel / objet de JPA. Pour simplifier, nous nous plaçons dans le cas 2 où la table T associée à la classe C va être créée d'après les annotations JPA de la classe C. Commentons les annotations JPA de la classe [Personne] : ligne 4 : 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 : 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, est donc superflue. ligne 8 : 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 : 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 : 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, 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

22 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. utilise un objet appelé SEQUENCE qui là encore jouele rôle d'un générateur de valeurs Oracle 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 : 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 V 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 V 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. 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 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 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 : sert à indiquer quel type SQL donner à une colonne / champ de type date / heure. Le type TemporalType.DATE désigne une date seule sans heure associée. Les autres types possibles sont TemporalType.TIME pour coder une heure et TemporalType.TIMESTAMP pour coder une date avec heure. Commentons maintenant le reste du code de la classe [Personne] : ligne 6 : la classe implémente l'interface Serializable. La sérialisation d'un objet consiste à le transformer en une suite de bits. La désérialisation est l'opération inverse. La sérialisation / désérialisation est notamment utilisée dans les applications client / 22/257

23 3.2 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 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 Configuration de la couche JPA Les tests de la couche JPA peuvent être faits avec l'architecture suivante : Programme de test console [main] 3 Objets image de la BD 4 Interface [JPA] 5 Implémentation 6 [Hibernate] [JDBC] Base de Données 7 en [7] : la base de données qui sera générée à partir des annotations de l'entité [Personne] ainsi que de configurations complémentaires faites dans un fichier appelé [persistence.xml] en [5, 6] : une couche JPA implémentée par Hibernate en [4] : l'entité [Personne] en [3] : un programme de test de type console La configuration de la couche JPA est assurée par le fichier [META-INF/persistence.xml] : 1 2 A l'exécution, le fichier [META-INF/persistence.xml] est cherché dans le Classpath de l'application. Examinons la configuration de la couche JPA faite dans le fichier [persistence.xml] de notre projet : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="1.0" xmlns=" 3. <persistence-unit name="jpa" transaction-type="resource_local"> 4. <!-- provider --> 5. <provider>org.hibernate.ejb.hibernatepersistence</provider> 6. <properties> 7. <!-- Classes persistantes --> 8. <property name="hibernate.archive.autodetection" value="class, hbm" /> 9. <!-- logs SQL 10. <property name="hibernate.show_sql" value="true"/> 11. <property name="hibernate.format_sql" value="true"/> 23/257

24 <property name="use_sql_comments" value="true"/> --> <!-- connexion JDBC --> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.driver" /> <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" /> <property name="hibernate.connection.username" value="jpa" /> <property name="hibernate.connection.password" value="jpa" /> <!-- création automatique du schéma --> <property name="hibernate.hbm2ddl.auto" value="create" /> <!-- Dialecte --> <property name="hibernate.dialect" value="org.hibernate.dialect.mysql5innodbdialect" /> 23. <!-- propriétés DataSource c3p0 --> 24. <property name="hibernate.c3p0.min_size" value="5" /> 25. <property name="hibernate.c3p0.max_size" value="20" /> 26. <property name="hibernate.c3p0.timeout" value="300" /> 27. <property name="hibernate.c3p0.max_statements" value="50" /> 28. <property name="hibernate.c3p0.idle_test_period" value="3000" /> 29. </properties> 30. </persistence-unit> 31. </persistence> Pour comprendre cette configuration, il nous faut revenir sur l'architecture de l'accès aux données de notre application : Programme de test console [main] 3 [JPA/Hibernate] 4 Pool de connexions [c3p0] 5 [JDBC] 6 Base de Données 7 le fichier [persistence.xml] va configurer les couches [4, 5, 6] [4] : implémentation Hibernate de JPA [5] : Hibernate accède à la base de données via un pool de connexions. Un pool de connexions est une réserve de connexions ouvertes avec le SGBD. Un SGBD est accédé par de multiples utilisateurs alors même que pour des raisons de performances, il ne peut dépasser un nombre limite N de connexions ouvertes simultanément. Un code bien écrit ouvre une connexion avec le SGBD un minimum de temps : il émet des ordres SQL et ferme la connexion. Il va faire cela de façon répétée, à chaque fois qu'il a besoin de travailler avec la base. Le coût d'ouverture / fermeture d'une connexion n'est pas négligeable et c'est là qu'intervient le pool de connexions. Celui-ci va au démarrage de l'application ouvrir N1 connexions avec le SGBD. C'est à lui que l'application demandera une connexion ouverte lorsqu'elle en aura besoin. Celleci sera rendue au pool dès que l'application n'en aura plus besoin, de préférence le plus vite possible. La connexion n'est pas fermée et reste disponible pour l'utilisateur suivant. Un pool de connexions est donc un système de partage de connexions ouvertes. [6] : le pilote JDBC du SGBD utilisé Maintenant voyons comment le fichier [persistence.xml] configure les couches [4, 5, 6] ci-dessus : ligne 2 : la balise racine du fichier XML est <persistence>. ligne 3 : <persistence-unit> sert à définir une unité de persistance. Il peut y avoir plusieurs unités de persistance. Chacune d'elles a un nom (attribut name) et un type de transactions (attribut transaction-type). L'application aura accès à l'unité de persistance via le nom de celle-ci, ici JPA. Le type de transaction RESOURCE_LOCAL indique que l'application gère elle-même les transactions avec le SGBD. Ce sera le cas ici. Lorsque l'application s'exécute dans un conteneur EJB3, elle peut utiliser le service de transactions de celui-ci. Dans ce cas, on mettra transaction-type=jta (Java Transaction API). JTA est la valeur par défaut lorsque l'attribut transaction-type est absent. ligne 5 : la balise <provider> sert à définir une classe implémentant l'interface [javax.persistence.spi.persistenceprovider], interface qui permet à l'application d'initialiser la couche de persistance. Parce qu'on utilise une implémentation JPA / Hibernate, la classe utilisée ici est une classe d'hibernate. ligne 6 : la balise <properties> introduit des propriétés propres au provider particulier choisi. Ainsi selon qu'on a choisi Hibernate, Toplink, Kodo,... on aura des propriétés différentes. Celles qui suivent sont propres à Hibernate. ligne 8 : demande à Hibernate d'explorer le classpath du projet pour y trouver les classes ayant afin de les gérer. Les peuvent également être déclarées par des balises <class>nom_de_la_classe</class>, directement sous la balise <persistence-unit>. C'est ce que nous ferons avec le provider JPA / Toplink. 24/257

25 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 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 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 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 lui permet de connaître les tables à générer les lignes et 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 Exemple 2 : relation un-à-plusieurs Le schéma de la base de données 25/257

26 1 1. alter table jpa06_article drop 3. foreign key FKFFBDD9D8ECCE8750; drop table if exists jpa06_article; drop table if exists jpa06_categorie; create table jpa06_article ( 10. id bigint not null auto_increment, 11. version integer not null, 1 nom varchar(30), 13. categorie_id bigint not null, 14. primary key (id) ) ENGINE=InnoDB; create table jpa06_categorie ( 18. id bigint not null auto_increment, 19. version integer not null, 20. nom varchar(30), 21. primary key (id) 2 ) ENGINE=InnoDB; alter table jpa06_article 25. add index FKFFBDD9D8ECCE8750 (categorie_id), 26. add constraint FKFFBDD9D8ECCE foreign key (categorie_id) 28. references jpa06_categorie (id); en [1], la base de données et en [2], sa DDL (MySQL5) Un article A(id, version, nom) appartient exactement à une catégorie C(id, version, nom). Une catégorie C peut contenir 0, 1 ou plusieurs articles. On a une relation un-à-plusieurs (Categorie -> Article) et la relation inverse plusieurs-à-un (Article -> Categorie). Cette relation est matérialisée par la clé étrangère que possède la table [article] sur la table [categorie] (lignes de la DDL) Les représentant la base de données Un article est représenté par l'@entity [Article] suivante : 1. package entites; public class Article implements Serializable { // champs = GenerationType.AUTO) 11. private Long id; private int version; 16. = 30) 18. private String nom; // relation principale Article (many) -> Category (one) 26/257

27 21. // implémentée par une clé étrangère (categorie_id) dans Article 2 // 1 Article a nécessairement 1 Categorie (nullable=false) = "categorie_id", nullable = false) 25. private Categorie categorie; // constructeurs 28. public Article() { 29. } // getters et setters // tostring 34. public String tostring() { 35. return String.format("Article[%d,%d,%s,%d]", id, version, nom, categorie.getid()); 36. } } lignes 9-11 : clé primaire de l'@entity lignes : son n de version lignes : nom de l'article lignes : relation plusieurs-à-un qui relie l'@entity Article à l'@entity Categorie : ligne 23 : l'annotation ManyToOne. Le Many se rapport à l'@entity Article dans lequel on se trouve et le One à l'@entity Categorie (ligne 25). Une catégorie (One) peut avoir plusieurs articles (Many). ligne 24 : l'annotation ManyToOne définit la colonne clé étrangère dans la table [article]. Elle s'appellera (name) categorie_id et chaque ligne devra avoir une valeur dans cette colonne (nullable=false). ligne 25 : la catégorie à laquelle appartient l'article. Lorsqu'un article sera mis dans le contexte de persistance, on demande à ce que sa catégorie n'y soit pas mise immédiatement (fetch=fetchtype.lazy, ligne 23). On ne sait pas si cette demande a un sens. On verra. Une catégorie est représentée par l'@entity [Categorie] suivante : 1. package entites; public class Categorie implements Serializable { // champs = GenerationType.AUTO) 10. private Long id; private int version; 15. = 30) 17. private String nom; // 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 2 // cascade suppression Categorie -> suppression Articles = "categorie", cascade = { CascadeType.ALL }) 24. private Set<Article> articles = new HashSet<Article>(); // constructeurs 27. public Categorie() { 28. } /257

28 30. // getters et setters // tostring 33. public String tostring() { 34. return String.format("Categorie[%d,%d,%s]", id, version, nom); 35. } // 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 4 article.setcategorie(this); 43. } 44. } 3.3 lignes 8-11 : la clé primaire de l'@entity lignes : sa version lignes : le nom de la catégorie lignes : l'ensemble (set) des articles de la catégorie ligne 23 : désigne une relation un-à-plusieurs. Le One désigne l'@entity [Categorie] dans laquelle on se trouve, le Many le type [Article] de la ligne 24 : une (One) catégorie a plusieurs (Many) articles. ligne 23 : l'annotation est l'inverse (mappedby) de l'annotation ManyToOne placée sur le champ categorie de l'@entity Article : mappedby=categorie. La relation ManyToOne placée sur le champ categorie de l'@entity Article est la relation principale. Elle est indispensable. Elle matérialise la relation de clé étrangère qui lie l'@entity Article à l'@entity Categorie. La relation OneToMany placée sur le champ articles de l'@entity Categorie est la relation inverse. Elle n'est pas indispensable. C'est une commodité pour obtenir les articles d'une catégorie. Sans cette commodité, ces articles seraient obtenus par une requête JPQL. ligne 23 : cascadetype.all demande à que les opérations (persist, merge, remove) faites sur Categorie soient cascadées sur ses articles. ligne 24 : les articles d'une catégorie seront placés dans un objet de type Set<Article>. Le type Set n'accepte pas les doublons. Ainsi on ne peut mettre deux fois le même article dans l'objet Set<Article>. Que veut dire "le même article"? Pour dire que l'article a est le même que l'article b, Java utilise l'expression a.equals(b). Dans la classe Object, mère de toutes les classes, a.equals(b) est vraie si a==b, c.a.d. si les objets a et b ont le même emplacement mémoire. On pourrait vouloir dire que les articles a et b sont les mêmes s'ils ont le même nom. Dans ce csa, le développeur doit redéfinir deux méthodes dans la classe [Article] : equals : qui doit rendre vrai si les deux articles ont le même nom hashcode : doit rendre une valeur entière identique pour deux objets [Article] que la méthode equals considère comme égaux. Ici, la valeur sera donc construite à partir du nom de l'article. La valeur rendue par hashcode peut être un entier quelconque. Elle est utilisée dans différents conteneurs d'objets, notamment les dictionnaires (Hashtable). La relation OneToMany peut utiliser d'autres types que le Set pour stocker le Many, des objets List, par exemple. Nous n'aborderons pas ces cas dans ce document. Le lecteur les trouvera dans [ref1]. ligne 38 : la méthode [addarticle] nous permet d'ajouter un article à une catégorie. La méthode prend soin de mettre à jour les deux extrémités de la relation OneToMany qui lie [Categorie] à [Article]. L'API de la couche JPA Explicitons l'environnement d'exécution d'un client JPA : 28/257

29 Interface [JPA] = EntityManager Client JPA 1 2 Base de Données 4 3 Objets image de la BD = 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. 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 du contexte de persistance. Nous présentons ci-dessous, les principales méthodes de cette interface : void persist(object entity) met entity dans le contexte de persistance void remove(object entity) enlève entity du contexte de persistance <T> T merge(t entity) fusionne un objet entity du client non géré par le contexte de persistance avec l'objet entity du contexte de persistance ayant la même clé primaire. Le résultat rendu est l'objet entity du contexte de persistance. met dans le contexte de persistance, un objet cherché dans la base de données via sa clé primaire. Le type T de l'objet permet à la couche JPA de savoir quelle table requêter. L'objet persistant ainsi créé est rendu au client. crée un objet Query à partir d'une requête JPQL (Java Persistence Query Language). Une requête JPQL est analogue à une requête SQL si ce n'est qu'on requête des objets plutôt que des tables. méthode analogue à la précédente, si ce n'est que querytext est un ordre SQL et non JPQL. <T> T find(class<t> entityclass, Object primarykey) Query createquery(string querytext) Query createnativequery(string querytext) Query createnamedquery(string name) méthode identique à createquery, si ce n'est que l'ordre JPQL querytext a été externalisé dans un fichier de configuration et associé à un nom. C'est ce nom qui est le paramètre de la méthode. Un objet EntityManager a un cycle de vie qui n'est pas forcément celui de l'application. Il a un début et une fin. Ainsi un client JPA peut travailler successivement avec différents objets EntityManager. Le contexte de persistance associé à un EntityManager a le même cycle de vie que lui. Ils sont indissociables l'un de l'autre. Lorsqu'un objet EntityManager est fermé, son contexte de persistance est si nécessaire synchronisé avec la base de données puis il n'existe plus. Il faut créer un nouvel EntityManager pour avoir de nouveau un contexte de persistance. Le client JPA peut créer un EntityManager et donc un contexte de persistance avec l'instruction suivante : EntityManagerFactory emf = Persistence.createEntityManagerFactory("nom d'une unité de persistance"); javax.persistence.persistence est une classe statique permettant d'obtenir une fabrique (factory) d'objets EntityManager. Cette fabrique est liée à une unité de persistance précise. On se rappelle que le fichier de configuration [METAINF/persistence.xml] permet de définir des unités de persistance et que celles-ci ont un nom : <persistence-unit name="elections-dao-jpa-mysql-01pu" transaction-type="resource_local"> Ci-dessus, l'unité de persistance s'appelle elections-dao-jpa-mysql-01pu. Avec elle, vient toute une configuration qui lui est propre, notamment le SGBD avec lequel elle travaille. L'instruction [Persistence.createEntityManagerFactory("elections-daoJPA-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

30 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. le contexte de persistance est vidé de tous ses objets mais pas fermé. void clear() void flush() le contexte de persistance est synchronisé avec la base de données de la façon décrite pour close() Le client JPA peut forcer la synchronisation du contexte de persistance avec la base de données avec la méthode [EntityManager].flush précédente. La synchronisation peut être explicite ou implicite. Dans le premier cas, c'est au client de faire des opérations flush lorsqu'il veut faire des synchronisations, sinon celles-ci se font à certains moments que nous allons préciser. Le mode de synchronisation est géré par les méthodes suivantes de l'interface [EntityManager] : void setflushmode(flushmodetype flushmode) FlushModeType getflushmode() Il y a deux valeurs possibles pour flushmode : FlushModeType.AUTO (défaut): la synchronisation a lieu avant chaque requête SELECT faite sur la base. FlushModeType.COMMIT : la synchronisation n'a lieu qu'à la fin des transactions sur la base. rend le mode actuel de synchronisation 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 à 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 cidessus en mode AUTO, et le cas 2 uniquement en mode COMMIT. Terminons par l'api de l'interface Query, interface qui permet d'émettre des ordres JPQL sur le contexte de persistance ou bien des ordres SQL directement sur la base pour y retrouver des données. L'interface Query est la suivante : 30/257

31 la méthode getresultlist execute un SELECT qui ramène plusieurs objets. Ceux-ci seront obtenus dans un objet List. Cet objet est une interface. Celle-ci offre un objet Iterator qui permet de parcourir les éléments de la liste L sous la forme suivante : 1. Iterator iterator = L.iterator(); while (iterator.hasnext()) { 3. // exploiter l'objet iterator.next() qui représente l'élément courant de la liste } La liste L peut être également exploitée avec un for : } 3.4 for (Object o : L) { // exploiter objet o 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. Les requêtes JPQL Note : le projet Netbeans et le script SQL de la base de données de ce paragraphe sont disponibles dans le support du document. 31/257

32 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 [DAO] [JPA / Hibernate] [JDBC] SGBD BD 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 La table [MEDECINS] Elle contient des informations sur les médecins ID : n identifiant le médecin - clé primaire de la table VERSION : n identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une modification est apportée à la ligne. NOM : le nom du médecin PRENOM : son prénom TITRE : son titre (Melle, Mme, Mr) La table [CLIENTS] Les clients des différents médecins sont enregistrés dans la table [CLIENTS] : ID : n identifiant le client - clé primaire de la table VERSION : n identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une modification est apportée à la ligne. 32/257

33 NOM : le nom du client PRENOM : son prénom TITRE : son titre (Melle, Mme, Mr) La table [CRENEAUX] Elle liste les créneaux horaires où les RV sont possibles : 1 ID : n identifiant le créneau horaire - clé primaire de la table (ligne 8) VERSION : n identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une modification est apportée à la ligne. ID_MEDECIN : n identifiant le médecin auquel appartient ce créneau clé étrangère sur la colonne MEDECINS(ID). HDEBUT : heure début créneau MDEBUT : minutes début créneau HFIN : heure fin créneau MFIN : minutes fin créneau La seconde ligne de la table [CRENEAUX] (cf [1] ci-dessus) indique, par exemple, que le créneau n 2 commence à 8 h 20 et se termine à 8 h 40 et appartient au médecin n 1 (Mme Marie PELISSIER) La table [RV] Elle liste les RV pris pour chaque médecin : 1 ID : n identifiant le RV de façon unique clé primaire JOUR : jour du RV ID_CRENEAU : créneau horaire du RV - clé étrangère sur le champ [ID] de la table [CRENEAUX] fixe à la fois le créneau horaire et le médecin concerné. ID_CLIENT : n du client pour qui est faite la réservation clé étrangère sur le champ [ID] de la table [CLIENTS] 33/257

34 Cette table a une contrainte d'unicité sur les valeurs des colonnes jointes (JOUR, ID_CRENEAU) : ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU); Si une ligne de la table[rv] a la valeur (JOUR1, ID_CRENEAU1) pour les colonnes (JOUR, ID_CRENEAU), cette valeur ne peut se retrouver nulle part ailleurs. Sinon, cela signifierait que deux RV ont été pris au même moment pour le même médecin. D'un point de vue programmation Java, le pilote JDBC de la base lance une SQLException lorsque ce cas se produit. La ligne d'id égal à 3 (cf [1] ci-dessus) signifie qu'un RV a été pris pour le créneau n 20 et le client n 4 le 23/08/2006. La table [CRENEAUX] nous apprend que le créneau n 20 correspond au créneau horaire 16 h 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 Génération de la base Pour créer les tables et les remplir on pourra utiliser le script [dbrdvmedecinssql] (support du cours). Avec [WampServer], on pourra procéder comme suit : en [1], on clique sur l'icône de [WampServer] et on choisit l'option [PhpMyAdmin] [2], en [3], dans la fenêtre qui s'est ouverte, on sélectionne le lien [Bases de données], 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

35 9 8 en [8], on importe un fichier SQL, qu'on désigne dans le système de fichiers avec le bouton [9], 35/257

36 en [11], on sélectionne le script SQL et en [12] on l'exécute, en [13], les quatre tables de la base ont été créées. On suit l'un des liens, 14 en [14], le contenu de la table. Par la suite, nous ne reviendrons plus sur cette base. Mais le lecteur est invité à suivre son évolution au fil des programmes surtout lorsque ça ne marche pas La couche [JPA] Revenons à l'architecture de l'exemple : couche [DAO] [JPA / Hibernate] [JDBC] SGBD BD Nous construisons maintenant le projet Maven de la couche [JPA] Le projet Netbeans C'est le suivant : 36/257

37 3 2 1 en [1], on construit un projet Maven de type [Java Application] [2], en [3], on donne un nom au projet, en [4], le projet généré. Génération de la couche [JPA] Revenons à l'architecture que nous devons construire : couche [DAO] [JPA / Hibernate] [JDBC] SGBD BD 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 Création d'une connexion Netbeans à la base de données lancer le SGBD MySQL 5 afin que la BD soit disponible, créer une connexion Netbeans sur la base [dbrdvmedecins2], 37/257

38 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, en [10], la connexion est créée. On y voit les quatre tables de la base de données connectée. Création d'une unité de persistance Revenons à l'architecture en cours de construction : 38/257

39 couche [DAO] [JDBC] [JPA / Hibernate] SGBD BD Nous sommes en train de construire la couche [JPA]. La configuration de celle-ci est faite dans un fichier [persistence.xml] dans lequel on définit des unités de persistance. Chacune d'elles a besoin des informations suivantes : les caractéristiques JDBC d'accès à la base (URL, utilisateur, mot de passe), les classes qui seront les images des tables de la base de données, l'implémentation JPA utilisée. En effet, JPA est une spécification implémentée par divers produits. Ici, nous utiliserons Hibernate. Netbeans peut générer ce fichier de persistance via l'utilisation d'un assistant. 2 1 cliquer droit sur le projet et choisir la création d'une unité de persistance [1], en [2], créer une unité de persistance, en [3], donner un nom à l'unité de persistance que l'on crée, en [4], choisir l'implémentation JPA Hibernate (JPA 0), en [5], indiquer que les tables de la BD sont déjà créées et que donc on ne les crée pas. On valide l'assistant, en [6], le nouveau projet, en [7], le fichier [persistence.xml] a été généré dans le dossier [META-INF], en [8], de nouvelles dépendances ont été ajoutées au projet Maven. Le fichier [META-INF/persistence.xml] généré ressemble à ceci : 39/257

40 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <persistence-unit name="mv-rdvmedecins-jpql-hibernatepu" transactiontype="resource_local"> 4. <provider>org.hibernate.ejb.hibernatepersistence</provider> 5. <properties> 6. <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/> 7. <property name="javax.persistence.jdbc.password" value=""/> 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> 1 </persistence-unit> 13. </persistence> Il reprend les informations données dans l'assistant : ligne 3 : le nom de l'unité de persistance, ligne 3 : le type de transactions avec la base de données. Ici, RESOURCE_LOCAL indique que l'application va gérer ellemême ses transactions, lignes 6-9 : les propriétés JDBC de la source de données. Dans l'onglet [Design], on peut avoir une vue globale du fichier [persistence.xml] : 14 Pour avoir des logs d'hibernate, nous complétons le fichier [persistence.xml] de la façon suivante : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <persistence-unit name="mv-rdvmedecins-jpql-hibernatepu" transactiontype="resource_local"> 4. <provider>org.hibernate.ejb.hibernatepersistence</provider> 5. <properties> 6. <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/> 7. <property name="javax.persistence.jdbc.password" value=""/> 40/257

41 <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.driver"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="hibernate.cache.provider_class" value="org.hibernate.cache.nocacheprovider"/> 11. <property name="hibernate.show_sql" value="true"/> 1 <property name="hibernate.format_sql" value="true"/> 13. </properties> 14. </persistence-unit> 15. </persistence> ligne 11 : on demande à voir les ordres SQL émis par Hibernate, ligne 12 : cette propriété permet d'avoir un affichage formaté de ceux-ci. Des dépendances ont été ajoutées au projet. Le fichier [pom.xml] est le suivant : 1. <project xmlns=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-rdvmedecins-jpql-hibernate</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-rdvmedecins-jpql-hibernate</name> 11. <url> <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <dependencies> 18. <dependency> 19. <groupid>junit</groupid> 20. <artifactid>junit</artifactid> 21. <version>3.8.1</version> 2 <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> 3 <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> 4 <version>4.1.2</version> 43. </dependency> 44. <dependency> 45. <groupid>antlr</groupid> 46. <artifactid>antlr</artifactid> 41/257

42 47. <version>7.7</version> 48. </dependency> 49. <dependency> 50. <groupid>dom4j</groupid> 51. <artifactid>dom4j</artifactid> 5 <version>1.6.1</version> 53. </dependency> 54. <dependency> 55. <groupid>org.hibernate.javax.persistence</groupid> 56. <artifactid>hibernate-jpa-0-api</artifactid> 57. <version>1.0.1.final</version> 58. </dependency> 59. <dependency> 60. <groupid>org.javassist</groupid> 61. <artifactid>javassist</artifactid> 6 <version> 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=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-rdvmedecins-jpql-hibernate</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-rdvmedecins-jpql-hibernate</name> 11. <url> <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <dependencies> 18. <dependency> 19. <groupid>mysql</groupid> 20. <artifactid>mysql-connector-java</artifactid> 21. <version>5.1.6</version> 2 </dependency> 23. <dependency> 24. <groupid>org.hibernate</groupid> 42/257

43 25. <artifactid>hibernate-entitymanager</artifactid> 26. <version>4.1.2</version> 27. </dependency> 28. </dependencies> 29. </project> Génération des entités JPA Les entités JPA peuvent être générées par un assistant de Netbeans : 1 en [1], on crée des entités JPA à partir d'une base de données, 2 3 en [2], on sélectionne la connexion [dbrdvmedecins2] créée précédemment, en [3], on sélectionne toutes les tables de la base de données associée, en [4], on donne un nom aux classes Java associées aux quatre tables. Ici on a enlevé le pluriel des classes, ainsi qu'un nom de paquetage [5], en [6], JPA rassemble des lignes de tables de BD dans des collections. Nous choisissons la liste comme collection, 43/257

44 7 en [7], les classes Java créées par l'assistant Les entités JPA générées L'entité [Medecin] est l'image de la table [medecins]. La classe Java est truffée d'annotations qui rendent le code peu lisible au premier abord. Si on ne garde que ce qui est essentiel à la compréhension du rôle de l'entité, on obtient le code suivant : 1. package rdvmedecins.jpa; = "medecins") 6. public class Medecin implements Serializable { 7. = GenerationType.IDENTITY) = "ID") 11. private Long id; 1 = "TITRE") 14. private String titre; 15. = "NOM") 17. private String nom; 18. = "VERSION") 20. private int version; 21. = "PRENOM") 23. private String prenom; 24. = CascadeType.ALL, mappedby = "idmedecin") 26. private List<Creneau> creneaulist; // constructeurs // getters et setters public int hashcode() { } /257

45 public boolean equals(object object) {... public String tostring() {... } ligne 4, 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 : le champ id de la classe [Medecin] correspond au champ [ID] (ligne 10) de la table [medecins], lignes : le champ titre de la classe [Medecin] correspond au champ [TITRE] (ligne 13) de la table [medecins], lignes : le champ nom de la classe [Medecin] correspond au champ [NOM] (ligne 16) de la table [medecins], lignes : 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 Version. Nous le ferons dans une prochaine étape, lignes : le champ prenom de la classe [Medecin] correspond au champ [PRENOM] de la table [medecins], lignes : le champ id correspond à la clé primaire [ID] de la table. Les annotations des lignes 8-9 précisent ce point, ligne 8 : 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 : 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 par JPA (ligne 25). Le champ de la ligne 26 contiendra tous les créneaux du médecin. Ceci sans programmation. Pour comprendre totalement la ligne 25, il nous faut présenter la classe [Creneau]. Celle-ci est la suivante : 1. package rdvmedecins.jpa; 3. import java.io.serializable; 4. import java.util.list; 5. import javax.persistence.*; 6. import javax.validation.constraints.notnull; 7. = "creneaux") 10. public class Creneau implements Serializable { = GenerationType.IDENTITY) = "ID") 14. private Long id; 15. = "MDEBUT") 17. private int mdebut; 18. = "HFIN") 20. private int hfin; 21. = "HDEBUT") 23. private int hdebut; /257

46 = "MFIN") 26. private int mfin; 27. = "VERSION") 29. private int version; 30. = "ID_MEDECIN", referencedcolumnname = "ID") = false) 33. private Medecin idmedecin; 34. = CascadeType.ALL, mappedby = "idcreneau") 36. private List<Rv> rvlist; // constructeurs // getters et setters public int hashcode() {... public boolean equals(object object) {... public String tostring() {... } Nous ne commentons que les nouvelles annotations : nous avons dit que la table [creneaux] avait une clé étrangère vers la table [medecins] : un créneau est associé à un médecin. Plusieurs créneaux peuvent être asssociés au même médecin. On a une relation de la table [creneaux] vers la table [medecins] qui est qualifiée de plusieurs (créneaux) à un (médecin). C'est de la ligne 32 qui sert à qualifier la clé étrangère, la ligne 31 avec précise la relation de clé étrangère : la colonne [ID_MEDECIN] de la table [creneaux] est clé étrangère sur la colonne [ID] de la table [medecins], ligne 33 : une référence sur le médecin propriétaire du créneau. On l'obtient là encore sans programmation. Le lien de clé étrangère entre l'entité [Creneau] et l'entité [Medecin] est donc matérialisé par deux annotations : dans l'entité [Creneau] : = "ID_MEDECIN", referencedcolumnname = = false) 3. private Medecin idmedecin; dans l'entité [Medecin] : = CascadeType.ALL, mappedby = "idmedecin") private List<Creneau> creneaulist; Les deux annotations reflètent la même relation : celle de la clé étrangère de la table [creneaux] vers la table [medecins]. On dit qu'elles sont inverses l'une de l'autre. Seule la est indispensable. Elle qualifie sans ambiguïté la relation de clé étrangère. La est facultative. Si elle est présente, elle se contente de référencer la à 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 qui spécifie la clé étrangère. Toujours dans 46/257

47 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; = "clients") 6. public class Client implements Serializable { = GenerationType.IDENTITY) = "ID") 10. private Long id; 11. = "TITRE") 13. private String titre; 14. = "NOM") 16. private String nom; 17. = "VERSION") 19. private int version; 20. = "PRENOM") 2 private String prenom; 23. = CascadeType.ALL, mappedby = "idclient") 25. private List<Rv> rvlist; // constructeurs // getters et setters public int hashcode() { } public boolean equals(object object) { } public String tostring() { } } les lignes reflètent la relation de clé étrangère entre la table [rv] et la table [clients]. 47/257

48 L'entité [Rv] : 1. package rdvmedecins.jpa; = "rv") 6. public class Rv implements Serializable { = GenerationType.IDENTITY) = "ID") 10. private Long id; 11. = "JOUR") 14. private Date jour; 15. = "ID_CRENEAU", referencedcolumnname = "ID") = false) 18. private Creneau idcreneau; 19. = "ID_CLIENT", referencedcolumnname = "ID") = false) 2 private Client idclient; // constructeurs // getters et setters public int hashcode() { } public boolean equals(object object) { } public String tostring() { } } 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 : qualifient la relation de clé étrangère qu'a la table [rv] vers la table [creneaux], lignes : 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 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 inverses des Elles ne sont pas indispensables et elles amènent des complications de programmation. On supprime l'annotation et le champ annoté, 48/257

49 on supprime la sur les clés primaires. Lorsqu'on persiste une entité JPA avec MySQL, l'entité au départ a une clé primaire null. Ce n'est qu'après persistance dans la base, que la clé primaire de l'élément persisté a une valeur. Avec ces spécifications, les différentes classes deviennent les suivantes : La classe Personne est utilisée pour représenter les médecins et les clients : 1. package rdvmedecins.jpa; 3. import java.io.serializable; 4. import javax.persistence.*; public class Personne implements Serializable { 8. private static final long serialversionuid = 1L; = GenerationType.IDENTITY) = "ID") 1 private Long id; 13. = false) = "TITRE") 16. private String titre; 17. = false) = "NOM") 20. private String nom; 21. = false) = "VERSION") 25. private int version; 26. = false) = "PRENOM") 29. private String prenom; // constructeurs public Personne() { 34. } public Personne(Long id) { 37. this.id = id; 38. } public Personne(Long id, String titre, String nom, int version, String prenom) { 41. this.id = id; 4 this.titre = titre; 43. this.nom = nom; 44. this.version = version; 45. this.prenom = prenom; 46. } // getters et setters public String tostring() { 53. return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom); 54. } /257

50 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. 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; 3. import java.io.serializable; 4. import javax.persistence.*; 5. = "clients") 8. public class Client extends Personne implements Serializable { 9. private static final long serialversionuid = 1L; // constructeurs 1 public Client() { 13. super(); 14. } public Client(Long id) { 17. super(id); 18. } public Client(Long id, String titre, String nom, int version, String prenom) { 21. super(id, titre, nom, version, prenom); 2 } public int hashcode() { } public boolean equals(object object) { } public String tostring() { 36. return String.format("Client[%s,%s,%s,%s]", getid(), gettitre(), getprenom(), getnom()); 37. } } ligne 6 : la classe [Client] est une entité Jpa, ligne 7 : elle est associée à la table [clients], ligne 8 : elle dérive de la classe [Personne]. L'entité [Medecin] qui encapsule les lignes de la table [medecins] suit le même modèle : package rdvmedecins.jpa; import java.io.serializable; = "medecins") public class Medecin extends Personne implements Serializable { private static final long serialversionuid = 1L; 50/257

51 // constructeurs public Medecin() { super(); } public Medecin(Long id) { super(id); } public Medecin(Long id, String titre, String nom, int version, String prenom) { super(id, titre, nom, version, prenom); public int hashcode() {... public boolean equals(object object) {... public String tostring() { return String.format("Médecin[%s,%s,%s,%s]", getid(), gettitre(), getprenom(), getnom()); 37. } } L'entité [Creneau] encapsule les lignes de la table [creneaux] : 1. package rdvmedecins.jpa; 3. import java.io.serializable; 4. import java.util.list; 5. import javax.persistence.*; 6. = "creneaux") 9. public class Creneau implements Serializable { private static final long serialversionuid = 1L; = GenerationType.IDENTITY) = false) = "ID") 16. private Long id; 17. = false) = "MDEBUT") 20. private int mdebut; 21. = false) = "HFIN") 24. private int hfin; 25. = false) = "HDEBUT") 29. private int hdebut; 51/257

52 = = "MFIN") private int = = private int = "ID_MEDECIN", referencedcolumnname = = false) private Medecin medecin; // constructeurs... // getters et public int hashcode() {... public boolean equals(object object) { // TODO: Warning - this method won't work in the case the id fields are not set... public String tostring() { return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin); 64. } 65. } les lignes modélisent la relation "plusieurs à un" qui existe entre la table [creneaux] et la table [medecins] de la base de données : un médecin a plusieurs créneaux, un créneau appartient à un seul médecin. L'entité [Rv] encapsule les lignes de la table [rv] : 1. package rdvmedecins.jpa; 3. import java.io.serializable; 4. import java.util.date; 5. import javax.persistence.*; 6. = "rv") 9. public class Rv implements Serializable { private static final long serialversionuid = 1L; = GenerationType.IDENTITY) = false) = "ID") 16. private Long id; 17. = false) = "JOUR") 52/257

53 21. private Date jour; 2 = "ID_CRENEAU", referencedcolumnname = "ID") = false) 25. private Creneau creneau; 26. = "ID_CLIENT", referencedcolumnname = "ID") = false) 29. private Client client; // constructeurs // getters et setters public int hashcode() { } public boolean equals(object object) { } public String tostring() { 49. return String.format("Rv[%s, %s, %s]", id, creneau, client); 50. } 51. } les lignes 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 la relation "plusieurs à un" qui existe entre la table [rv] et la table [creneaux] (un créneau peut apparaître dans plusieurs Rv). Le code d'accès aux données Nous allons ajouter maintenant au projet, le code d'accès aux données via la couche JPA : couche [console] [JPA / Hibernate] [JDBC] SGBD BD 53/257

54 La classe [MainJpql] est la suivante : 1. package rdvmedecins.console; 3. import java.util.scanner; 4. import javax.persistence.entitymanager; 5. import javax.persistence.entitymanagerfactory; 6. import javax.persistence.persistence; public class MainJpql { public static void main(string[] args) { 11. // EntityManagerFactory 1 EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-rdvmedecins-jpqlhibernatePU"); 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 { 2 // 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 3 System.out.println(" "); 33. System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :"); 34. requete = clavier.nextline(); 35. } 36. // fermeture des ressources 37. em.close(); 38. emf.close(); 39. } 40. } ligne 12 : création de l'entitymanagerfactory associé à l'unité de persistance que nous avons créée précédemment. le paramètre de la méthode createentitymanagerfactory est le nom de cette unité de persistance : 1. <persistence-unit name="mv-rdvmedecins-jpql-hibernatepu" transactiontype="resource_local"> </persistence-unit> ligne 14 : création de l'entitymanager qui gère la couche de persistance, ligne 19 : saisie d'une requête JPQL select, lignes : affichage du résultat de la requête, ligne 20 : la saisie s'arrête lorsque l'utilisateur tape *. Question : donner les requêtes JPQL permettant d'obtenir les informations suivantes : liste des médecins dans l'orde décroissant de leurs noms liste des médecins dont titre='mr' liste des créneaux horaires de Mme Pelissier liste des Rv pris dans l'ordre croissant des jours 54/257

55 liste des clients (nom) ayant pris RV avec Mme PELISSIER le 24/08/2006 nombre de clients de Mme PELISSIER le 24/08/2006 les clients n'ayant pas pris de Rdv les médecins n'ayant pas de Rdv On s'inspirera de l'exemple du paragraphe 7 de [ref1]. Voici un exemple d'exécution : 1. Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) : select c from Client c 3. Hibernate: 4. select 5. client0_.id as ID2_, 6. client0_.nom as NOM2_, 7. client0_.prenom as PRENOM2_, 8. client0_.titre as TITRE2_, 9. client0_.version as version2_ 10. from 11. clients client0_ 1 Client[1,Mr,Jules,MARTIN] 13. Client[2,Mme,Christine,GERMAN] 14. Client[3,Mr,Jules,JACQUARD] 15. Client[4,Melle,Brigitte,BISTROU] 3.5 ligne 2 : la requête JPQL, lignes 3-11 : la requête SQL correspondante, lignes : le résultat de la requête JPQL. Liens entre contexte de persistance et SGBD Note : Jusqu'à la fin du chapitre 3, il n'y a pas de projets à construire. Il faut simplement lire le cours La classe Personne 1. package entites; = "jpa01_personne") 7. public class Personne { 8. = "ID", nullable = false) = GenerationType.AUTO) 1 private Integer id; 13. = "VERSION", nullable = false) 16. private int version; 17. = "NOM", length = 30, nullable = false, unique = true) 19. private String nom; 20. = "PRENOM", length = 30, nullable = false) 2 private String prenom; 23. = "DATENAISSANCE", nullable = false) 26. private Date datenaissance; 27. = "MARIE", nullable = false) 55/257

56 private boolean = "NBENFANTS", nullable = false) private int nbenfants; // constructeurs public Personne() { } public Personne(String nom, String prenom, Date datenaissance, boolean marie, int nbenfants) { 40. setnom(nom); 41. setprenom(prenom); 4 setdatenaissance(datenaissance); 43. setmarie(marie); 44. setnbenfants(nbenfants); 45. } // tostring 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()); 5 } // getters and setters } Le programme de test 1. package tests; import entites.personne; public class Test1 { // constantes 10. private final static String TABLE_NAME = "jpa01_personne"; // Contexte de persistance 11. private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPA"); 1 private static Personne p1; public static void main(string[] args) throws Exception { 15. // nettoyage base 16. log("clean"); 17. clean(); // dump 20. log("dump"); 21. dump(); // test1 24. log("test1"); 25. test1(); // test2 28. log("test2"); 29. test2(); 56/257

57 // fermeture EntityManagerFactory emf.close(); } // affichage contenu table private static void dump() { // contexte de persistance EntityManager em = emf.createentitymanager(); // début transaction EntityTransaction tx = em.gettransaction(); tx.begin(); // affichage personnes 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. } // 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 6 tx.commit(); 63. // fin contexte 64. em.close(); 65. } // logs 68. private static void log(string message) { 69. System.out.println("main : " + message); 70. } // 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"); 8 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

58 System.out.println(String.format("Personne p1 %s persistée", p1)); // personne p2 // INSERT est généré dès l'opération persist System.out.println(String.format("Personne p2 %s non persistée", p2)); System.out.println("em.persist(p2)"); em.persist(p2); System.out.println(String.format("Personne p2 %s persistée", p2)); psetmarie(true); System.out.println(String.format("Personne p2 %s modifiée", p2)); // 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)); 10 // modification p 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. } // 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 12 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 P 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(); 13 // fin contexte 133. em.close(); 134. // on affiche la table 135. dump(); 136. } 137.} La configuration d'hibernate dans [persistence.xml] 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="1.0" xmlns=" 3. <persistence-unit name="jpa" transaction-type="resource_local"> 4. <!-- provider --> 5. <provider>org.hibernate.ejb.hibernatepersistence</provider> 6. <properties> 7. <!-- Classes persistantes --> 8. <property name="hibernate.archive.autodetection" value="class, hbm" /> 9. <property name="hibernate.show_sql" value="true"/> 58/257

59 <!-- création automatique du schéma --> 1 <property name="hibernate.hbm2ddl.auto" value="create" /> </properties> 15. </persistence-unit> 16. </persistence> Les résultats 1. init: deps-jar: 3. Compiling 1 source file to C:\data\travail\ \netbeans\JPA\hibernate-personnesentites\build\classes 4. compile-single: 5. run-single: 6. main : clean 7. Hibernate: delete from jpa01_personne 8. main : dump 9. Hibernate: select personne0_.id as ID0_, personne0_.datenaissance as DATENAIS2_0_, personne0_.marie as MARIE0_, personne0_.nbenfants as NBENFANTS0_, personne0_.nom as NOM0_, personne0_.prenom as PRENOM0_, personne0_.version as VERSION0_ from jpa01_personne personne0_ order by personne0_.nom asc 10. main : test1 11. début transaction 1 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 (?,?,?,?,?,?) :57:26,312 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: :57:26,312 DEBUG BooleanType:133 - binding 'true' to parameter: :57:26,312 DEBUG IntegerType:133 - binding '2' to parameter: :57:26,312 DEBUG StringType:133 - binding 'Martin' to parameter: :57:26,312 DEBUG StringType:133 - binding 'Paul' to parameter: :57:26,312 DEBUG IntegerType:133 - binding '0' to parameter: Personne p1 [1,0,Martin,Paul,31/01/2000,true,2] persistée 2 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 (?,?,?,?,?,?) :57:26,328 DEBUG DateType:133 - binding '05 juillet 2001' to parameter: :57:26,328 DEBUG BooleanType:133 - binding 'false' to parameter: :57:26,328 DEBUG IntegerType:133 - binding '0' to parameter: :57:26,328 DEBUG StringType:133 - binding 'Durant' to parameter: :57:26,328 DEBUG StringType:133 - binding 'Sylvie' to parameter: :57:26,328 DEBUG IntegerType:133 - binding '0' to parameter: Personne p2 [2,0,Durant,Sylvie,05/07/2001,false,0] persistée 3 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=? :57:26,343 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: :57:26,343 DEBUG BooleanType:133 - binding 'true' to parameter: :57:26,343 DEBUG IntegerType:133 - binding '2' to parameter: :57:26,343 DEBUG StringType:133 - binding 'P1' to parameter: :57:26,359 DEBUG StringType:133 - binding 'Paul' to parameter: :57:26,359 DEBUG IntegerType:133 - binding '1' to parameter: :57:26,359 DEBUG IntegerType:133 - binding '1' to parameter: :57:26,359 DEBUG IntegerType:133 - binding '0' to parameter: Hibernate: delete from jpa01_personne where ID=? and VERSION=? :57:26,359 DEBUG IntegerType:133 - binding '2' to parameter: 1 59/257

60 47. 17:57:26,359 DEBUG IntegerType:133 - binding '0' to parameter: 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 :57:26,375 DEBUG IntegerType:172 - returning '1' as column: ID0_ :57:26,390 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_ :57:26,390 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_ 5 17:57:26,390 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_ :57:26,390 DEBUG StringType:172 - returning 'P1' as column: NOM0_ :57:26,390 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_ :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) 6 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=? :57:26,406 DEBUG IntegerType:133 - binding '1' to parameter: :57:26,406 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_0_ :57:26,406 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_0_ :57:26,406 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_0_ :57:26,406 DEBUG StringType:172 - returning 'P1' as column: NOM0_0_ :57:26,406 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_0_ :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 7 Hibernate: update jpa01_personne set DATENAISSANCE=?, MARIE=?, NBENFANTS=?, NOM=?, PRENOM=?, VERSION=? where ID=? and VERSION=? :57:26,406 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: :57:26,406 DEBUG BooleanType:133 - binding 'false' to parameter: :57:26,406 DEBUG IntegerType:133 - binding '2' to parameter: :57:26,421 DEBUG StringType:133 - binding 'P1' to parameter: :57:26,421 DEBUG StringType:133 - binding 'Paul' to parameter: :57:26,421 DEBUG IntegerType:133 - binding '2' to parameter: :57:26,421 DEBUG IntegerType:133 - binding '1' to parameter: :57:26,421 DEBUG IntegerType:133 - binding '1' to parameter: 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 8 17:57:26,453 DEBUG IntegerType:172 - returning '1' as column: ID0_ :57:26,453 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_ :57:26,453 DEBUG BooleanType:172 - returning 'false' as column: MARIE0_ :57:26,453 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_ :57:26,453 DEBUG StringType:172 - returning 'P1' as column: NOM0_ :57:26,453 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_ :57:26,453 DEBUG IntegerType:172 - returning '2' as column: VERSION0_ 89. [1,2,P1,Paul,31/01/2000,false,2] 90. BUILD SUCCESSFUL (total time: 3 seconds) Question : faites le lien entre le code Java et les résultats affichés. 60/257

61 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 : [ui] [metier] [DAO] Objets image de la BD 7 Interface [JPA] Implémentation [Hibernate / EclipseLink] [JDBC] BD Spring Note : lire jusqu'au paragraphe 4.4 inclus, puis passer à la pratique. 4.1 La base de données Les données statiques utiles pour construire la fiche de paie seront placées dans une base de données que nous désignerons par la suite dbpam. Cette base de données pourrait avoir les tables suivantes : Table EMPLOYES : rassemble des informations sur les différentes assistantes maternelles Structure : ID VERSION SS NOM PRENOM ADRESSE VILLE CODEPOSTAL INDEMNITE_ID clé primaire n de version augmente à chaque modification de la ligne numéro de sécurité sociale de l'employé - unique nom de l'employé son prénom son adresse sa ville son code postal clé étrangère sur le champ [ID] de la table [INDEMNITES] Son contenu pourrait être le suivant : Table COTISATIONS : rassemble des pourcentages nécessaires au calcul des cotisations sociales Structure : clé primaire ID 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 pourcentage : contribution sociale généralisée déductible CSGD pourcentage : sécurité sociale, veuvage, vieillesse SECU RETRAITE pourcentage : retraite complémentaire + assurance chômage Son contenu pourrait être le suivant : 61/257

62 Les taux des cotisations sociales sont indépendants du salarié. La table précédente n'a qu'une ligne. Table INDEMNITES : rassemble les éléments permettant le calcul du salaire à payer. ID VERSION INDICE BASEHEURE ENTRETIENJOUR REPASJOUR INDEMNITESCP clé primaire n de version augmente à chaque modification de la ligne indice de traitement - unique prix net en euro d une heure de garde indemnité d entretien en euro par jour de garde indemnité de repas en euro par jour de garde indemnité de congés payés. C'est un pourcentage à appliquer au salaire de base. Son contenu pourrait être le suivant : On notera que les indemnités peuvent varier d'une assistante maternelle à une autre. Elles sont en effet associées à une assistante maternelle précise via l'indice de traitement de celle-ci. Ainsi Mme Marie Jouveinal qui a un indice de traitement de 2 (table EMPLOYES) a un salaire horaire de 2,1 euro (table INDEMNITES). 4.2 Mode de calcul du salaire d'une assistante maternelle Nous présentons maintenant le mode de calcul du salaire mensuel d'une assistante maternelle. Il ne prétend pas être celui utilisé dans la réalité. Nous prenons pour exemple, le salaire de Mme Marie Jouveinal qui a travaillé 150 h sur 20 jours pendant le mois à payer. Les éléments suivants sont pris en compte [TOTALHEURES]: total des heures travaillées dans le mois : [TOTALHEURES]=150 [TOTALJOURS]= 20 [TOTALJOURS]: total des jours travaillés dans le mois Le salaire de base de l'assistante maternelle est donné par la formule suivante : [SALAIREBASE]=([TOTALHEURES]*[B ASEHEURE])*(1+ [INDEMNITESCP]/100) Un certain nombre de cotisations sociales Contribution sociale généralisée et contribution au doivent être prélevées sur ce salaire de remboursement de la dette base : sociale : [SALAIREBASE]*[CSGRDS/100] Contribution sociale généralisée déductible : [SALAIREBASE]*[CSGD/100] [SALAIREBASE]=(150*[1])*( )= 362,25 CSGRDS : 12,64 CSGD : 22,28 Sécurité sociale : 34,02 Retraite : 28,55 Sécurité sociale, veuvage, vieillesse : [SALAIREBASE]*[SECU/100] Retraite Complémentaire + AGPF + Assurance Chômage : [SALAIREBASE]*[RETRAITE/100] 62/257

63 Les éléments suivants sont pris en compte [TOTALHEURES]: total des heures : travaillées dans le mois [TOTALHEURES]=150 [TOTALJOURS]= 20 [TOTALJOURS]: total des jours travaillés dans le mois Total des cotisations sociales : [COTISATIONSSOCIALES]=[SALAIREB ASE]*(CSGRDS+CSGD+SECU+RETRAITE )/100 [COTISATIONSSOCIALES]=97,48 Par ailleurs, l'assistante maternelle a droit, chaque jour travaillé, à une indemnité d'entretien ainsi qu'à une indemnité de repas. A ce titre elle reçoit les indemnités suivantes : [INDEMNITÉS]=[TOTALJOURS]*(ENTR ETIENJOUR+REPASJOUR) [INDEMNITES]=104 Au final, le salaire net à payer à l'assistante [SALAIREBASE]maternelle est le suivant : [COTISATIONSSOCIALES]+ [salaire NET]=368,77 [INDEMNITÉS] 4.3 Fonctionnement de l'application console Voici un exemple d'exécution de l'application console dans une fenêtre Dos : 1. dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar Valeurs saisies : 4. N de sécurité sociale de l'employé : Nombre d'heures travaillées : Nombre de jours travaillés : Informations Employé : 9. Nom : Jouveinal 10. Prénom : Marie 11. Adresse : 5 rue des Oiseaux 1 Ville : St Corentin 13. Code Postal : Indice : Informations Cotisations : 17. CSGRDS : 3.49 % 18. CSGD : 6.15 % 19. Retraite : 7.88 % 20. Sécurité sociale : 9.39 % Informations Indemnités : 23. Salaire horaire : 1 euro 24. Entretien/jour : 1 euro 25. Repas/jour : 3.1 euro 26. Congés Payés : 15.0 % Informations Salaire : 29. Salaire de base : 3625 euro 30. Cotisations sociales : euro 31. Indemnités d'entretien : 40 euro 3 Indemnités de repas : 60 euro 33. Salaire net : euro On écrira un programme qui recevra les informations suivantes : 63/257

64 1. 3. n de sécurité sociale de l'assistante maternelle ( dans l'exemple - ligne 1) nombre total d'heures travaillées (150 dans l'exemple - ligne 1) 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 : affichent les taux des différentes cotisations lignes : affichent les indemnités associées à l'indice de traitement de l'employé (ici l'indice 2) lignes : affichent les éléments constitutifs du salaire à payer L'application signale les erreurs éventuelles : Appel sans paramètres : dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés Appel avec des données erronées : dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar Le nombre d'heures travaillées [150x] est erroné Le nombre de jours travaillés [20x] est erroné x 20x Appel avec un n de sécurité sociale erroné : dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar xx L'erreur suivante s'est produite : L'employé de n [xx] est introuvable 4.4 Fonctionnement de l'application graphique L'application graphique permet le calcul des salaires des assistantes maternelles au travers d'un formulaire Swing : 64/257

65 les informations passées en paramètres au programme console, sont maintenant saisies au moyen des champs de saisie [1, 2, 3]. le bouton [4] demande le calcul du salaire le formulaire affiche les différents éléments du salaire jusqu'au salaire net à payer [5] La liste déroulante [1, 6] ne présente pas les n s SS des employés mais les noms et prénoms de ceux-ci. On fait ici l'hypothèse qu'il n'y a pas deux employés de mêmes nom et prénom. 4.5 Création de la base de données Nous lançons WampServer et utilisons l'outil PhpMyAdmin [1] : 1 2 en [2], on prend l'option [Bases de données], 65/257

66 3 4 en [3], on crée une base de données [dbpam_hibernate], en [4], la base créée. On la sélectionne, 5 6 en [5], on veut importer un script SQL, en [6], on utilise le bouton [Parcourir] pour désigner le fichier, en [7,8], on sélectionne le script SQL, en [9], on l'exécute, 10 en [10], les tables ont été créées. Leur contenu est le suivant : 66/257

67 table EMPLOYES table INDEMNITES table COTISATIONS Implémentation JPA JPA / Hibernate Nous allons configurer la couche JPA dans l'environnement suivant : Programme console Interface [JPA] Implémentation [Hibernate] [JDBC] Base de données Un programme console travaillera avec la base de données. Pour cela, il faut : avoir une base de données, avoir le pilote JDBC du SGBD, ici MySQL, implémenter la couche JPA avec Hibernate, écrire le programme console. Nous créons le projet Maven [mv-pam-jpa-hibernate] [1] : 1 Dans l'architecture de notre application il nous faut les éléments suivants : la base de données, le pilote JDBC du SGBD MySQL, 67/257

68 la couche JPA / Hibernate (entités et configuration), le programme console de test. La base de données Créons tout d'abord la base de données vide. Nous lançons WampServer et utilisons l'outil PhpMyAdmin [1] : 1 2 en [2], on prend l'option [Bases de données], en [3], on crée une base de données [dbpam_hibernate], en [4], la base créée. Configuration de la couche JPA La liaison entre la couche JDBC et la base de données se fait dans le fichier [persistence.xml] qui configure la couche JPA. Ce fichier peut être construit avec Netbeans : 68/257

69 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 la connexion apparaît en [8] et en [9], en [10], on ajoute un nouvel élément au projet, /257

70 en [11] on choisit la catégorie [Persistence] et en [12] l'élément [Persistence Unit], en [13], on donne un nom à cette unité de persistance, en [14], on choisit une implémentation Hibernate, en [15], on désigne la connexion que nous venons de créer vers la base MySQL, en [16], on indique qu'à l'instanciation de la couche JPA, celle-ci doit construire (create) les tables correspondant aux entités JPA du projet. La fin de l'assistant génère le fichier [persistence.xml] : le fichier apparaît dans une nouvelle branche du projet, dans un dossier [META-INF] [1], qui correspond au dossier [src/main/resources] du projet [2,3]. Son contenu est le suivant : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <persistence-unit name="mv-pam-jpa-hibernatepu" transaction-type="resource_local"> 4. <provider>org.hibernate.ejb.hibernatepersistence</provider> 5. <properties> 6. <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/> 7. <property name="javax.persistence.jdbc.password" value=""/> 8. <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.driver"/> 9. <property name="javax.persistence.jdbc.user" value="root"/> 10. <property name="hibernate.cache.provider_class" value="org.hibernate.cache.nocacheprovider"/> 11. <property name="hibernate.hbm2ddl.auto" value="create-drop"/> 1 </properties> 13. </persistence-unit> 14. </persistence> ligne 3 : le nom de l'unité de persistance et le type de transactions. RESOURCE_LOCAL indique que le projet gère luimême les transactions. C'est ici le programme console qui devra le faire, ligne 4 : l'implémentation JPA utilisée est Hibernate, lignes 6-9 : les caractéristiques JDBC de la connexion à la base de données, ligne 11 : demande la création des tables correspondant aux entités JPA. En fait, Netbeans génère ici une configuration erronée. La configuration doit être la suivante : <property name="hibernate.hbm2ddl.auto" value="create"/> Avec l'option create, Hibernate, à l'instanciation de la couche JPA, supprime puis crée les tables correspondant aux entités JPA. L'option create-drop fait la même chose mais à la fin de vie de la couche JPA, elle supprime toutes les tables. Il existe une autre option : <property name="hibernate.hbm2ddl.auto" value="update"/> 70/257

71 Cette option crée les tables si elles n'existent pas mais elle ne les détruit pas si elles existent déjà. Nous ajouterons trois autres propriétés à la configuration d'hibernate : 1. <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> 3. <property name="use_sql_comments" value="true"/> Elles demandent à Hibernate d'afficher les ordres SQL qu'il envoie à la base de données. Le fichier complet est donc le suivant : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <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"/> 1 <property name="javax.persistence.jdbc.user" value="root"/> 13. <property name="hibernate.cache.provider_class" value="org.hibernate.cache.nocacheprovider"/> 14. <property name="hibernate.hbm2ddl.auto" value="create"/> 15. <property name="hibernate.show_sql" value="true"/> 16. <property name="hibernate.format_sql" value="true"/> 17. <property name="use_sql_comments" value="true"/> 18. </properties> 19. </persistence-unit> 20. </persistence> Les dépendances Revenons à l'architecture du projet : Programme console Interface [JPA] Implémentation [Hibernate] [JDBC] Base de données Nous avons configuré la couche JPA via le fichier [persistence.xml]. L'implémentation choisie a été Hibernate. Cela a amené des dépendances dans le projet : 71/257

72 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=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-pam-jpa-hibernate</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-pam-jpa-hibernate</name> 11. <url> <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <dependencies> 18. <dependency> 19. <groupid>org.hibernate</groupid> 20. <artifactid>hibernate-entitymanager</artifactid> 21. <version>4.1.2</version> 2 <type>jar</type> 23. </dependency> 24. <dependency> 25. <groupid>mysql</groupid> 26. <artifactid>mysql-connector-java</artifactid> 27. <version>5.1.6</version> 28. </dependency> 29. </dependencies> 30. </project> Les lignes ajoutent la dépendance du pilote JDBC de MySQL Les entités JPA 72/257

73 1 Question : Ecrire les entités [Cotisation, Indemnite, Employe]. Notes : les entités feront partie d'un paquetage nommé [jpa], chaque entité aura un n de version, si deux entités sont liées par une relation, seule la relation ManyToOne sera construite. La relation ne le sera pas. Note : Les squelettes des entités sans les annotations sont disponibles dans le support de cours. 1. package jpa; 3. public class Cotisation implements Serializable { private Long id; 6. private int version; 7. private double csgrds; 8. private double csgd; 9. private double secu; 10. private double retraite; public Cotisation() { 13. } 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; 3. public class Employe implements Serializable { 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; 1 private String codepostal; 13. private Indemnite indemnite; /257

74 public Employe() { } public Employe(String SS, String nom, String prenom, String adresse, String ville, String codepostal, Indemnite indemnite){ 19. setss(ss); 20. setnom(nom); 21. setprenom(prenom); 2 setadresse(adresse); 23. setville(ville); 24. setcodepostal(codepostal); 25. setindemnite(indemnite); 26. } 27. } 1. package jpa; 3. public class Indemnite implements Serializable { 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; public Indemnite() { 14. } 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); 2 } 23. } Le code de la classe principale Nous incluons dans le projet les entités JPA développées précédemment [1] : 1 2 puis nous rajoutons [2], la classe [main.main] suivante : 74/257

75 1. package main; 3. import javax.persistence.entitymanager; 4. import javax.persistence.entitymanagerfactory; 5. import javax.persistence.persistence; public class Main { public static void main(string[] args) { 10. // créer l'entity Manager suffit à construire la couche JPA 11. EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpahibernatePU"); 1 EntityManager em=emf.createentitymanager(); 13. // libération ressources 14. em.close(); 15. emf.close(); 16. } 17. } ligne 10 : on crée l'entitymanagerfactory de l'unité de persistance nommée [mv-pam-jpa-hibernatepu]. Ce nom vient du fichier [persistence.xml] : <persistence-unit name="mv-pam-jpa-hibernatepu" transaction-type="resource_local">... </persistence-unit> ligne 12 : on crée l'entitymanager. Cette création crée la couche JPA. Le fichier [persistence.xml] va être exploité et donc les tables de la base de données vont être créées, lignes : on libère les ressources. Tests Revenons à l'architecture de notre projet : Programme console Interface [JPA] [JDBC] Implémentation [Hibernate] Base de données Toutes les couches ont été implémentées. On exécute le projet [2]. 2 Les résultats console sont les suivants : 75/257

76 Building mv-pam-jpa-hibernate 1.0-SNAPSHOT exec-maven-plugin:1.1:exec mv-pam-jpa-hibernate --6. sept. 09, :05:13 PM org.hibernate.annotations.common.version <clinit> 7. INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final} 8. sept. 09, :05:13 PM org.hibernate.version logversion 9. INFO: HHH000412: Hibernate Core {4.1.2} 10. sept. 09, :05:13 PM org.hibernate.cfg.environment <clinit> 11. INFO: HHH000206: hibernate.properties not found 1 sept. 09, :05:13 PM org.hibernate.cfg.environment buildbytecodeprovider 13. INFO: HHH000021: Bytecode provider name : javassist 14. sept. 09, :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, :05:13 PM org.hibernate.service.jdbc.connections.internal.drivermanagerconnectionproviderimpl configure 17. INFO: HHH000115: Hibernate connection pool size: sept. 09, :05:13 PM org.hibernate.service.jdbc.connections.internal.drivermanagerconnectionproviderimpl configure 19. INFO: HHH000006: Autocommit mode: true 20. sept. 09, :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] 2 sept. 09, :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, :05:17 PM org.hibernate.dialect.dialect <init> 25. INFO: HHH000400: Using dialect: org.hibernate.dialect.mysqldialect 26. sept. 09, :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 sept. 09, :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, :05:18 PM org.hibernate.hql.internal.ast.astquerytranslatorfactory <init> 31. INFO: HHH000397: Using ASTQueryTranslatorFactory 3 sept. 09, :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 FK75C8D6BC73F24A sept. 09, :05:18 PM org.hibernate.tool.hbm2ddl.schemaexport perform 39. ERROR: HHH000389: Unsuccessful: alter table EMPLOYES drop foreign key FK75C8D6BC73F24A sept. 09, :05:18 PM org.hibernate.tool.hbm2ddl.schemaexport perform 41. ERROR: Error on rename of '.\dbpam_hibernate\employes' to '.\dbpam_hibernate\#sql a' (errno: 152) 4 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

77 48. Hibernate: 49. create table COTISATIONS ( 50. id bigint not null auto_increment, 51. CSGD double precision not null, 5 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, 6 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: 7 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. ) 8 Hibernate: 83. alter table EMPLOYES 84. add index FK75C8D6BC73F24A67 (INDEMNITE_ID), 85. add constraint FK75C8D6BC73F24A foreign key (INDEMNITE_ID) 87. references INDEMNITES (id) 88. sept. 09, :05:19 PM org.hibernate.tool.hbm2ddl.schemaexport execute 89. INFO: HHH000230: Schema export complete 90. sept. 09, :05:20 PM org.hibernate.service.jdbc.connections.internal.drivermanagerconnectionproviderimpl stop 91. INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306/dbpam_hibernate] BUILD SUCCESS Total time: s 96. Finished at: Mon Sep 09 14:05:20 CEST Final Memory: 5M/122M On trouve dans la console uniquement des logs d'hibernate puisque le programme exécuté ne fait rien en-dehors d'instancier la couche JPA. On notera les points suivants : ligne 35 : Hibernate essaie de supprimer la clé étrangère de la table [EMPLOYES], lignes : suppression des trois tables, ligne 49 : création de la table [COTISATIONS], ligne 59 : création de la table [EMPLOYES], ligne 72 : création de la table [INDEMNITES], ligne 83 : création de la clé étrangère de la table [EMPLOYES]. Dans Netbeans, on peut voir les tables dans la connexion qui a été créée précédemment : 77/257

78 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 JPA / EclipseLink Nous allons construire un nouveau projet Maven dans l'environnement suivant : Programme console Interface [JPA] Implémentation [EclipseLink] [JDBC] Base de données On suivra la démarche du paragraphe précédent : créer une base MySQL [dbpam_eclipselink]. On utilisera le script [dbpam_eclipselink.sql] pour la générer, créer le fichier [persistence.xml] du projet. Prendre l'implémentation JPA 0 EclipseLink, ajouter dans les dépendances générées la dépendance du pilote JDBC de MySQL, 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, faire les tests. Le fichier [persistence.xml] sera le suivant : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <persistence-unit name="pam-jpa-eclipselinkpu" transaction-type="resource_local"> 4. <provider>org.eclipse.persistence.jpa.persistenceprovider</provider> 5. <class>jpa.cotisation</class> 6. <class>jpa.employe</class> 7. <class>jpa.indemnite</class> 8. <properties> 9. <property name="eclipselink.target-database" value="mysql"/> 10. <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/> 11. <property name="javax.persistence.jdbc.password" value=""/> 1 <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.driver"/> 78/257

79 13. <property name="javax.persistence.jdbc.user" value="root"/> 14. <property name="eclipselink.logging.level" value="fine"/> 15. <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/> 16. </properties> 17. </persistence-unit> 18. </persistence> les propriétés 9-13 ont été générées par l'assistant Netbeans, ligne 14 : cette propriété nous permet de fixer le niveau de logs d'eclipselink. Le niveau FINE nous permet de connaître les ordres SQL qu'eclipselink va émettre sur la base de données, ligne 15 : à l'instanciation de la couche JPA / EclipseLink, les tables des entités JPA seront détruites puis créées. Les résultats console obtenus sont les suivants : Building mv-pam-jpa-eclipselink 1.0-SNAPSHOT exec-maven-plugin:1.1:exec mv-pam-jpa-eclipselink --[EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The access type for the persistent class [class JPA.Cotisation] is set to [FIELD]. 7. [EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The access type for the persistent class [class JPA.Employe] is set to [FIELD]. 8. [EL Config]: :10: ServerSession( )-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]: :10: ServerSession( )-Thread(Thread[main,5,main])--The access type for the persistent class [class JPA.Indemnite] is set to [FIELD]. 10. [EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The alias name for the entity class [class JPA.Cotisation] is being defaulted to: Cotisation. 11. [EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID. 1 [EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The alias name for the entity class [class JPA.Employe] is being defaulted to: Employe. 13. [EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID. 14. [EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The alias name for the entity class [class JPA.Indemnite] is being defaulted to: Indemnite. 15. [EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID. 16. [EL Config]: :10: ServerSession( )-Thread(Thread[main,5,main])--The primary key column name for the mapping element [field indemnite] is being defaulted to: ID. 17. [EL Info]: :10: ServerSession( )--Thread(Thread[main,5,main])-EclipseLink, version: Eclipse Persistence Services v r [EL Config]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--connecting(DatabaseLogin( 19. platform=>mysqlplatform 20. user name=> "root" 21. datasource URL=> "jdbc:mysql://localhost:3306/dbpam_eclipselink" 2 )) 23. [EL Config]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--Connected: jdbc:mysql://localhost:3306/dbpam_eclipselink 24. User: root@localhost 25. Database: MySQL Version: log 26. Driver: MySQL-AB JDBC Driver Version: mysql-connector-java ( Revision: $ {svn.revision} ) 79/257

80 27. [EL Info]: :10: ServerSession( )--Thread(Thread[main,5,main])-file:/D:/data/istia-1314/netbeans/mv-pam/05/mv-pam-JPA-eclipselink/target/classes/_pam-JPAeclipselinkPU login successful 28. [EL Fine]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES DROP FOREIGN KEY FK_EMPLOYES_INDEMNITE_ID 29. [EL Fine]: :10: ServerSession( )--Thread(Thread[main,5,main])-SELECT [EL Warning]: :10: ServerSession( )-Thread(Thread[main,5,main])--Exception [EclipseLink-4002] (Eclipse Persistence Services 3.0.v r9504): org.eclipse.persistence.exceptions.databaseexception 31. Internal Exception: java.sql.sqlexception: Error on rename of '.\dbpam_eclipselink\employes' to '.\dbpam_eclipselink\#sql b' (errno: 152) 3 Error Code: 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]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--DROP TABLE COTISATIONS 36. [EL Fine]: :10: ServerSession( )--Connection( )-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]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--DROP TABLE EMPLOYES 38. [EL Fine]: :10: ServerSession( )--Connection( )-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]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--DROP TABLE INDEMNITES 40. [EL Fine]: :10: ServerSession( )--Connection( )-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]: :10:2807--ServerSession( )--Connection( )-Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES ADD CONSTRAINT FK_EMPLOYES_INDEMNITE_ID FOREIGN KEY (INDEMNITE_ID) REFERENCES INDEMNITES (ID) 4 [EL Fine]: :10: ServerSession( )--Connection( )-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]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--DELETE FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN 44. [EL Fine]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN 45. [EL Fine]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values (SEQ_GEN, 0) 46. [EL Config]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--disconnect 47. [EL Info]: :10: ServerSession( )--Thread(Thread[main,5,main])-file:/D:/data/istia-1314/netbeans/mv-pam/05/mv-pam-JPA-eclipselink/target/classes/_pam-JPAeclipselinkPU logout successful 48. [EL Config]: :10: ServerSession( )--Connection( )-Thread(Thread[main,5,main])--disconnect BUILD SUCCESS Total time: 1940s 53. Finished at: Mon Sep 09 14:10:26 CEST Final Memory: 4M/122M lignes : connexion à la base de données MySQL, lignes : confirmation que la connexion a réussi, 80/257

81 lignes : 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 : déconnexion de la base. L'existence des tables générées peut être vérifiée dans Netbeans [1] : 1 Donc, à partir des mêmes entités JPA, les implémentations JPA Hibernate et EclipseLink ne génèrent pas les mêmes tables. Dans la suite du document, lorsque l'implémentation JPA utilisée est : Hibernate, on utilisera la base de données [dbpam_hibernate], EclipseLink, on utilisera la base de données [dbpam_eclipselink]. Travail à faire En suivant la même démarche que précédemment, créer et tester un projet [mv-pam-jpa-hibernate-oracle] utilisant une implémentation JPA Hibernate et un SGBD Oracle, créer et tester un projet [mv-pam-jpa-hibernate-mssql] utilisant une implémentation JPA Hibernate et un SGBD SQL server, créer et tester un projet [mv-pam-jpa-eclipselink-oracle] utilisant une implémentation JPA EclipseLink et un SGBD Oracle, créer et tester un projet [mv-pam-jpa-eclipselink-mssql] utilisant une implémentation JPA EclipseLink et un SGBD SQL server, Lazy ou Eager? Revenons à une définition possible de l'entité [Employe] : 1. package jpa; 81/257

82 public class Employe implements Serializable { 8. = GenerationType.AUTO) 11. private Long id; 14. private int version; nullable=false, unique=true, length=15) 16. private String SS; nullable=false, length=30) 18. private String nom; nullable=false, length=20) 20. private String prenom; nullable=false, length=50) 2 private String adresse; nullable=false, length=30) 24. private String ville; nullable=false, length=5) 26. private String codepostal; FetchType.LAZY) 29. private Indemnite indemnite; } Les lignes définissent la clé étrangère de la table [EMPLOYES] vers la table [INDEMNITES]. L'attribut fetch de la ligne 27 définit la stratégie de recherche du champ indemnite de la ligne 29. Il y a deux modes : FetchType.LAZY : lorsqu'un employé est cherché, l'indemnité qui lui correspond n'est pas ramenée. Elle le sera lorsque le champ [Employe].indemnite sera référencé pour la première fois. FetchType.EAGER : lorsqu'un employé est cherché, l'indemnité qui lui correspond est ramenée. C'est le mode par défaut lorsqu'aucun mode n'est précisé. Pour comprendre l'intérêt de l'option FetchType.LAZY, on peut prendre l'exemple suivant. Une liste d'employés sans les indemnités est présentée dans une page web avec un lien [Details]. Un clic sur ce lien présente alors les indemnités de l'employé sélectionné. On voit que : pour afficher la première page on n'a pas besoin des employés avec leurs indemnités. Le mode FetchType.LAZY convient alors, pour afficher la seconde page avec les détails, une requête supplémentaire doit être faite à la base de données pour avoir les indemnités de l'employé sélectionné. Le mode FetchType.LAZY évite de ramener trop de données dont l'application n'a pas besoin tout de suite. Voyons un exemple. Le projet [mv-pam-jpa-hibernate] est dupliqué : 82/257

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

84 en [2], on renomme le projet et son artifactid, en [3], le nouveau projet. Nous modifions le programme [Main.java] de la façon suivante : 1. package main; 3. import java.util.list; 4. import javax.persistence.entitymanager; 5. import javax.persistence.entitymanagerfactory; 6. import javax.persistence.persistence; 7. import jpa.employe; public class Main { // la requête JPQL ci-dessous ramène un employé 1 // 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-jpahibernatePU"); 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 { 2 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é 3 try { 33. System.out.println(employe); 34. } catch (Exception ex) { 35. System.out.println(ex); 36. } 37. // libération ressources 38. emf.close(); 39. } 40. } ligne 15 : on crée l'entitymanagerfactory de la couche JPA, ligne 17 : on obtient l'entitymanager qui nous permet de dialoguer avec la couche JPA, ligne 18 : on demande l'employé de nom Jouveinal, ligne 19 : on ferme l'entitymanager. Cela a pour effet de fermer le contexte de persistence. ligne 22 : on affiche l'employé reçu. La classe [Employe] est la suivante : /257

85 7. public class Employe implements Serializable { 8. = GenerationType.AUTO) 11. private Long id; 14. private int version; nullable=false, unique=true, length=15) 16. private String SS; nullable=false, length=30) 18. private String nom; nullable=false, length=20) 20. private String prenom; nullable=false, length=50) 2 private String adresse; nullable=false, length=30) 24. private String ville; nullable=false, length=5) 26. private String codepostal; FetchType.LAZY) 29. private Indemnite indemnite; /** 33. * Returns a string representation of the object. This implementation constructs 34. * that representation based on the id fields. 35. a string representation of the object. 36. */ 38. public String tostring() { 39. return "jpa.employe[id=" + getid() ",version="+getversion() 41. +",SS="+getSS() 4 + ",nom="+getnom() ",prenom="+getprenom() ",adresse="+getadresse() 45. +",ville="+getville() 46. +",code postal="+getcodepostal() 47. +",indice="+getindemnite().getindice() 48. +"]"; 49. } } ligne 27 : le champ indemnite est ramené en mode LAZY, ligne 47 : utilise le champ indemnite. Si la méthode tostring est appelée alors que le champ indemnite n'a pas été encore ramené, il le sera à ce moment là. Sauf si le contexte de persistance a été fermé comme dans l'exemple. Revenons au code du [Main] : lignes : 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 : 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

86 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <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"/> 1 <property name="javax.persistence.jdbc.user" value="root"/> 13. <property name="hibernate.cache.provider_class" value="org.hibernate.cache.nocacheprovider"/> 14. </properties> 15. </persistence-unit> 16. </persistence> on a enlevé l'option qui créait les tables. La base de données ici existe déjà et est remplie, on a enlevé les options qui faisaient qu'hibernate loguait les ordres SQL qu'il émettait vers la base de données. L'exécution du projet donne les deux affichages suivants dans la console : 1. org.hibernate.lazyinitializationexception: could not initialize proxy - no Session jpa.employe[id=31,version=0,ss= ,nom=jouveinal,prenom=marie,adresse=5 rue des oiseaux,ville=st Corentin,code postal=49203,indice=2] ligne 1 : l'exception qui s'est produite lorsqu'il a fallu chercher l'indemnité qui manquait alors que la session était fermée. On voit que l'indemnité n'avait pas été ramenée à cause du mode LAZY, ligne 2 : l'employé avec son indemnité obtenue par une requête qui a contourné le mode LAZY Travail à faire En suivant une démarche analogue à celle qui vient d'être suivie, créez un projet [mv-pam-jpa-eclipselink-lazy] qui montre le comportement d'eclipselink face au mode LAZY. On obtient les résultats suivants : 1. jpa.employe[id=453,version=1,ss= ,nom=jouveinal,prenom=marie,adresse=5 rue des oiseaux,ville=st Corentin,code postal=49203,indice=2] jpa.employe[id=453,version=1,ss= ,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) : 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

87 4.6.6 Pour la suite L'architecture de l'application à construire est la suivante : [DAO] [metier] [ui] Objets image de la BD 7 Interface [JPA] Implémentation [Hibernate] [JDBC] Spring Pour la suite du document, on dupliquera le projet Maven [mv-pam-jpa-hibernate] dans le projet [mv-pam-spring-hibernate] [1, 2, 3] : puis on renomme le nouveau projet [4, 5, 6]. On changera les dépendances du nouveau projet. Le fichier [pom.xml] devient le suivant : 1. <project xmlns=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-pam-spring-hibernate</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-pam-spring-hibernate</name> 87/257

88 <url> <repositories> <repository> <url> <id>swing-layout</id> <layout>default</layout> <name>repository for library Library[swing-layout]</name> </repository> </repositories> <properties> <project.build.sourceencoding>utf-8</project.build.sourceencoding> </properties> <dependencies> <dependency> <groupid>junit</groupid> <artifactid>junit</artifactid> <version>4.10</version> <scope>test</scope> <type>jar</type> </dependency> <dependency> <groupid>commons-dbcp</groupid> <artifactid>commons-dbcp</artifactid> <version>1.2</version> </dependency> <dependency> <groupid>commons-pool</groupid> <artifactid>commons-pool</artifactid> <version>1.6</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-tx</artifactid> <version>3.1.1.release</version> <type>jar</type> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-beans</artifactid> <version>3.1.1.release</version> <type>jar</type> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context</artifactid> <version>3.1.1.release</version> <type>jar</type> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-orm</artifactid> <version>3.1.1.release</version> <type>jar</type> </dependency> <dependency> <groupid>org.hibernate</groupid> <artifactid>hibernate-entitymanager</artifactid> <version>4.1.2</version> <type>jar</type> </dependency> <dependency> <groupid>mysql</groupid> 88/257

89 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> 8 </dependencies> 83. </project> lignes : la dépendance pour les tests JUnit, lignes : les dépendances pour le pool de connexions Apache DBCP, lignes : les dépendances pour le framework Spring, lignes : les dépendances pour l'implémentation JPA / Hibernate, lignes : la dépendance pour le pilote JDBC de MySQL, lignes : la dépendance pour l'interface Swing. Celle-ci est automatiquement ajoutée par Netbeans lorsqu'on ajoute une interface Swing au projet. Par ailleurs, on génèrera les deux bases MySQL : [dbpam_hibernate] à partir du script [dbpam_hibernate.sql], [dbpam_eclipselink] à partir du script [dbpam_eclipselink.sql], 4.7 Les interfaces des couches [metier] et [DAO] Note : le prochain travail pratique est au paragraphe 4.9, page 97. D'ici là, il faut lire le cours. Revenons à l'architecture de l'application : [ui] [metier] [DAO] Objets image de la BD 7 Interface [JPA] Implémentation [Hibernate / EclipseLink] [JDBC] Spring Dans l'architecture ci-dessus, quelle interface doit offrir la couche [DAO] à la couche [metier] et quelle interface doit offrir la couche [metier] à la couche [ui]? Une première approche pour définir les interfaces des différentes couches est d'examiner les différents cas d'usage (use cases) de l'application. Ici nous en avons deux, selon l'interface utilisateur choisie : console ou formulaire graphique. Examinons le mode d'utilisation de l'application console : 1. dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar Valeurs saisies : 4. N de sécurité sociale de l'employé : Nombre d'heures travaillées : Nombre de jours travaillés : Informations Employé : 9. Nom : Jouveinal Informations Cotisations : 13. CSGRDS : 3.49 % Informations Indemnités : 89/257

90 Informations Salaire : 20. Salaire de base : 3625 euro 21. Cotisations sociales : euro 2 Indemnités d'entretien : 40 euro 23. Indemnités de repas : 60 euro 24. Salaire net : euro L'application reçoit trois informations de l'utilisateur (cf ligne 1 ci-dessus) le n de sécurité sociale de l'assistante maternelle le nombre d'heures travaillées dans le mois le nombre de jours travaillés dans le mois A partir de ces information et d'autres enregistrées dans des fichiers de configuration, l'application affiche les informations suivantes : lignes 4-6 : les valeurs saisies lignes 8-10 : les informations liées à l'employé dont on a donné le n de sécurité sociale lignes : les taux des différentes cotisations sociales lignes : les différentes indemnités versées à l'assistante maternelle lignes : les éléments de la feuille de salaire de l'assistante maternelle Un certain nombre d'informations doivent être fournies par la couche [metier] à la couche [ui] : 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. 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-1 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 les éléments constitutifs du salaire affichés lignes 18-2 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; 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; 3. import jpa.cotisation; 4. import jpa.employe; 5. import jpa.indemnite; public class FeuilleSalaire { 8. // champs privés 9. private Employe employe; 10. private Cotisation cotisation; 11. private ElementsSalaire elementssalaire; } 90/257

91 ligne 9 : l'employé concerné par la feuille de salaire - information n 1 affichée par la couche [ui] ligne 10 : les différents taux de cotisation - information n 2 affichée par la couche [ui] ligne 11 : les différentes indemnités liées à l'indice de l'employé - information n 3 affichée par la couche [ui] ligne 12 : les éléments constitutifs de son salaire - information n 4 affichée par la couche [ui] Un second cas d'usage de la couche [métier] apparaît avec l'interface graphique : 2 1 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 : package metier; ligne [10] : la méthode qui va permettre à la couche [ui] de demander la liste de tous les employés à la couche [métier]. import java.util.list; import jpa.employe; public interface IMetier { // obtenir la feuille de salaire public FeuilleSalaire calculerfeuillesalaire(string SS, double nbheurestravaillées, int nbjourstravaillés ); 9. // liste des employés 10. public List<Employe> findallemployes(); 11. } 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; 3. import java.util.list; 4. import jpa.cotisation; 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 1 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(); } 91/257

92 ligne 6, l'interface [ICotisationDao] gère les accès à l'entité [Cotisation] et donc à la table [COTISATIONS] de la base de données. Notre application n'a besoin que de la méthode [findall] de la ligne 16 qui permet de retrouver tout le contenu de la table [COTISATIONS]. On a voulu ici se mettre dans un cas plus général où toutes les opérations CRUD (Create, Read, Update, Delete) sont effectuées sur l'entité. ligne 8 : la méthode [create] crée une nouvelle entité [Cotisation] ligne 10 : la méthode [edit] modifie une entité [Cotisation] existante ligne 12 : la méthode [destroy] supprime une entité [Cotisation] existante ligne 14 : la méthode [find] permet de retrouver une entité [Cotisation] existante via son identifiant id ligne 16 : la méthode [findall] rend dans une liste toutes les entités [Cotisation] existantes Attardons-nous sur la signature de la méthode [create] : 1. // créer une nouvelle cotisation Cotisation create(cotisation cotisation); La méthode create a un paramètre cotisation de type Cotisation. Le paramètre cotisation doit être persisté, c.a.d. ici mis dans la table [COTISATIONS]. Avant cette persistance, le paramètre cotisation a un identifiant id sans valeur. Après la persistance, le champ id a une valeur qui est la clé primaire de l'enregistrement ajouté à la table [COTISATIONS]. Le paramètre cotisation est donc un paramètre d'entrée / sortie de la méthode create. Il ne semble pas nécessaire que méthode create rende de plus le paramètre cotisation comme résultat. La méthode appelante détenant une référence sur l'objet [Cotisation cotisation], si celui-ci est modifié, elle a accès à l'objet modifié puisqu'elle a une référence dessus. Elle peut donc connaître la valeur que la méthode create a donné au champ id de l'objet [Cotisation cotisation]. La signature de la méthode pourrait donc être plus simplement : 1. // créer une nouvelle cotisation void create(cotisation cotisation); Lorsqu'on écrit une interface, il est bon de se rappeler qu'elle peut être utilisée dans deux contextes différents : local et distant. Dans le contexte local, la méthode appelante et la méthode appelée sont exécutées dans la même JVM : utilisateur interface utilisateur [ui] métier [metier] d'accès aux données [DAO] Données 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 : 1 utilisateur interface utilisateur [ui] JVM 1 métier [metier] 2 3 Réseau tcp /ip d'accès aux données [DAO] Données JVM 2 Ci-dessus, la couche [metier] s'exécute dans la JVM 1 et la couche [DAO] dans la JVM 2 sur deux machines différentes. Les deux couches ne communiquent pas directement. Entre-elles s'intercale une couche qu'on appellera couche de communication [1]. Celleci est composée d'une couche d'émission [2] et d'une couche de réception [3]. Le développeur n'a en général pas à écrire ces couches de communication. Elles sont générées automatiquement par des outils logiciels. La couche [metier] est écrite comme si elle s'exécutait dans la même JVM que la couche [DAO]. Il n'y a donc aucune modification de code. Le mécanisme de communication entre la couche [metier] et la couche [DAO] est le suivant : la couche [metier] fait appel à la méthode create de la couche [DAO] en lui passant le paramètre [Cotisation cotisation1] ce paramètre est en fait passé à la couche d'émission [2]. Celle-ci va transmettre sur le réseau, la valeur du paramètre cotisation1 et non sa référence. La forme exacte de cette valeur dépend du protocole de communication utilisé. 92/257

93 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 cotisation la couche de réception va passer l'objet cotisation2 à la méthode create de la couche [DAO] qui va le persister en base de données. Après cette opération, le champ id de l'objet cotisation2 a été initialisé par la clé primaire de l'enregistrement ajouté à la table [COTISATIONS]. Ce n'est pas le cas de l'objet cotisation1 sur lequel la couche [metier] a une référence. Si on veut que la couche [metier] ait une référence sur l'objet cotisation2, il faut le lui envoyer. Aussi est-on amenés à changer la signature de la méthode create de la couche [DAO] : 1. // créer une nouvelle cotisation Cotisation create(cotisation cotisation); avec cette nouvelle signature, la méthode create va rendre comme résultat l'objet persisté cotisation 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 : 1 utilisateur 2 interface utilisateur [ui] JVM 1 3 métier [metier] Réseau tcp /ip d'accès aux données [DAO] Données JVM 2 Dans cette architecture, ce sont les méthodes de la couche [metier] qui doivent rendre des résultats et non celles de la couche [DAO]. Néanmoins la signature suivante de la méthode create de la couche [DAO] : 1. // créer une nouvelle cotisation Cotisation create(cotisation cotisation); nous permet de ne pas faire d'hypothèses sur l'architecture réellement mise en place. Utiliser des signatures qui fonctionneront quelque soit l'architecture retenue, locale ou distante, implique que dans le cas où une méthode appelée modifie certains de ses paramètres : ceux-ci doivent faire également partie du résultat de la méthode appelée la méthode appelante doit utiliser le résultat de la méthode appelée et non les références des paramètres modifiés qu'elle a transmis à la méthode appelée. On se laisse ainsi la possibilité de passer une couche d'une architecture locale à une architecture distante sans modification de code. Réexaminons, à cette lumière, l'interface [ICotisationDao] : 1. package dao; 3. import java.util.list; 4. import jpa.cotisation; 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 1 public void destroy(cotisation cotisation); 13. // chercher une cotisation particulière 14. public Cotisation find(long id); 93/257

94 15. // obtenir tous les objets Cotisation 16. public List<Cotisation> findall(); } ligne 8 : le cas de la méthode create a été traité ligne 10 : la méthode edit utilise son paramètre [Cotisation cotisation1] pour mettre à jour l'enregistrement de la table [COTISATIONS] ayant la même clé primaire que l'objet cotisation. Elle rend comme résultat l'objet cotisation2 image de l'enregistrement modifié. Le paramètre cotisation1 n'est lui pas modifié. La méthode doit rendre cotisation2 comme résultat qu'on soit dans le cadre d'une architecture distante ou locale. ligne 12 : la méthode destroy supprime l'enregistrement de la table [COTISATIONS] ayant la même clé primaire que l'objet cotisation passé en paramètre. Celui-ci n'est pas modifié. Il n'a donc pas à être rendu. ligne 14 : le paramètre id de la méthode find n'est pas modifié par la méthode. Il n'a pas à faire partie du résultat. ligne 16 : la méthode findall n'a pas de paramètres. On n'a donc pas à l'étudier. Au final, seule la signature de la méthode create doit être adaptée pour être utilisable dans le cadre d'une architecture distante. Les raisonnements précédents seront valables pour les autres interfaces [DAO]. Nous ne les répèterons pas et utiliserons directement des signatures utilisables aussi bien dans le cadre d'une architecture distante que locale. L'interface [DAO] pour les accès aux entités [Indemnite] de la table [INDEMNITES] sera la suivante : 1. package dao; 3. import java.util.list; 4. import jpa.indemnite; 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 1 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(); } 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; 3. import java.util.list; 4. import jpa.employe; 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

95 } 4.8 public void destroy(employe employe); // chercher une entité Employe via son identifiant id public Employe find(long id); // chercher une entité Employe via son n SS public Employe find(string SS); // obtenir toutes les entités Employe public List<Employe> findall(); 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 classe [PamException] La couche [DAO] va travailler avec l'api JDBC de Java. Cette API lance des exceptions contrôlées de type [SQLException] qui présentent deux inconvénients : elles alourdissent le code qui doit obligatoirement gérer ces exceptions avec des try / catch. elles doivent être déclarées dans la signature des méthodes de l'interface [IDao] par un "throws SQLException". Ceci a pour conséquence d'empêcher l'implémentation de cette interface par des classes qui lanceraient une exception contrôlée d'un type différent de [SQLException]. Pour remédier à ce problème, la couche [DAO] ne "remontera" que des exceptions non contrôlées de type [PamException]. PamException [ui] [metier] [JPA]Exception [DAO] SQLException Implémentation [JPA / Hibernate] [JDBC] Spring la couche [JDBC] lance des exceptions de type [SQLException] la couche [JPA] lance des exceptions propres à l'implémentation JPA utilisée la couche [DAO] lance des exceptions de type [PamException] non contrôlées Ceci a deux conséquences : la couche [metier] n'aura pas l'obligation de gérer les exceptions de la couche [DAO] avec des try / catch. Elle pourra simplement les laisser remonter jusqu'à la couche [ui]. les méthodes de l'interface [IDao] n'ont pas à mettre dans leur signature la nature de l'exception [PamException], ce qui laisse la possiblité d'implémenter cette interface avec des classes qui lanceraient un autre type d'exception non contrôlée. La classe [PamException] sera placée dans le paquetage [exception] du projet Netbeans : 95/257

96 Son code est le suivant : 1. package exception; 4. public class PamException extends RuntimeException { // code d'erreur 7. private int code; public PamException(int code) { 10. super(); 11. this.code = code; 1 } public PamException(String message, int code) { 15. super(message); 16. this.code = code; 17. } public PamException(Throwable cause, int code) { 20. super(cause); 21. this.code = code; 2 } public PamException(String message, Throwable cause, int code) { 25. super(message, cause); 26. this.code = code; 27. } // getter et setter public int getcode() { 3 return code; 33. } public void setcode(int code) { 36. this.code = code; 37. } } ligne 4 : [PamException] dérive de [RuntimeException]. C'est donc un type d'exceptions que le compilateur ne nous oblige pas à gérer par un try / catch ou à mettre dans la signature des méthodes. C'est pour cette raison, que [PamException] n'est pas dans la signature des méthodes de l'interface [IDao]. Cela permet à cette interface d'être implémentée par une classe lançant un autre type d'exceptions, pourvu que celui-ci dérive également de [RuntimeException]. pour différencier les erreurs qui peuvent se produire, on utilise le code erreur de la ligne 7. Les trois constructeurs des lignes 14, 19 et 24 sont ceux de la classe parente [RuntimeException] auxquels on a rajouté un paramètre : celui du code d'erreur qu'on veut donner à l'exception. Le fonctionnement de l'application, du point de vue des exceptions, sera le suivant : 96/257

97 la couche [DAO] encapsulera toute exception rencontrée, dans une exception de type [PamException], et relancera cette dernière pour la couche [métier]. la couche [métier] laissera remonter les exceptions lancées par la couche [DAO]. Elle encapsulera toute exception survenant dans la couche [métier], dans une exception de type [PamException] et relancera cette dernière pour la couche [ui]. la couche [ui] intercepte toutes les exceptions qui remontent des couches [métier] et [DAO]. Elle se contentera d'afficher l'exception sur la console ou l'interface graphique. Examinons maintenant successivement l'implémentation des couches [DAO] et [metier]. 4.9 La couche [DAO] de l'application [PAM] Nous nous plaçons dans le cadre de l'architecture suivante : [ui] [metier] [DAO] Objets image de la BD Interface [JPA] Implémentation [Hibernate] [JDBC] Spring Implémentation Lectures conseillées : paragraphe de [ref1] Question : En utilisant l'intégration Spring / JPA, écrire les classes [CotisationDao, IndemniteDao, EmployeDao] d'implémentation des interfaces [ICotisationDao, IIndemniteDao, IEmployeDao]. Chaque méthode de classe interceptera une éventuelle exception et l'encapsulera dans une exception de type [PamException] avec un code d'erreur propre à l'exception interceptée. Les classes d'implémentation feront partie du paquetage [dao] : Note : les classes du paquetage [dao] auront la forme suivante : package dao;... import public class CotisationDao implements ICotisationDao { 97/257

98 10. private EntityManager em; // constructeur 13. public CotisationDao() { 14. } // créer une cotisation 17. public Cotisation create(cotisation cotisation) { 18. try{ 19. em.persist(cotisation); 20. return cotisation; 21. }catch (Throwable th){ 2 throw new PamException(th,11); 23. } 24. } } ligne 6 : l'annotation Elle indique à Spring que chaque méthode de la classe soit se dérouler dans une transaction ; ligne 9 : l'annotation injecte dans le champ em de la ligne 10, le gestionnaire du contexte de persistance de la couche JPA ; lignes : à 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). Configuration Lectures conseillées : paragraphe de [ref1] L'intégration DAO / JPA est configurée par le fichier Spring [spring-config-dao.xml] et le fichier JPA [persistence.xml] : Question : écrire le contenu de ces deux fichiers. On supposera que la base de données utilisée est la base MySQL5 [dbpam_hibernate] générée par le script SQL [dbpam_hibernate.sql]. Le fichier Spring définira les trois beans suivants : employedao de type EmployeDao, indemnitedao de type IndemniteDao, cotisationdao de type CotisationDao. Par ailleurs, l'implémentation JPA utilisée sera Hibernate. Note : suivre le paragraphe de [ref1] 98/257

99 4.9.3 Tests Lectures conseillées : paragraphes et de [ref1] Maintenant que la couche [DAO] est écrite et configurée, nous pouvons la tester. L'architecture des tests sera la suivante : [tests] [DAO] Objets image de la BD Interface [JPA] Implémentation [Hibernate] [JDBC] Spring 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

100 2 1 Les classes placées dans la branche [Test Packages] ont connaissance des classes présentes dans la branche [Source Packages] ainsi que des bibliothèques de classes du projet. Si les tests ont besoin de bibliothèques autres que celles du projet, celles-ci doivent être déclarées dans la branche [Test Libraries] [2]. Les classes de tests utilisent l'outil de tests unitaires JUnit : [JUnitInitDB] ne fait aucun test. Elle remplit la base de données avec quelques enregistrements et affiche ensuite ceux-ci sur la console. [JUnitDao] fait une série de tests dont elle vérifie le résultat. Le squelette de la classe [JUnitInitDB] est le suivant : 1. package dao; public class JUnitInitDB { static private IEmployeDao employedao = null; 8. static private ICotisationDao cotisationdao = null; 9. static private IIndemniteDao indemnitedao = null; 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. } public void initdb(){ 23. // on remplit la base // on affiche le contenu de la base } public void clean(){ 31. // on vide la base } 34. } 100/257

101 la méthode [init] est exécutée avant le début de la série des tests 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 Elle vide la base de données. la méthode [initdb] est un 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 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 Mise en oeuvre des tests Nous sommes désormais prêts pour exécuter [InitDB]. Nous décrivons la procédure avec le SGBD MySQL5 : les classes [1], les fichiers de configuration [2] et les classes de test de la couche [DAO] [3] sont mis en place, le projet est construit [4] 101/257

102 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 : Standard Output Employés jpa.employe[id=5,version=0,ss= ,nom=jouveinal,prenom=marie,adresse=5 rue des oiseaux,ville=st Corentin,code postal=49203,indice=2] 4. jpa.employe[id=6,version=0,ss= ,nom=laverti,prenom=justine,adresse=la brûlerie,ville=st Marcel,code postal=49014,indice=1] 5. Indemnités jpa.indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour0,repas jour=3.0,indemnités CP=10] 7. jpa.indemnite[id=6,version=0,indice=2,base heure=1,entretien jour1,repas jour=3.1,indemnités CP=15.0] 8. Cotisations jpa.cotisation[id=3,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88] Les tables [EMPLOYES, INDEMNITES, COTISATIONS] ont été remplies. On peut le vérifier avec une connexion Netbeans à la base [dbpam_hibernate] 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. JUnitDao Note : la classe [JUnitDao] peut être trouvée dans le support de cours. Nous nous intéressons maintenant à une seconde classe de tests [JUnitDao] : Le squelette de la classe sera le suivant : 102/257

103 1. package dao; 3. import exception.pamexception; public class JUnitDao { // couches DAO 9. static private IEmployeDao employedao; 10. static private IIndemniteDao indemnitedao; 11. static private ICotisationDao cotisationdao; 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"); 2 cotisationdao = (ICotisationDao) ctx.getbean("cotisationdao"); 23. } public static void terminate() { 27. } public void clean() { } // logs 35. private static void log(string message) { 36. System.out.println(" " + message); 37. } // tests 41. public void test01() { 4 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); 5 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); 6 // vérifications 103/257

104 63. Assert.assertEquals(cotisation.getVersion() + 1, cotisationgetversion()); 64. Assert.assertEquals(-1, cotisationgetcsgrds(), 1e-6); 65. Assert.assertEquals(-1, cotisationgetcsgd(), 1e-6); 66. Assert.assertEquals(-1, cotisationgetretraite(), 1e-6); 67. Assert.assertEquals(-1, cotisationgetsecu(), 1e-6); 68. // on demande l'élément modifié 69. Cotisation cotisation3 = cotisationdao.find(cotisationgetid()); 70. // vérifications 71. Assert.assertEquals(cotisation3.getVersion(), cotisationgetversion()); 7 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(); 8 Assert.assertEquals(nbCotisations, cotisations.size()); 83. } public void test02(){ 88. log("test02"); 89. // on demande la liste des indemnités // on ajoute une Indemnite indemnite // on va chercher indemnite en base on récupère indemnite // on vérifie que indemnite1 = indemnite // on modifie l'indemnité obtenue et on persiste la modification en BD. On obtient indemnite // on vérifie la version de indemnite // on va chercher indemnite2 en base on obtient indemnite // on vérifie que indemnite3 = indemnite // on supprime en base l'image de indemnite // on va chercher indemnite3 en base // on vérifie qu'on a obtenu une référence null } public void test03(){ 115. log("test03"); 116. // on répète un test analogue aux précédents pour Employe } public void test04(){ 12 log("test04"); 123. // on teste la méthode [IEmployeDao].find(String SS) 124. // d'abord avec un employé existant 104/257

105 // puis avec un employé inexistant... public void test05(){ log("test05"); // on crée deux indemnités avec le même indice // enfreint la contrainte d'unicité de l'indice // on vérifie qu'une exception de type PamException se produit // et qu'elle a le n d'erreur attendu... public void test06(){ log("test06"); // on crée deux employés avec le même n SS // enfreint la contrainte d'unicité sur le n SS // on vérifie qu'une exception de type PamException se produit // et qu'elle a le n d'erreur attendu... public void test07(){ log("test07"); // on crée deux employés avec le même n SS, le 1er avec create, le 2ème avec edit // enfreint la contrainte d'unicité sur le n SS // on vérifie qu'une exception de type PamException se produit // et qu'elle a le n d'erreur attendu... public void test08(){ log("test08"); // supprimer un employé qui n'existe pas ne provoque pas d'exception // il est ajouté puis détruit on le vérifie... public void test09(){ log("test09"); // modifier un employé sans avoir la bonne version doit provoquer une exception // on le vérifie... public void test10(){ log("test10"); // supprimer un employé sans avoir la bonne version doit provoquer une exception // on le vérifie... } } // getters et setters /257

106 Dans la classe de tests précédente, la base est vidée avant chaque test. Question : écrire les méthodes suivantes : - test02 : on s'inspirera de test01 - test03 : un employé a un champ de type Indemnite. Il faut donc créer une entité Indemnite et une entité Employe - test04. En procédant de la même façon que pour la classe de tests [JUnitInitDB], on obtient les résultats suivants : 2 1 en [1], on exécute la classe de tests en [2], les résultats des tests dans la fenêtre [Test Results] Provoquons une erreur pour voir comment cela est signalé dans la page des résultats : 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 1 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 } 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

107 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. La couche [metier] de l'application [PAM] Maintenant que la couche [DAO] a été écrite, nous passons à l'étude de la couche métier [2] : [ui] [metier] [DAO] Objets image de la BD Interface [JPA] Implémentation [Hibernate] [JDBC] Spring L'interface Java [IMetier] Celle-ci a été décrite au paragraphe??, page 91. Nous la rappelons ci-dessous : package metier; import java.util.list; import jpa.employe; public interface IMetier { // obtenir la feuille de salaire public FeuilleSalaire calculerfeuillesalaire(string SS, double nbheurestravaillées, int nbjourstravaillés ); 9. // liste des employés 10. public List<Employe> findallemployes(); 11. } L'implémentation de la couche [metier] sera faite dans un paquetage [metier] : 107/257

108 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 La classe [FeuilleSalaire] La méthode [calculerfeuillesalaire] de l'interface [IMetier] rend un objet de type [FeuilleSalaire] qui représente les différents éléments d'une feuille de salaire. Sa définition est la suivante : 1. package metier; 3. import jpa.cotisation; 4. import jpa.employe; 5. import jpa.indemnite; public class FeuilleSalaire implements Serializable{ 8. // champs privés 9. private Employe employe; 10. private Cotisation cotisation; 11. private ElementsSalaire elementssalaire; // constructeurs 14. public FeuilleSalaire() { } public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementssalaire) { 19. setemploye(employe); 20. setcotisation(cotisation); 21. setelementssalaire(elementssalaire); 2 } // tostring 25. public String tostring() { 26. return "[" + employe + "," + cotisation + "," + elementssalaire + "]"; 27. } // accesseurs } 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 : les deux constructeurs de la classe lignes : 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; 3. import java.io.serializable; public class ElementsSalaire implements Serializable{ // champs privés 8. private double salairebase; 9. private double cotisationssociales; 10. private double indemnitesentretien; 108/257

109 private double indemnitesrepas; private double salairenet; // constructeurs public ElementsSalaire() { } public ElementsSalaire(double salairebase, double cotisationssociales, double indemnitesentretien, double indemnitesrepas, double salairenet) { setsalairebase(salairebase); setcotisationssociales(cotisationssociales); setindemnitesentretien(indemnitesentretien); setindemnitesrepas(indemnitesrepas); setsalairenet(salairenet); } // public String tostring() { return "[salaire base=" + salairebase + ",cotisations sociales=" + cotisationssociales + ",indemnités d'entretien=" indemnitesentretien + ",indemnités de repas=" + indemnitesrepas + ",salaire net=" salairenet + "]"; 35. } // getters et setters } 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 : les constructeurs de la classe lignes : méthode [tostring] identifiant un objet [ElementsSalaire] particulier lignes 38 et au-delà : les accesseurs publics aux champs privés de la classe La classe d'implémentation [Metier] de la couche [metier] La classe d'implémentation [Metier] de la couche [metier] pourrait être la suivante : 1. package metier; public class Metier implements IMetier { // références sur la couche [DAO] 9. private ICotisationDao cotisationdao = null; 10. private IEmployeDao employedao=null; // obtenir la feuille de salaire 14. public FeuilleSalaire calculerfeuillesalaire(string SS, 15. double nbheurestravaillées, int nbjourstravaillés) { /257

110 17. } // liste des employés 20. public List<Employe> findallemployes() { } // getters et setters } ligne 5 : l'annotation 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 : la méthode [calculerfeuillesalaire] ; lignes : la méthode [findallemployes] ; ligne 24 et au-delà : les accesseurs publics des champs privés de la classe. Question : écrire le code de la méthode [findallemployes]. Question : écrire le code de la méthode [calculerfeuillesalaire]. On notera les points suivants : le mode de calcul du salaire a été expliqué au paragraphe 4.2, page 6 si le paramètre [SS] ne correspond à aucun employé (la couche [DAO] a renvoyé un pointeur null), la méthode lancera une exception de type [PamException] avec un code d'erreur approprié Tests de la couche [metier] Nous créons deux programmes de test : Les classes de tests [3] sont créés dans un paquetage [metier] [2] de la branche [Test Packages] [1] du projet. La classe [JUnitMetier_1] pourrait être la suivante : 1. package metier; public class JUnitMetier_1 { // couches dao 8. static private IMetier metier; public static void init(){ 1 // log 13. log("init"); 14. // configuration de l'application 15. // instanciation couche [metier] 110/257

111 16. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metierdao.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"); 2 // 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. } 3 // on la remplit 33. Indemnite indemnite1=new Indemnite(1,1.93,2,3,12); 34. Indemnite indemnite2=new Indemnite(2,1,1,3.1,15); 35. indemnitedao.create(indemnite1); 36. indemnitedao.create(indemnite2); 37. employedao.create(new Employe(" ","Jouveinal","Marie","5 rue des oiseaux","st Corentin","49203",indemnite2)); 38. employedao.create(new Employe(" ","Laverti","Justine","La brã»lerie","st Marcel","49014",indemnite1)); 39. cotisationdao.create(new Cotisation(3.49,6.15,9.39,7.88)); 40. } public static void terminate() { 44. } // logs 47. static private void log(string message) { 48. System.out.println(" " + message); 49. } // test 53. public void test01(){ 54. // calcul de feuilles de salaire 55. System.out.println(metier.calculerFeuilleSalaire(" ",30, 5)); 56. System.out.println(metier.calculerFeuilleSalaire(" ",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. } 6 } 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 : Testsuite: metier.junitmetier_ init... [jpa.employe[id=22,version=0,ss= ,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 jour0,repas 111/257

112 jour=3.0,indemnités CP=10],[salaire base=64.85,cotisations sociales=17.45,indemnités d'entretien=10.0,indemnités de repas=15.0,salaire net=74]] 5. [jpa.employe[id=21,version=0,ss= ,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=1,entretien jour1,repas jour=3.1,indemnités CP=15.0],[salaire base=3625,cotisations sociales=97.48,indemnités d'entretien=40,indemnités de repas=60,salaire net=368.77]] 6. PamException[Code=101, message=l'employé de n [xx] est introuvable] 7. Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,234 sec ligne 4 : la feuille de salaire de Justine Laverti ligne 5 : la feuille de salaire de Marie Jouveinal ligne 6 : l'exception due au fait que l'employé de n SS 'xx' n'existe pas. Question : la ligne 17 de [JUnitMetier_1] utilise le bean Spring nommé metier. Donner la définition de ce bean dans le fichier [spring-config-metier-dao.xml]. Ce fichier est identique au fichier Spring utilisé pour tester la couche [DAO]. On y ajoute simplement la définition du bean de la couche [métier]. La classe [JUnitMetier_2] pourrait être la suivante : 1. package metier; public class JUnitMetier_2 { // couche métier 7. static private IMetier metier; public static void init(){ } // logs 15. static private void log(string message) { 16. System.out.println(" " + message); 17. } // test 21. public void test01(){ } 24. } La classe [JUnitMetier_2] est une copie de la classe [JUnitMetier_1] où cette fois, des assertions ont été placées dans la méthode test01. Question : écrire la méthode test01. Lors de l'exécution de la classe [JUnitMetier_2], on obtient les résultats suivants si tout va bien : 112/257

113 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] : [ui] [metier] Objets image de la BD [DAO] 7 Interface [JPA] Implémentation [Hibernate] [JDBC] Spring Nous créerons deux implémentations différentes de la couche [ui] : une version console et une version graphique swing : La classe [ui.console.main] Nous nous intéressons tout d'abord à l'application console implémentée par la classe [ui.console.main] ci-dessus. Son fonctionnement a été décrit au paragraphe 4.3, page 63. Le squelette de la classe [Main] pourrait être le suivant : 1. package ui.console; 3. import exception.pamexception; 4. import metier.feuillesalaire; 5. import metier.imetier; import java.util.arraylist; 8. import org.springframework.context.applicationcontext; 9. import org.springframework.context.support.classpathxmlapplicationcontext; public class Main { /** 14. 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 // liste des erreurs 2 ArrayList erreurs = new ArrayList(); 23. // le second paramètre doit être un nombre réel > // erreur? 26. if (...) { 27. erreurs.add("le nombre d'heures travaillées [" + args[1] 113/257

114 28. + "] est erroné"); 29. } 30. // le troisième paramètre doit être un nombre entier > // erreur? 33. if (...) { 34. erreurs.add("le nombre de jours travaillés [" + args[2] "] 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. } 4 return; 43. } 44. // c'est bon - on peut demander la feuille de salaire 45. FeuilleSalaire feuillesalaire = null; 46. try { 47. // instanciation couche [metier] // calcul de la feuille de salaire } catch (PamException ex) { 5 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. } // affichage détaillé 60. String output = "Valeurs saisies :\n"; 61. output += ajouteinfo("n de sécurité sociale de l'employé", args[0]); 6 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", ""); 7 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", ""); 8 output += ajouteinfo("salaire de base", ""+ feuillesalaire.getelementssalaire().getsalairebase()+ " euro"); 114/257

115 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"); System.out.println(output); 89. } static String ajouteinfo(string message, String valeur) { 9 return message + " : " + valeur + "\n"; 93. } 94. } Question : compléter le code ci-dessus Exécution Pour exécuter la classe [ui.console.main], on procèdera de la façon suivante : /257

116 en [1], sélectionner les propriétés du projet, en [2], sélectionner la propriété [Run] du projet, utiliser le bouton [3] pour désigner la classe (dite classe principale) à exécuter, sélectionner la classe [4], la classe apparaît en [5]. Celle-ci a besoin de trois arguments pour s'exécuter (n SS, nombre d'heures travaillées, nombre de jours travaillés). Ces arguments sont placés en [6], ceci fait, on peut exécuter le projet [7]. La configuration précédente fait que c'est la classe [ui.console.main] qui va être exécutée. Les résultats de l'exécution sont obtenus dans la fenêtre [output] : 4.12 La couche [ui] de l'application [PAM] version graphique Nous implémentons maintenant la couche [ui] avec une interface graphique : [ui] [metier] Objets image de la BD [DAO] 7 Interface [JPA] Implémentation [Hibernate] [JDBC] Spring 116/257

117 2 1 en [1], la classe [PamJFrame] de l'interface graphique en[2] : l'interface graphique 4.11 Un rapide tutoriel Pour créer l'interface graphique, on pourra procéder de la façon suivante : [1] : on crée un nouveau fichier avec le bouton [1] [New File...] [2] : on choisit la catégorie du fichier [Swing GUI Forms], c.a.d. formulaires graphiques [3] : on choisit le type [JFrame Form], un type de formulaire vide /257

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

119 en [13], nous sélectionnons un composant [JLabel] par un clic simple en [14], nous le déposons sur le formulaire en mode [Design] en [15], nous définissons les propriétés du JLabel (text, font). 119/257

120 en [16], le résultat obtenu. en [17], on demande la prévisualisation du formulaire en [18], le résultat en [19], le label [JLabel1] a été ajouté à l'arborescence des composants dans la fenêtre [Inspector] en [20] et [21] : dans la perspective [Source] du formulaire, du code Java a été ajouté pour gérer le JLabel ajouté. Un tutoriel sur la construction de formulaires avec Netbeans est disponible à l'url [ L'interface graphique [PamJFrame] On construira l'interface graphique suivante : 120/257

121 1 2 en [1], l'interface graphique en [2], l'arborescence de ses composants : un JLabel et six conteneurs JPanel JLabel1 JPanel1 JPanel2 121/257

122 JPanel3 JPanel4 JPanel5 Travail pratique : construire l'interface graphique précédente en s'aidant du tutoriel [ Les événements de l'interface graphique Lectures conseillées : chapitre [Interfaces graphiques] de [ref2]. Nous gèrerons le clic sur le bouton [jbuttonsalaire]. Pour créer la méthode de gestion de cet événement, on pourra procéder comme suit : 122/257

123 Le gestionnaire du clic sur le bouton [JButtonSalaire] est généré : } private void jbuttonsalaireactionperformed(java.awt.event.actionevent evt) { // TODO add your handling code here: 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"); jbuttonsalaire.addactionlistener(new java.awt.event.actionlistener() { 3. public void actionperformed(java.awt.event.actionevent evt) { 4. jbuttonsalaireactionperformed(evt); 5. } 6. }); Ce sont les lignes 2-5 qui indiquent que le clic (evt de type ActionPerformed) sur le bouton [jbuttonsalaire] (ligne 2) doit être géré par la méthode [jbuttonsalaireactionperformed] (ligne 4). Nous gèrerons également, l'événement [caretupdate] (déplacement du curseur de saisie) sur le champ de saisie [jtextfieldht]. Pour créer le gestionnaire de cet événement, nous procédons comme précédemment : Le gestionnaire de l'événement [caretupdate] sur le champ de saisie [jtextfieldht] est généré : private void jtextfieldhtcaretupdate(javax.swing.event.caretevent evt) {... } Le code Java qui associe la méthode précédente à l'événement [caretupdate] sur le champ de saisie [jtextfieldht] est lui aussi généré : 1. jtextfieldht.addcaretlistener(new javax.swing.event.caretlistener() { 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) Initialisation de l'interface graphique 123/257

124 Revenons à l'architecture de notre application : [ui] [metier] Objets image de la BD [DAO] 7 Interface [JPA] Implémentation [Hibernate] [JDBC] Spring La couche [ui] a besoin d'une référence sur la couche [metier]. Rappelons comment cette référence avait été obtenue dans l'application console : 1. // instanciation couche [metier] ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metierdao.xml"); 3. IMetier metier = (IMetier) ctx.getbean("metier"); La méthode est la même dans l'application graphique. Il faut que lorsque celle-ci s'initialise, la référence [IMetier metier] de la ligne 3 ci-dessus soit également initialisée. Le code généré pour l'interface graphique est pour l'instant le suivant : 1. package ui.swing; public class PamJFrame extends javax.swing.jframe { /** Creates new form PamJFrame */ 7. public PamJFrame() { 8. initcomponents(); 9. } /** This method is called from within the constructor to 1 * 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() { }// </editor-fold> private void jtextfieldhtcaretupdate(javax.swing.event.caretevent evt) { } private void jbuttonsalaireactionperformed(java.awt.event.actionevent evt) { } public static void main(string args[]) { 30. java.awt.eventqueue.invokelater(new Runnable() { 31. public void run() { 3 new PamJFrame().setVisible(true); 33. } 34. }); 35. } // Variables declaration - do not modify 38. private javax.swing.jbutton jbuttonsalaire; // End of variables declaration 124/257

125 41. 4 } lignes : la méthode statique [main] qui lance l'application ligne 32 : une instance de l'interface graphique [PamJFrame] est créée et rendue visible. lignes 7-9 : le constructeur de l'interface graphique. ligne 8 : appel à la méthode [initcomponents] définie ligne 17. Cette méthode est auto-générée à partir du travail fait en mode [Design]. On ne doit pas y toucher. ligne 21 : la méthode qui va gérer le déplacement du curseur de saisie dans le champ [jtextfieldht] ligne 25 : la méthode qui va gérer le clic sur le bouton [jbuttonsalaire] Pour ajouter au code précédent nos propres initialisations, nous pouvons procéder comme suit : 1. /** Creates new form PamJFrame */ public PamJFrame() { 3. initcomponents(); 4. domyinit(); 5. } // variables d'instance 10. private IMetier metier=null; 11. private List<Employe> employes=null; 1 private String[] employescombo=null; 13. private double heurestravaillées=0; // initialisations propriétaires 16. public void domyinit(){ 17. // init contexte 18. try{ 19. // instanciation couche [metier] // liste des employés }catch (PamException ex){ 24. // le message de l'exception est placé dans [jtextareastatus] // retour 27. return; 28. } 29. // bouton salaire inhibé // jscrollpane1 caché // spinner jours travaillés 34. jspinnerjt.setmodel(new SpinnerNumberModel(0,0,31,1)); 35. // combobox employés 36. employescombo=new String[employes.size()]; 37. int i=0; 38. for(employe employe : employes){ 39. employescombo[i++]=employe.getprenom()+" "+employe.getnom(); 40. } 41. jcomboboxemployes.setmodel(new DefaultComboBoxModel(employesCombo)); 4 } ligne 4 : on appelle une méthode propriétaire pour faire nos propres initialisations. Celles-ci sont définies par le code des lignes Question : en vous aidant des commentaires, compléter le code de la procédure [domyinit]. 125/257

126 4.15 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] Exécution de l'interface graphique Pour exécuter l'interface graphique, on modifiera la configuration [Run] du projet : 1 en [1], mettre la classe de l'interface graphique Le projet doit être complet avec ses fichiers de configuration (persistence.xml, spring-config-metier-dao.xml) et la classe de l'interface graphique. On lancera Le SGBD cible avant d'exécuter le projet Implémentation de la couche JPA avec EclipseLink Nous nous intéressons à l'architecture suivante où la couche JPA est désormais implémentée par EclipseLink : [ui] [metier] [DAO] Objets image de la BD Interface [JPA] Implémentation [EclipseLink] [JDBC] Spring Le projet Netbeans Le nouveau projet Netbeans est obtenu par recopie du projet précédent : 126/257

127 en [1] : après un clic droit sur le projet Hibernate, choisir Copy à l'aide du bouton [2], choisir le dossier parent du nouveau projet. Le nom du dossier apparaît en [3]. en [4], donner un nom au nouveau projet en [5], le nom du dossier du projet 127/257

128 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. 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=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-pam-spring-eclipselink</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-pam-spring-eclipselink</name> 11. <url> 1 <repositories> 13. <repository> 14. <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

129 <url> <id>eclipselink</id> <layout>default</layout> <name>repository for library Library[eclipselink]</name> </repository> </repositories> <properties> <project.build.sourceencoding>utf-8</project.build.sourceencoding> </properties> <dependencies> <dependency> <groupid>junit</groupid> <artifactid>junit</artifactid> <version>4.10</version> <scope>test</scope> <type>jar</type> </dependency> <dependency> <groupid>commons-dbcp</groupid> <artifactid>commons-dbcp</artifactid> <version>1.2</version> </dependency> <dependency> <groupid>commons-pool</groupid> <artifactid>commons-pool</artifactid> <version>1.6</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-tx</artifactid> <version>3.1.1.release</version> <type>jar</type> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-beans</artifactid> <version>3.1.1.release</version> <type>jar</type> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context</artifactid> <version>3.1.1.release</version> <type>jar</type> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-orm</artifactid> <version>3.1.1.release</version> <type>jar</type> </dependency> <dependency> <groupid>org.eclipse.persistence</groupid> <artifactid>eclipselink</artifactid> <version>3.0</version> </dependency> <dependency> <groupid>org.eclipse.persistence</groupid> <artifactid>javax.persistence</artifactid> <version>0.3</version> </dependency> 129/257

130 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> 9 </dependency> 93. </dependencies> 94. </project> lignes : les dépendances pour l'implémentation JPA EclipseLink, lignes : le dépôt Maven pour EclipseLink. Les fichiers de configuration de Spring doivent être modifiés pour indiquer que l'implémentation JPA a changé. Dans les deux fichiers, seule la section configurant la couche JPA change. Par exemple dans [spring-config-metier-dao.xml] on a : 1. <?xml version="1.0" encoding="utf-8"?> <beans xmlns=" xmlns:xsi=" 3. xmlns:tx=" 4. xsi:schemalocation=" <!-- couches applicatives --> 7. <!-- DAO --> 8. <bean id="employedao" class="dao.employedao" /> 9. <bean id="indemnitedao" class="dao.indemnitedao" /> 10. <bean id="cotisationdao" class="dao.cotisationdao" /> 11. <!-- métier --> 1 <bean id="metier" class="metier.metier"> 13. <property name="employedao" ref="employedao"/> 14. <property name="indemnitedao" ref="indemnitedao"/> 15. <property name="cotisationdao" ref="cotisationdao"/> 16. </bean> <!-- configuration JPA --> 19. <bean id="entitymanagerfactory" class="org.springframework.orm.jpa.localcontainerentitymanagerfactorybean"> 20. <property name="datasource" ref="datasource" /> 21. <property name="jpavendoradapter"> 2 <bean class="org.springframework.orm.jpa.vendor.hibernatejpavendoradapter"> 23. <!-24. <property name="showsql" value="true" /> > 26. <property name="databaseplatform" value="org.hibernate.dialect.mysql5innodbdialect" /> 27. <property name="generateddl" value="true" /> 28. <!-29. <property name="generateddl" value="true" /> > 31. </bean> 3 </property> 33. <property name="loadtimeweaver"> 34. <bean class="org.springframework.instrument.classloading.instrumentationloadtimeweaver" /> 35. </property> 36. </bean> /257

131 <!-- la source de donnéees DBCP --> <bean id="datasource" class="org.apache.commons.dbcp.basicdatasource" destroymethod="close"> 40. <property name="driverclassname" value="com.mysql.jdbc.driver" /> 41. <property name="url" value="jdbc:mysql://localhost:3306/dbpam_hibernate" /> 4 <property name="username" value="root" /> 43. <!-44. <property name="password" value="" /> > 46. </bean> </beans> Les lignes 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 ci-dessus sont remplacées par les lignes ci-dessous : 1. <!-- configuration JPA --> <bean id="entitymanagerfactory" class="org.springframework.orm.jpa.localcontainerentitymanagerfactorybean"> 3. <property name="datasource" ref="datasource" /> 4. <property name="jpavendoradapter"> 5. <bean class="org.springframework.orm.jpa.vendor.eclipselinkjpavendoradapter"> 6. <!-7. <property name="showsql" value="true" /> 8. --> 9. <property name="databaseplatform" value="org.eclipse.persistence.platform.database.mysqlplatform" /> 10. <!-11. <property name="generateddl" value="true" /> 1 --> 13. </bean> 14. </property> 15. <property name="loadtimeweaver"> 16. <bean class="org.springframework.instrument.classloading.instrumentationloadtimeweaver" /> 17. </property> 18. </bean> ligne 5 : l'implémentation JPA utilisée est EclipseLink ligne 9 : la propriété databaseplatform fixe le SGBD cible, ici MySQL ligne 11 : pour générer les tables de la base de données lorsque la couche JPA est instanciée. Ici, la propriété est en commentaires. ligne 7 : pour visualiser sur la console les ordres SQL émis par la couche JPA. Ici, la propriété est en commentaires. Par ailleurs, la base de données cible devient [dbpam_eclipselink] (ligne 4 ci-dessous) : 1. <!-- la source de donnéees DBCP --> <bean id="datasource" class="org.apache.commons.dbcp.basicdatasource" destroymethod="close"> 3. <property name="driverclassname" value="com.mysql.jdbc.driver" /> 4. <property name="url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink" /> 5. <property name="username" value="root" /> 6. <!-7. <property name="password" value="" /> 8. --> 9. </bean> Mise en oeuvre des tests 131/257

132 Avant de tester l'application entière, il est bon de vérifier si les tests JUnit passent avec la nouvelle implémentation JPA. Avant de les faire, on commencera par supprimer les tables de la base de données. Pour cela, dans l'onglet [Runtime] de Netbeans, si besoin est, on créera une connexion sur la base dbpam_eclipselink / MySQL5. Une fois connecté à la base dbpam_eclipselink / MySQL5, on pourra procéder à la suppression des tables comme montré ci-dessous : [1] : avant la suppression [2] : après la suppression 2 1 Ceci fait, on peut exécuter le premier test sur la couche [DAO] : InitDB qui remplit la base. Pour que les tables détruites précédemment soient recréées par l'application, il faut s'assurer que dans la configuration JPA / EclipseLink de Spring la ligne : <property name="generateddl" value="true" /> existe et n'est pas mise en commentaires. Nous construisons le projet (Build) puis nous exécutons le test [JUnitInitDB] : 2 1 en [1], le test InitDB est exécuté. en [2], il échoue. L'exception est lancée par Spring et non par un test qui aurait échoué. Caused by: org.springframework.beans.factory.beancreationexception: Error creating bean with name 'entitymanagerfactory' defined in class path resource [spring-config-dao.xml]: Invocation of init method failed; nested exception is java.lang.illegalstateexception: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation. 132/257

133 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 de [ref1]. Pour que la configuration Spring / EclipseLink fonctionne, la JVM qui exécute l'application doit être lancée avec un paramètre particulier, un agent Java. La forme de ce paramètre est la suivante : -javaagent:c:\...\spring-agent.jar [spring-agent.jar] est l'agent Java dont a besoin la JVM pour gérer la configuration Spring / EclipseLink. Lorsqu'on exécute un projet, il est possible de passer des arguments à la JVM : en [1], on accède aux propriétés du projet ; en [2], les propriétés du Run ; en [3], on passe le paramètre -javaagent à la JVM. On trouvera l'archive [spring-agent] dans le support du cours et on adaptera la ligne 9 au chemin réel de cette archive. S'il y a des espaces dans ce chemin, entourez-le d'apostrophes comme dans '-javaagent:mon chemin'. La configuration de Netbeans pour passer l'agent Java fonctionne bien pour le programme principal mais pas pour les tests (les programmes dans la branche Tests). Pour ceux-ci, on ajoute le plugin [maven-surefire] dans le fichier [pom.xml] : 1. <build> <plugins> 3. <plugin> 4. <groupid>org.apache.maven.plugins</groupid> 5. <artifactid>maven-surefire-plugin</artifactid> 6. <version>12</version> 7. <configuration> 8. <forkmode>pertest</forkmode> 9. <argline>-javaagent:d:/temp/ /spring-agent-5.6.jar</argline> 10. </configuration> 11. </plugin> 1 </plugins> 13. </build> 133/257

134 Le plugin [maven-surefire] est utilisé pour les tests. Il permet de produire des rapports. Ici, nous ne l'utilisons que pour passer l'agent Java à la JVM (ligne 9). On trouvera l'archive [spring-agent] dans le support du cours et on adaptera la ligne 9 au chemin réel de cette archive. S'il y a des espaces dans ce chemin, entourez-le d'apostrophes comme dans 'mon chemin'. Note : on trouvera le [pom.xml] complet dans le support de cours InitDB Maintenant, nous sommes prêts pour tester de nouveau [InitDB]. Cette fois-ci les résultats obtenus sont les suivants : en [1], le test a été réussi en [2], dans l'onglet [Services], on rafraîchit la connexion qu'a Netbeans avec la base [dbpam_eclipselink] en [3], quatre tables ont été créées en [5], on visualise le contenu de la table [employes] en [6], le résultat. JUnitDao Note : la classe [JUnitDao] est disponible dans le support de cours. L'exécution de la classe de tests [JUnitDao] peut échouer, même si avec l'implémentation JPA / Hibernate, elle avait réussi. Pour comprendre pourquoi, analysons un exemple. La méthode testée est la méthode IndemniteDao.create suivante : 1. package dao; public class IndemniteDao implements IIndemniteDao{ private EntityManager em; // constructeur 11. public IndemniteDao() { 1 } // créer une indemnité 134/257

135 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; 2 } } lignes : la méthode testée La méthode de test est la suivante : 1. package dao; public class JUnitDao { // couches DAO 8. static private IEmployeDao employedao; 9. static private IIndemniteDao indemnitedao; 10. static private ICotisationDao cotisationdao; 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"); 2 } 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); 3 } 33. for (Indemnite indemnite : indemnitedao.findall()) { 34. indemnitedao.destroy(indemnite); 35. } 36. } // logs 39. private static void log(string message) { 40. System.out.println(" " + message); 41. } // tests public void test05() { 47. log("test05"); 135/257

136 } } // on crée deux indemnités avec le même indice // enfreint la contrainte d'unicité de l'indice boolean erreur = true; Indemnite indemnite1 = null; Indemnite indemnite2 = null; Throwable th = null; try { indemnite1 = indemnitedao.create(new Indemnite(1, 1.93, 2, 3, 12)); indemnite2 = indemnitedao.create(new Indemnite(1, 1.93, 2, 3, 12)); erreur = false; } catch (PamException ex) { th = ex; // vérifications Assert.assertEquals(31, ex.getcode()); } catch (Throwable th1) { th = th1; } // vérifications Assert.assertTrue(erreur); // chaîne des exceptions System.out.println("Chaîne des exceptions "); System.out.println(th.getClass().getName()); while (th.getcause()!= null) { th = th.getcause(); System.out.println(th.getClass().getName()); } // la 1ère indemnité a du être persistée Indemnite indemnite = indemnitedao.find(indemnite1.getid()); // vérification Assert.assertNotNull(indemnite); Assert.assertEquals(1, indemnite.getindice()); Assert.assertEquals(1.93, indemnite.getbaseheure(), 1e-6); Assert.assertEquals(2, indemnite.getentretienjour(), 1e-6); Assert.assertEquals(3, indemnite.getrepasjour(), 1e-6); Assert.assertEquals(12, indemnite.getindemnitescp(), 1e-6); // la seconde indemnité n'a pas du être persistée List<Indemnite> indemnites = indemnitedao.findall(); int nbindemnites = indemnites.size(); Assert.assertEquals(nbIndemnites, 1); 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 : test05 4 juin :45:43 org.hibernate.util.jdbcexceptionreporter logexceptions 3. ATTENTION: SQL Error: 1062, SQLState: juin :45:43 org.hibernate.util.jdbcexceptionreporter logexceptions 5. GRAVE: Duplicate entry '1' for key 2 6. Chaîne des exceptions exception.pamexception 8. javax.persistence.entityexistsexception 9. org.hibernate.exception.constraintviolationexception 10. com.mysql.jdbc.exceptions.mysqlintegrityconstraintviolationexception Le test passe, c.a.d. que les assertions sont vérifiées et il n'y a pas d'exception qui sort de la méthode de test. Question : expliquer ce qui s'est passé. 136/257

137 Les résultats obtenus avec une couche JPA / EclipseLink sont les suivants : test05 [EL Warning]: :48: UnitOfWork(749304)--Exception [EclipseLink-4002] (Eclipse Persistence Services v r5931): org.eclipse.persistence.exceptions.databaseexception 3. Internal Exception: com.mysql.jdbc.exceptions.mysqlintegrityconstraintviolationexception: Duplicate entry '1' for key 2 4. Error Code: Call: INSERT INTO INDEMNITES (ID, ENTRETIEN_JOUR, REPAS_JOUR, INDICE, INDEMNITES_CP, BASE_HEURE, VERSION) VALUES (?,?,?,?,?,?,?) 6. bind => [108, 0, 3.0, 1, 10, 1.93, 1] 7. Query: InsertObjectQuery(JPA.Indemnite[id=108,version=1,indice=1,base heure=1.93,entretien jour0,repas jour=3.0,indemnités CP=10]) 8. Chaîne des exceptions org.springframework.transaction.transactionsystemexception 10. javax.persistence.rollbackexception 11. org.eclipse.persistence.exceptions.databaseexception 1 com.mysql.jdbc.exceptions.mysqlintegrityconstraintviolationexception Comme précédemment avec Hibernate, le test passe, c.a.d. que les assertions sont vérifiées et il n'y a pas d'exception qui sort de la méthode de test. Question : expliquer ce qui s'est passé. Question : de ces deux exemples, que peut-on conclure de l'interchangeabilité des implémentations JPA? Est-elle totale ici? Les autres tests Une fois la couche [DAO] testée et considérée correcte, on pourra passer aux tests de la couche [metier] et à ceux du projet luimê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 à faire Travail pratique : refaire les tests des applications console et swing avec différents SGBD : Postgres, Oracle XE, SQL Server. 137/257

138 5 Version 2 : Architecture OpenEJB / JPA 5.1 Introduction aux principes du portage Note : le cours est à lire jusqu'au paragraphe 5.2, page 143 où commence le travail pratique. Nous présentons ici les principes qui vont gouverner le portage d'une application JPA / Spring / Hibernate vers une application JPA / OpenEJB / EclipseLink. On attendra le paragraphe 5.2, page 143 pour créer les projets Maven Les nouvelles architectures L'implémentation actuelle avec Spring / Hibernate [ui] [metier] [DAO] Objets image de la BD 71 Interface [JPA] Implémentation [Hibernate] 2 [JDBC] Implémentation [EclipseLink] 2 [JDBC] Implémentation [EclipseLink] [JDBC] Spring Les deux implémentations à construire avec OpenEJB / EclipseLink [ui] [metier] [DAO] Objets image de la BD Interface [JPA] OpenEjb 1 Dans cette implémentation, la couche [ui] sera un client local de la couche [métier]. [ui] [metier] [DAO] 1 Objets image de la BD Interface [JPA] OpenEjb Dans cette implémentation, la couche [ui] sera un client distant de la couche [métier] Les bibliothèques des projets les couches [DAO] et [metier] ne sont plus instanciées par Spring. Elles le sont par le conteneur OpenEJB. les bibliothèques du conteneur Spring et sa configuration disparaissent au profit des bibliothèques du conteneur OpenEJB et sa configuration. les bibliothèques de la couche JPA / Hibernate sont remplacées par celles de la couche JPA / EclipseLink 138/257

139 5.1.3 Configuration de la couche JPA / EclipseLink / OpenEJB le fichier [META-INF/persistence.xml] configurant la couche JPA devient le suivant : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <persistence-unit name="pam-openejb-ui-metier-dao-jpa-eclipselinkpu" transactiontype="jta"> 4. <!-- entités JPA --> 5. <class>jpa.cotisation</class> 6. <class>jpa.employe</class> 7. <class>jpa.indemnite</class> 8. <!-- le fournisseur JPA est EclipseLink --> 9. <provider>org.eclipse.persistence.jpa.persistenceprovider</provider> 10. <!-- propriétés provider --> 11. <properties> 1 <property name="eclipselink.ddl-generation" value="create-tables"/> 13. </properties> 14. </persistence-unit> 15. </persistence> ligne 3 : les transactions dans un conteneur EJB sont de type JTA (Java Transaction API). Elles étaient de type RESOURCE_LOCAL avec Spring. ligne 9 : l'implémentation JPA utilisée est EclipseLink lignes 5-7 : les entités gérées par la couche JPA lignes : 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"?> <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] Implémentation de la couche [DAO] par des EJB Les classes implémentant la couche [DAO] deviennent des EJB. Prenons l'exemple de la classe [CotisationDao] : L'interface [ICotisationDao] dans la version Spring était la suivante : package dao; import java.util.list; import jpa.cotisation; public interface ICotisationDao { // créer une nouvelle cotisation Cotisation create(cotisation cotisation); // modifier une cotisation existante 139/257

140 } Cotisation edit(cotisation cotisation); // supprimer une cotisation existante void destroy(cotisation cotisation); // chercher une cotisation particulière Cotisation find(long id); // obtenir tous les objets Cotisation List<Cotisation> findall(); 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 : package dao; 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 : en fait une interface locale pour l'ejb qui l'implémentera. import public interface ICotisationDaoLocal extends ICotisationDao{ } L'interface distante : package dao; 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 : en fait une interface distante pour l'ejb qui l'implémentera. import public interface ICotisationDaoRemote extends ICotisationDao{ } La couche [DAO] est implémentée par un EJB implémentant les deux interfaces (ce n'est pas obligatoire) 3. public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote { private EntityManager em; ligne 1 : qui fait de la classe un EJB ligne 2 : qui fait que chaque méthode de la classe s'exécutera au sein d'une transaction. ligne 5 : qui injecte dans la classe [CotisationDao] l'entitymanager de la couche JPA. Elle est identique à ce qu'on avait dans la version Spring. Lorsque l'interface locale de la couche [DAO] est utilisée, le client de cette interface s'exécute dans la même JVM. 140/257

141 utilisateur interface utilisateur [ui] métier [metier] d'accès aux données [DAO] Données 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. 1 utilisateur interface utilisateur [ui] JVM 1 métier [metier] 2 3 d'accès aux données [DAO] Réseau tcp /ip Données JVM 2 Ci-dessus, les couches [metier] et [DAO] échangent des objets par valeur (sérialisation de l'objet échangé). Lorsqu'une couche change un objet partagé, l'autre couche ne voit ce changement que si l'objet modifié lui est renvoyé Implémentation de la couche [metier] par un EJB La classe implémentant la couche [metier] devient elle aussi un EJB implémentant une interface locale et distante. L'interface initiale [IMetier] était la suivante : package metier; import java.util.list; import jpa.employe; public interface IMetier { // obtenir la feuille de salaire 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 : package metier; package metier; import public interface IMetierLocal extends IMetier{ } import public interface IMetierRemote extends IMetier{ 141/257

142 7. } L'EJB de la couche [metier] implémente ces deux interfaces 3. public class Metier implements IMetierLocal, IMetierRemote { // référence sur les couches [DAO] locales 7. private ICotisationDaoLocal cotisationdao = null; 9. private IEmployeDaoLocal employedao = null; 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 : 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. 1 utilisateur 2 interface utilisateur [ui] JVM métier [metier] Réseau tcp /ip d'accès aux données [DAO] Données JVM 2 Les clients des EJB 1 utilisateur 2 interface utilisateur [ui] JVM 1 3 métier [metier] Réseau tcp /ip d'accès aux données [DAO] Données JVM 2 Dans le schéma ci-dessus, pour communiquer avec la couche [metier], la couche [ui] doit obtenir une référence sur l'interface distante de l'ejb de la couche [metier]. utilisateur interface utilisateur [ui] métier [metier] d'accès aux données [DAO] Données JVM 142/257

143 Dans le schéma ci-dessus, pour communiquer avec la couche [metier], la couche [ui] doit obtenir une référence sur l'interface locale de l'ejb de la couche [metier]. La méthode pour obtenir ces références diffère d'un conteneur à l'autre. Pour le conteneur OpenEJB, on pourra procéder comme suit : Référence sur l'interface locale : // on configure le conteneur Open EJB embarqué Properties properties = new Properties(); properties.setproperty(context.initial_context_factory, "org.apache.openejb.client.localinitialcontextfactory"); // initialisation du contexte JNDI avec les propriétés précédentes InitialContext initialcontext = new InitialContext(properties); // instanciation couches DAO employedao = (IEmployeDaoLocal) initialcontext.lookup("employedaolocal"); cotisationdao = (ICotisationDaoLocal) initialcontext.lookup("cotisationdaolocal"); indemnitedao = (IIndemniteDaoLocal) initialcontext.lookup("indemnitedaolocal"); lignes 2-5 : le conteneur OpenEJB est initialisé. ligne 5 : on a un contexte JNDI (Java Naming and Directory Interface) qui permet d'obtenir des références sur les EJB. Chaque EJB est désigné par un nom JNDI : pour l'interface locale on ajoute Local au nom de l'ejb (lignes 7-9) pour l'interface distante on ajoute Remote au nom de l'ejb Avec Java EE 5, ces règles changent selon le conteneur EJB. C'est une difficulté. Java EE 6 a introduit une notation JNDI portable sur tous les serveurs d'applications. Le code précédent récupère des références sur les interfaces locales des EJB via leurs noms JNDI. Nous avons dit précédemment que celles-ci pouvaient également être obtenues via On pourrait donc vouloir écrire private IemployeDaoLocal employedaolocal ; 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 configure le conteneur Open EJB embarqué Properties properties = new Properties(); properties.setproperty(context.initial_context_factory, "org.apache.openejb.client.localinitialcontextfactory"); 4. // initialisation du contexte JNDI du conteneur EJB 5. InitialContext initialcontext = new InitialContext(properties); // instanciation couche métier distante 8. metier = (IMetierRemote) initialcontext.lookup("metierremote"); 5.2 Travail pratique On se propose de porter l'application Netbeans Spring / Hibernate vers une architecture OpenEJB / EclipseLink. L'implémentation actuelle avec Spring / Hibernate 143/257

144 [ui] [metier] [DAO] Objets image de la BD Interface [JPA] Implémentation [Hibernate] 2 [JDBC] Interface [JPA] Implémentation [EclipseLink] [JDBC] Spring L'implémentation à construire avec OpenEJB / EclipseLink [ui] [metier] [DAO] Objets image de la BD OpenEjb 5.1 Mise en place de la base de données [dbpam_eclipselink] Si elle n'existe pas, créez la base MySQL [dbpam_eclipselink]. Si elle existe, supprimez toutes ses tables. Créez une connexion Netbeans vers cette base comme il a été décrit page Configuration initiale du projet Netbeans charger le projet Maven [mv-pam-spring-hibernate] créer un nouveau projet Maven Java [mv-pam-openejb-eclipselink] [1] 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"?> <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

145 créer le dossier [src / main/ resources/ META-INF] [9] y mettre le fichier [persistence.xml] [10] suivant : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="1.0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <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> 1 <property name="eclipselink.logging.level" value="fine"/> 13. <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/> 14. </properties> 15. </persistence-unit> 16. </persistence> ligne 12 : on demande des logs fins à EclipseLink, ligne 13 : les tables seront créées à l'instanciation de la couche JPA, ajouter les bibliothèques OpenEJB, EclipseLink ainsi que le pilote JDBC de MySQL au fichier [pom.xml] du projet : 1. <project xmlns=" xmlns:xsi=" 145/257

146 xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-pam-openejb-eclipselink</artifactid> 7. <version>1.0-snapshot</version> 8. <packaging>jar</packaging> <name>mv-pam-openejb-eclipselink</name> 11. <url> <properties> 14. <project.build.sourceencoding>utf-8</project.build.sourceencoding> 15. </properties> <dependencies> 18. <dependency> 19. <groupid>org.apache.openejb</groupid> 20. <artifactid>openejb-core</artifactid> 21. <version>4.0.0</version> 2 </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> 3 <artifactid>eclipselink</artifactid> 33. <version>3.0</version> 34. </dependency> 35. <dependency> 36. <groupid>org.eclipse.persistence</groupid> 37. <artifactid>javax.persistence</artifactid> 38. <version>0.3</version> 39. </dependency> 40. <dependency> 41. <groupid>mysql</groupid> 4 <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> <repositories> 53. <repository> 54. <url> 55. <id>eclipselink</id> 56. <layout>default</layout> 57. <name>repository for library Library[eclipselink]</name> 58. </repository> 59. </repositories> </project> lignes : la dépendance OpenEJB, 146/257

147 5.3 lignes : les dépendances EclipseLink, lignes : la dépendance du pilote JDBC de MySQL Portage de la couche [DAO] Nous allons faire le portage de la couche [DAO] par copie de paquetages du projet [mv-pam-spring-hibernate] vers le projet [mvpam-openejb-eclipselink]. copier les packages [dao, exception, jpa] Les erreurs signalées ci-dessus viennent du fait que la couche [DAO] copiée utilise Spring et que les bibliothèques Spring ne font plus partie du projet L'EJB [CotisationDao] Nous créons les interfaces locale et distante du futur EJB [CotisationDao] : L'interface locale ICotisationDaoLocal : package dao; import public interface ICotisationDaoLocal extends ICotisationDao{ } Pour avoir les bons import de paquetages, faire [clic droit sur le code / Fix Imports]. L'interface distante ICotisationDaoRemote : package dao; import public interface ICotisationDaoRemote extends ICotisationDao{ } Puis nous modifions la classe [CotisationDao] pour en faire un EJB : import javax.persistence.persistencecontext; public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote { 147/257

148 private EntityManager em; L'import que faisait cette classe sur le framework Spring disparaît. Faire un [Clean and Build] du projet : 1 2 En [1], il n'y a plus d'erreurs sur la classe [CotisationDao] Les EJB [EmployeDao] et [IndemniteDao] On répète la même démarche pour les autres éléments de la couche [DAO] : interfaces IEmployeDaoLocal, IEmployeDaoRemote dérivées de IEmployeDao EJB EmployeDao implémentant ces deux interfaces interfaces IIndemniteDaoLocal, IIndemniteDaoRemote dérivées de IIndemniteDao EJB IndemniteDao implémentant ces deux interfaces Ceci fait, il n'y a plus d'erreurs dans le projet [2] La classe [PamException] La classe [PamException] reste ce qu'elle était à un détail près : 1. package exception; 3. import javax.ejb.applicationexception; public class PamException extends RuntimeException implements Serializable{ // code d'erreur 9. private int code; 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

149 utilisateur interface utilisateur [ui] métier [metier] d'accès aux données [DAO] Proxy EJB Données 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. Entités sérialisables Revenons sur l'architecture où la couche [UI] est un client distant de la couche [métier] : [ui] [metier] 1 [DAO] Objets image de la BD Interface [JPA] Implémentation [EclipseLink] [JDBC] OpenEjb Les couches [ui] et [metier] vont s'échanger des objets. Dans la pratique, ces deux couches sont dans deux JVM différentes. Si la couche [ui] veut passer un objet à la couche [métier], elle ne peut pas passer la référence de cet objet. La couche [métier] ne peut en effet référencer des objets qui ne sont pas dans sa JVM. La couche [ui] va alors passer la valeur de l'objet et non sa référence. On appelle cela la sérialisation de l'objet. La couche [métier] va recevoir cette valeur et va créer un nouvel objet à partir d'elle. On appelle cela la désérialisation de l'objet. La couche [ui] et la couche [métier] ont alors deux objets identiques mais chacun dans sa JVM. Dans notre exemple, les types suivants peuvent être échangés entre les couches [ui] et [metier] : [Employe, Cotisation, Indemnite, FeuilleSalaire, ElementsSalaire, PamException]. Ces classes doivent pouvoir être sérialisées. Cela est obtenu par la simple déclaration : 1. public [Classe] extends... implements Serializable{.. 3. } On fera donc en sorte que les classes citées implémentent l'interface [Serializable]. Il n'y a rien d'autre à faire pour qu'une classe puisse être sérialisée Test de la couche [DAO] Notre couche [DAO] implémentée par des EJB peut être testée. Nous commençons par copier le package [dao] de [Test Packages] du projet [mv-pam-springhibernate] dans le projet en cours de construction [1] : 149/257

150 3 2 1 Nous ne conservons que le test [JUnitInitDB] qui initialise la base avec quelques données [2]. Nous renommons la classe [ JUnitInitDbLocal] [3]. La classe [JUnitInitDBLocal] utilisera l'interface locale des EJB de la couche [DAO]. Nous modifions tout d'abord la classe [JUnitInitDBLocal] de la façon suivante : 1. public class JUnitInitDBLocal { 3. static private IEmployeDaoLocal employedao = null; 4. static private ICotisationDaoLocal cotisationdao = null; 5. static private IIndemniteDaoLocal indemnitedao = null; 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"); 1 // initialisation du contexte JNDI avec les propriétés précédentes 13. InitialContext initialcontext = new InitialContext(properties); 14. // instanciation couches DAO locales 15. employedao = (IEmployeDaoLocal) initialcontext.lookup("employedaolocal"); 16. cotisationdao = (ICotisationDaoLocal) initialcontext.lookup("cotisationdaolocal"); 17. indemnitedao = (IIndemniteDaoLocal) initialcontext.lookup("indemnitedaolocal"); 18. } lignes 3-5 : des références sur les interfaces locales des EJB de la couche [DAO] ligne 7 annote la méthode exécutée au démarrage du test JUnit lignes : 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 : on demande au contexte JNDI, une référence sur les interfaces locales des EJB [EmployeDao, CotisationDao, IndemniteDao] /257

151 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] en [1], dans l'onglet [Services], on supprime les tables de la connexion Netbeans établie au paragraphe 5.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é en [5], le test a été réussi en [6], on rafraîchit la connexion Netbeans en [7], on voit les 4 tables créées par la couche JPA. Le test avait pour but de les remplir. On visualise le contenu de l'une d'entre-elles 8 en [8], le contenu de la table [EMPLOYES] Le conteneur OpenEJB a affiché des logs dans la console : 1. Infos - PersistenceUnit(name=dbpam_eclipselinkPU, provider=org.eclipse.persistence.jpa.persistenceprovider) - provider time 396ms 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

152 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) 1 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 Infos - Adding OpenWebBeansPlugin : [CdiPlugin] 20. Infos - All injection points were validated successfully. 21. Infos - OpenWebBeans Container has started, it took [70] ms. 2 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-pamopenejb-eclipselink\classpath.ear) lignes 2-3 : les deux noms JNDI de l'ejb [CotisationDaoLocal], lignes 4-5 : les deux noms JNDI de l'ejb [CotisationDaoRemote], lignes 7-8 :les deux noms JNDI de l'ejb [EmployeDaoLocal], lignes 9-10 :les deux noms JNDI de l'ejb [EmployeDaoRemote], lignes :les deux noms JNDI de l'ejb [IndemniteDaoLocal], lignes :les deux noms JNDI de l'ejb [EmployeDaoRemote]. Nous refaisons le même test, en utilisant cette fois-ci l'interface distante des EJB. 1 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

153 1. public class JUnitInitDBRemote { 3. static private IEmployeDaoRemote employedao = null; 4. static private ICotisationDaoRemote cotisationdao = null; 5. static private IIndemniteDaoRemote indemnitedao = null; 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"); 1 // initialisation du contexte JNDI avec les propriétés précédentes 13. InitialContext initialcontext = new InitialContext(properties); 14. // instanciation couches DAO distantes 15. employedao = (IEmployeDaoRemote) initialcontext.lookup("employedaoremote"); 16. cotisationdao = (ICotisationDaoRemote) initialcontext.lookup("cotisationdaoremote"); 17. indemnitedao = (IIndemniteDaoRemote) initialcontext.lookup("indemnitedaoremote"); 18. } Ceci fait, la nouvelle classe de test peut être exécutée. Auparavant, avec la connexion Netbeans [dbpam_eclipselink], supprimez les tables de la base [dbpam_eclipselink]. Avec la connexion Netbeans [dbpam_eclipselink], vérifiez que la base a été remplie. 5.4 Portage de la couche [metier] Nous allons faire le portage de la couche [metier] par copie de packages du projet [mv-pam-spring-hibernate] vers le projet [mvpam-openejb-eclipselink] 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 L'EJB [Metier] Nous suivons la même démarche que celle décrite pour l'ejb [CotisationDao]. Nous créons tout d'abord en [2] les interfaces locale et distante du futur EJB [Metier]. Elles dérivent toutes deux de l'interface initiale [IMetier]. 1. package metier; 153/257

154 import public interface IMetierLocal extends IMetier{ } package metier; import public interface IMetierRemote extends IMetier{ } Ceci fait, en [3] nous modifions la classe [Metier] afin qu'elle devienne un EJB 3. public class Metier implements IMetierLocal, IMetierRemote { // références sur la couche [DAO] locale 7. private ICotisationDaoLocal cotisationdao = null; 9. private IEmployeDaoLocal employedao = null; 11. private IIndemniteDaoLocal indemnitedao = null; // 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é ligne 1 : fait de la classe un EJB ligne 2 : chaque méthode de la classe s'exécutera dans une transaction ligne 3 : l'ejb [Metier] implémente les deux interfaces locale et distante que nous venons de définir ligne 7 : l'ejb [Metier] va utiliser l'ejb [CotisationDao] via l'interface locale de celui-ci. Cela signifie que les couches [metier] et [DAO] doivent s'exécuter dans la même JVM. ligne 6 : fait en sorte que le conteneur EJB injecte lui-même la référence sur l'interface locale de l'ejb [CotisationDao]. L'autre façon que nous avons rencontrée est d'utiliser un contexte JNDI. lignes 8-11 : le même mécanisme est utilisé pour les deux autres EJB de la couche [DAO]. Test de la couche [metier] Notre couche [metier] implémentée par un EJB peut être testée. Nous commençons par copier le package [metier] de [Test Packages] du projet [mv-pam-spring-hibernate] dans le projet en cours de construction [1] : 154/257

155 2 3 1 en [1], le résultat de la copie en [2], on supprime le 1er test en [3], le test restant est renommé [JUnitMetierLocal] La classe [JUnitMetierLocal] devient la suivante : 1. public class JUnitMetierLocal { 3. // couche métier locale 4. static private IMetierLocal metier; public static void init() throws NamingException { 8. // on configure le conteneur Open EJB embarqué 9. Properties properties = new Properties(); 10. properties.setproperty(context.initial_context_factory, "org.apache.openejb.client.localinitialcontextfactory"); 11. // initialisation du contexte JNDI avec les propriétés précédentes 1 InitialContext initialcontext = new InitialContext(properties); // instanciation couches DAO locales 15. IEmployeDaoLocal employedao = (IEmployeDaoLocal) initialcontext.lookup("employedaolocal"); 16. ICotisationDaoLocal cotisationdao = (ICotisationDaoLocal) initialcontext.lookup("cotisationdaolocal"); 17. IIndemniteDaoLocal indemnitedao = (IIndemniteDaoLocal) initialcontext.lookup("indemnitedaolocal"); 18. // instanciation couche métier locale 19. metier = (IMetierLocal) initialcontext.lookup("metierlocal"); // on vide la base } ligne 4 : une référence sur l'interface locale de l'ejb [Metier] lignes 8-12 : configuration du conteneur OpenEJB identique à celle faite dans le test de la couche [DAO] lignes : on demande au contexte JNDI de la ligne 12, des références sur les 3 EJB de la couche [DAO] et sur l'ejb de la couche [metier]. Les EJB de la couche [DAO] vont servir à initialiser la base, l'ejb de la couche [metier] à faire des tests de calculs de salaire. Note : avant d'exécuter ce test, il faut modifier le fichier [persistence.xml] pour que les tables ne soient plus détruites et recréées au moment de l'instanciation de la couche [JPA] (lignes ci-dessous) : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="1.0" xmlns=" xmlns:xsi=" xsi:schemalocation=" /257

156 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> 1 <property name="eclipselink.logging.level" value="fine"/> 13. <!-14. <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/> > 16. </properties> 17. </persistence-unit> 18. </persistence> L'exécution du test [JUnitMetierLocal] donne le résultat suivant [1] : 1 2 En [2], on duplique [JUnitMetierLocal] en [JUnitMetierRemote] pour tester cette fois-ci l'interface distante de l'ejb [Metier]. Le code de [JUnitMetierRemote] est modifié pour utiliser cette interface distante. Le reste ne change pas. 1. public class JUnitMetierRemote { 3. // couche métier distante 4. static private IMetierRemote metier; public static void init() throws NamingException { 8. // on configure le conteneur Open EJB embarqué 9. Properties properties = new Properties(); 10. properties.setproperty(context.initial_context_factory, "org.apache.openejb.client.localinitialcontextfactory"); 11. // initialisation du contexte JNDI avec les propriétés précédentes 1 InitialContext initialcontext = new InitialContext(properties); // instanciation couches DAO distantes 15. IEmployeDaoRemote employedao = (IEmployeDaoRemote) initialcontext.lookup("employedaoremote"); 16. ICotisationDaoRemote cotisationdao = (ICotisationDaoRemote) initialcontext.lookup("cotisationdaoremote"); 17. IIndemniteDaoRemote indemnitedao = (IIndemniteDaoRemote) initialcontext.lookup("indemnitedaoremote"); 18. // instanciation couche métier distante 19. metier = (IMetierRemote) initialcontext.lookup("metierremote"); // on vide la base 2 for(employe employe:employedao.findall()){ 23. employedao.destroy(employe); 24. } 25. for(cotisation cotisation:cotisationdao.findall()){ 26. cotisationdao.destroy(cotisation); 27. } 156/257

157 for(indemnite indemnite : indemnitedao.findall()){ indemnitedao.destroy(indemnite); } // on la remplit Indemnite indemnite1=new Indemnite(1,1.93,2,3,12); Indemnite indemnite2=new Indemnite(2,1,1,3.1,15); indemnite1=indemnitedao.create(indemnite1); indemnite2=indemnitedao.create(indemnite2); employedao.create(new Employe(" ","Jouveinal","Marie","5 rue des oiseaux","st Corentin","49203",indemnite2)); 37. employedao.create(new Employe(" ","Laverti","Justine","La brûlerie","st Marcel","49014",indemnite1)); 38. cotisationdao.create(new Cotisation(3.49,6.15,9.39,7.88)); 39. } 40. } lignes 4 et 19 : on utilise l'interface distante de l'ejb [Metier]. lignes : on utilise les interfaces distantes de la couche [DAO] lignes : parce qu'avec les interfaces distantes, les objets échangés entre le client et le serveur le sont par valeur, il faut récupérer le résultat rendu par la méthode create(indemnite i). Ce n'était pas obligatoire avec les interfaces locales où là les objets sont passés par référence. Ceci fait, le projet peut être construit et le test [JUnitMetierRemote] exécuté : 5.5 Portage de la couche [console] Nous allons faire le portage de la couche [console] par copie de packages du projet [mv-pam-spring-hibernate] vers le projet [mvpam-openejb-eclipselink] Les erreurs signalées ci-dessus [1] viennent du fait que la couche [metier] copiée utilise Spring et que les bibliothèques Spring ne font plus partie du projet. En [2], la classe [Main] est renommée [MainLocal]. Elle utilisera l'interface locale de l'ejb [Metier]. Le code de la classe [MainLocal] évolue de la façon suivante : public static void main(string[] args) { // données locales final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés"; // des erreurs? 6. if (erreurs.size()!= 0) { 157/257

158 for (int i = 0; i < erreurs.size(); i++) { System.err.println(erreurs.get(i)); } return; } // c'est bon - on peut demander la feuille de salaire à la couche [métier] IMetierLocal metier = null; FeuilleSalaire feuillesalaire = null; try { // on configure le conteneur Open EJB embarqué Properties properties = new Properties(); properties.setproperty(context.initial_context_factory, "org.apache.openejb.client.localinitialcontextfactory"); 19. // initialisation du contexte JNDI avec les propriétés précédentes 20. InitialContext initialcontext = new InitialContext(properties); 21. // instanciation couche métier locale 2 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. } 3 // affichage détaillé 33. String output = "Valeurs saisies :\n"; 34. output += ajouteinfo("n de sécurité sociale de l'employé", args[0]); Les modifications se situent dans les lignes C'est la façon d'avoir une référence sur la couche [metier] qui change (lignes 1722). 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] : 1 Pour que l'application console s'exécute normalement, il faut qu'il y ait des données dans la base. Pour cela, il faut modifier le fichier [META-INF/persistence.xml] : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="1.0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <persistence-unit name="dbpam_eclipselinkpu" transaction-type="jta"> 4. <!-- le fournisseur JPA est EclipseLink --> 5. <provider>org.eclipse.persistence.jpa.persistenceprovider</provider> 6. <!-- entités Jpa --> 158/257

159 7. <class>jpa.cotisation</class> 8. <class>jpa.employe</class> 9. <class>jpa.indemnite</class> 10. <!-- propriétés provider EclipseLink --> 11. <properties> 1 <property name="eclipselink.logging.level" value="fine"/> 13. <!-14. <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/> > 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 : INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=indemnitedao, container=default Stateless Container) 3. INFO - Deployed Application(path=classpath.ear) 4. [EL Info]: :09: ServerSession( )--EclipseLink, version: Eclipse Persistence Services v r [EL Info]: :09: ServerSession( )--file:/C:/temp/ /pamconsole-metier-dao-openejb-eclipselink-0910/build/classes/-JPA login successful 6. Valeurs saisies : 7. N de sécurité sociale de l'employé : Nombre d'heures travaillées : Nombre de jours travaillés : Informations Employé : 1 Nom : Jouveinal 13. Prénom : Marie 14. Adresse : 5 rue des oiseaux 15. Ville : St Corentin 16. Code Postal : Indice : Informations Cotisations : 20. CSGRDS : 3.49 % 21. CSGD : 6.15 % 2 Retraite : 7.88 % 23. Sécurité sociale : 9.39 % Informations Indemnités : 26. Salaire horaire : 1 euro 27. Entretien/jour : 1 euro 28. Repas/jour : 3.1 euro 29. Congés Payés : 15.0 % Informations Salaire : 3 Salaire de base : 3625 euro 33. Cotisations sociales : euro 34. Indemnités d'entretien : 40 euro 35. Indemnités de repas : 60 euro 36. Salaire net : euro BUILD SUCCESSFUL (total time: 4 seconds) Nous avons utilisé ici l'interface locale de la couche [metier]. Nous utilisons maintenant son interface distante dans une seconde classe console : 159/257

160 2 1 En [1], la classe [MainLocal] a été dupliquée dans [MainRemote]. Le code de [MainRemote] est modifié pour utiliser l'interface distante de la couche [metier] : 1. // c'est bon - on peut demander la feuille de salaire à la couche [metier] IMetierRemote metier = null; 3. FeuilleSalaire feuillesalaire = null; 4. try { 5. // on configure le conteneur Open EJB embarqué // instanciation couche métier distante 8. metier = (IMetierRemote) initialcontext.lookup("metierremote"); 9. // calcul de la feuille de salaire 10. feuillesalaire = metier.calculerfeuillesalaire(args[0], nbheurestravaillées, nbjourstravaillés); 11. } catch (PamException ex) { } catch (Exception ex) { } Les modifications sont faites aux lignes 2 et 8. Le projet est configuré [2] pour exécuter la classe [MainRemote]. Son exécution donne les mêmes résultats que précédemment. 5.3 Conclusion Nous avons montré comment porter une architecture Spring / Hibernate vers une architecture OpenEJB / EclipseLink. L'architecture Spring / Hibernate [ui] [metier] [DAO] Objets image de la BD Interface [JPA] Implémentation [Hibernate] [JDBC] Interface [JPA] Implémentation [EclipseLink] [JDBC] Spring L'architecture OpenEJB / EclipseLink [ui] [metier] [DAO] Objets image de la BD OpenEjb 160/257

161 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

162 6 Version 3 : Portage de l'application PAM sur un serveur d'applications Glassfish On se propose de placer les EJB des couches [metier] et [DAO] de l'architecture OpenEJB / EclipseLink dans le conteneur d'un serveur d'applications Glassfish. L'implémentation actuelle avec OpenEJB / EclipseLink [ui] [metier] [DAO] Objets image de la BD Interface [JPA] Implémentation [EclipseLink] [JDBC] OpenEjb Ci-dessus, la couche [ui] utilise l'interface distante de la couche [metier]. Nous avons testé deux contextes d'exécution : local et distant. Dans ce dernier mode, la couche [ui] était cliente de la couche [metier], couche implémentée par des EJB. Pour fonctionner en mode client / serveur, dans lequel le client et le serveur s'exécutent dans deux JVM différentes, nous allons placer les couches [metier, DAO, JPA] sur le serveur Java EE Glassfish. Ce serveur est livré avec Netbeans. L'implémentation à construire avec le serveur Glassfish [ui] C Jvm1 - Java SE [metier] [DAO] [JPA / EclipseLink] [JDBC] Jvm2 Java EE - serveur Glassfish v3 la couche [ui] s'exécutera dans un environnement Java SE (Standard Edition) les couches [metier, DAO, JPA] s'exécuteront dans un environnement Java EE (Enterprise Edition) sur un serveur Glassfish v3 le client communiquera avec le serveur via un réseau tcp-ip. Les échanges réseau sont transparents pour le développeur, si ce n'est qu'il doit être quand même conscient que le client et le serveur s'échangent des objets sérialisés pour communiquer et non des références d'objets. Le protocole réseau utilisé pour ces échanges s'appelle RMI (Remote Method Invocation), un protocole utilisable uniquement entre deux applications Java. l'implémentation JPA utilisée sur le serveur Glassfish sera EclipseLink. La partie serveur de l'application client / serveur PAM L'architecture de l'application Nous étudions ici la partie serveur qui sera hébergée par le conteneur EJB3 du serveur Glassfish : 162/257

163 Conteneur Ejb3 Client Java SE Jpa / Eclipselink Données 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 Le projet Netbeans Commençons par créer un nouveau projet Netbeans : en [1], nouveau projet en [2], choisir la catégorie Maven et en [3] le type EJB Module. Il s'agit en effet de construire un projet qui sera hébergé et exécuté par un conteneur EJB, celui du serveur Glassfish b 4a 7 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 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

164 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. [ui] C [metier] [DAO] [JPA / Eclipselink] [JDBC] SGBD BD Java EE - serveur Glassfish Java SE 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] : 1 Ceci fait, on peut passer à la création de la ressource JDBC utilisée par le module EJB : 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

165 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 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

166 1. <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" " 3. <resources> 4. <jdbc-resource enabled="true" jndi-name="jdbc/dbpam_eclipselink" object-type="user" poolname="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" connectionvalidation-method="auto-commit" datasourceclassname="com.mysql.jdbc.jdbcoptional.mysqldatasource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-levelguaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-timein-millis="60000" name="dbpameclipselinkconnectionpool" non-transactionalconnections="false" pool-resize-quantity="2" res-type="javax.sql.datasource" statementtimeout-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> 1 </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 : 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 en [5], donner un nom à l'unité de persistance 166/257

167 en [6], plusieurs implémentations JPA sont proposées. On choisira ici [EclipseLink]. D'autres implémentations sont utilisables à condition de mettre les bibliothèques qui les implémentent avec celles du serveur Glassfish. dans la liste déroulante [7], choisir la source de données JDBC [jdbc/dbpam_eclipselink] qui vient d'être créée. en [8], indiquer que les transactions sont gérées par le conteneur EJB en [9], indiquer qu'aucune opération ne doit être faite sur la source de données lors du déploiement du module EJB sur le serveur. En effet, le module EJB va utiliser une base [dbpam_eclipselink] déjà créée. à la fin de l'assistant, un fichier [persistence.xml] a été créé [10]. Son contenu est le suivant : 1. <?xml version="1.0" encoding="utf-8"?> <persistence version="0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <persistence-unit name="mv-pam-ejb-metier-dao-eclipselinkpu" transaction-type="jta"> 4. <jta-data-source>jdbc/dbpam_eclipselink</jta-data-source> 5. <exclude-unlisted-classes>false</exclude-unlisted-classes> 6. <properties/> 7. </persistence-unit> 8. </persistence> ligne 3 : le nom de l'unité de persistance [ mv-pam-ejb-metier-dao-eclipselinkpu ] et le type de transactions (JTA pour un conteneur EJB) ligne 5 : le nom JNDI de la source de données utilisée par la couche de persistance : jdbc/dbpam_eclipselink ligne 6 : les entités JPA ne sont pas précisées. Elles seront cherchées dans le Classpath du module EJB. le nom de l'implémentation JPA (Hibernate, EclipseLink,...) utilisée n'est pas indiquée. Dans ce cas, Glassfish v3 utilise par défaut EclipseLink Insertion des couches [JPA, DAO, metier] Maintenant que le fichier [persistence.xml] a été défini, nous pouvons passer à l'insertion dans le projet des couches [metier, dao, JPA] de l'application d'entreprise [pam] : [ui] [metier] [DAO] [JPA / EclipseLink] [JDBC] SGBD BD Java EE - serveur Glassfish Java SE 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] Configuration du serveur Glassfish Il nous reste à configurer le serveur Glassfish sur deux points : 167/257

168 la couche JPA est implémentée par EclipseLink. Il faut nous assurer que le serveur Glassfish a les bibliothèques de cette implémentation JPA. la source de données est une base MySQL. Il faut nous assurer que le serveur Glassfish a le pilote JDBC de ce SGBD. On peut découvrir l'absence de ces bibliothèques lors du déploiement du module EJB. Voici une façon de procéder parmi d'autres pour ajouter des bibliothèques manquantes au serveur Glassfish : en [1], visualiser les propriétés du serveur Glassfish en [2], noter le dossier des domaines du serveur. Nous le notons par la suite <domains> dans le dossier <domains>\domain1\lib, mettre les bibliothèques manquantes. Dans l'exemple, les bibliothèques d'hibernate (lib / hibernate-tools) et le pilote JDBC de MySQL (lib / divers) ont été rajoutés. Par défaut, le serveur Glassfish a les bibliothèques d'eclipselink. On ne rajoutera donc que le pilote JDBC de MySQL en [1], dans l'onglet [Services], nous lançons le serveur Glassfish v3 en [2], il est actif Déploiement du module EJB Nous déployons maintenant le module EJB sur le serveur Glassfish : 168/257

169 en [1], le module EJB est déployé en [2], l'arborescence du serveur Glassfish est rafraîchie en [3], après déploiement, le module EJB apparaît dans la branche [Applications] du serveur Glassfish en [4], la ressource JDBC [jdbc / dbpam_eclipselink] a été créée sur le serveur Glassfish. On rappelle que nous l'avions définie page 164. Lors du déploiement, le serveur Glassfish logue dans la console des informations intéressantes : 1. dao INFO: Portable JNDI names for EJB IndemniteDao : [java:global/pam-serveur-metier-dao-jpaeclipselink/indemnitedao!dao.iindemnitedaolocal, java:global/pam-serveur-metier-dao-jpaeclipselink/indemnitedao!dao.iindemnitedaoremote] 4. INFO: Glassfish-specific (Non-portable) JNDI names for EJB IndemniteDao : [DAO.IIndemniteDaoRemote#DAO.IIndemniteDaoRemote, dao.iindemnitedaoremote] INFO: Portable JNDI names for EJB CotisationDao : [java:global/pam-serveur-metier-dao-jpaeclipselink/cotisationdao!dao.icotisationdaolocal, java:global/pam-serveur-metier-dao-jpaeclipselink/cotisationdao!dao.icotisationdaoremote] 7. INFO: Glassfish-specific (Non-portable) JNDI names for EJB CotisationDao : [DAO.ICotisationDaoRemote, dao.icotisationdaoremote#dao.icotisationdaoremote] 8. INFO: Portable JNDI names for EJB Metier : [java:global/pam-serveur-metier-dao-jpaeclipselink/metier!metier.imetierremote, java:global/pam-serveur-metier-dao-jpaeclipselink/metier!metier.imetierlocal] 9. INFO: Glassfish-specific (Non-portable) JNDI names for EJB Metier : [metier.imetierremote#metier.imetierremote, metier.imetierremote] INFO: Portable JNDI names for EJB EmployeDao : [java:global/pam-serveur-metier-dao-jpaeclipselink/employedao!dao.iemployedaolocal, java:global/pam-serveur-metier-dao-jpaeclipselink/employedao!dao.iemployedaoremote] 1 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 milliseconds. On notera aux lignes 3, 6, 8 et 11 les noms portables JNDI des EJB déployés. Java EE 6 a introduit la notion de nom JNDI portable. Cela dénote un nom JNDI reconnu par tous les serveurs Java EE 6. Avec Java EE 5, les noms JNDI sont spécifiques au serveur utilisé. 4, 7, 9, 12 : les noms JNDI des EJB déployés sous une forme spécifique à Glassfish v3. Ces noms seront utiles à l'application console que nous allons écrire pour utiliser le module EJB déployé. 6.2 Client console - version 1 169/257

170 Maintenant que nous avons déployé la partie serveur de notre application client / serveur, nous en venons à étudier la partie client [1] : [ui] [metier] [DAO] [JDBC] [JPA / Toplink] 1 SGBD BD Java EE - serveur Glassfish Java SE Nous créons un nouveau projet Maven de type [Java Application] nommé [mv-pam-client-ejb-metier-dao-eclipselink] : en [1], le projet du client ; en [2], les dépendances du projet ; en [3], le projet a une dépendance sur le projet précédent. Pour construire la dépendance [3], on procède de la façon suivante : en [4], on ajoute une dépendance ; en [5], on sélectionne l'onglet [Open Projects] ; en [6], on désigne le projet précédent. Il faut que celui-ci soit chargé dans Netbeans pour apparaître. Le fichier [pom.xml] est le suivant : 1. <project xmlns=" xmlns:xsi=" xsi:schemalocation=" <modelversion>4.0.0</modelversion> <groupid>istia.st</groupid> 6. <artifactid>mv-pam-client-ejb-metier-dao-eclipselink</artifactid> 7. <version>1.0-snapshot</version> 170/257

171 8. <packaging>jar</packaging> <name>mv-pam-client-ejb-metier-dao-eclipselink</name> 11. <url> 1 <repositories> 13. <repository> 14. <url> 15. <id>eclipselink</id> 16. <layout>default</layout> 17. <name>repository for library Library[eclipselink]</name> 18. </repository> 19. <repository> 20. <url> 21. <id>swing-layout</id> 2 <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> <dependencies> 31. <dependency> 3 <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> 4 <dependency> 43. <groupid>org.swinglabs</groupid> 44. <artifactid>swing-layout</artifactid> 45. <version>1.0.3</version> 46. </dependency> 47. </dependencies> 48. </project> lignes : la dépendance sur la bibliothèque [gf-client] qui permet à un client Glassfish de communiquer avec un serveur distant, lignes : la dépendance sur le projet Maven du module EJB. Nous voulons récupérer ici les définitions des entités JPA et celles des différentes interfaces ainsi que celle de la classe d'exception [PamException], A partir du projet [mv-pam-openejb-eclipselink], nous copions la classe [MainRemote] : La classe [MainRemote] doit obtenir une référence sur l'ejb de la couche [metier]. Le code de la classe [MainRemote] évolue de la façon suivante : 171/257

172 1. // c'est bon - on peut demander la feuille de salaire 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-ejbmetier-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) { 1 System.err.println("L'erreur suivante s'est produite : " ex.getmessage()); 14. return; 15. } catch (Exception ex) { 16. System.err.println("L'erreur suivante s'est produite : " ex.tostring()); 18. return; 19. } ligne 6 : initialisation du contexte JNDI du serveur Glassfish. ligne 8 : on demande à ce contexte JNDI une référence sur l'interface distante de la couche [metier]. D'après les logs de Glassfish, on sait que l'interface distante de la couche [metier] a deux noms possibles : 1. Infos: EJB5181:Portable JNDI names for EJB Metier: [java:global/istia.st_mv-pam-ejb-metierdao-eclipselink_ejb_1.0-snapshot/metier!metier.imetierlocal, java:global/istia.st_mv-pamejb-metier-dao-eclipselink_ejb_1.0-snapshot/metier!metier.imetierremote] Infos: EJB5182:Glassfish-specific (Non-portable) JNDI names for EJB Metier: [metier.imetierremote#metier.imetierremote, metier.imetierremote] Ligne 1, le nom JNDI utilisable avec tout serveur d'applications JAVA EE 6. Ligne 2, le nom JNDI spécifique à Glassfish. Dans le code, ligne 9, nous utilisons le nom JNDI portable. le reste du code ne change pas 1 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: Valeurs saisies : 3. N de sécurité sociale de l'employé : Nombre d'heures travaillées : Nombre de jours travaillés : Informations Employé : 8. Nom : Jouveinal 9. Prénom : Marie 10. Adresse : 5 rue des oiseaux 11. Ville : St Corentin 172/257

173 1 Code Postal : Indice : Informations Cotisations : 16. CSGRDS : 3.49 % 17. CSGD : 6.15 % 18. Retraite : 7.88 % 19. Sécurité sociale : 9.39 % Informations Indemnités : 2 Salaire horaire : 1 euro 23. Entretien/jour : 1 euro 24. Repas/jour : 3.1 euro 25. Congés Payés : 15.0 % Informations Salaire : 28. Salaire de base : 3625 euro 29. Cotisations sociales : euro 30. Indemnités d'entretien : 40 euro 31. Indemnités de repas : 60 euro 3 Salaire net : euro 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: L'erreur suivante s'est produite : L'employé de n [ x] est introuvable 3. BUILD SUCCESSFUL (total time: 2 seconds) Note : si vous rencontrez une exception de type [unmarshalling / marshalling exception] cela signifie que le serveur a voulu envoyer au client un objet qui n'a pas pu être sérialisé. Vérifiez les points suivants : les entités échangées entre le client et le serveur [Employe, Cotisation, Indemnite, FeuilleSalaire, ElementsSalaire, PamException] doivent implémenter l'interface [Serializable] ; l'entité JPA [Indemnite] ne doit pas voir l'annotation [@OneToMany]. Si elle est présente, enlevez-la ainsi que le champ qu'elle annote ; si les points précédents sont remplis, alors regardez les logs de Glassfish. La cause la plus probable est qu'une exception non sérialisable s'est produite côté serveur. Lorsque le serveur veut l'envoyer au client, une exception de sérialisation se produit alors. 6.3 Client console - version 2 Dans les versions précédentes, l'environnement JNDI du serveur Glassfish était configuré à partir d'un fichier [jndi.properties] trouvé quelque part dans les archives du projet. Son contenu par défaut est le suivant : # accès JNDI à Sun Application Server java.naming.factory.initial=com.sun.enterprise.naming.serialinitcontextfactory java.naming.factory.url.pkgs=com.sun.enterprise.naming # Required to add a javax.naming.spi.statefactory for CosNaming that # supports dynamic RMI-IIOP. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.jndistatefactoryimpl org.omg.corba.orbinitialhost=localhost org.omg.corba.orbinitialport=3700 Les lignes 7 et 8 désignent la machine du service JNDI et le port d'écoute de celui-ci. Ce fichier ne permet pas d'interroger un serveur JNDI autre que localhost ou travaillant sur un port autre que le port 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

174 2 1 en [1], le nouveau projet en [2], le fichier [spring-config-client.xml] de configuration de Spring. Son contenu est le suivant : Le fichier de configuration de Spring est le suivant : 1. <?xml version="1.0" encoding="utf-8"?> <beans xmlns=" xmlns:xsi=" 3. xmlns:tx=" 4. xmlns:jee=" 5. xsi:schemalocation=" <!-- métier --> 14. <jee:jndi-lookup id="metier" jndi-name="java:global/istia.st_mv-pam-ejb-metier-daoeclipselink_ejb_1.0-snapshot/metier!metier.imetierremote"> 15. <jee:environment> 16. java.naming.factory.initial=com.sun.enterprise.naming.serialinitcontextfactory 17. java.naming.factory.url.pkgs=com.sun.enterprise.naming 18. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.jndistatefactoryimpl 19. org.omg.corba.orbinitialhost=localhost 20. org.omg.corba.orbinitialport= </jee:environment> 2 </jee:jndi-lookup> 23. </beans> Nous utilisons ici une balise <jee> (ligne 14) apparue avec Spring 0. L'usage de cette balise nécessite la définition du schéma auquel elle appartient, lignes 4, 10 et 11. ligne 14 : la balise <jee:jndi-lookup> permet d'obtenir la référence d'un objet auprès d'un service JNDI. Ici, on associe le bean nommé " metier " à la ressource JNDI associée à l'ejb [Metier]. Le nom JNDI utilisé ici est le nom portable (Java EE 6) de l'ejb. le contenu du fichier [jndi.properties] devient le contenu de la balise <jee:environment> (ligne 15) qui sert à définir les paramètres de connexion au service JNDI. La classe principale [MainRemote] évolue de la façon suivante : // 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-configclient.xml"); 8. metier = (IMetierRemote) ctx.getbean("metier"); 9. // calcul de la feuille de salaire 174/257

175 10. feuillesalaire = metier.calculerfeuillesalaire(args[0], nbheurestravaillées, nbjourstravaillés); 11. } catch (PamException ex) { 1 System.err.println("L'erreur suivante s'est produite : " ex.getmessage()); 14. return; 15. } catch (Exception ex) { 16. System.err.println("L'erreur suivante s'est produite : " ex.tostring()); 18. return; 19. } 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 Le lecteur est invité à tester cette nouvelle version. 6.4 Le client Swing Nous construisons maintenant le client swing de notre application client / serveur EJB. 1 Le fichier [pom.xml] doit avoir la dépendance nécessaire aux applications swing : 1. <dependency> <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 : [ui] swing [metier] [DAO] Objets image de la BD 7 Interface [JPA] Implémentation [Hibernate] [JDBC] Spring Maintenant cette classe doit devenir le client distant d'un EJB déployé sur le serveur Glassfish. 175/257

176 [ui] swing Jvm1 - Java SE [metier] [DAO] [JPA / EclipseLink] [JDBC] Jvm2 Java EE - serveur Glassfish v3 Travail pratique : en suivant l'exemple du client console [ui.console.mainremote] du projet, modifier la façon utilisée par la méthode [domyinit] (cf page 125) de la classe [PamJFrame] pour acquérir une référence sur la couche [metier] qui est maintenant distante. 176/257

177 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 : [ui] C RMI 1 S 2 [metier] [DAO] [JPA / EclipseLink] [JDBC] SGBD BD Java EE - serveur Glassfish Java SE Ci-dessus, une couche de communication [C, RMI, S] permettait une communication transparente entre le client [ui] et la couche distante [metier]. Nous allons utiliser une architecture analogue, où la couche de communication [C, RMI, S] sera remplacée par une couche [C, HTTP / SOAP, S] : [ui] C S 2 1 Java SE HTTP / SOAP [metier] [DAO] [JPA / EclipseLink] [JDBC] SGBD BD Java EE - serveur Glassfish Le protocole HTTP / SOAP a l'avantage sur le protocole RMI / EJB précédent d'être multi-plateformes. Ainsi le service web peut être écrit en Java et déployé sur le serveur Glassfish alors que le client lui, pourrait être un client.net ou PHP. Nous allons développer cette architecture selon trois modes différents : le service web sera assuré par l'ejb [Metier] le service web sera assuré par une application web utilisant l'ejb [Metier] le service web sera assuré par une application web utilisant Spring Un service web peut être implémenté de diverses façons au sein d'un serveur Java EE : par une classe qui s'exécute dans un conteneur web Client du service web tcp-ip Conteneur web Conteneur Ejb3 Jpa Données serveur Java EE par un EJB qui s'exécute dans un conteneur EJB Client du service web Conteneur Ejb3 Jpa Données serveur Java EE Nous commençons par cette dernière architecture. 177/257

178 Service web implémenté par un EJB La partie serveur Commençons par créer un nouveau projet maven copie du projet EJB [mv-pam-ejb-metier-dao-jpa-eclipselink] : Dans l'architecture suivante : [ui] C 2 1 Java SE S HTTP / SOAP [metier] [DAO] [JPA / EclipseLink] [JDBC] SGBD BD Java EE - serveur Glassfish la couche [metier] va être le service web contacté par la couche [ui]. Cette classe n'a pas besoin d'implémenter une interface. Ce sont des annotations qui transforment un POJO (Plain Ordinary Java Object) en service web. La classe [Metier] qui implémente la couche [metier] ci-dessus, est transformée de la façon suivante : 1. package metier; public class Metier implements IMetierLocal,IMetierRemote { // références sur les couches [DAO] 11. private ICotisationDaoLocal cotisationdao = null; 13. private IEmployeDaoLocal employedao=null; 15. private IIndemniteDaoLocal indemnitedao=null; // obtenir la feuille de salaire 20. public FeuilleSalaire calculerfeuillesalaire(string SS, } // liste des employés 26. public List<Employe> findallemployes() { } 29. // important - pas de getters et setters pour les EJB qui deviennent des services web 178/257

179 30. } ligne 4, fait de la classe [Metier] un service web. Un service web expose des méthodes à ses clients. Celles-ci doivent être annotées par 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. 1 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é : en [1], dans le projet courant, nous testons le service web [Metier] le service web est accessible via différentes URL. L'URL [2] permet de tester le service web 179/257

180 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

181 7.1.2 La partie cliente [ui] C 1 S 2 [metier] [DAO] [JPA / EclipseLink] [JDBC] SGBD BD Java EE - serveur Glassfish Java SE RMI Le projet Netbeans du client console Nous créons maintenant un projet Java de type [Java Application] pour la partie client de l'application. Il n'a pas été possible (juin 2012) de créer un projet Maven pour ce client. Une erreur survient, semble connue mais reste non résolue. Une fois le projet créé, nous indiquons qu'il sera client du service web que nous venons de déployer sur le serveur Glassfish : en [2], nous sélectionnons le nouveau projet et activons le bouton [New File] en [3], nous indiquons que nous voulons créer un client de service web avec [4], nous allons désigner le projet Netbeans du service web dans la fenêtre [5], sont listés tous les projets ayant une branche [Web Services], ici uniquement le projet [mv-pam-wsmetier-dao-eclipselink]. un projet peut déployer plusieurs services web. En [6], nous désignons le service web auquel on veut se connecter. 181/257

182 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. [ui] 3 S 2 1 Java SE C HTTP / SOAP [metier] [DAO] 4 [JPA / Toplink] [JDBC] SGBD BD Java EE - serveur Glassfish la couche cliente [C] [1] qui va être générée est constituée d'un ensemble de classes Java qui vont être mises dans un même paquetage. Le nom de celui-ci est fixé en [8]. une fois l'assistant de création du client du service web terminé avec le bouton [Finish], la couche [C] ci-dessus est créée. Ceci est reflété par un certain nombre de changements dans le projet : en [10] ci-dessus, apparaît une arborescence [Generated Sources] qui contient les classes de la couche [C] qui permettent au client [3] de communiquer avec le service web. Cette couche permet au client [3] de communiquer avec la couche [metier] [4] comme si elle était locale et non distante. en [11], apparaît une arborescence [Web Service References] qui liste les services web pour lesquels une couche cliente a été générée. On notera que dans la couche [C] [10] générée, nous retrouvons des classes qui ont été déployées côté serveur : Indemnite, Cotisation, Employe, FeuilleSalaire, ElementsSalaire, Metier. Metier est le service web et les autres classes sont des classes nécessaires à ce service. On pourra avoir la curiosité de consulter leur code. On verra que la définition des classes qui, instanciées, représentent des objets manipulés par le service, consiste en la définition des champs de la classe et de leurs accesseurs ainsi qu'à l'ajout d'annotations permettant la sérialisation de la classe en flux XML. La classe Metier est devenue une interface avec dedans les deux méthodes qui ont été 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 : = "MetierPort") public Metier getmetierport() { return super.getport(new QName(" "MetierPort"), Metier.class); La méthode getmetierport de la ligne 2 permet d'obtenir une référence sur le service web Metier distant. 182/257

183 7.1.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-clientmetier-dao-jpa-eclipselink] qui était un client d'un serveur EJB, dans le nouveau projet en [1], la classe du client du service web. La classe [MainRemote] présente des erreurs. Pour les corriger, on commencera par supprimer toutes les instructions [import] existantes dans la classe et on les régènerera par l'option [Fix Imports]. En effet, certaines des classes utilisées par la classe [MainRemote] font désormais partie du package [client] généré. en [3], le morceau de code où la couche [metier] est instanciée [3]. Elle l'est avec du code JNDI pour obtenir une référence sur un EJB distant. Nous faisons évoluer le code de la façon suivante : le code JNDI est supprimé la classe [PamException] n'existant pas côté client, nous supprimons le catch associé pour ne garder que le catch sur la classe mère [Exception]. 4 5 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 /257

184 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 : // 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 1 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 La ligne 7 récupère une référence sur le service web Metier. Ceci fait, le code de la classe ne change pas, si ce n'est quand même qu'en ligne 10, ce n'est pas l'exception de type [Exception] qui est gérée mais le type plus général Throwable, la classe parent de la classe Exception. S'il y a exception, nous affichons toutes les causes imbriquées de celle-ci jusqu'à la cause originelle. Nous sommes prêts pour les tests : s'assurer que le SGBD MySQL5 est lancé, que la base dbpam_eclipselink est créée et initialisée s'assurer que le service web est déployé sur le serveur Glassfish construire le client (Clean and Build) configurer l'exécution du client exécuter le client Les résultats dans la console sont les suivants : Valeurs saisies : 3. N de sécurité sociale de l'employé : Nombre d'heures travaillées : Nombre de jours travaillés : Informations Employé : 8. Nom : Jouveinal 9. Prénom : Marie 10. Adresse : 5 rue des oiseaux /257

185 Avec la configuration suivante : on obtient les résultats suivants : Chaîne des exceptions javax.xml.ws.soap.soapfaultexception:l'employé de n [xx] est introuvable com.sun.xml.internal.ws.developer.serversideexception:l'employé de n [xx] est introuvable Java Result: 1 On notera qu'alors que le service web [Metier] envoie une exception de type [PamException] l'exception reçue par le client est de type [SOAPFaultException]. Même dans la chaîne des exceptions, on ne voit pas apparaître le type [PamException] Le client swing du service web Metier Travail à faire : porter le client swing du projet [mv-pam-client-ejb-metier-dao-jpa-eclipselink] dans le nouveau projet afin que lui aussi soit client du service web déployé sur le serveur Glassfish. 7.2 Service web implémenté par une application web Nous nous plaçons maintenant dans le cadre de l'architecture suivante : Client du service web tcp-ip Conteneur web Conteneur EJB3 Jpa Données 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 EJB La partie serveur Nous créons une application web : en [1], nous créons un nouveau projet en [2], ce projet est de type [Web Application] en [3], nous lui donnons le nom [mv-pam-ws-ejb-metier-dao-eclipselink] 185/257

186 6 4 en [4], nous choisissons la version Java EE 6 en [6], le projet créé Sur le schéma ci-dessous, l'application web créée va s'exécuter dans le conteneur web. Elle va utiliser l'ejb [Metier] qui lui, sera déployé dans le conteneur EJB du serveur. Client du service web tcp-ip Conteneur web Conteneur EJB3 Jpa Données serveur Java EE Pour que l'application web créée ait accès aux classes associées à l'ejb [Metier], nous ajoutons aux bibliothèques de l'application web [mv-pam-ws-ejb-metier-dao-eclipselink], la dépendance du serveur EJB [mv-pam-ejb-metier-dao-eclipselink] déjà étudié en [1], on ajoute un projet aux dépendances du projet web, en [2], on sélectionne le projet [mv-pam-ejb-metier-dao-eclipselink], en [4], la portée de la dépendance est provided, c'est à dire qu'elle sera fournie par l'environnement d'exécution, en [5], la dépendance a été ajoutée. Pour créer le même service web que précédemment, il nous faut : créer une classe avec deux méthodes calculerfeuillesalaire et findallemployes Nous créons une classe [PamWsEjbMetier] dans un package [pam.ws] : 186/257

187 La classe [PamWsEjbMetier] est la suivante : 1. package pam.ws; 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; public class PamWsEjbMetier implements IMetier{ private IMetierLocal metier; public FeuilleSalaire calculerfeuillesalaire(string SS, double nbheurestravaillées, int nbjourstravaillés) { 20. return metier.calculerfeuillesalaire(ss, nbheurestravaillées, nbjourstravaillés); 21. } public List<Employe> findallemployes() { 25. return metier.findallemployes(); 26. } } 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 : la méthode calculerfeuillesalaire est exposée comme méthode du service web lignes : la méthode findallemployes est exposée comme méthode du service web lignes : 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 : Pour déployer le service web sur le serveur Glassfish, il nous faut à la fois déployer : le module web dans le conteneur web du serveur le module EJB dans le conteneur EJB du serveur Pour cela, nous avons besoin de créer une application de type [Enterprise Application] qui va déployer les deux modules en même temps. Pour ce faire, il faut que les deux projets soient chargés dans Netbeans [2]. 187/257

188 Ceci fait, nous créons un nouveau projet [3]. 5 4 en [4], nous choisissons un projet de type [Enterprise Application]. en [5], nous donnons un nom au projet 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-pamwebapp]. Nous ne nous en occuperons pas. en [8], nous ajoutons des dépendances au projet d'entreprise 188/257

189 10 9 en [9], on ajoute le projet web de type war, en [10], on ajoute le projet EJB de type ejb, 11 en [11], le projet d'entreprise avec ses deux dépendances. Nous construisons le projet d'entreprise par un Clean and Build. Nous sommes quasiment prêts à le déployer sur le serveur Glassfish. Auparavant il peut être nécessaire de décharger les applications déjà chargées sur le serveur afin d'éviter d'éventuels conflits de noms d'ejb [11] : Le serveur MySQL doit être lancé et la base [dbpam_eclipselink] disponible et remplie. Ceci fait, l'application d'entreprise peut être déployée [12]. En [13], on peut voir qu'elle a bien été déployée sur le serveur Glassfish. Nous pouvons tester le service web qui vient d'être déployé : 189/257

190 en [1], nous demandons à tester le service web [PamWsEjbMetier] en [2], la page de test. Nous laissons au lecteur le soin de conduire les tests. La partie cliente Travail à faire : en suivant la démarche décrite au paragraphe 7.1.1, page 181, construire un client console du service web précédent. 7.3 Service web implémenté avec Spring et Tomcat Nous nous plaçons maintenant dans le cadre de l'architecture suivante : Client du service web tcp-ip Conteneur web JPA Données serveur Tomcat Le service web est assuré par une application web exécutée au sein du conteneur web du serveur Tomcat. L'architecture de l'application sera la suivante : [web] [metier] [DAO] Interface [JPA] Implémentation [Hibernate] [JDBC] Spring 7 Nous nous appuierons sur le projet [mv-pam-spring-hibernate] construit au paragraphe 4.11, page 113 : 190/257

191 7.3.1 La partie serveur Nous créons une application Maven de type web nommée [mv-pam-ws-spring-tomcat] [1]: 2 1 Nous modifions le fichier [pom.xml] pour y inclure les dépendances [2] suivantes : 1. <dependencies> <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>12</version> 1 </dependency> 13. <dependency> 14. <groupid>org.apache.cxf</groupid> 15. <artifactid>cxf-rt-transports-http</artifactid> 16. <version>12</version> 17. </dependency> 18. </dependencies> lignes 3-7 : la dépendance sur le projet [mv-pam-spring-hibernate], lignes 8-17 : les dépendances sur le framework Apache CXF [ Celui-ci facilite la création de services web. Ce fichier [pom.xml] amène de nombreuses dépendances [2]. Revenons à l'architecture de l'application : 191/257

192 [web] [metier] [DAO] Interface [JPA] 7 Implémentation [Hibernate] [JDBC] Spring Les appels au service web que nous allons construire sont gérés par une servlet du framework CXF. Cela se traduit dans le fichier [WEB-INF / web.xml] de la façon suivante : 1. <?xml version="1.0" encoding="utf-8"?> <web-app version="5" xmlns=" xmlns:xsi=" xsi:schemalocation=" <display-name>mv-pam-ws-spring-tomcat</display-name> 4. <listener> 5. <listener-class>org.springframework.web.context.contextloaderlistener</listener-class> 6. </listener> 7. <!-- Configuration de CXF --> 8. <servlet> 9. <servlet-name>cxfservlet</servlet-name> 10. <servlet-class>org.apache.cxf.transport.servlet.cxfservlet</servlet-class> 11. <load-on-startup>1</load-on-startup> 1 </servlet> 13. <servlet-mapping> 14. <servlet-name>cxfservlet</servlet-name> 15. <url-pattern>/ws/*</url-pattern> 16. </servlet-mapping> 17. <session-config> 18. <session-timeout> </session-timeout> 21. </session-config> 2 <welcome-file-list> 23. <welcome-file>index.jsp</welcome-file> 24. </welcome-file-list> 25. </web-app> le framework CXF a une dépendance sur Spring. Lignes 4-6 : un listener est déclaré. La classe correspondante va être chargée en même temps que l'application web. Elle va exploiter le fichier de configuration de Spring [WEB-INF / applicationcontext.xml] : lignes 8-12 : la servlet CXF qui va gérer les appels au service web que nous allons créer, lignes : 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

193 L'interface [IWsMetier] sera la suivante : package pam.ws; 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. import javax.jws.webservice; import public interface IWsMetier extends IMetier{ } La classe d'implémentation de cette interface est la suivante : 1. package pam.ws; 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; public class PamWsMetier implements IWsMetier { // couche métier 14. private IMetier metier; // constructeur 17. public PamWsMetier(){ } public FeuilleSalaire calculerfeuillesalaire(string SS, double nbheurestravaillees, int nbjourstravailles) { 23. return metier.calculerfeuillesalaire(ss, nbheurestravaillees, nbjourstravailles); 24. } public List<Employe> findallemployes() { 28. return metier.findallemployes(); 29. } // getters et setters public void setmetier(imetier metier) { 34. this.metier = metier; 35. } /257

194 37. } ligne 11 : la classe [PamWsMetier] implémente l'interface définie précédemment, ligne 10 : définit la classe comme un service web, ligne 14 : la couche [métier] sera injectée par Spring, lignes 21, 26 : fait d'une méthode, une méthode exposée par le service web, lignes 23, 28 : les méthodes sont implémentées à l'aide de la couche [métier]. Il nous reste à définir le contenu du fichier de configuration de Spring [applicationcontext.xml] : Son contenu est le suivant : 1. <?xml version="1.0" encoding="utf-8"?> <beans xmlns=" xmlns:xsi=" 3. xmlns:tx=" 4. xmlns:jaxws=" 5. xsi:schemalocation=" <!-- Apache CXF --> 13. <import resource="classpath:meta-inf/cxf/cxf.xml" /> 14. <import resource="classpath:meta-inf/cxf/cxf-extension-soap.xml" /> 15. <import resource="classpath:meta-inf/cxf/cxf-servlet.xml" /> <!-- couches basses --> 18. <import resource="classpath:spring-config-metier-dao.xml" /> <!-- service web --> 21. <bean id="wsmetier" class="pam.ws.pamwsmetier"> 2 <property name="metier" ref="metier"/> 23. </bean> 24. <jaxws:endpoint id="wsmetier" 25. implementor="#wsmetier" 26. address="/metier"> 27. </jaxws:endpoint> </beans> lignes : 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 : définissent le bean du service web avec sa dépendance sur la couche [métier] (ligne 22), lignes : 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

195 Notre projet est prêt à être exécuté. Nous l'exécutons (Run) et demandons l'url [ 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 : 2 1 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 La partie cliente Travail à faire : en suivant la démarche décrite au paragraphe 7.1.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

196 3 On mettra en [3], l'url notée précédemment en [2]. 196/257

197 8 Introduction à Java Server Faces On lira le document Introduction à Java Server Faces, Primefaces et Primefaces [ On suivra le tutoriel sur Java Server Faces (JSF). mobile à l'url 197/257

198 9 Version 5 - Application PAM Web / JSF 9.1 Architecture de l'application L'architecture de l'application web PAM sera la suivante : [web / JSF] EJB3 [metier] EJB3 [DAO] Objets image de la BD 7 Implémentation [EclipseLink] Interface [JPA] [JDBC] Serveur Glassfish v3 Dans cette version, le serveur Glassfish hébergera la totalité des couches de l'application : la couche [web] est hébergée par le conteneur de servlets du serveur (1 ci-dessous) les autres couches [metier, DAO, JPA] sont hébergées par le conteneur EJB3 du serveur (2 ci-dessous) Conteneur web [web / jsf] Navigateur HTTP 1 Jpa 3 Conteneur EJB3 [metier, DAO] 2 EclipseLink SGBD serveur Java EE Les éléments [metier, DAO] de l'application s'exécutant dans le conteneur EJB3 ont déjà été écrits dans l'application client / serveur étudiée au paragraphe 6.1, page 162 et dont l'architecture était la suivante : client [ui] Jvm1 - Java SE serveur [DAO] [metier] [JPA / EclipseLink] [JDBC] Jvm2 Java EE - serveur Glassfish Les couches [metier, DAO] s'exécutaient dans le conteneur EJB3 du serveur Glassfish et la couche [ui] dans une application console ou swing sur une autre machine : serveur client Client Java SE Conteneur Ejb3 Jpa / EclipseLink Données serveur Java EE Dans l'architecture de la nouvelle application : 198/257

199 Jpa 3 Conteneur EJB3 [metier, DAO] 2 EclipseLink Conteneur web [web / jsf] Navigateur 1 HTTP SGBD serveur Java EE seule la couche [web / JSF] est à écrire. Les autres couches [metier, DAO, JPA] sont acquises. Dans le document [ref3], il est montré qu'une application web où la couche web est implémentée avec Java Server Faces a une architecture similaire à la suivante : Application web couche [web] 1 Navigateur 4b JSF1 JSF2 JSFn 2b 2a Faces Servlet 3 Gestionnaire d'évts Modèles couche [metier, dao, jpa] Données 2c 4a 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. réponse - le contrôleur C demande à la page JSF choisie de s'afficher. C'est la vue, le V de MVC. La page JSF utilise un modèle M pour initialiser les parties dynamiques de la réponse qu'elle doit envoyer au client. Ce modèle est une classe Java qui peut faire appel à la couche [métier] [4a] pour fournir à la vue V les données dont elle a besoin. Si la demande est faite avec un POST, deux étapes supplémentaires s'insèrent entre la demande et la réponse : 1. demande - le client navigateur fait une demande au contrôleur [Faces Servlet]. traitement - le contrôleur C traite cette demande. Une demande POST est accompagnée de données qu'il faut traiter. Pour ce faire, le contrôleur se fait aider par des gestionnaires d'événements spécifiques à l'application écrite [2a]. Ces gestionnaires peuvent avoir besoin de la couche métier [2b]. Le gestionnaire de l'événement peut être amené à mettre à jour certains modèles M [2c]. Une fois la demande du client traitée, celle-ci peut appeler diverses réponses. Un exemple classique est : une page d'erreurs si la demande n'a pu être traitée correctement une page de confirmation sinon Le gestionnaire d'événement rend au contrôleur [Faces Servlet] un résultat de type chaîne de caractères appelée clé de navigation. 3. navigation - le contrôleur choisit la page JSF (= vue) à envoyer au client. Ce choix se fait à partir de la clé de navigation rendue par le gestionnaire d'événement. 4. réponse - la page JSF choisie va envoyer la réponse au client. Elle utilise son modèle M pour initialiser ses parties dynamiques. Ce modèle peut lui aussi faire appel à la couche [métier] [4a] pour fournir à la page JSF les données dont elle a besoin. Dans un projet JSF : le contrôleur C est la servlet [javax.faces.webapp.facesservlet]. On trouve celle-ci dans la bibliothèque [JSF-api.jar]. les vues V sont implémentées par des pages JSF. les modèles M et les gestionnaires d'événements sont implémentés par des classes Java souvent appelées "backing beans". dans les version JSF 1.x la définition des beans ainsi que les règles de navigation d'une page à l'autre sont définies dans le fichier [faces-config.xml]. On y trouve la liste des vues et les règles de transition de l'une à l'autre. A partir de la version JSF 2, les définitions des beans peuvent se faire à l'aide d'annotations et les transitions entre pages peuvent se faire en " dur " dans le code des beans. 199/257

200 9.2 Fonctionnement de l'application Lorsque l'application est demandée la première fois, on obtient la page suivante : A On remplit alors le formulaire puis on demande le salaire : B On obtient le résultat suivant : 200/257

201 C Cette version calcule un salaire fictif. Il ne faut pas prêter attention au contenu de la page mais à sa mise en forme. Lorsqu'on utilise le bouton [Raz], on revient à la page [A]. Les saisies erronées sont signalées, comme le montre l'exemple suivant : D 9.3 Le projet Netbeans Nous allons construire une première version de l'application où la couche [métier] sera simulée. Nous aurons l'architecture suivante : 201/257

202 Application web couche [web] 1 Faces Servlet Navigateur 4b JSF1 JSF2 JSFn 2b 2a 3 Gestionnaire d'évts couche [metier] simulée 2c Modèles 4a 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] 1 Faces Servlet Navigateur 4b JSF1 JSF2 JSFn 2b 2a 3 Gestionnaire d'évts Modèles couche [metier, dao, jpa] Données 2c 4a Ce sera la version 2 de la version web de notre application PAM. Le projet Netbeans de la version 1 est le projet Maven suivant : en [1], les fichiers de configuration en [2], les pages XHTML et la feuille de style en [3], les classes de la couche [web] en [4], les objets échangés entre la couche [web] et la couche [métier] et la couche [métier] elle-même en [5], le fichier des messages pour l'internationalisation de l'application en [6], les dépendances de l'application 202/257

203 Nous passons en revue certains de ces éléments Les fichiers de configuration Le fichier [web.xml] est celui généré par défaut par Netbeans avec de plus la configuration d'une page d'exception : 1. <?xml version="1.0" encoding="utf-8"?> <web-app version="3.0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <context-param> 4. <param-name>javax.faces.state_saving_method</param-name> 5. <param-value>client</param-value> 6. </context-param> 7. <context-param> 8. <param-name>javax.faces.facelets_skip_comments</param-name> 9. <param-value>true</param-value> 10. </context-param> 11. <context-param> 1 <param-name>javax.faces.project_stage</param-name> 13. <param-value>development</param-value> 14. </context-param> 15. <servlet> 16. <servlet-name>faces Servlet</servlet-name> 17. <servlet-class>javax.faces.webapp.facesservlet</servlet-class> 18. <load-on-startup>1</load-on-startup> 19. </servlet> 20. <servlet-mapping> 21. <servlet-name>faces Servlet</servlet-name> 2 <url-pattern>/faces/*</url-pattern> 23. </servlet-mapping> 24. <session-config> 25. <session-timeout> </session-timeout> 28. </session-config> 29. <welcome-file-list> 30. <welcome-file>faces/index.xhtml</welcome-file> 31. </welcome-file-list> 3 <error-page> 33. <error-code>500</error-code> 34. <location>/faces/exception.xhtml</location> 35. </error-page> 36. <error-page> 37. <exception-type>java.lang.exception</exception-type> 38. <location>/faces/exception.xhtml</location> 39. </error-page> 40. </web-app> ligne 30 : [index.html] est la page d'accueil de l'application lignes : configuration de la page d'exception La page [exception.html] est tirée de [ref3]. Son code est le suivant : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " <html xmlns=" 5. xmlns:h=" 6. xmlns:f=" 203/257

204 <f:view locale="#{changelocale.locale}"> <h:head> <title>jsf</title> <h:outputstylesheet library="css" name="styles.css"/> </h:head> <h:body style="background-image: url('$ {request.contextpath}/resources/images/standard.jpg');"> 13. <h:form id="formulaire"> 14. <h3><h:outputtext value="#{msg['exception.header']}"/></h3> 15. <h:panelgrid columnclasses="col1,col2" columns="2" border="1"> 16. <h:outputtext value="#{msg['exception.httpcode']}"/> 17. <h:outputtext value="#{requestscope['javax.servlet.error.status_code']}"/> 18. <h:outputtext value="#{msg['exception.message']}"/> 19. <h:outputtext value="#{requestscope['javax.servlet.error.exception']}"/> 20. <h:outputtext value="#{msg['exception.requesturi']}"/> 21. <h:outputtext value="#{requestscope['javax.servlet.error.request_uri']}"/> 2 <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 cidessous : Le fichier [faces-config.xml] sera le suivant : 1. <?xml version="1.0" encoding="utf-8"?> <!-- =========== FULL CONFIGURATION FILE ================================== --> 3. <faces-config version="0" 4. xmlns=" 5. xmlns:xsi=" 6. xsi:schemalocation=" <application> 9. <resource-bundle> 10. <base-name> 11. messages 1 </base-name> 13. <var>msg</var> 14. </resource-bundle> 15. <message-bundle>messages</message-bundle> 16. </application> 17. </faces-config> On notera les points suivants : lignes 9-14 : le fichier [messages.properties] sera utilisé pour l'internationalisation des pages. Il sera accessible dans les pages XHTML via la clé msg. 204/257

205 9.3.2 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. La feuille de style Le fichier [styles.css] est le suivant : 1..libelle{ 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. } error{ 1 color: #ff } info{ 16. background-color: #99cc } titreInfos{ 20. background-color: #ffcc } Voici des exemples de code JSF utilisant ces styles : <h:outputtext value="#{msg['form.infos.employé']}" styleclass="titreinfos"/> <h:panelgrid columns="3" rowclasses="libelle,info"> <h:message for="heurestravaillées" styleclass="error"/> Le fichier des messages Le fichier des messages [messages_fr.properties] est le suivant : 1. form.titre=feuille de salaire 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 1 exception.httpcode=code HTTP de l'erreur 205/257

206 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 2 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 3 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 utilisés dans la page [exception.xhtml] La portée des beans Le bean [web.forms.form] aura une portée request : import java.io.serializable; import javax.faces.bean.managedbean; public class Form implements Serializable { Le bean [web.utils.changelocale] aura une portée session : 1. package web.utils; 3. import java.io.serializable; 4. import javax.faces.bean.managedbean; 5. import javax.faces.bean.sessionscoped; public class ChangeLocale implements Serializable{ 10. // la locale des pages 11. private String locale="fr"; public ChangeLocale() { 14. } public String setfrenchlocale(){ 17. locale="fr"; 18. return null; 206/257

207 } } public String setenglishlocale(){ locale="en"; return null; } public String getlocale() { return locale; } public void setlocale(string locale) { this.locale = locale; } La couche [métier] La couche [métier] implémente l'interface IMetierLocal suivante : 1. package metier; 3. import java.util.list; 4. import javax.ejb.local; 5. import jpa.employe; 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 1 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 16 La classe Metier que nous allons utiliser pour tester la couche [web] implémente cette interface de la façon suivante : 1. package metier; public class Metier implements IMetierLocal { // 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; // obtenir la feuille de salaire 1 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. } /257

208 // liste des employés public List<Employe> findallemployes() { if(listemployes==null){ // on crée une liste de deux employés listemployes=new ArrayList<Employe>(); listemployes.add(new Employe(" ","Jouveinal","Marie","5 rue des oiseaux","st Corentin","49203",new Indemnite(2,1,1,3.1,15))); 26. listemployes.add(new Employe(" ","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. } 3 // on rend la liste des employés 33. return listemployes; 34. } 35. } Nous laissons au lecteur le soin de décrypter ce code. On notera la méthode utilisée : afin de ne pas avoir à mettre en place la partie EJB de l'application, nous simulons la couche [métier]. Lorsque la couche [web] sera déclarée correcte, nous pourrons alors la remplacer par la véritable couche [métier]. 9.4 Le formulaire [index.xhtml] et son modèle [Form.java] Nous construisons maintenant la page XHTML du formulaire ainsi que son modèle. Lectures conseillées dans [ref3] : exemple n 3 (mv-jsf2-03) pour la listes des balises utilisables dans un formulaire exemple n 4 (mv-jsf2-04) pour les listes déroulantes remplies par le modèle exemple n 6 (mv-jsf2-06) pour la validation des saisies exemple n 7 (mv-jsf2-07) pour la gestion du bouton [Raz] étape 1 Question : Construire le formulaire [index.xhtml] et son modèle [Form.java] nécessaires pour obtenir la page suivante : Les composants de saisie sont les suivants : n id type JSF modèle 1 comboemployes <h:selectonemenu> String comboemployesvalue 2 heurestravaillees <h:inputtext> List<Employe> getemployes() String heurestravaillées 3 jourstravailles String jourstravaillés <h:inputtext> rôle contient la liste des employés sous la forme "prénom nom". nombre d'heures travaillées - nombre réel nombre de jours travaillés - nombre entier 208/257

209 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 é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é : 1 2 Le bouton [Salaire] sera connecté au gestionnaire d'événement calculersalaire du modèle. Cette méthode utilisera la méthode calculerfeuillesalaire de la couche [métier]. Cette feuille de salaire sera faite pour l'employé sélectionné en [1]. Dans le modèle, la feuille de salaire sera représentée par le champ privé suivant : private FeuilleSalaire feuillesalaire; disposant des méthodes get et set. Pour obtenir les informations contenues dans cet objet, on pourra écrire dans la page JSF, des expressions comme la suivante : <h:outputtext value="#{form.feuillesalaire.employe.nom}"/> L'expression de l'attribut value sera évaluée comme suit : 209/257

210 [form].getfeuillesalaire().getemploye().getnom() où [form] représente une instance de la classe [Form.java]. Le lecteur pourra vérifier que les méthodes get utilisées ici existent bien respectivement dans les classes [Form], [FeuilleSalaire] et [Employe]. Si ce n'était pas le cas, une exception serait lancée lors de l'évaluation de l'expression. Testez cette nouvelle version étape 3 Question : compléter le formulaire [index.xhtml] et son modèle [Form.java] pour obtenir les informations supplémentaires suivantes : 1 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}"> <f:param value="#{form.feuillesalaire.employe.indemnite.entretienjour}"/> 3. </h:outputformat> On aurait pu écrire : <h:outputtext value="#{form.feuillesalaire.employe.indemnite.entretienjour} є"> mais avec la locale en_gb (Anglais GB) on continuerait à avoir un affichage en euros alors qu'il faudrait utiliser la livre. La balise <h:outputformat> permet d'afficher des informations en fonction de la locale de la page JSF affichée : ligne 1 : affiche le paramètre {0} qui est un nombre (number) représentant une somme d'argent (currency) ligne 2 : la balise <f:param> donne une valeur au paramètre {0}. Une deuxième balise <f:param> donnerait une valeur au paramètre noté {1} et ainsi de suite. étape 4 Lectures conseillées : exemple n 7 (mv-jsf2-07) dans [ref3]. Question : compléter le formulaire [index.xhtml] et son modèle [Form.java] pour gérer le bouton [Raz]. Le bouton [Raz] ramène le formulaire dans l'état qu'il avait lorsqu'on l'a demandé la première fois par un GET. Il y a plusieurs difficultés ici. Certaines ont été expliquées dans [ref3]. 210/257

211 Le formulaire rendu par le bouton [Raz] n'est pas tout le formulaire mais seulement la partie saisie de celui-ci : Ce résultat peut être obtenu avec une balise <f:subview> utilisée de la façon suivante : 1. <f:subview id="viewinfos" rendered="#{form.viewinfosisrendered}">... la partie du formulaire qu'on veut pouvoir ne pas afficher 3. </f:subview> La balise <f:subview> encadre toute la partie du formulaire qui est susceptible d'être affichée ou cachée. Tout composant peut être affiché ou caché grâce à l'attribut rendered. Si rendered="true", le composant est affiché, si rendered="false", il ne l'est pas. Si l'attribut rendered prend sa valeur dans le modèle, alors l'affichage du composant peut être contrôlé par programme. Ci-dessus, on contrôlera l'affichage de la vue viewinfos avec le champ suivant : private boolean viewinfosisrendered; accompagné de ses méthodes get et set. Les méthodes gérant les clics sur les bouton [Salaire] [Raz] mettront à jour ce booléen selon que la vue viewinfos doit être affichée ou non. 211/257

212 10 Version 6 - Intégration de la couche web dans une architecture 3 couches JSF / EJB 10.1 Architecture de l'application L'architecture de l'application web précédente était la suivante : Application web couche [web] 1 Navigateur 4b JSF1 JSF2 JSFn 2b 2a Faces Servlet 3 Gestionnaire d'évts couche [metier] simulée 2c Modèles 4a 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] 1 Navigateur 4b JSF1 JSF2 JSFn b 2a Faces Servlet 3 Gestionnaire d'évts Modèles couches [metier, DAO, JPA] Données 2c 4a Le projet Netbeans de la couche web Le projet Netbeans de la version web n 2 est obtenue par copie du projet précédent : 212/257

213 2 3 1 [1] : on copie le nouveau projet et on le colle dans l'onglet [Projects], [2] : on lui donne un nom et on fixe son dossier, [3] : le projet créé, Le nouveau projet porte le même nom que l'ancien. Nous changeons cela : 5 4 [4] : nous renommons le projet, [5] : on change son nom ainsi que celui de l'artifactid. 213/257

214 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 16 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 en [1], on ajoute une dépendance au projet, en [2], on sélectionne le projet Maven de la couche [métier]. En [3], on précise son type et en [4] sa portée. Celle-ci est provided pour indiquer que celui-ci sera fourni (provided) au module web par son environnement de travail. Nous verrons prochainement qu'il lui sera fourni par une application d'entreprise, en [5], la dépendance a été ajoutée. Le fichier [pom.xml] est alors le suivant : 1. <dependencies> <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> 1 <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

215 Il nous faut également modifier le code du bean [Form.java] : 1.public class Form { 3. public Form() { 4. } // couche métier 7. private IMetierLocal metier=new Metier(); // champs du formulaire La ligne 7 instanciait la couche [métier] simulée. Désormais elle doit référencer la couche [métier] réelle. Le code précédent devient le suivant : 1. public class Form { 3. public Form() { 4. } // couche métier 8. private IMetierLocal metier; // champs du formulaire Ligne 7, 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] 1 Navigateur 4b JSF1 JSF2 JSFn 2b 2a Faces Servlet 3 Gestionnaire d'évts Modèles Conteneur de servlets couche [metier, dao, jpa] Données 2c 4a Conteneur Ejb Les classes du conteneur de servlets peuvent référencer directement les classes EJB du conteneur EJB. C'est tout. Notre couche web est prête. La transformation a été simple parce qu'on avait pris soin de simuler la couche [métier] par une classe qui respectait l'interface IMetierLocal implémentée par la couche [métier] réelle Le projet Netbeans de l'application d'entreprise Une application d'entreprise permet le déploiement simultané sur un serveur d'applications, de la couche [web] et de la couche EJB d'une application, respectivement dans le conteneur de servlets et dans le conteneur EJB. Nous procédons de la façon suivante : 215/257

216 en [1], on crée un nouveau projet en [2], on choisit la catégorie [Maven] en [3], on choisit le type [Enterprise Application] en [4], on donne un nom au projet en [5], nous choisissons Java EE 6 en [6], un projet d'entreprise peut comprendre jusqu'à deux types de modules : un module EJB un module web On peut demander en même temps que la création du projet d'entreprise, la création de ces deux modules qui seront vides au départ. Un projet d'entreprise ne sert qu'au déploiement des modules qui en font partie. En-dehors de ça, c'est une coquille vide. Ici, nous voulons déployer : un module web existant [mv-pam-jsf2-alone]. Il est donc inutile de créer un nouveau module web. un module EJB existant [mv-pam-ejb-metier-dao-eclipselink]. Là également, il est inutile d'en créer un nouveau. En [6], nous créons un projet d'entreprise sans modules. Nous allons lui ajouter ses modules web et ejb ultérieurement. en [7], deux projets Maven ont été créés. Le projet d'entreprise est celui qui a le suffixe ear. L'autre projet est un projet Maven parent du précédent. Nous ne nous en occuperons pas. Nous ajoutons le module web et le module EJB au projet d'entreprise : /257

217 en [1], ajout d'une nouvelle dépendance, en [2], ajout du projet EJB [mv-pam-ejb-metier-dao-eclipselink]. On notera son type ejb, en [3], ajout du projet web [mv-pam-jsf2-ejb]. On notera son type war. Le fichier [pom.xml] est alors le suivant : 1. <dependencies> <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> 1 <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] : 2 1 en [1], l'application d'entreprise est déployée en [2], l'application d'entreprise [mv-pam-webapp-ear] a bien été déployée. Dans le navigateur, on obtient la page suivante : 1 2 en [1], l'url demandée en [2], la liste des employés a été remplie avec les éléments de la table [Employes] de la base dbpam. 217/257

218 Le lecteur est invité à refaire les tests de la version web n 1. Voici un exemple d'exécution : 218/257

219 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] 1 Faces Servlet Navigateur 4b JSF1 JSF2 JSFn 2b 2a 3 Gestionnaires d'évts Modèles couche [metier] simulée 2c 4a Une application JSF est de type MVC (Modèle Vue Contrôleur) : la servlet [Faces Servlet] est le contrôleur générique fourni par JSF. Ce contrôleur est étendu par les gestionnaires d'événements spécifiques à l'application. Les gestionnaires d'événements rencontrés jusqu'ici étaient des méthodes des classes servant de modèles aux pages JSF les pages JSF envoient les réponses au navigateur client. Ce sont les vues de l'application. les pages JSF comportent des éléments dynamiques qu'on appelle le modèle de la page. On rappelle que pour certains auteurs, le modèle recouvre les entités manipulées par l'application, telles par exemple les classes FeuilleSalaire ou Employe. Pour distinguer ces deux modèles, on pourra parler de modèle de l'application et modèle d'une page JSF. Dans l'architecture JSF, le passage d'une page JSFi à une page JSFj peut être problématique. la page JSFi a été affichée. A partir de cette page, l'utilisateur provoque un POST par un événement quelconque [1] en JSF, ce POST sera traité [2a,2b] en général par une méthode C du modèle M i de la page JSFi. On peut dire que la méthode C est un contrôleur secondaire. si à l'issue de cette méthode, la page JSFj doit être affichée, le contrôleur C doit : 1. mettre à jour [2c] le modèle Mj de la page JSFj rendre [2a] au contrôleur principal, la clé de navigation qui permettra l'affichage de la page JSF j L'étape 1 nécessite que le modèle Mi de la page JSFi ait une référence sur modèle Mj de la page JSFj. Cela complique un peu les choses rendant les modèles Mi dépendant les uns des autres. En effet, le gestionnaire C du modèle M i qui met à jour le modèle Mj doit connaître celui-ci. Si on est amené à changer le modèle M j, on sera alors amené à changer le gestionnaire C du modèle Mi. Il existe un cas où la dépendance des modèles entre-eux peut être évitée : celui où il y a un unique modèle M qui sert à toutes les pages JSF. Cette architecture n'est utilisable que dans les applications n'ayant que quelques vues mais elle se révèle alors très simple d'usage. C'est celle que nous utilisons maintenant. Dans ce contexte, l'architecture de l'application est la suivante : Application web couche [web] 1 4 Faces Servlet 2a 3 JSF1 JSF2 JSFn 2b [MC] Form.java Modèle M Gestionnaires d'évts couche [metier] simulée 219/257

220 11.1 Les vues de l'application Les différentes vues présentées à l'utilisateur seront les suivantes : - la vue [VueSaisies] qui présente le formulaire de simulation - la vue [VueSimulation] utilisée pour afficher le résultat détaillé de la simulation : - la vue [VueSimulations] qui donne la liste des simulations faites par le client 220/257

221 - la vue [VueSimulationsVides] qui indique que le client n'a pas ou plus de simulations : la vue [VueErreur] qui indique une ou plusieurs erreurs : 11.2 Le projet Netbeans de la couche [web] Le projet Netbeans de cette version sera le projet Maven suivant : 221/257

222 en [1], les fichiers de configuration, en [2], les pages JSF, en [3], la feuille de style et l'image de fond des vues, en [4], les classes de la couche [web], en [5], les couches basses de l'application, en [6], le fichier des messages pour l'internationalisation de l'application, en [7], les dépendances du projet. Nous passons en revue certains de ces éléments Les fichiers de configuration Les fichiers [web.xml] et [faces-config.xml] sont identiques à ceux du projet précédent à l'exception d'un détail dans [web.xml] : 1. <?xml version="1.0" encoding="utf-8"?> <web-app version="3.0" xmlns=" xmlns:xsi=" xsi:schemalocation=" <welcome-file-list> 5. <welcome-file>faces/saisie.xhtml</welcome-file> 6. </welcome-file-list> </web-app> 11.2 lignes 4-6 : la page d'accueil est la page [saisie.xhtml] La feuille de style Le fichier [styles.css] est le suivant : 1..simulationsHeader { text-align: center; 3. font-style: italic; 4. color: Snow; 5. background: Teal; 6. } 222/257

223 7. 8..simuNum { 9. height: 25px; 10. text-align: center; 11. background: MediumTurquoise; 1 } 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; 2 } 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; 3 color: Black; 33. background: MediumTurquoise; 34. } 35..simuSalaireBase { 36. width: 9em; 37. text-align: center; 38. color: Black; 39. background: PowderBlue; 40. } 41..simuIndemnites { 4 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. } simuSalaireNet { 54. width: 10em; 55. text-align: center; 56. background: MediumTurquoise; 57. } erreursHeaders { 60. background: Teal; 61. background-color: #ff6633; 6 color: Snow; 63. font-style: italic; 64. text-align: center } erreurClasse { 69. background: MediumTurquoise; 223/257

224 70. background-color: #ffcc66; 71. height: 25px; 7 text-align: center 73. } erreurMessage { 76. background: PowderBlue; 77. background-color: #ffcc99; 78. text-align: left 79. } Voici des exemples de code JSF utilisant ces styles : Vue Simulations <h:datatable value="#{form.simulations}" var="simulation" headerclass="simulationsheaders" columnclasses="simunum,simunom,simuprenom,simuht,simujt,simusalairebase,simuindemnites,simucotisat ionssociales,simusalairenet"> Le résultat obtenu est le suivant : Vue Erreur <h:datatable value="#{form.erreurs}" var="erreur" headerclass="erreursheaders" columnclasses="erreurclasse,erreurmessage"> 11.3 Le fichier des messages Le fichier des messages [messages_fr.properties] est le suivant : 1. form.titre=simulateur de calcul de paie 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 1 exception.httpcode=code HTTP de l'erreur 13. exception.message=message de l'exception 224/257

225 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 2 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 3 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 4 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 5 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\u00e La couche [métier] La couche [métier] simulée est modifiée de la façon suivante : 1. package metier; public class Metier implements IMetierLocal, Serializable { // liste des employes 7. private Map<String,Employe> hashemployes=new HashMap<String,Employe>(); 8. private List<Employe> listemployes; // obtenir la feuille de salaire 11. public FeuilleSalaire calculerfeuillesalaire(string SS, 1 double nbheurestravaillées, int nbjourstravaillés) { 225/257

226 // on récupère l'employé Employe e=hashemployes.get(ss); // on rend une exception si l'employé n'existe pas if(e==null){ throw new PamException(String.format("L'employé de n SS [%s] n'existe pas",ss),1); } // on rend une feuille de salaire fictive return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),new ElementsSalaire(100,100,100,100,100)); 21. } // 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(" ","Jouveinal","Marie","5 rue des oiseaux","st Corentin","49203",new Indemnite(2,1,1,3.1,15))); 29. listemployes.add(new Employe(" ","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){ 3 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. } 11.3 ligne 8 : la liste des employés ligne 7 : la même liste sous forme de dictionnaire indexé par le n SS des employés lignes : la méthode findallemployes qui rend la liste des employés. Cette méthode crée une liste en dur et la référence par le champ employés de la ligne 8. lignes : une liste et un dictionnaire de deux employés sont créés ligne 35 : un employé est ajouté à la liste employes (ligne 8) mais pas au dictionnaire hashemployes (ligne 7). Ceci pour qu'il apparaisse dans le combo des employés mais qu'ensuite il ne soit pas reconnu par la méthode calculerfeuillesalaire (ligne 14) afin que celle-ci lance une exception (ligne 17). lignes : la méthode calculerfeuillesalaire ligne 14 : l'employé est cherché dans le dictionnaire hashemployes via son n SS. S'il n'est pas trouvé, une exception est lancée (lignes 16-18). Ainsi aurons-nous une exception pour l'employé de n SS X ajouté ligne 35 dans la liste employés mais pas dans le dictionnaire hashemployes. ligne 20, une feuille de salaire fictive est créée et rendue. Les beans de l'application Il y aura trois beans de trois portées différentes : 226/257

227 Le bean ApplicationData Le bean ApplicationData sera de portée application : 1. package web.beans.application; 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; public class ApplicationData implements Serializable { // couche métier 16. private IMetierLocal metier = new Metier(); 17. // logger 18. private boolean logsenabled = true; 19. private final Logger logger = Logger.getLogger("pam"); public ApplicationData() { 2 } public void init() { 26. // log 27. if (islogsenabled()) { 28. logger.info("applicationdata"); 29. } 30. } // getters et setters } ligne 11 : fait de la classe un bean managé. On notera qu'à la différence du projet précédent, on n'a pas utilisé La raison en est que la référence de cette classe doit être injectée dans une autre classe à l'aide de et que celle-ci n'injecte que des classes ligne 12 : fait de la classe, un objet de portée application. On notera que la classe de l'annotation est [javax.enterprise.context.applicationscoped] (ligne 6) et non [javax.faces.bean.applicationscoped] comme dans les beans du projet précédent. Le bean ApplicationData sert à deux choses : ligne 16 : maintenir une référence sur la couche [métier], lignes : définir un logueur qui pourra être utilisé par les autres beans pour faire des logs sur la console de Glassfish. Le bean SessionData Le bean SessionData sera de portée session : package web.beans.session; import import import import java.io.serializable; java.util.arraylist; java.util.list; javax.annotation.postconstruct; 227/257

228 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; public class SessionData implements Serializable { // application 19. private ApplicationData applicationdata; 20. // simulations 21. private List<Simulation> simulations = new ArrayList<Simulation>(); 2 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 3 private String locale="fr_fr"; // constructeur 35. public SessionData() { 36. } public void init() { 40. // log 41. if (applicationdata.islogsenabled()) { 4 applicationdata.getlogger().info("sessiondata"); 43. } 44. } // 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); 5 this.setmenuretoursimulateurisrendered(menuretoursimulateurisrendered); 53. this.setmenuterminersessionisrendered(menuterminersessionisrendered); 54. } // getters et setters } 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 : une référence sur le bean ApplicationData lui est injecté (@Inject), lignes : 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

229 lignes : 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) Le bean Form Le bean Form est de portée requête : 1. package web.beans.request; 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; 1 import web.beans.application.applicationdata; 13. import web.beans.session.*; 14. import web.entities.erreur; 15. import web.entities.simulation; public class Form { public Form() { 2 } 23. // autres beans 25. private ApplicationData applicationdata; 27. private SessionData sessiondata; 28. // le modèle des vues 29. private String comboemployesvalue = ""; 30. private String heurestravaillées = ""; 31. private String jourstravaillés = ""; 3 private Integer numsimulationtodelete; 33. private List<Erreur> erreurs = new ArrayList<Erreur>(); 34. private FeuilleSalaire feuillesalaire; // liste des employés 38. public List<Employe> getemployes(){ 39. return applicationdata.getmetier().findallemployes(); 40. } // action du menu 43. public String fairesimulation() { } public String enregistrersimulation() { } /257

230 51. public String effacersimulation() { } public String voirsimulations() { } public String retoursimulateur() { } public String terminersession() { } public String retirersimulation() { } private void razformulaire() { } // getters et setters } ligne 17, la classe est un bean managé (@Named), ligne 18, de portée requête (@RequestScoped), lignes : injection d'une référence sur le bean de portée application ApplicationData, lignes : injection d'une référence sur le bean de portée session SessionData. Les pages de l'application [layout.xhtml] La page [layout.xhtml] assure la mise en page de toutes les vues : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 230/257

231 xmlns:h=" xmlns:f=" xmlns:ui=" <f:view locale="#{sessiondata.locale}"> <h:head> <title><h:outputtext value="#{msg['form.titre']}"/></title> <h:outputstylesheet library="css" name="styles.css"/> </h:head> <script type="text/javascript"> function raz(){ // on change les valeurs postées document.forms['formulaire'].elements['formulaire:comboemployes'].value="0"; document.forms['formulaire'].elements['formulaire:heurestravaillees'].value="0"; document.forms['formulaire'].elements['formulaire:jourstravailles'].value="0"; } </script> <h:body style="background-image: url('$ {request.contextpath}/resources/images/standard.jpg');"> 2 <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> 3 </f:view> 33. </html> Toute vue est constituée des éléments suivants : un entête affiché par le fragment [entete.xhtml] (ligne 24), un corps formé de deux fragments appelés part1 (lignes 26-28) et part2 (ligne 29), ligne 8 : l'attribut locale assure l'internationalisation des pages. Ici il n'y en aura qu'une : fr_fr, lignes : un code Javascript. L'affichage de la page [layout.xhtml] est le suivant : 1 2 en [1], l'entête [entete.xhtml], en [2], le fragment part1. 231/257

232 L'entête [entete.xhtml] Le code de la page [entete.xhtml] est le suivant : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 4. xmlns:h=" 5. xmlns:f=" 6. xmlns:ui=" <ui:composition> 9. <!-- entete --> 10. <h:panelgrid columns="2"> 11. <h:panelgroup> 1 <h2><h:outputtext value="#{msg['form.titre']}"/></h2> 13. </h:panelgroup> 14. <h:panelgroup> 15. <h:panelgrid columns="1"> 16. <h:commandlink id="cmdfairesimulation" value="#{msg['form.menu.fairesimulation']}" action="#{form.fairesimulation}" rendered="#{sessiondata.menufairesimulationisrendered}"/> 17. <h:commandlink id="cmdeffacersimulation" onclick="raz()" value="#{msg['form.menu.effacersimulation']}" action="#{form.effacersimulation}" rendered="#{sessiondata.menueffacersimulationisrendered}"/> 18. <h:commandlink id="cmdenregistrersimulation" immediate="true" value="#{msg['form.menu.enregistrersimulation']}" action="#{form.enregistrersimulation}" rendered="#{sessiondata.menuenregistrersimulationisrendered}"/> 19. <h:commandlink id="cmdvoirsimulations" immediate="true" value="#{msg['form.menu.voirsimulations']}" action="#{form.voirsimulations}" rendered="#{sessiondata.menuvoirsimulationsisrendered}"/> 20. <h:commandlink id="cmdretoursimulateur" immediate="true" value="#{msg['form.menu.retoursimulateur']}" action="#{form.retoursimulateur}" rendered="#{sessiondata.menuretoursimulateurisrendered}"/> 21. <h:commandlink id="cmdterminersession" immediate="true" value="#{msg['form.menu.terminersession']}" action="#{form.terminersession}" rendered="#{sessiondata.menuterminersessionisrendered}"/> 2 </h:panelgrid> 23. </h:panelgroup> 24. </h:panelgrid> 25. <hr/> 26. </ui:composition> 27. </html> ligne 12 : le titre de l'application, lignes : les six liens correspondant aux six actions que peut faire l'utilisateur. Ces liens sont contrôlés (attribut rendered) par des booléens du bean SessionData, ligne 17 : un clic sur le lien [Effacer la simulation] provoque l'exécution de la fonction Javascript raz. Celle-ci a été définie dans le modèle [layout.xhtml], ligne 18 : l'attribut immediate=true fait que la validité des données n'est pas vérifiée avant exécution de la méthode [Form].enregistrerSimulation. C'est voulu. On peut vouloir enregistrer la dernière simulation faite il y a une minute même si les données saisies depuis dans le formulaire (mais pas encore validées) ne sont pas valides. Il est fait de même pour les actions [Voir les simulations], [Effacer la simulation], [Retour au simulateur] et [Terminer la session]. Les cas d'utilisation de l'application Affichage de la page d'accueil 232/257

233 La page d'accueil est la page [saisie.xhtml] suivante : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 4. xmlns:h=" 5. xmlns:f=" 6. xmlns:ui=" <ui:composition template="layout.xhtml"> 9. <ui:define name="part1"> 10. <ui:include src="saisiexhtml"/> 11. </ui:define> 1 </ui:composition> 13. </html> ligne 8 : elle s'affiche à l'intérieur de la page [layout.xhtml], ligne 9 : à la place du fragment nommé part1. Dans ce fragment, on affiche la page [saisiexhtml] : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 4. xmlns:h=" 5. xmlns:f=" 6. xmlns:ui=" <h:panelgrid columns="3"> 9. <h:outputtext value="#{msg['form.comboemployes.libellé']}"/> 10. <h:outputtext value="#{msg['form.heurestravaillées.libellé']}"/> 11. <h:outputtext value="#{msg['form.jourstravaillés.libellé']}"/> 1 <h:selectonemenu id="comboemployes" value="#{form.comboemployesvalue}"> 13. <f:selectitems.../> 14. </h:selectonemenu> 15. <h:inputtext id="heurestravaillees" value="#{form.heurestravaillées}" required="true" requiredmessage="#{msg['form.heurestravaillées.required']}" validatormessage="#{msg['form.heurestravaillées.validation']}"> 16. <f:validatedoublerange minimum="0" maximum="300"/> 17. </h:inputtext> 18. <h:inputtext id="jourstravailles" value="#{form.jourstravaillés}" required="true" requiredmessage="#{msg['form.jourstravaillés.required']}" validatormessage="#{msg['form.jourstravaillés.validation']}"> 19. <f:validatelongrange minimum="0" maximum="31"/> 20. </h:inputtext> 21. <h:panelgroup></h:panelgroup> 2 <h:message for="heurestravaillees" styleclass="error"/> 23. <h:message for="jourstravailles" styleclass="error"/> 24. </h:panelgrid> 25. <hr/> 26. </html> Ce code affiche la vue suivante : 233/257

234 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? L'action [fairesimulation] Le code JSF de l'action [fairesimulation] est le suivant : <h:commandlink id="cmdfairesimulation" value="#{msg['form.menu.fairesimulation']}" action="#{form.fairesimulation}" rendered="#{sessiondata.menufairesimulationisrendered}"/> L'action [fairesimulation] calcule une feuille de salaire : A partir de la page précédente, on obtient le résultat suivant : 234/257

235 La simulation est affichée avec la page [simulation.xhtml] suivante : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 4. xmlns:h=" 5. xmlns:f=" 6. xmlns:ui=" <ui:composition template="layout.xhtml"> 9. <ui:define name="part1"> 10. <ui:include src="saisiexhtml"/> 11. </ui:define> 1 <ui:define name="part2"> 13. <h:outputtext value="#{msg['form.infos.employé']}" styleclass="titreinfos"/> 14. <br/><br/> 15. <h:panelgrid columns="3" rowclasses="libelle,info"> 16. <h:outputtext value="#{msg['form.employe.nom']}"/> 17. <h:outputtext value="#{msg['form.employe.prénom']}"/> 18. <h:outputtext value="#{msg['form.employe.adresse']}"/> 19. <h:outputtext value="#{form.feuillesalaire.employe.nom}"/> 20. <h:outputtext value="#{form.feuillesalaire.employe.prenom}"/> 21. <h:outputtext value="#{form.feuillesalaire.employe.adresse}"/> 2 </h:panelgrid> 23. <h:panelgrid columns="3" rowclasses="libelle,info"> 24. <h:outputtext value="#{msg['form.employe.ville']}"/> 25. <h:outputtext value="#{msg['form.employe.codepostal']}"/> 26. <h:outputtext value="#{msg['form.employe.indice']}"/> 235/257

236 <h:outputtext value="#{form.feuillesalaire.employe.ville}"/> <h:outputtext value="#{form.feuillesalaire.employe.codepostal}"/> <h:outputtext value="#{form.feuillesalaire.employe.indemnite.indice}"/> </h:panelgrid> <br/> <h:outputtext value="#{msg['form.infos.cotisations']}" styleclass="titreinfos"/> <br/><br/> <h:panelgrid columns="4" rowclasses="libelle,info"> <h:outputtext value="#{msg['form.cotisations.csgrds']}"/> <h:outputtext value="#{msg['form.cotisations.csgd']}"/> <h:outputtext value="#{msg['form.cotisations.retraite']}"/> <h:outputtext value="#{msg['form.cotisations.secu']}"/> <h:outputtext value="#{form.feuillesalaire.cotisation.csgrds} %"/> <h:outputtext value="#{form.feuillesalaire.cotisation.csgd} %"/> <h:outputtext value="#{form.feuillesalaire.cotisation.retraite} %"/> <h:outputtext value="#{form.feuillesalaire.cotisation.secu} %"/> </h:panelgrid> <br/> <h:outputtext value="#{msg['form.infos.indemnites']}" styleclass="titreinfos"/> <br/><br/> <h:panelgrid columns="4" rowclasses="libelle,info"> <h:outputtext value="#{msg['form.indemnites.salairehoraire']}"/> <h:outputtext value="#{msg['form.indemnites.entretienjour']}"/> <h:outputtext value="#{msg['form.indemnites.repasjour']}"/> <h:outputtext value="#{msg['form.indemnites.congéspayés']}"/> <h:outputformat value="{0,number,currency}"> <f:param value="#{form.feuillesalaire.employe.indemnite.baseheure}"/> </h:outputformat> <h:outputformat value="{0,number,currency}"> <f:param value="#{form.feuillesalaire.employe.indemnite.entretienjour}"/> </h:outputformat> <h:outputformat value="{0,number,currency}"> <f:param value="#{form.feuillesalaire.employe.indemnite.repasjour}"/> </h:outputformat> <h:outputtext value="#{form.feuillesalaire.employe.indemnite.indemnitescp} %"/> </h:panelgrid> <br/> <h:outputtext value="#{msg['form.infos.salaire']}" styleclass="titreinfos"/> <br/><br/> <h:panelgrid columns="4" rowclasses="libelle,info"> <h:outputtext value="#{msg['form.salaire.base']}"/> <h:outputtext value="#{msg['form.salaire.cotisationssociales']}"/> <h:outputtext value="#{msg['form.salaire.entretien']}"/> <h:outputtext value="#{msg['form.salaire.repas']}"/> <h:outputformat value="{0,number,currency}"> <f:param value="#{form.feuillesalaire.elementssalaire.salairebase}"/> </h:outputformat> <h:outputformat value="{0,number,currency}"> <f:param value="#{form.feuillesalaire.elementssalaire.cotisationssociales}"/> </h:outputformat> <h:outputformat value="{0,number,currency}"> <f:param value="#{form.feuillesalaire.elementssalaire.indemnitesentretien}"/> </h:outputformat> <h:outputformat value="{0,number,currency}"> <f:param value="#{form.feuillesalaire.elementssalaire.indemnitesrepas}"/> </h:outputformat> </h:panelgrid> <br/> <h:panelgrid columns="3" columnclasses="libelle,col2,info"> <h:outputtext value="#{msg['form.salaire.net']}"/> <h:panelgroup></h:panelgroup> <h:outputformat value="{0,number,currency}"> <f:param value="#{form.feuillesalaire.elementssalaire.salairenet}"/> 236/257

237 90. </h:outputformat> 91. </h:panelgrid> 9 </ui:define> 93. </ui:composition> 94. </html> ligne 8, la page [simulation.xhtml] s'insère dans la page [layout.xhtml], lignes 9-11 : la zone des saisies est affichée dans le fragment part1 de la page layout, lignes : la simulation est affichée dans le fragment part2 de la page layout. Question : écrire la méthode [fairesimulation] de la classe [Form]. La simulation sera enregistrée dans le bean SessionData La gestion des erreurs On veut pouvoir gérer proprement les exceptions qui peuvent survenir lors du calcul d'une simulation. Pour cela le code de la méthode [fairesimulation] utilisera un try / catch : 1. // action du menu public String fairesimulation(){ 3. try{ 4. // on calcule la feuille de salaire 5. feuillesalaire= // on affiche la simulation // on met à jour le menu // on rend la vue simulation 11. return "simulation"; 1 }catch(throwable th){ 13. // on vide la liste des erreurs précédentes // on crée la nouvelle liste des erreurs // on affiche la vue vueerreur // on met à jour le menu // on affiche la vue erreur 2 return "erreurs"; 23. } 24.} La liste des erreurs créée ligne 15 est la suivante : 1. // le modèle des vues private List<Erreur> erreurs=new ArrayList<Erreur>(); 4... La classe Erreur est définie comme suit : 1. package web.entities; 3. public class Erreur { public Erreur() { 6. } // champ 9. private String classe; 10. private String message; 237/257

238 11. 1 // constructeur 13. public Erreur(String classe, String message){ 14. this.setclasse(classe); 15. this.message=message; 16. } // getters et setters } Les erreurs seront des exceptions dont on mémorise le nom de la classe dans le champ classe et le message dans le champ message. La liste des erreurs construite dans la méthode [fairesimulation] est constituée de : l'exception initiale th de type Throwable qui s'est produite, de sa cause th.getcause() si elle en a une, de la cause de la cause h.getcause().getcause() si elle existe.... Voici un exemple de liste d'erreurs : Ci-dessus, l'employé [Z Y] n'existe pas dans le dictionnaire des employés utilisé par la couche [métier] simulée. Dans ce cas, la couche [métier] simulée lance une exception (ligne 6 ci-dessous) : 1. public FeuilleSalaire calculerfeuillesalaire(string SS, double nbheurestravaillées, int nbjourstravaillés) { // on récupère l'employé 3. Employe e=hashemployes.get(ss); 4. // on rend une exception si l'employé n'existe pas 5. if(e==null){ 6. throw new PamException(String.format("L'employé de n SS [%s] n'existe pas",ss),1); 7. } } Le résultat obtenu est le suivant : Cette vue est affichée par la page [erreurs.xhtml] suivante : 238/257

239 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 4. xmlns:h=" 5. xmlns:f=" 6. xmlns:ui=" <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" 1 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> 2 <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] L'action [effacersimulation] L'action [effacersimulation] permet à l'utilisateur de retrouver un formulaire vide : Le code JSF du lien [effacersimulation] est le suivant : <h:commandlink id="cmdeffacersimulation" onclick="raz()" value="#{msg['form.menu.effacersimulation']}" action="#{form.effacersimulation}" rendered="#{sessiondata.menueffacersimulationisrendered}"/> Un clic sur le lien [EffacerSimulation] provoque d'abord l'appel de la fonction Javascript raz(). Cette méthode est définie dans la page [layout.xhtml] : 239/257

240 1. <script type="text/javascript"> function raz(){ 3. // on change les valeurs postées 4. document.forms['formulaire'].elements['formulaire:comboemployes'].value="0"; 5. document.forms['formulaire'].elements['formulaire:heurestravaillees'].value="0"; 6. document.forms['formulaire'].elements['formulaire:jourstravailles'].value="0"; 7. } 8. </script> Les lignes 4-6 changent les valeurs postées. On notera que les valeurs postées sont des valeurs valides, c.a.d. qu'elles passeront les tests de validation des champs de saisie heurestravaillees et jourstravailles. la fonction raz ne poste pas le formulaire. En effet, celui-ci va être posté par le lien cmdeffacersimulation. Ce post se fera après exécution de la fonction Javascript raz. Au cours du post, les valeurs postées vont suivre un cheminement normal : validation puis affectation aux champs du modèle. Ceuxci sont les suivants dans la classe [Form] : // le modèle des vues private String comboemployesvalue; private String heurestravaillées; private String jourstravaillés;... Ces trois champs vont recevoir les trois valeurs postées {"0","0","0"}. Une fois cette affectation opérée, la méthode effacersimulation va être exécutée. Question : écrire la méthode [effacersimulation] de la classe [Form]. On fera en sorte que : - seule la zone des saisies soit affichée, - le combo soit positionné sur son 1er élément, - les zones de saisie heurestravaillees et jourstravailles affichent des chaînes vides L'action [enregistrersimulation] Le code JSF du lien [enregistrersimulation] est le suivant : <h:commandlink id="cmdenregistrersimulation" immediate="true" value="#{msg['form.menu.enregistrersimulation']}" action="#{form.enregistrersimulation}" rendered="#{sessiondata.menuenregistrersimulationisrendered}"/> L'action [enregistrersimulation] associée au lien permet d'enregistrer la simulation courante dans une liste de simulations maintenue dans la classe [SessionData] : private List<Simulation> simulations=new ArrayList<Simulation>(); La classe Simulation est la suivante : 1. package web.entities; 3. import metier.feuillesalaire; public class Simulation { public Simulation() { 8. } // champs d'une simulation 11. private Integer num; 1 private FeuilleSalaire feuillesalaire; 13. private String heurestravaillées; 14. private String jourstravaillés; 240/257

241 // constructeur 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); 2 } public double getindemnites(){ 25. return feuillesalaire.getelementssalaire().getindemnitesentretien()+ feuillesalaire.getelementssalaire().getindemnitesrepas(); 26. } // getters et setters } Cette classe permet de mémoriser une simulation faite par l'utilisateur : ligne 11 : le n de la simulation, ligne 12 : la feuille de salaire qui a été calculée, ligne 13 : le nombre d'heures travaillées, ligne 14 : le nombre de jours travaillés. Voici un exemple d'enregistrement : A partir de la page précédente, on obtient le résultat qui suit : Le n de la simulation est un nombre incrémenté à chaque nouvel enregistrement. Il appartient au bean SessionData : 1. // simulations private List<Simulation> simulations = new ArrayList<Simulation>(); 3. private int numdernieresimulation = 0; 4. private Simulation simulation; 241/257

242 ligne 2 : le n de la dernière simulation faite. La méthode [enregistrersimulation] peut procéder ainsi : récupérer le n de la dernière simulation dans le bean [SessionData] et l'incrémenter, ajouter la nouvelle simulation à la liste des simulations maintenue par la classe [SessionData], faire afficher le tableau des simulations : Le tableau des simulations est affiché par la page [simulations.xhtml] : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 4. xmlns:h=" 5. xmlns:f=" 6. xmlns:ui=" <ui:composition template="layout.xhtml"> 9. <ui:define name="part1"> 10. <!-- tableau des simulations --> 11. <h:datatable value="#{sessiondata.simulations}" var="simulation" 1 headerclass="simulationsheaders" columnclasses="simunum,simunom,simuprenom,simuht,simujt,simusalairebase,simuindemnites,simu CotisationsSociales,simuSalaireNet"> 13. <h:column> 14. <f:facet name="header"> 15. <h:outputtext value="#{msg['simulations.headers.numero']}"/> 16. </f:facet> 17. <h:outputtext value="#{simulation.num}"/> 18. </h:column> 19. <h:column> 20. <f:facet name="header"> 21. <h:outputtext value="#{msg['simulations.headers.nom']}"/> 2 </f:facet> 23. <h:outputtext value="#{simulation.feuillesalaire.employe.nom}"/> 24. </h:column> 25. <h:column> 26. <f:facet name="header"> 27. <h:outputtext value="#{msg['simulations.headers.prenom']}"/> 28. </f:facet> 29. <h:outputtext value="#{simulation.feuillesalaire.employe.prenom}"/> 30. </h:column> 31. <h:column> 3 <f:facet name="header"> 33. <h:outputtext value="#{msg['simulations.headers.heurestravaillees']}"/> 34. </f:facet> 35. <h:outputtext value="#{simulation.heurestravaillées}"/> 36. </h:column> 37. <h:column> 242/257

243 <f:facet name="header"> <h:outputtext value="#{msg['simulations.headers.jourstravailles']}"/> </f:facet> <h:outputtext value="#{simulation.jourstravaillés}"/> </h:column> <h:column> <f:facet name="header"> <h:outputtext value="#{msg['simulations.headers.salairebase']}"/> </f:facet> <h:outputtext value="#{simulation.feuillesalaire.elementssalaire.salairebase}"/> </h:column> <h:column> <f:facet name="header"> <h:outputtext value="#{msg['simulations.headers.indemnites']}"/> </f:facet> <h:outputtext value="#{simulation.indemnites}"/> </h:column> <h:column> <f:facet name="header"> <h:outputtext value="#{msg['simulations.headers.cotisationssociales']}"/> </f:facet> <h:outputtext value="#{simulation.feuillesalaire.elementssalaire.cotisationssociales}"/> 60. </h:column> 61. <h:column> 6 <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> 7 </h:datatable> 73. </ui:define> 74. </ui:composition> 75. </html> ligne 8, la page [simulations.xhtml] s'insère à l'intérieur de la page [layout.xhtml], ligne 9, à la place du fragment nommé part1, ligne 11, la balise <h:datatable> utilise le champ #{sessiondata.simulations} comme source de données, c.a.d. le champ suivant : 1. // simulations 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

244 Le code JSF de la colonne Nom est le suivant : 1. <h:column> <f:facet name="header"> 3. <h:outputtext value="#{msg['simulations.headers.nom']}"/> 4. </f:facet> 5. <h:outputtext value="#{simulation.feuillesalaire.employe.nom}"/> 6. </h:column> lignes 2-4 : la balise <f:facet name="header"> définit le titre de la colonne ligne 5 : le nom de l'employé est écrit : simulation fait référence à l'attribut var de la balise <h:datatable...> : <h:datatable value="#{sessiondata.simulations}" var="simulation"...> simulation désigne la simulation courante de la liste des simulations : d'abord la 1ère, puis la 2ème,... simulation.feuillesalaire fait référence au champ feuillesalaire de la simulation courante simulation.feuillesalaire.employe fait référence au champ employe du champ feuillesalaire simulation.feuillesalaire.employe.nom fait référence au champ nom du champ employe La même technique est répétée pour toutes les colonnes du tableau. Il y a une difficulté pour la colonne Indemnités qui est générée avec le code suivant : 1. <h:column> <f:facet name="header"> 3. <h:outputtext value="#{msg['simulations.headers.indemnites']}"/> 4. </f:facet> 5. <h:outputtext value="#{simulation.indemnites}"/> 6. </h:column> Ligne 5, on affiche la valeur de simulation.indemnites. Or la classe Simulation n'a pas de champ indemnites. Il faut se rappeler ici que le champ indemnites n'est pas utilisé directement mais via la méthode simulation.getindemnites(). Il suffit donc que cette méthode existe. Le champ indemnites peut lui ne pas exister. La méthode getindemnites doit rendre le total des indemnités de l'employé. Cela nécessite un calcul intermédiaire car ce total n'est pas disponible directement dans la feuille de salaire. La méthode getindemnites est donnée page 240. Question : écrire la méthode [enregistrersimulation] de la classe [Form] L'action [retoursimulateur] Le code JSF du lien [retoursimulateur] est le suivant : <h:commandlink id="cmdretoursimulateur" immediate="true" value="#{msg['form.menu.retoursimulateur']}" action="#{form.retoursimulateur}" rendered="#{sessiondata.menuretoursimulateurisrendered}"/> L'action [retoursimulateur] associée au lien permet à l'utilisateur de revenir de la vue [vuesimulations] à la vue [vuesaisies] : 244/257

245 Le résultat obtenu : Question : écrire la méthode [retoursimulateur] de la classe [Form]. Le formulaire de saisie présenté doit être vide comme cidessus L'action [voirsimulations] Le code JSF du lien [voirsimulations] est le suivant : <h:commandlink id="cmdvoirsimulations" immediate="true" value="#{msg['form.menu.voirsimulations']}" action="#{form.voirsimulations}" rendered="#{sessiondata.menuvoirsimulationsisrendered}"/> L'action [voirsimulations] associée au lien permet à l'utilisateur d'avoir le tableau des simulations, ceci quelque soit l'état de ses saisies : 245/257

246 Le résultat obtenu : Question : écrire la méthode [voirsimulations] de la classe [Form]. On fera en sorte que si la liste des simulations est vide, la vue affichée soit [vuesimulationsvides] : Pour afficher la vue ci-dessus, on utilisera la page [simulationsvides.xhtml] suivante : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 4. xmlns:h=" 5. xmlns:f=" 6. xmlns:ui=" <ui:composition template="layout.xhtml"> 9. <ui:define name="part1"> 10. <h2>votre liste de simulations est vide.</h2> 11. </ui:define> 1 </ui:composition> 13. </html> L'action [retirersimulation] L'utilisateur peut retirer des simulations de sa liste : 246/257

247 Le résultat obtenu est le suivant : Si ci-dessus, on retire la dernière simulation, on obtiendra le résultat suivant : Le code JSF de la colonne [Retirer] du tableau des simulations est le suivant : <h:datatable value="#{form.simulations}" var="simulation" headerclass="simulationsheaders" columnclasses="simunum,simunom,simuprenom,simuht,simujt,simusalairebase,simuindemnites,simu CotisationsSociales,simuSalaireNet">... <h:column> <h:commandlink value="retirer" action="#{form.retirersimulation}"> <f:setpropertyactionlistener target="#{form.numsimulationtodelete}" value="#{simulation.num}"/> </h:commandlink> </h:column> </h:datatable> ligne 5 : le lien [Retirer] est associé à la méthode [retirersimulation] de la classe [Form]. Cette méthode a besoin de connaître le n de la simulation à retirer. Celui-ci lui est fourni par la balise <f:setpropertyactionlistener> de la ligne 8. Cette balise a deux attributs target et value : l'attribut target désigne un champ du modèle auquel la valeur de l'attribut value sera affectée. Ici le n de la simulation à retirer #{simulation.num} sera affectée au champ numsimulationtodelete de la classe [Form] : // le modèle des vues... private Integer numsimulationtodelete; Lorsque la méthode [retirersimulation] de la classe [Form] s'exécutera, elle pourra utiliser la valeur qui aura été stockée auparavant dans le champ numsimulationtodelete. Question : écrire la méthode [retirersimulation] de la classe [Form] L'action [terminersession] Le code JSF du lien [Terminer la session] est le suivant : 247/257

248 <h:commandlink id="cmdterminersession" immediate="true" value="#{msg['form.menu.terminersession']}" action="#{form.terminersession}" rendered="#{sessiondata.menuterminersessionisrendered}"/> L'action [terminersession] associée au lien permet à l'utilisateur d'abandonner sa session et de revenir au formulaire de saisies vide : Si l'utilisateur avait une liste de simulations, celle-ci est vidée. Par ailleurs, la numérotation des simulations repart à 1. Question : écrire la méthode [terminersession] de la classe [Form] Intégration de la couche web dans une architecture 3 couches JSF / EJB L'architecture de l'application web précédente était la suivante : Application web couche [web] 1 4 Faces Servlet 2a 3 JSF1 JSF2 JSFn 2b [MC] Form.java Modèle M Gestionnaires d'évts couche [metier] simulée 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

249 Application web couche [web] 1 4 Faces Servlet 2a 3 JSF1 JSF2 JSFn 2b [MC] Form.java Modèle M Gestionnaires d'évts couche [metier, DAO, JPA] Travail pratique : réaliser l'intégration des couches JSF et EJB en suivant la méthodologie du paragraphe 10, page /257

250 12 Version 8 : Portage de l'application dans un environnement Spring / Tomcat Question : En suivant l'exemple " Application exemple 02 : rdvmedecins-jsf2-spring " de [ref3], portez l'application précédente dans un environnement Spring / Tomcat / Hibernate. La nouvelle application web est [mv-pam-jsf2-spring-multipages]. L'interface web ne change pas : Voici une démarche possible : créer un projet Maven de type [Java Application] nommé [mv-pam-spring-metier], ajouter les dépendances nécessaires, copier les éléments du projet EJB précédent dans le nouveau projet Spring, corriger les erreurs qui apparaissent dans le projet Spring, créer le fichier [persistence.xml] qui configure la couche JPA, créer le fichier de configuration de Spring. A ce stade, le projet [mv-pam-spring-metier] doit être correct. Imaginez un test JUnit qui en apporte la preuve, créer un projet Maven de type [Web Application], ajouter les dépendances nécessaires (JSF et la couche [métier]), copier les éléments du projet JSF / EJB de la version précédente dans le nouveau projet JSF / Spring, corriger les erreurs qui apparaissent, copier le fichier de configuration de Spring du projet [mv-pam-spring-metier] dans le projet web, ajouter au bean [ApplicationData] le code permettant d'instancier les beans Spring, compléter le fichier de configuration [faces-config.xml], tester l'application web. 250/257

251 13 Version 9 : Implémentation de la couche web avec Primefaces Pré-requis : on lira " Introduction à Primefaces " dans [ref3]. Question 1 : construire un nouveau projet Maven de type [Web Application] où les pages XHTML de l'exemple précédent seraient construites avec des composants Primefaces. On ne changera rien aux beans. La page d'accueil utilisera les composants <p:panel>, <p:inputtext>, <p:selectonemenu>, <p:message> : Une saisie erronée : Une simulation : 251/257

252 La liste des simulations utilise les composants <p:datatable>, <p:commandlink> : Les méthodes AJAX mettront à jour la zone d'id='formulaire' qui inclut la totalité de la page (cf ligne 11 ci-dessous) : 1. <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 3. <html xmlns=" 4. xmlns:h=" 5. xmlns:f=" 6. xmlns:ui=" <f:view locale="#{sessiondata.locale}"> <h:body style="background-image: url('$ {request.contextpath}/resources/images/standard.jpg');"> 11. <h:form id="formulaire"> 1 <!-- entete --> 13. <ui:include src="entete.xhtml" /> 252/257

La persistance des données dans les applications : DAO, JPA, Hibernate... COMPIL 2010 francois.jannin@inp-toulouse.fr 1

La persistance des données dans les applications : DAO, JPA, Hibernate... COMPIL 2010 francois.jannin@inp-toulouse.fr 1 La persistance des données dans les applications : DAO, JPA, Hibernate... COMPIL 2010 francois.jannin@inp-toulouse.fr 1 Plan 1. Généralités sur la persistance des données dans les applications 2. La connection

Plus en détail

Compte Rendu d intégration d application

Compte Rendu d intégration d application ISMA 3EME ANNEE Compte Rendu d intégration d application Compte Rendu Final Maxime ESCOURBIAC Jean-Christophe SEPTIER 19/12/2011 Table des matières Table des matières... 1 Introduction... 3 1. Le SGBD:...

Plus en détail

1. Installation d'un serveur d'application JBoss:

1. Installation d'un serveur d'application JBoss: EPITA Ala Eddine BEN SALEM App-Ing2 J2EE T.P. 4 EJB3, Serveur d'application JBoss 1. Installation d'un serveur d'application JBoss: télécharger l'archive du serveur JBoss à l'adresse: http://sourceforge.net/projects/jboss/files/jboss/jboss-5.0.0.ga/jboss-5.0.0.ga.zip/download

Plus en détail

Création d une application JEE

Création d une application JEE Création d une application JEE Rédacteurs : Alexandre Baillif, Philippe Lacomme, Raksmey Phan et Michaël PLAN Date : juillet 2010 Mise à jour : Michaël PLAN Date : octobre 2014 Avertissement : - ce document

Plus en détail

Application web de gestion de comptes en banques

Application web de gestion de comptes en banques Application web de gestion de comptes en banques Objectif Réaliser une application Web permettant à un client de gérer ses comptes en banque Diagramme de cas d'utilisation 1 Les cas d'utilisation Connexion

Plus en détail

Quelques patterns pour la persistance des objets avec DAO DAO. Principe de base. Utilité des DTOs. Le modèle de conception DTO (Data Transfer Object)

Quelques patterns pour la persistance des objets avec DAO DAO. Principe de base. Utilité des DTOs. Le modèle de conception DTO (Data Transfer Object) Quelques patterns pour la persistance des objets avec DAO Ce cours présente des modèles de conception utilisés pour effectuer la persistance des objets Université de Nice Sophia-Antipolis Version 1.4 30/8/07

Plus en détail

Tutoriel d installation de Hibernate avec Eclipse

Tutoriel d installation de Hibernate avec Eclipse Tutoriel d installation de Hibernate avec Eclipse Table des matières 1. Création de la base de données... 4 2. Installation de Hibernate Synchronizer... 5 3. Utilisation de Hibernate... 6 3.1 Création

Plus en détail

Refonte front-office / back-office - Architecture & Conception -

Refonte front-office / back-office - Architecture & Conception - Refonte front-office / back-office - Architecture & Conception - GLG204 - Architectures Logicielles Java 2008/2009 Nom : Cédric Poisson Matricule : 06-49012 Version : 1.0 Jeudi 28 mai 2009 1 / 23 Table

Plus en détail

OpenPaaS Le réseau social d'entreprise

OpenPaaS Le réseau social d'entreprise OpenPaaS Le réseau social d'entreprise Spécification des API datastore SP L2.3.1 Diffusion : Institut MinesTélécom, Télécom SudParis 1 / 12 1OpenPaaS DataBase API : ODBAPI...3 1.1Comparaison des concepts...3

Plus en détail

Types d applications pour la persistance. Outils de développement. Base de données préexistante? 3 modèles. Variantes avec passerelles

Types d applications pour la persistance. Outils de développement. Base de données préexistante? 3 modèles. Variantes avec passerelles Types d applications pour la persistance Université de Nice Sophia-Antipolis Version 0.9 28/8/07 Richard Grin Toutes les applications n ont pas une complexité qui nécessite une architecture n- tiers Ce

Plus en détail

Java DataBaseConnectivity

Java DataBaseConnectivity Java DataBaseConnectivity JDBC JDBC est une API Java (ensemble de classes et d interfaces défini par SUN et les acteurs du domaine des SGBD) permettant d accéder aux bases de données à l aide du langage

Plus en détail

TP1 : Initiation à Java et Eclipse

TP1 : Initiation à Java et Eclipse TP1 : Initiation à Java et Eclipse 1 TP1 : Initiation à Java et Eclipse Systèmes d Exploitation Avancés I. Objectifs du TP Ce TP est une introduction au langage Java. Il vous permettra de comprendre les

Plus en détail

Institut Supérieur de Gestion. Cours pour 3 ème LFIG. Java Enterprise Edition Introduction Bayoudhi Chaouki

Institut Supérieur de Gestion. Cours pour 3 ème LFIG. Java Enterprise Edition Introduction Bayoudhi Chaouki Institut Supérieur de Gestion Cours pour 3 ème LFIG Java Enterprise Edition Introduction Bayoudhi Chaouki 1 Java EE - Objectifs Faciliter le développement de nouvelles applications à base de composants

Plus en détail

Java pour le Web. Cours Java - F. Michel

Java pour le Web. Cours Java - F. Michel Java pour le Web Cours Java - F. Michel Introduction à JEE 6 (ex J2EE) Historique Qu'est-ce que JEE JEE : Java Entreprise Edition (ex J2EE) 1. Une technologie outils liés au langage Java + des spécifications

Plus en détail

24/11/2011. Cours EJB/J2EE Copyright Michel Buffa. Plan du cours. EJB : les fondamentaux. Enterprise Java Bean. Enterprise Java Bean.

24/11/2011. Cours EJB/J2EE Copyright Michel Buffa. Plan du cours. EJB : les fondamentaux. Enterprise Java Bean. Enterprise Java Bean. Plan du cours 2 Introduction générale : fondamentaux : les fondamentaux Michel Buffa (buffa@unice.fr), UNSA 2002, modifié par Richard Grin (version 1.1, 21/11/11), avec emprunts aux supports de Maxime

Plus en détail

TD/TP 1 Introduction au SDK d Android

TD/TP 1 Introduction au SDK d Android TD/TP 1 Introduction au SDK d Android Romain Raveaux 1 Introduction Android est un système d'exploitation pour téléphone portable de nouvelle génération développé par Google. Celui-ci met à disposition

Plus en détail

Etude de cas : PGE JEE V2

Etude de cas : PGE JEE V2 Arrivés à ce point du tutoriel, nous savons créer une application Web implémentant la persistance des données. Toutefois, le modèle de cette application était simple et composé d'une unique classe et les

Plus en détail

TP JEE Développement Web en Java. Dans ce TP nous commencerons la programmation JEE par le premier niveau d une application JEE : l application web.

TP JEE Développement Web en Java. Dans ce TP nous commencerons la programmation JEE par le premier niveau d une application JEE : l application web. ASTRIUM - Toulouse JEE Formation 2013 TP JEE Développement Web en Java Dans ce TP nous commencerons la programmation JEE par le premier niveau d une application JEE : l application web. Figure 1 Architecture

Plus en détail

La base de données XML exist. A. Belaïd

La base de données XML exist. A. Belaïd La base de données XML exist Introduction Qu est-ce-que exist? C est une base de donnée native, entièrement écrite en Java XML n est pas une base de données en soi Bien qu il possède quelques caractéristiques

Plus en détail

JOnAS Day 5.1. Outils de développements

JOnAS Day 5.1. Outils de développements JOnAS Day 5.1 Outils de développements Agenda Introduction Plugin Eclipse (JOPE) Plugin NetBeans (JOnbAS) Cargo 2 Bull, 2009 JOnAS Day 5.1 Objectifs - Réduire les temps de développement - Construction

Plus en détail

Introduction à JDBC. Accès aux bases de données en Java

Introduction à JDBC. Accès aux bases de données en Java Introduction à JDBC Accès aux bases de données en Java Eric Cariou Université de Pau et des Pays de l'adour Département Informatique Eric.Cariou@univ-pau.fr 1 Introduction JDBC : Java Data Base Connectivity

Plus en détail

1 JBoss Entreprise Middleware

1 JBoss Entreprise Middleware 1 JBoss Entreprise Middleware Les produits de la gamme JBoss Entreprise Middleware forment une suite de logiciels open source permettant de construire, déployer, intégrer, gérer et présenter des applications

Plus en détail

E-mail : contact@nqicorp.com - Web : http://www.nqicorp.com

E-mail : contact@nqicorp.com - Web : http://www.nqicorp.com - 5, rue Soutrane - 06560 Valbonne Sophia-Antipolis E-mail : contact@nqicorp.com - Web : http://www.nqicorp.com NQI Orchestra 3.3 - Guide d'installation Windows.................................................................

Plus en détail

Chapitre 1 : Introduction aux bases de données

Chapitre 1 : Introduction aux bases de données Chapitre 1 : Introduction aux bases de données Les Bases de Données occupent aujourd'hui une place de plus en plus importante dans les systèmes informatiques. Les Systèmes de Gestion de Bases de Données

Plus en détail

Reporting Services - Administration

Reporting Services - Administration Reporting Services - Administration Comment administrer SQL Server Reporting Services Cet article a pour but de présenter comment gérer le serveur depuis le "portail" de Reporting Services. Nous verrons

Plus en détail

Guide de configuration de SQL Server pour BusinessObjects Planning

Guide de configuration de SQL Server pour BusinessObjects Planning Guide de configuration de SQL Server pour BusinessObjects Planning BusinessObjects Planning XI Release 2 Copyright 2007 Business Objects. Tous droits réservés. Business Objects est propriétaire des brevets

Plus en détail

La gestion de la persistance avec Hibernate. Hibernate et la gestion de persistance. La gestion de la persistance (1/2) Introduction

La gestion de la persistance avec Hibernate. Hibernate et la gestion de persistance. La gestion de la persistance (1/2) Introduction La gestion de la persistance avec et la gestion de persistance Claude Duvallet Université du Havre UFR Sciences et Techniques 25 rue Philippe Lebon - BP 540 76058 LE HAVRE CEDEX Claude.Duvallet@gmail.com

Plus en détail

1. Installation du Module

1. Installation du Module 1 sur 10 Mise en place du Module Magento V 1.5.7 1. Installation du Module Vous pouvez installer le module de deux façons différentes, en passant par Magento Connect, ou directement via les fichiers de

Plus en détail

Initiation à JAVA et à la programmation objet. raphael.bolze@ens-lyon.fr

Initiation à JAVA et à la programmation objet. raphael.bolze@ens-lyon.fr Initiation à JAVA et à la programmation objet raphael.bolze@ens-lyon.fr O b j e c t i f s Découvrir un langage de programmation objet. Découvrir l'environnement java Découvrir les concepts de la programmation

Plus en détail

TP1. Outils Java Eléments de correction

TP1. Outils Java Eléments de correction c sep. 2008, v2.1 Java TP1. Outils Java Eléments de correction Sébastien Jean Le but de ce TP, sur une séance, est de se familiariser avec les outils de développement et de documentation Java fournis par

Plus en détail

1/ Présentation de SQL Server :

1/ Présentation de SQL Server : Chapitre II I Vue d ensemble de Microsoft SQL Server Chapitre I : Vue d ensemble de Microsoft SQL Server Module: SQL server Semestre 3 Année: 2010/2011 Sommaire 1/ Présentation de SQL Server 2/ Architerture

Plus en détail

Auto-évaluation Aperçu de l architecture Java EE

Auto-évaluation Aperçu de l architecture Java EE Auto-évaluation Aperçu de l architecture Java EE Document: f1218test.fm 22/03/2012 ABIS Training & Consulting P.O. Box 220 B-3000 Leuven Belgium TRAINING & CONSULTING INTRODUCTION AUTO-ÉVALUATION APERÇU

Plus en détail

ORACLE TUNING PACK 11G

ORACLE TUNING PACK 11G ORACLE TUNING PACK 11G PRINCIPALES CARACTÉRISTIQUES : Conseiller d'optimisation SQL (SQL Tuning Advisor) Mode automatique du conseiller d'optimisation SQL Profils SQL Conseiller d'accès SQL (SQL Access

Plus en détail

Installation et prise en main

Installation et prise en main TP1 Installation et prise en main Android est le système d'exploitation pour smartphones, tablettes et autres appareils développé par Google. Pour permettre aux utilisateurs d'installer des applications

Plus en détail

Mise en œuvre des serveurs d application

Mise en œuvre des serveurs d application Nancy-Université Mise en œuvre des serveurs d application UE 203d Master 1 IST-IE Printemps 2008 Master 1 IST-IE : Mise en œuvre des serveurs d application 1/54 Ces transparents, ainsi que les énoncés

Plus en détail

Sage CRM. 7.2 Guide de Portail Client

Sage CRM. 7.2 Guide de Portail Client Sage CRM 7.2 Guide de Portail Client Copyright 2013 Sage Technologies Limited, éditeur de ce produit. Tous droits réservés. Il est interdit de copier, photocopier, reproduire, traduire, copier sur microfilm,

Plus en détail

Rapport de projet de fin d études

Rapport de projet de fin d études TELECOM SUDPARIS VAP : ARCHITECTE DE SERVICES EN RESEAU Rapport de projet de fin d études Comparaison et évaluation de serveurs d'applications Java Sarafou BALDE Muqiu ZHENG Encadrante: Mme Sophie CHABRIDON

Plus en détail

Paginer les données côté serveur, mettre en cache côté client

Paginer les données côté serveur, mettre en cache côté client Paginer les données côté serveur, mettre en cache côté client Vous voulez sélectionner des lignes dans une table, mais celle-ci comporte trop de lignes pour qu il soit réaliste de les ramener en une seule

Plus en détail

Architecture N-Tier. Ces données peuvent être saisies interactivement via l interface ou lues depuis un disque. Application

Architecture N-Tier. Ces données peuvent être saisies interactivement via l interface ou lues depuis un disque. Application Architecture Multi-Tier Traditionnellement une application informatique est un programme exécutable sur une machine qui représente la logique de traitement des données manipulées par l application. Ces

Plus en détail

Projet Viticulture - TP 3 : bases de données distantes BTS Services informatiques aux organisations

Projet Viticulture - TP 3 : bases de données distantes BTS Services informatiques aux organisations Projet Viticulture TP 3 : bases de données externes Description du thème Partie 1 : bases de données locales SQLite Partie 2 : projet H2O stockage local Partie 3 : bases de données distantes Partie 4 :

Plus en détail

RMI le langage Java XII-1 JMF

RMI le langage Java XII-1 JMF Remote Method Invocation (RMI) XII-1 Introduction RMI est un ensemble de classes permettant de manipuler des objets sur des machines distantes (objets distants) de manière similaire aux objets sur la machine

Plus en détail

Avant-propos 1. Avant-propos...3 2. Organisation du guide...3 3. À qui s'adresse ce guide?...4

Avant-propos 1. Avant-propos...3 2. Organisation du guide...3 3. À qui s'adresse ce guide?...4 Les exemples cités tout au long de cet ouvrage sont téléchargeables à l'adresse suivante : http://www.editions-eni.fr. Saisissez la référence ENI de l'ouvrage EP5EJAV dans la zone de recherche et validez.

Plus en détail

Environnements de développement (intégrés)

Environnements de développement (intégrés) Environnements de développement (intégrés) Tests unitaires, outils de couverture de code Patrick Labatut labatut@di.ens.fr http://www.di.ens.fr/~labatut/ Département d informatique École normale supérieure

Plus en détail

E-mail : contact@nqicorp.com - Web : http://www.nqicorp.com

E-mail : contact@nqicorp.com - Web : http://www.nqicorp.com - 5, rue Soutrane - 06560 Valbonne Sophia-Antipolis E-mail : contact@nqicorp.com - Web : http://www.nqicorp.com NQI Orchestra 3.3 - Guide d'installation Linux....................................................................

Plus en détail

Application de lecture de carte SESAM-Vitale Jeebop

Application de lecture de carte SESAM-Vitale Jeebop Application de lecture de carte SESAM-Vitale Jeebop Présentation Le module de lecture de carte SESAM-Vitale Jeebop est une application Java Web Start, c'est à dire une application Java qui se télécharge

Plus en détail

Gestion des applications, TI. Tout droits réservés, Marcel Aubin

Gestion des applications, TI. Tout droits réservés, Marcel Aubin Gestion des applications, TI Techniques 1 Virtual box P. 3 P. 5 Table de contenu «cloner» un disque Créer une machine virtuelle d'un fichier.vdi existant P. 7 A faire pour les machines de «Remedy» P. 8

Plus en détail

Utilisation de JAVA coté Application serveur couplé avec Oracle Forms Hafed Benteftifa www.degenio.com Novembre 2008

Utilisation de JAVA coté Application serveur couplé avec Oracle Forms Hafed Benteftifa www.degenio.com Novembre 2008 Introduction Utilisation de JAVA coté Application serveur couplé avec Oracle Forms Hafed Benteftifa www.degenio.com Novembre 2008 Forms 10g permet l utilisation du JAVA côté client et côté application

Plus en détail

Europa. Développement JEE 5. avec Eclipse. K a r i m D j a a f a r. A v e c l a c o n t r i b u t i o n d e O l i v i e r S a l v a t o r i

Europa. Développement JEE 5. avec Eclipse. K a r i m D j a a f a r. A v e c l a c o n t r i b u t i o n d e O l i v i e r S a l v a t o r i Développement JEE 5 avec Eclipse Europa K a r i m D j a a f a r A v e c l a c o n t r i b u t i o n d e O l i v i e r S a l v a t o r i Groupe Eyrolles, 2008, ISBN : 978-2-212-12061-5 10 L API JPA et la

Plus en détail

Initiation aux bases de données (SGBD) Walter RUDAMETKIN

Initiation aux bases de données (SGBD) Walter RUDAMETKIN Initiation aux bases de données (SGBD) Walter RUDAMETKIN Bureau F011 Walter.Rudametkin@polytech-lille.fr Moi Je suis étranger J'ai un accent Je me trompe beaucoup en français (et en info, et en math, et...)

Plus en détail

Compte-rendu de projet de Système de gestion de base de données

Compte-rendu de projet de Système de gestion de base de données Compte-rendu de projet de Système de gestion de base de données Création et utilisation d'un index de jointure LAMBERT VELLER Sylvain M1 STIC Université de Bourgogne 2010-2011 Reponsable : Mr Thierry Grison

Plus en détail

INTRODUCTION A JAVA. Fichier en langage machine Exécutable

INTRODUCTION A JAVA. Fichier en langage machine Exécutable INTRODUCTION A JAVA JAVA est un langage orienté-objet pur. Il ressemble beaucoup à C++ au niveau de la syntaxe. En revanche, ces deux langages sont très différents dans leur structure (organisation du

Plus en détail

Plan du cours. Historique du langage http://www.oracle.com/technetwork/java/index.html. Nouveautés de Java 7

Plan du cours. Historique du langage http://www.oracle.com/technetwork/java/index.html. Nouveautés de Java 7 Université Lumière Lyon 2 Faculté de Sciences Economiques et Gestion KHARKIV National University of Economic Introduction au Langage Java Master Informatique 1 ère année Julien Velcin http://mediamining.univ-lyon2.fr/velcin

Plus en détail

EJB. Karim Bouzoubaa Issam Kabbaj

EJB. Karim Bouzoubaa Issam Kabbaj EJB Karim Bouzoubaa Issam Kabbaj Problématique html jsp Objets java jdbc table Pour accéder aux données : Établir une connexion avec la BD Envoyer une requête Traiter la requête Assurer l'intégrité Or

Plus en détail

TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile

TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile Dans ce TP, vous apprendrez à définir le type abstrait Pile, à le programmer en Java à l aide d une interface

Plus en détail

4. Utilisation d un SGBD : le langage SQL. 5. Normalisation

4. Utilisation d un SGBD : le langage SQL. 5. Normalisation Base de données S. Lèbre slebre@unistra.fr Université de Strasbourg, département d informatique. Présentation du module Contenu général Notion de bases de données Fondements / Conception Utilisation :

Plus en détail

Cedric Dumoulin (C) The Java EE 7 Tutorial http://docs.oracle.com/javaee/7/tutorial/doc/

Cedric Dumoulin (C) The Java EE 7 Tutorial http://docs.oracle.com/javaee/7/tutorial/doc/ Cedric Dumoulin (C) The Java EE 7 Tutorial http://docs.oracle.com/javaee/7/tutorial/doc/ Webographie The Java EE 7 Tutorial http://docs.oracle.com/javaee/7/tutorial/doc/ Les slides de cette présentation

Plus en détail

Didacticiel de mise à jour Web

Didacticiel de mise à jour Web Didacticiel de mise à jour Web Copyright 1995-2012 Esri All rights reserved. Table of Contents Didacticiel : Création d'une application de mise à jour Web.................. 0 Copyright 1995-2012 Esri.

Plus en détail

MODE OPERATOIRE OPENOFFICE BASE

MODE OPERATOIRE OPENOFFICE BASE MODE OPERATOIRE OPENOFFICE BASE Openoffice Base est un SGBDR : Système de Gestion de Base de Données Relationnelle. L un des principaux atouts de ce logiciel est de pouvoir gérer de façon efficace et rapide

Plus en détail

2 Grad Info Soir Langage C++ Juin 2007. Projet BANQUE

2 Grad Info Soir Langage C++ Juin 2007. Projet BANQUE 2 Grad Info Soir Langage C++ Juin 2007 Projet BANQUE 1. Explications L'examen comprend un projet à réaliser à domicile et à documenter : - structure des données, - objets utilisés, - relations de dépendance

Plus en détail

TP1 : Initiation à Java et Eclipse

TP1 : Initiation à Java et Eclipse TP1 : Initiation à Java et Eclipse 1 I. Objectif du TP TP1 : Initiation à Java et Eclipse Programmation Mobile Initiation à l environnement Eclipse et aux notions de base du langage Java. II. Environnement

Plus en détail

1. Qu'est-ce que SQL?... 2. 2. La maintenance des bases de données... 2. 3. Les manipulations des bases de données... 5

1. Qu'est-ce que SQL?... 2. 2. La maintenance des bases de données... 2. 3. Les manipulations des bases de données... 5 1. Qu'est-ce que SQL?... 2 2. La maintenance des bases de données... 2 2.1 La commande CREATE TABLE... 3 2.2 La commande ALTER TABLE... 4 2.3 La commande CREATE INDEX... 4 3. Les manipulations des bases

Plus en détail

Java 7 Les fondamentaux du langage Java

Java 7 Les fondamentaux du langage Java 184 Java 7 Les fondamentaux du langage Java 1.1 Les bibliothèques graphiques Le langage Java propose deux bibliothèques dédiées à la conception d'interfaces graphiques. La bibliothèque AWT et la bibliothèque

Plus en détail

FileMaker 13. Guide ODBC et JDBC

FileMaker 13. Guide ODBC et JDBC FileMaker 13 Guide ODBC et JDBC 2004-2013 FileMaker, Inc. Tous droits réservés. FileMaker, Inc. 5201 Patrick Henry Drive Santa Clara, Californie 95054 FileMaker et Bento sont des marques commerciales de

Plus en détail

JAVA 8. JAVA 8 - Les fondamentaux du langage. Les fondamentaux du langage Java. Avec exercices pratiques et corrigés JAVA 8 29,90.

JAVA 8. JAVA 8 - Les fondamentaux du langage. Les fondamentaux du langage Java. Avec exercices pratiques et corrigés JAVA 8 29,90. Analyste et développeur pendant plus de 10 ans, Thierry GROUSSARD s est ensuite orienté vers la formation et plus particulièrement dans le domaine du développement. Sa connaissance approfondie des besoins

Plus en détail

DEVAKI NEXTOBJET PRESENTATION. Devaki Nextobjects est un projet sous license GNU/Public.

DEVAKI NEXTOBJET PRESENTATION. Devaki Nextobjects est un projet sous license GNU/Public. DEVAKI NEXTOBJET 1 Présentation...2 Installation...3 Prérequis...3 Windows...3 Linux...3 Exécution...4 Concevoir une BDD avec Devaki NextObject...5 Nouveau MCD...5 Configurer la connexion à la base de

Plus en détail

SOA Open Source Intégration des services et business process dans une architecture SOA Open Source. Bruno Georges JBoss, a Division of Red Hat

SOA Open Source Intégration des services et business process dans une architecture SOA Open Source. Bruno Georges JBoss, a Division of Red Hat SOA Open Source Intégration des services et business process dans une architecture SOA Open Source Bruno Georges JBoss, a Division of Red Hat Agenda Cas d etude Contexte métier Les bénéfices Open Source

Plus en détail

1. Considérations sur le développement rapide d'application et les méthodes agiles

1. Considérations sur le développement rapide d'application et les méthodes agiles Chapitre 1 Introduction 1. Considérations sur le développement rapide d'application et les méthodes agiles 1.1 Rappel Longtemps les méthodes en cascade ou en V ont été opposées aux démarches empiriques

Plus en détail

Formation Webase 5. Formation Webase 5. Ses secrets, de l architecture MVC à l application Web. Adrien Grand <jpountz@via.ecp.fr> Centrale Réseaux

Formation Webase 5. Formation Webase 5. Ses secrets, de l architecture MVC à l application Web. Adrien Grand <jpountz@via.ecp.fr> Centrale Réseaux Formation Webase 5 Ses secrets, de l architecture MVC à l application Web Adrien Grand Centrale Réseaux Sommaire 1 Obtenir des informations sur Webase 5 2 Composants de Webase 5 Un

Plus en détail

Généralités sur le Langage Java et éléments syntaxiques.

Généralités sur le Langage Java et éléments syntaxiques. Généralités sur le Langage Java et éléments syntaxiques. Généralités sur le Langage Java et éléments syntaxiques....1 Introduction...1 Genéralité sur le langage Java....1 Syntaxe de base du Langage...

Plus en détail

TAGREROUT Seyf Allah TMRIM

TAGREROUT Seyf Allah TMRIM TAGREROUT Seyf Allah TMRIM Projet Isa server 2006 Installation et configuration d Isa d server 2006 : Installation d Isa Isa server 2006 Activation des Pings Ping NAT Redirection DNS Proxy (cache, visualisation

Plus en détail

SOMMAIRE. Travailler avec les requêtes... 3

SOMMAIRE. Travailler avec les requêtes... 3 Access Les requêtes SOMMAIRE Travailler avec les requêtes... 3 A) Créer une requête sélection en mode QBE... 3 B) Exécuter une requête à partir du mode Modifier (QBE)... 3 C) Passer du mode Feuille de

Plus en détail

TD Objets distribués n 3 : Windows XP et Visual Studio.NET. Introduction à.net Remoting

TD Objets distribués n 3 : Windows XP et Visual Studio.NET. Introduction à.net Remoting IUT Bordeaux 1 2005-2006 Département Informatique Licence Professionnelle ~ SI TD Objets distribués n 3 : Windows XP et Visual Studio.NET Introduction à.net Remoting Partie 1 : l'analyseur de performances

Plus en détail

Cours Base de données relationnelles. M. Boughanem, IUP STRI

Cours Base de données relationnelles. M. Boughanem, IUP STRI Cours Base de données relationnelles 1 Plan 1. Notions de base 2. Modèle relationnel 3. SQL 2 Notions de base (1) Définition intuitive : une base de données est un ensemble d informations, (fichiers),

Plus en détail

Projet de Veille Technologique

Projet de Veille Technologique Projet de Veille Technologique Programmation carte à puce - JavaCard Ing. MZOUGHI Ines (i.mzoughi@gmail.com) Dr. MAHMOUDI Ramzi (mahmoudr@esiee.fr) TEST Sommaire Programmation JavaCard Les prérequis...

Plus en détail

Serveur Acronis Backup & Recovery 10 pour Linux. Update 5. Guide d'installation

Serveur Acronis Backup & Recovery 10 pour Linux. Update 5. Guide d'installation Serveur Acronis Backup & Recovery 10 pour Linux Update 5 Guide d'installation Table des matières 1 Avant l'installation...3 1.1 Composants d'acronis Backup & Recovery 10... 3 1.1.1 Agent pour Linux...

Plus en détail

LES ACCES ODBC AVEC LE SYSTEME SAS

LES ACCES ODBC AVEC LE SYSTEME SAS LES ACCES ODBC AVEC LE SYSTEME SAS I. Présentation II. SAS/ACCESS to ODBC III. Driver ODBC SAS IV. Driver ODBC SAS Universel V. Version 8 VI. Références I. Présentation Introduction ODBC, qui signifie

Plus en détail

Quick Start Installation de MDweb version 2.3

Quick Start Installation de MDweb version 2.3 Quick Start Installation de MDweb version 2.3 Date : 2011.08.26 1. Quickstart Quick Start - Installation de MDweb version 2011 Installation Téléchargement et Installation des logiciels requis Déploiement

Plus en détail

TP Composants Java ME - Java EE. Le serveur GereCompteBancaireServlet

TP Composants Java ME - Java EE. Le serveur GereCompteBancaireServlet TP Composants Java ME - Java EE Vous allez, dans ce TP, construire une architecture client serveur, plus précisément MIDlet cliente, servlet serveur. Pour cela, on va d'abord installer la partie serveur

Plus en détail

4. SERVICES WEB REST 46

4. SERVICES WEB REST 46 4. SERVICES WEB REST 46 REST REST acronyme de REpresentational State Transfert Concept introduit en 2000 dans la thèse de Roy FIELDING Est un style d architecture inspiré de l architecture WEB En 2010,

Plus en détail

as Architecture des Systèmes d Information

as Architecture des Systèmes d Information Plan Plan Programmation - Introduction - Nicolas Malandain March 14, 2005 Introduction à Java 1 Introduction Présentation Caractéristiques Le langage Java 2 Types et Variables Types simples Types complexes

Plus en détail

Préparer la synchronisation d'annuaires

Préparer la synchronisation d'annuaires 1 sur 6 16/02/2015 14:24 En utilisant ce site, vous autorisez les cookies à des fins d'analyse, de pertinence et de publicité En savoir plus France (Français) Se connecter Rechercher sur TechNet avec Bing

Plus en détail

Configuration d'un annuaire LDAP

Configuration d'un annuaire LDAP Le serveur Icewarp Configuration d'un annuaire LDAP Version 10.3 Juillet 2011 Icewarp France / DARNIS Informatique i Sommaire Configuration d'un annuaire LDAP 1 Introduction... 1 Qu'est-ce que LDAP?...

Plus en détail

1-Introduction 2. 2-Installation de JBPM 3. 2-JBPM en action.7

1-Introduction 2. 2-Installation de JBPM 3. 2-JBPM en action.7 Sommaire 1-Introduction 2 1-1- BPM (Business Process Management)..2 1-2 J-Boss JBPM 2 2-Installation de JBPM 3 2-1 Architecture de JOBSS JBPM 3 2-2 Installation du moteur JBoss JBPM et le serveur d application

Plus en détail

Accès aux bases de données

Accès aux bases de données 13 Accès aux bases de données Les bases de données fournissent un mécanisme de stockage persistant pour les données d application et dans bien des cas, elles sont essentielles au fonctionnement des applications.

Plus en détail

Serveur d'application Client HTML/JS. Apache Thrift Bootcamp

Serveur d'application Client HTML/JS. Apache Thrift Bootcamp Serveur d'application Client HTML/JS Apache Thrift Bootcamp Pré-requis La liste ci-dessous de logiciels doit être installée et opérationnelle sur la machine des participants : Compilateur thrift http://thrift.apache.org/

Plus en détail

Manuel Utilisateur de l'installation du connecteur Pronote à l'ent

Manuel Utilisateur de l'installation du connecteur Pronote à l'ent de l'installation du connecteur Pronote à l'ent Page : 1/28 SOMMAIRE 1 Introduction...3 1.1 Objectif du manuel...3 1.2 Repères visuels...3 2 Paramétrage de la connexion entre l'ent et Pronote...4 2.1 Informations

Plus en détail

Programmation par composants (1/3) Programmation par composants (2/3)

Programmation par composants (1/3) Programmation par composants (2/3) Programmation par composants (1/3) La programmation par composant vise le développement de logiciel par aggrégation de briques logicielles existantes est indépendante de la POO La programmation par composant

Plus en détail

Petit guide à l'usage des profs pour la rédaction de pages pour le site Drupal du département

Petit guide à l'usage des profs pour la rédaction de pages pour le site Drupal du département Petit guide à l'usage des profs pour la rédaction de pages pour le site Drupal du département Le nouveau site du département Le nouveau site du département est situé, comme l'ancien à l'adresse suivante

Plus en détail

FORMATION 2012-2013. Offre de Formation - Packaging. Les bonnes pratiques du packaging avec Installshield et AdminStudio. Contact et inscriptions

FORMATION 2012-2013. Offre de Formation - Packaging. Les bonnes pratiques du packaging avec Installshield et AdminStudio. Contact et inscriptions www.experteam.fr Offre de Formation - Packaging 2012-2013 FORMATION Les bonnes pratiques du packaging avec Installshield et AdminStudio Numéro d agrément 11921398892 Contact et inscriptions Tél. +33 1

Plus en détail

Introduction à Eclipse

Introduction à Eclipse Introduction à Eclipse Eclipse IDE est un environnement de développement intégré libre (le terme Eclipse désigne également le projet correspondant, lancé par IBM) extensible, universel et polyvalent, permettant

Plus en détail

Cours en ligne Développement Java pour le web

Cours en ligne Développement Java pour le web Cours en ligne Développement Java pour le web We TrainFrance info@wetrainfrance Programme général du cours Développement Java pour le web Module 1 - Programmation J2ee A) Bases de programmation Java Unité

Plus en détail

Environnements de Développement

Environnements de Développement Institut Supérieur des Etudes Technologiques de Mahdia Unité d Enseignement: Environnements de Développement BEN ABDELJELIL HASSINE Mouna m.bnaj@yahoo.fr Développement des systèmes d Information Syllabus

Plus en détail

Architecture JEE. Objectifs attendus. Serveurs d applications JEE. Architectures JEE Normes JEE. Systèmes distribués

Architecture JEE. Objectifs attendus. Serveurs d applications JEE. Architectures JEE Normes JEE. Systèmes distribués Architecture JEE. Objectifs attendus Serveurs d applications JEE Systèmes distribués Architectures JEE Normes JEE couches logicielles, n-tiers framework JEE et design patterns 2007/02/28 Eric Hébert.eheb@yahoo.fr

Plus en détail

Exceptions. 1 Entrées/sorties. Objectif. Manipuler les exceptions ;

Exceptions. 1 Entrées/sorties. Objectif. Manipuler les exceptions ; CNAM NFP121 TP 10 19/11/2013 (Séance 5) Objectif Manipuler les exceptions ; 1 Entrées/sorties Exercice 1 : Lire un entier à partir du clavier Ajouter une méthode readint(string message) dans la classe

Plus en détail

Débuter avec OOo Base

Débuter avec OOo Base Open Office.org Cyril Beaussier Débuter avec OOo Base Version 1.0.7 Novembre 2005 COPYRIGHT ET DROIT DE REPRODUCTION Ce support est libre de droit pour une utilisation dans un cadre privé ou non commercial.

Plus en détail

Bases Java - Eclipse / Netbeans

Bases Java - Eclipse / Netbeans Institut Galilée PDJ Année 2014-2015 Master 1 Environnements Java T.P. 1 Bases Java - Eclipse / Netbeans Il existe plusieurs environnements Java. Il est ESSENTIEL d utiliser la bonne version, et un environnement

Plus en détail

27/11/12 Nature. SDK Python et Java pour le développement de services ACCORD Module(s)

27/11/12 Nature. SDK Python et Java pour le développement de services ACCORD Module(s) Propriétés du Document Source du Document SDK_accords.odt Titre du Document SDK Python et Java pour le développement de services ACCORD Module(s) PyaccordsSDK, JaccordsSDK Responsable Prologue Auteur(s)

Plus en détail