Dans ce TP vous allez écrire une application de gestion de comptes bancaires.
Contrairement au TP 1 vous ne partirez pas de tables pré-existantes. Vous écrirez une entité CompteBancaire
et la table correspondante sera générée automatiquement à partir de l'entité. Vous écrirez un EJB session bean GestionnaireDeCompteBancaire
pour gérer les comptes bancaires. Le TP comporte également l'écriture de pages JSF et de backing beans pour accèder à ces comptes, déposer ou retirer de l'argent, transférer de l'argent d'un compte bancaire à un autre, chercher des comptes...
Ce TP est long. Il vous faudra le terminer seul(e) si vous n'avez pas pu le faire pendant les séances du cours, car le TP suivant est la suite de ce TP.
Support sur JPA distribué pour ce cours.
Rappel : si vous avez des erreurs curieuses, faites un "Clean and Build" du projet avant de lancer son exécution.
Vous travaillerez sur une nouvelle base de données !
Créez une nouvelle base de données "banque" en utilisant Workbench de MySQL comme pour la création de la base de données "customer".
Lancez le workbench de MySQL (menu Windows >MySQL > MySQL Workbench).
Dans le workbench, menu File > New Query Tab.
Dans l'onglet créé, tapez
create database customer;
Lancez la commande en cliquant sur l'icône "Execute the selected portion of the script, or everything is there is no selection". La zone Output du bas devrez vous confirmer que tout s'est bien passé.
Vous pouvez sortir de Workbench.
Vous devriez maintenant voir cette nouvelle base depuis NetBeans, avec la même entrée que pour la base customer (dans la fenêtre Services), en ouvrant la sous-entrée "Other database" (éventuellement après un refresh de l'entrée par clic droit et Refresh).
Les tables de cette base vont êtres créées à partir des classes entités que vous allez écrire.
Créez un nouveau projet comme dans les TPs précédents. Donnez-lui un nom, par exemple "TPBanque". Donnez bien Payara comme serveur.
Rappel : pour accélérer les prochains démarrages du serveur d'application, vous ferez un Undeploy des précédents TPs dans l'entrée Applications du serveur (onglet Services) lorsque le serveur sera démarré.
Au lieu de créer une entité à partir d'une table de la base de données, vous ferez une simple création d'entité : clic droit sur le projet ejb TPBanque-ejb, choix New > "Entity Class..." (si "Entity Class" n'apparait pas, "Other..." > Persistence > "Entity Class").
Donnez-lui le nom CompteBancaire, et spécifiez un paquetage, entities par exemple.
Dans les copies d'écran ci-dessous ne tenez pas compte des noms de projet, nom de base de données ou d'unité de persistance qui pourraient être différents dans votre propre projet.
Dans le code source de l'entité, vérifiez qu'une propriété est le numéro d'identification (la clé primaire, déclaré par l'annotation @Id
), et qu'elle sera générée automatiquement par le SGBD (annotation @GeneratedValue(strategy=GenerationType.AUTO
).
Vous pouvez enlever la méthode setId de l'entité. Il ne faut pas modifier la clé d'une entité. JPA accédera aux champs de l'entité par ses champs et pas par ses propriétés (si vous ne voyez pas pourquoi, relisez le cours).
Un fichier persistence.xml (configuration pour JPA) a été créé. Vous pouvez le voir en ouvrant l'entrée "Other Sources" du projet ; il est placé sous META-INF. Vous allez le modifier par la suite. Pour voir sa position dans l'arborescence des fichiers, clic sur l'onglet Files en haut ; il est placé dans le répertoire src/main/resources selon les recommandations pour un projet Maven. Revenez ensuite à l'onglet Projects.
Modifier l'entité CompteBancaire afin que chaque compte bancaire possède :
Pour faire afficher ces valeurs dans les pages JSF il faut créer des propriétés avec getter et setter. Pour cela vous pouvez utiliser le menu contextuel (clic droit dans le code de l'entité) "Insert Code... > Add Property" pour générer en même temps une variable d'instance, un getter et un setter. Si vous utilisez ce menu, réorganisez le code pour mettre les variables d'instance nom et solde au début de la classe, juste après la variable d'instance "id". Vous pouvez aussi commencer par écrire les variables d'instance pour le nom et le solde et ensuite utiliser ajouter les getter et les setter : Alt-Insert et "Getter and Setter...".
Ajouter des méthodes pour retirer et ajouter de l'argent et pour consulter le solde, ainsi qu'un constructeur prenant en paramètre le nom du propriétaire et le solde initial
Pour le moment on fait simple : si on retire plus d'argent que le solde, le solde passe à 0. Dans la suite du TP ette générosité sera tempérée en ajoutant du code dans l'application pour ne valider que les retraits qui ne dépassent pas le solde (pour le transfert entre 2 comptes et pour le mouvement sur un compte) et on reviendra aussi dessus plus tard dans la partie optionnelle de ce TP.
public CompteBancaire(String nom, int solde) { this.nom = nom; this.solde = solde; } public void deposer(int montant) { solde += montant; } public void retirer(int montant) { if (montant < solde) { solde -= montant; } else { solde = 0; } }
Si vous connaissez bien le cours sur JPA, vous savez que vous devez aussi ajouter un autre constructeur... N'oubliez pas de le faire.
Ce n'est pas grave s'il n'y a pas de table pour les comptes bancaires, vous allez bientôt modifier le fichier persistence.xml pour que le serveur la crée automatiquement lors du déploiement de l'application, dès qu'un EntityManager sera ajouté au code de l'application.
Vous allez maintenant écrire un EJB session GestionnaireCompte
(sous-paquetage ejb
du paquetage de base du projet) pour gérer les comptes bancaires dans la base de données. Ce sera un EJB stateless.
Créez l'EJB : clic droit sur le sous-projet EJB, New > Session Bean comme pour le TP 1.
Cet EJB va utiliser une base de données pour conserver les informations sur les comptes bancaires. Il ne connait pas la base de données par son nom dans le SGBD mais plutôt par un nom JNDI de source de données (le nom qui va remplacer le nom actuellement dans persistence.xml), qui sera conservé sur le serveur d'application. Pour cela vous allez annoter la classe de l'EJB avec une déclaration de la source de données.
banque est le nom de la base de données que vous avez créée au début de ce TP. Vous retrouvez les propriétés non standards pour @DataSourceDefinition
que vous avez utilisées lors de l'installation de l'environnement pour la définition du pool de connexions associé à MySQL ; n'oubliez de modifier le nom de l'utilisateur (user ; sans doute root) et le mot de passe (password) de MySQL :
@DataSourceDefinition ( className="com.mysql.cj.jdbc.MysqlDataSource", name="java:app/jdbc/banque", serverName="localhost", portNumber=3306, user="votre nom", // nom et password="votre mot de passe", // mot de passe que vous avez donnés lors de la création de la base de données databaseName="banque", properties= { "characterEncoding=UTF-8", "nullCatalogMeansCurrent=true", "nullNamePatternMatchesAll=false", "serverTimezone=UTC", "useInformationSchema=true", "useSSL=false", "useUnicode=true", "zeroDateTimeBehavior=CONVERT_TO_NULL", "allowPublicKeyRetrieval=true" } ) @Stateless public class GestionnaireCompte {
Remarque : Lorsque l'application est déployée sur le serveur d'application, la base de données "banque" est associée sur le serveur d'application (Payara) au nom JNDI "java:app/jdbc/banque". La portée de ce nom JNDI sera l'application (java:app) et donc tout code Java de l'application, qui fera référence à ce nom JNDI désignera la base de données "banque". Cette annotation ne doit apparaître qu'une seule fois dans l'application pour chaque source de données. La base de données peut être alors utilisée pour tous les EJB par l'intermédiaire de l'unité de persistance définie dans persistence.xml.
Maintenant que la source de données a été définie, il est temps d'aller modifier le fichier persistence.xml
pour qu'il référence cette source de données.
Faites afficher le fichier persistence.xml (entrée "Other Sources" > META-INF du projet). Comme pour le TP 1, n'utilisez pas la vue "Design" qui ne fonctionne pas (au moment de l'écriture de ces lignes).
Commencez par changer le nom de l'unité de persistence pour lui donner un nom plus adapté, par exemple "banquePU".
Enlevez la balise <class>
qui est inutile car il n'y aura qu'une seule unité de persistance et donc toutes les entités seront gérées par elle.
Dans une balise <jta-data-source>
, donnez la valeur de l'attribut name
de l'annotation @DataSourceDefinition
ci-dessus : <jta-data-source>java:app/jdbc/banque
</jta-data-source>
. Il faudra faire un "deploy" du projet pour que le serveur d'application prenne en compte cette modification d'un fichier de configuration.
Vous allez ajouter une propriété standard pour que les tables de la base de données soient supprimées et recréées à chaque déploiement de l'application :
<properties> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> </properties>Cette option est utile au début du développement pour faire des tests mais il faudra évidemment la modifier par la suite. Vous pourrez passer en mode "
create
" lorsque les structures des tables seront fixées, pour ne pas perdre toutes les données déjà enregistrées dans la base de données, et il faudra passer en mode "none
" lorsque toutes les tables auront été créées.
Cet EJB va proposer pour le moment deux fonctionnalités :
public void creerCompte(CompteBancaire c) {...}
qui va créer le compte dans la base.public List<CompteBancaire> getAllComptes() {...}
qui va faire l'équivalent d'un "select *" dans la base.Pour cela, il faut commencer par injecter un EntityManager
. Vous pouvez vous aider de ce qui a été fait lors du TP 1.
Dès l'ajout du code pour injecter l'EntityManager, un déploiement du projet dans le serveur d'application (lancez un Run du projet, par exemple) suffit à créer la table pour les comptes bancaires dans la base de données (le serveur d'application doit évidemment être démarré). Vérifiez-le. Une autre table SEQUENCE est aussi créée ; elle servira à fournir des clés primaires lors des insertions de nouvelles lignes dans les tables.
Pour la méthode creerCompte
il suffit de modifier la méthode persist
en changeant son nom et le type du paramètre.
Pour écrire la méthode qui retourne tous les comptes vous pouvez ajouter une requête nommée à l'entité (inspirez-vous des entités du TP 1) ou donner le code de la requête directement dans l'EJB. Inspirez-vous des EJB session du TP 1 pour l'écriture de la méthode. Encore mieux : utilisez un TypedQuery<T>
à la place d'un Query
pour faire vérifier les types par le compilateur Java.
Ajoutez un EJB session singleton Init
(voir cours sur les EJB) pour créer 4 comptes au démarrage de l'application si la table des comptes est vide. Clic droit sur le module "EJB" et "New > Session Bean..." ; il y a une option pour un EJB singleton.
Les 4 comptes à créer : John Lennon, 150000 ; Paul McCartney, 950000 ; Ringo Starr, 20000 ; Georges Harrisson, 100000.
Un peu d'aide pour ce singleton ?
Passez en mode "create" à la place de "drop and create" dans persistence.xml pour tester cette initialisation en relançant plusieurs fois l'exécution du projet. Il ne doit y avoir que 4 comptes après chaque exécution.
Allez voir le fichier web.xml dans "Configuration Files". Comme pour le TP 1, modifiez-le pour mettre index.xhtml comme page "welcome".
Ajoutez aussi la classe de configuration de JSF.
En vous inspirant du TP sur les templates écrivez un template avec une barre de titre en haut, un menu à gauche et une page principale au centre (le même type de template que pour le TP 1). Enregistrez-le dans WEB-INF pour plus de sécurité.
Une fois généré par NetBeans, ajoutez-y une nouvelle partie "metadata" avant <h:head>
(<ui:insert name="metadata"/>
) ; par défaut elle est vide mais les clients du template pourront y mettre une section <metadata>
, en particulier pour les paramètres de vue.
N'oubliez pas de remplacer name="./css/default.css"
par name="/css/default.css"
et de même pour la ligne suivante.
Ce template utilise un fichier externe templates/menu.xhtml
qui contient un menu, comme dans le TP sur les templates. Le menu va être rempli dans la suite du TP ; pour le moment ne mettez qu'une liste vide avec la balise <ul>
dans menu.xhtml
:
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"> <ul> </ul> </ui:composition>
Indiquez que vous allez utiliser PrimeFaces. Le plus simple est de modifier directement pom.xml en ajoutant une dépendance :
<dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>7.0</version> </dependency>
Indiquez la dernière version de PrimeFaces, 7.0 à la date de l'écriture de ces lignes (voir comment trouver le numéro de la dernière version dans le TP 1).
Vous allez commencer par écrire une page qui affiche la liste de tous les comptes dans une table. Vous utiliserez une table PrimeFaces. La page doit utiliser le template de présentation que vous venez de créer. Voici comment faire :
GestionComptesBean
(New > "JSF CDI Bean") de portée view (la classe implémentera donc Serializable
), avec une méthode getAllComptes()
qui retourne la liste de tous les comptes (ce qui crée une propriété "read only" allComptes
). Dans le code, injectez l'EJB GestionnaireCompte
et utilisez-le pour récupérer la liste de tous les comptes.Ajoutez un lien vers cette page dans le fichier des menus, avec <h:link>
comme pour le TP sur les templates.
IMPORTANT : dans toutes vos pages il faut ajouter les zones d'affichage de message. Ajoutez donc <h:messages/>
juste après le formulaire inséré dans la page par NetBeans.
Ajoutez une page index.xhtml, cliente du template (n'oubliez pas, dans ce projet toutes les pages sont clientes du template), qui démarre l'application (modifiez le fichier web.xml).
Testez tout de suite en démarrant l'application ; n'oubliez pas de vérifier que Java DB est bien démarré.
Si vous avez ce message dans l'affichage de la page :
Warning: This page calls for XML namespace http://primefaces.org/ui declared with prefix p but no taglibrary exists for that namespace.
vérifiez que vous avez bien ajouté PrimeFaces au projet (Libraries doit contenir un fichier pour PrimeFaces).
Si vous ne voyez pas ce que vous devriez voir, vérifiez que de code pour le servlet "Faces" n'a pas été ajouté par NetBeans dans web.xml, en particulier un mapping <url-pattern>/faces/*</url-pattern> (ça m'est arrivé une fois mais je n'ai pas réussi à le reproduire...). En ce cas, enlevez les balises <servlet> et <servlet-mapping> qui ont été ajoutées.
On peut améliorer la présentation avec CSS pour cadrer les valeurs à droite ou à gauche, avec JSF pour présenter les nombres.
Réordonnez les colonnes selon votre convenance (dans l'image ci-dessous, l'id a été placé en premier).
Puisque vous allez utiliser PrimeFaces dans votre projet, ça serait bon de consulter la documentation, en particulier pour améliorer la présentation des tables. Etudiez la présentation ci-dessous. Remarquez la présentation des nombres (le rectangle rouge ne fait pas partie de la présentation), le cadrage à droite de leur colonne, la largeur des colonnes et la largeur de la table (elle ne couvre pas toute la largeur) :
Si vous avez le temps, cherchez un peu, mais il est plus important de terminer ce TP et le TP suivant. Je vous conseille donc d'étudier directement cette correction.
Implémentez les fonctionnalités suivantes (vous pouvez vous inspirer du TP 1 pour certaines des fonctionnalités).
Bien entendu, vous utiliserez un ou plusieurs backing beans et vous ajouterez dans l'EJB session GestionnaireComptes
les méthodes nécessaires. Choisissez bien la portée de chaque backing bean pour la limiter au maximum.
N'oubliez pas d'utiliser le modèle PRG pour éviter les soumissions multiples de formulaire. Utilisez une requête POST pour les requêtes qui modifient le serveur et une requête GET sinon.
Parmi les options du menu sur la gauche du template on trouvera les fonctionnalités suivantes :
<h:commandButton>
).find
de EntityManager
) ; vous utiliserez cette méthode dans le backing bean de cette page.<f:message>
). Par exemple si un id tapé par l'utilisateur ne correspond à aucun compte, ou si l'utilisateur a oublié de donner un id ou le montant du transfert, ou si le solde est insuffisant dans le compte source.<h:panelGrid>
à 3 colonnes : une pour le label, une pour la zone de saisie et une 3ème pour les messages d'erreur éventuels.
Il faut aussi donner un id au composant <form>
. C'est utile pour retrouver plus facilement les id "client" dans la page HTML ; cet id peut se lire dans le code source de la page HTML (voir fin du support de cours sur JSF). Ajoutez les fonctionnalités suivantes dans la page JSF qui liste tous les comptes :
<h:graphicImage>
(vous pouvez changer la taille de l'icône affichée avec l'attribut height
ou width
) dans un composant <h:commandLink>
pour lancer une action quand l'utilisateur clique sur l'icône. Ajoutez un attribut title
au lien pour faire apparaître une bulle d'aide quand l'utilisateur passe le pointeur de la souris sur l'icône. supprimerCompte
dans le backing bean de la page qui liste tous les comptes, en passant le compte en paramètre (la valeur de l'attribut var
de la table). Aide.Projet NetBeans qui contient les fonctionnalités demandées. Modifiez le nom et le mot de passe dans la définition de la source de données.
Avertissement : cette partie optionnelle vous prendra beaucoup de temps. Vous devez donc terminer d'abord tous les TPs suivants avant de la faire.
Ceux qui veulent aller plus loin dans l'étude et la pratique de Jakarta EE, et en particulier de JSF et PrimeFaces, peuvent améliorer l'application avec le fonctionnalités suivantes (qui pourront être utiles quand vous écrirez des projets) :
getAllComptes()
dans le gestionnaire de comptes bancaires, on aura une méthode List<CompteBancaire> getComptes(int start, int nombreDeComptes)
. En fait, si on veut faire les choses bien, c'est un peu plus compliqué que ça car il faut tenir compte des éventuels tris et champs de sélection, mais c'est déjà bien si vous le faites sans en tenir compte. Aide.
<property name="eclipselink.logging.level" value="FINE"/>
CompteBancaire
: si le montant est supérieur au solde, la méthode lance une CompteException
qui est une exception d'application (voir cours), et le retrait n'est pas effectué. Évidemment, cette modification impliquera d'autres modifications dans le code de l'application. N'oubliez pas les message d'erreur pour l'utilisateur. Aide.<h:commandLink>
). <p:dataTable>
: attributs selection
(pour indiquer où sera rangé le compte sélectionné) et rowKey
(pour indiquer la clé qui servira à identifier le compte, c'est-à-dire l'attribut id
du compte) de <p:dataTable>
, et colonne <p:column selectionMode="single"/>
pour la sélection.Projet NetBeans qui contient les fonctionnalités optionnelles décrites ci-dessus. Modifiez le nom et le mot de passe dans la définition de la source de données.
Dans le prochain TP nous allons utiliser des associations dans JPA pour enregistrer dans la base les opérations bancaires effectuées sur les comptes bancaires (débits, dépôts, transferts, créations des comptes, etc.).