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 bean CDI 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. Et n'oubliez pas les commits/push et de mettre à jour les plugins Maven.
MySQL doit être démarré.
La nouvelle base de données "banque" sera créée en utilisant le 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 banque;
Lancez la commande en cliquant sur l'icône "Execute the selected portion of the script, or everything if there is no selection". La zone Output du bas devrez vous confirmer que tout s'est bien passé. Il faut faire un refresh pour voir la nouvelle base de données dans la liste des schémas.
Si vous utilisez un autre utilisateur que root dans NetBeans pour accéder à MySQL, n'oubliez pas de lui donner les autorisations sur la nouvelle base, comme vous l'avez fait pour installer les logiciels.
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 Jakarta EE pour le Web, comme dans les TPs précédents. Donnez-lui un nom, par exemple "TPBanqueXxxx" (Xxxx pour votre nom, comme pour le TP 1).
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é.
Comme pour les TPs précédents (en particulier le TP 1), pensez à modifier pom.xml :
Comme pour les autres TPs, crééez un nouveau dépôt local Git pour ce TP (il servira aussi au TP suivant). Créez ensuite un dépôt distant sur GitHub (faites un push après avoir fait un premier commit).
Vérifiez que le projet est associé au serveur d'application que vous avez installé (clic droit sur le projet, Properties > Run).
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, choix New > "Entity Class..." (si "Entity Class" n'apparait pas, "Other..." > Persistence > "Entity Class").
Donnez-lui le nom CompteBancaire, et spécifiez le sous-paquetage du paquetage de base, xx.xxxxx.xxxxx.entity (remplacez le début par le paquetage de base de votre projet).
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.
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
). Changez le type de génération automatique car le type AUTO utilise une table pour générer les clés, ce qui n'est pas performant. La stratégie IDENTITY convient bien à MySQL: @GeneratedValue(strategy=GenerationType.IDENTITY.
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 cette 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.
Faites un commit et un push du projet sur Github, comme pour le TP 1. A chaque étape importante, de la suite de ce TP et aussi pour le TP suivant, n'oubliez de faire ce commit et ce push.
Vous allez maintenant écrire un bean CDI GestionnaireCompte
(sous-paquetage service
du paquetage de base du projet) pour gérer les comptes bancaires dans la base de données.
Créez le bean CDI comme pour le TP 1.
Ce bean CDI 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 dans persistence.xml, qui sera conservé sur le serveur d'application.
Dans le TP 1, la source de données a été définie dans le serveur d'application. Ca sera le plus souvent le cas. Cependant cette définition dans le serveur d'application a un inconvénient : elle n'est pas standardisée et elle dépend des serveurs d'application.
Il existe 2 façons de définir une source de données de façon standard (qui ne dépend pas du serveur d'application) :
Comme d'habitude avec Jakarta EE, une définition XML l'emporte sur une définition par annotation, ce qui permet, par exemple, de modifier l'emplacement de la base de données, le nom de l'utilisateur ou son mot de passe, au moment du déploiement, sans avoir besoin de recompiler l'application.
Pour ce TP, vous allez annoter la classe du bean 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 ; root, ou un autre, suivant ce que vous avez choisi au moment de la création de la base de données) 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 = { "useSSL=false", "allowPublicKeyRetrieval=true", "driverClass=com.mysql.cj.jdbc.Driver" } ) @RequestScoped 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 beans CDI 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).
Commencez par changer le nom de l'unité de persistence pour lui donner un nom plus adapté, par exemple "banquePU".
Si elle est présente, 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>
, à l'intérieur de la balise <persistence-unit>
, donnez la valeur de l'attribut name
de l'annotation @DataSourceDefinition
ci-dessus :
<jta-data-source>java:app/jdbc/banque
</jta-data-source>
.
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 (en plus de la propriété de EclipseLink qui permet d'afficher les ordres SQL, que vous avez déjà utilisée dans le TP 1) :
<properties> <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/> <property name="eclipselink.logging.level" value="FINE"/> </properties>
L'option standard 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.
Ce bean 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. Vérifiez-le (attendez un peu que le serveur d'application démarre).
Pour la méthode creerCompte
prenez exemple sur la méthode persist
du TP 1.
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 le bean CDI (voir cours sur JPA). Inspirez-vous des beans CDI 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 (voir cours sur JPA).
Ajoutez un bean CDI Init
dans le sous-paquetage config du paquetage de base du projet, pour créer 4 comptes au démarrage de l'application si la table des comptes est vide. Inspirez-vous de la section "Exécution d’une méthode au démarrage ou à l’arrêt d’une application" du support de cours CDI pour écrire une méthode de ce bean qui sera exécutée au démarrage de l'application.
Créez la classe du bean comme vous avez créé le bean GestionnaireCompte
.
Pour que les 4 comptes soient créés au démarrage de l'application, ajoutez l'annotation pour qu'une instance de ce bean soit créée au déploiement de l'application et créez les comptes dans une méthode qui sera exécutée juste après la création de l'instance du bean.
Les 4 comptes à créer : John Lennon, 150000 ; Paul McCartney, 950000 ; Ringo Starr, 20000 ; Georges Harrisson, 100000.
Un peu d'aide pour ce bean ?
Lancez un clean and build du projet.
Avant de lancer l'exécution de l'application, effacez les messages des logs du serveur d'application dans NetBeans (c'est une bonne pratique avant de lancer toute exécution pour que les nouveaux messages d'erreur éventuels se repèrent plus facilement).
Lancez l'exécution du projet. Est-ce que vous voyez bien les 4 comptes créés dans la base de données ? Si vous ne les voyez pas et que vous n'avez aucun message d'erreur dans les logs du serveur d'application, c'est sans doute que vous avez oublié de mettre la portée "application" pour le bean CDI Init
.
Si vous avez ce message d'erreur (cherchez un peu dans les logs du serveur d'application) :
org.apache.catalina.LifecycleException: org.jboss.weld.contexts.ContextNotActiveException: WELD-001303: No active contexts for scope type jakarta.enterprise.context.RequestScoped
Vous comprenez le problème ? Lorsque GestionnaireCompte
est créé, aucune requête HTTP n'est en cours alors que sa portée est RequestScoped. Vous pouvez résoudre ce problème en donnant la portée CDI "Application" (ou la portée "Dependent") à GestionnaireCompte
(ces 2 portées ne sont pas liées à HTTP).
Lorsque l'exécution s'est bien passée et que vous voyez les 4 comptes dans la base, passez en mode "create" à la place de "drop and create" dans persistence.xml. Si vous relancez plusieurs fois l'exécution du projet, il ne doit y avoir que 4 comptes après chaque exécution.
Avec la même connexion que pour la base "customer" vous devriez pouvoir accéder à la base "banque" depuis NetBeans (ouvrez "Other databases"). Si vous n'avez pas le même utilisateur pour les 2 bases, refaites pour la base "banque" la manipulation que vous avez faite dans NetBeans pour la base "customer" :
"Race condition" peut être traduit en français par "situation de compétition" ou "situation de concurrence" ou "séquencement critique". Il s'agit d'une situation caractérisée par un résultat différent suivant l'ordre dans lequel agissent les acteurs du système (Wikipedia).
Supposons qu'il n'y a aucun compte dans la base de données et que l'application n'est pas déjà démarrée dans le serveur d'application.
Est-ce qu'il y aura une race condition si on lance l'application dans un navigateur par un Run dans NetBeans (ce qui déploie l'application dans le serveur et la démarre), puis juste après dans un autre navigateur en tapant l'URL de lancement de l'application. Est-ce que les 4 comptes seront créés 2 fois dans le cas où l'exécution se déroulerait dans cet ordre :
Qu'en pensez-vous ? Réponse.
Si tout marche bien, n'oubliez pas le commit et le push.
Allez voir le fichier web.xml dans "Configuration Files". Comme pour le TP 1, créez-le s'il n'existe pas et modifiez-le. Mettez 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 sur les templates). Enregistrez-le dans WEB-INF (dans le sous-répertoire template) 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="jakarta.faces.html" xmlns:ui="jakarta.faces.facelets"> <ul> </ul> </ui:composition>
Dans le template :
<ui:insert name="left"> <ui:include src="menu.xhtml"/> </ui:insert>
Indiquez que vous allez utiliser PrimeFaces en ajoutant cette dépendance (choisissez la dernière version de PrimeFaces, celle que vous avez déjà utilisée dans le TP 1) :
<dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>13.0.0</version> <classifier>jakarta</classifier> </dependency>
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 :
ListeComptes
(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 le bean 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 (d'après votre fichier web.xml). Décochez les sections metadata et left. Ensuite, dans le code généré, dans la section "content", écrivez un message demandant à l'utilisateur de choisir une des options du menu et dans la section "top" écrivez "Gestion des comptes bancaires".
Testez tout de suite : Clean and build puis Run pour démarrer l'application ; n'oubliez pas de vérifier avant que votre base de données est bien démarré.
Si tout va bien (voir "Erreurs possibles ci-dessous), n'oubliez pas Git (commit et push).
Vous pouvez modifier la table PrimeFaces pour ajouter des tris et des filtres et la pagination, comme pour le TP 1.
1. Si vous avez ce message d'erreur dans les logs du serveur (j'ai mis les informations importantes en gras)
org.glassfish.deployment.common.DeploymentException: CDI deployment failure:WELD-001413: The bean Managed Bean [class xx.xxxxx.jsf.ListComptes] with qualifiers [@Default @Any @Named] declares a passivating scope but has a non-passivation-capable dependency Managed Bean [class xx.xxxxx.service.GestionnaireCompte] with qualifiers [@Any @Default]
...
c'est que vous avez choisi la portée "Dependent" pour le bean CDI GestionnaireCompte
. Remplacez la portée Dependent par la portée Application et vous ne devriez plus avoir d'erreur.
La portée "Dependent" convenait pour l'initialisation mais la portée View du backing bean ListeComptes
implique que le bean ne doit pas dépendre d'objets qui ne peuvent pas être mis temporairement en mémoire secondaire (disque dur) et remis ensuite en RAM. La portée Dependent ne convient pas car cela signifie que l'objet dépend d'un certain objet et ne peut pas être associé ensuite à un autre objet (le backing bean désérialisé).
2. Autre erreur possible :
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 (dans l'URL remplacez éventuellement par la version qui vous intéresse). Clic sur "Get started" ; descendez dans le menu de gauche pour trouver "DataTable". Vous pourrez, par exemple. trouver les informations pour améliorer la présentation des tables.
Étudiez la présentation ci-dessous. Remarquez la présentation des nombres (le rectangle rouge ne fait pas partie de la présentation), cadré à droite dans 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 aide.
Toujours si vous avez le temps, ajoutez, comme au TP 1, un paginateur, un tri sur les id, noms et les soldes (n'oubliez pas que, pour les tris, le backing bean doit avoir un champ qui contient la liste à afficher dans la table et la table doit être incluse dans un formulaire) et une sélection (filtre dans le vocabulaire PrimeFaces) sur les noms et les soldes. Le filtre sur les soldes n'affichent que les comptes qui ont un solde supérieur ou égal au nombre entré dans le filtre. Le plus simple est d'utiliser une fonction d'un backing bean pour faire la sélection. Dans cette fonction il faut comparer la valeur dans la table (un entier) à la valeur tapée par l'utilisteur dans la zone du filtre (c'est une String
qu'il faut donc transformer en Integer
. Voir documentation de PrimeFaces, choisissez "DataTable dans le menu des composants à gauche et ensuite cherchez "filterFunction".
Vous allez ajouter des fonctionnalités à l'application (vous pouvez vous inspirer du TP 1 pour certaines des fonctionnalités).
Principes à suivre :
Vous allez enrichir le menu du template pour permettre à l'utilisateur (un employé de la banque) de
Ensuite vous allez ajouter des fonctionnalités dans la page qui liste tous les comptes :
Pour cela, vous allez créer une page JSF, cliente du template (n'oubliez pas...), et son backing bean. Il vous faudra aussi modifier le bean GestionnaireCompte
pour y ajouter les méthodes utilisées par le backing bean.
L'utilisateur va saisir les id des comptes entre lesquels le transfert se fera : l'id du compte source du transfert et l'id du compte destination du transfert.
Il faut bien réfléchir à la façon dont va se faire le transfert. En effet, un transfert ne peut se faire "à moitié" : on ne peut retirer l'argent de la source, enregistrer le retrait dans la base de données et ensuite ne pas pouvoir transférer l'argent dans la destination. Le transfert doit se faire dans une seule transaction. Comment allez-vous faire ? Revoyez le cours sur les transactions. Aide.
Comme il faudra récupérer les comptes à partir des id saisis par l'utilisateur, ajoutez aussi une méthode pour cela dans le bean CDI qui gère les comptes (utilisez la méthode find
de EntityManager
, comme dans le TP 1) ; vous utiliserez cette méthode dans le backing bean de cette page.
Commencez donc par ajouter ces méthodes dans le bean CDI.
Créez la page cliente du template. Décochez les sections "metadata" (ne sera pas utilisé pour cette page) et "left" du template (le code ne change pas par rapport au template, le menu est inséré dans cette section).
La section "content" de la page affiche un formulaire dans lequel l'utilisateur entre les clés primaires du compte source, du compte destination, puis la somme à transférer. Ces informations sont enregistrées dans le backing bean de la page (il faut le créer) quand l'utilisateur soumet le formulaire en cliquant sur un bouton (<h:commandButton>
). Aide pour la mise en page de la page JSF.
Le backing bean de la page aura la tâche d'enregistrer dans ses propriétés les informations saisies par l'utilisateur et de lancer le transfert (avec la méthode exécutée au moment de la soumission du formulaire). Quelle portée aura ce backing bean ? Si vous n'êtes pas sûr, commencez par la portée request ; si ça ne marche pas, élargissez la portée. Le symptôme d'une portée insuffisante est la levée d'une NullPointerException
.
Pour le moment, ne traitez pas les erreurs éventuelles (par exemple un id de compte qui n'existe pas). Elles seront traitées dans la section suivante.
Après la transfert, la liste de tous les comptes est affichée (rappel : la prochaine page à afficher est indiquée par la valeur retour de la méthode action exécutée à la soumission du formulaire).
Dans la page prévoyez les emplacements pour l'affichage des messages d'erreur (<h: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.
Ajouter un lien dans le menu du template vers la page que vous venez de créer.
Testez en donnant des id qui existent.
Si tout marche bien, commit Git, et GitHub.
Dans les étapes suivantes vous allez gérer les erreurs éventuelles. A chaque étape testez pour voir si les bons messages d'erreur sont affichés.
Les cas les plus simples sont gérés par JSF. Par exemple testez lorsque le montant ou une des clés primaires n'est pas un nombre ou si le montant n'est pas donné (effacez le 0 ; si vous avez bien mis required="true"
).
Optionnel : si les messages standards d'erreur ne vous conviennent pas, vous pouvez les modifier. Dans le code qui vous a été proposé, le message d'erreur en cas de problème de conversion de String
vers Long
ou int
est explicite. Sinon, un message standard serait affiché, contenant le nom du champ qui a provoqué l'erreur. Pour voir le format d'un message standard, enlevez temporairement l'attribut convertMessage
d'un des champs de saisie et saisissez une mauvaise valeur dans ce champ. Pour améliorer un tel message standard, une première possibilité est d'ajouter un attribut label
aux h:inputText
pour que le champ qui a crée l'erreur soit plus explicite (par exemple avec la valeur "id du compte source"). Si vous êtes en avance, relisez la fin du cours JSF pour savoir comment modifier les textes standards pour les erreurs, sinon passez à la suite.
Ensuite ajoutez la gestion des erreurs qui ne sont pas prises en compte automatiquement par JSF : cas où une clé primaire n'existe pas et cas où le solde de la source est insuffisant. Vous allez modifier la méthode "action" du bouton de soumission du formulaire qui enregistre le transfert. C'est cette méthode qui contiendra le code pour faire afficher des messages d'erreur. Évidemment, si des erreurs sont repérées, il ne faut pas enregistrer de transfert.
La fin de cours JSF explique comment ajouter un message d'erreur qui sera affiché dans les zones d'affichage des erreurs.
Les applications JSF comportent souvent une classe qui contient des méthodes utilitaires pour générer des messages (d'erreur ou de succès), ou pour d'autres tâches utiles. Voici un exemple d'une telle classe que vous pouvez rajouter dans votre application.
Arrangez-vous pour que tous les messages d'erreur soient affichés et pas seulement le premier (par exemple si les 2 id ne correspondent pas à des comptes existants).
Aide pour coder les messages d'erreur et de succès.
Important pour l'ergonomie : pour tout traitement qu'il a lancé, l'utilisateur doit savoir si tout s'est bien passé ou non. Le plus souvent le traitement doit être suivi d'un message d'erreur ou d'un message de succès si tout s'est bien passé.
Dans quelle page sera affiché le message de succès ? N'oubliez pas de modifier cette page pour que le message s'affiche.
Ça serait bien de prévoir aussi une zone de message sous le menu pour les messages de succès. Aide.
Faites afficher un message de succès dans le cas où le transfert s'est bien déroulé. Le message doit contenir les noms des clients titulaires des comptes et le montant du transfert.
Vérifiez que vous n'avez pas de double soumission du formulaire si vous rechargez la page qui liste tous les comptes après avoir fait un transfert.
Si vous devez modifier le code pour cela, vérifiez que le message de succès s'affiche toujours.
N'oubliez pas Git...
Modifiez l'application pour qu'il soit possible de créer un nouveau compte bancaire en donnant le nom du titulaire du compte et le solde du compte.
Vous voulez de l'aide ?
Testez et Git si tout va bien.
Ajoutez les fonctionnalités suivantes dans la page JSF qui liste tous les comptes.
Le clic sur le numéro de compte dans la liste des comptes ouvre une page pour ajouter ou retirer de l'argent sur le compte.
Le clic lance une requête GET vers une page qui permet à l'utilisateur de donner les caractéristiques de ce mouvement (retrait ou versement, montant). Utilisez un paramètre de vue pour récupérer l'identificateur du compte dans la page et chargez ensuite les informations sur le compte à partir de cet identificateur (avec une viewAction), comme dans le TP 1. Aide.
Testez, en particulier l'ajout d'argent. Git si tout va bien.
Ajoutez une colonne dans la table de la liste des comptes, qui contient une icône sur laquelle on peut cliquer pour supprimer le compte.
Vous pouvez choisir l'icône que vous voulez ou utiliser cette icône : . Pour savoir où mettre l'image, lisez la section "Ressources" du support de cours sur JSF.
Mettez un composant <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.
Comme attribut du <h:commandLink>
, utilisez une méthode "action" supprimerCompte
que vous ajoutez 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.
Testez et Git si tout va bien.
Dans cette nouvelle colonne de la table, ajoutez la possibilité de modifier le nom du possesseur du compte bancaire. On pourrait de la même façon permettre de modifier le solde ou toute autre valeur associée à un compte, ou même toutes les valeurs du compte.
Lancez une requête GET vers une page qui permet de modifier le nom. Utilisez un paramètre de vue pour récupérer l'id du compte dans la page comme vous avez fait pour "Déposer ou retirer de l'argent sur un compte". La méthode loadCompte()
du backing bean va récupérer le compte en utilisant son id. Cette méthode est utilisée dans la section metadata de la page pour donner une valeur à la propriété compteBancaire
du backing bean. La page affiche les valeurs de l'id et du nom de compteBancaire
et permet de modifier le nom.
Choisissez bien la portée du backing bean.
Arrangez-vous pour que le message de succès soit du type "Nom Toto changé en Titi" (il indique l'ancien et le nouveau nom).
Vous ne devriez pas avoir besoin d'aide cette fois-ci pour terminer car vous avez déjà vu ce type de situation plusieurs fois...
Testez et Git si tout va bien.
Le cours n'a pas couvert la création de tests par manque de temps.
Dans une application d'entreprise (et pas seulement d'entreprise...) il est indispensable d'écrire au moins des tests unitaires pour tous les composants de l'application. Les tests permettent de repérer les erreurs dans le code, de rassurer le développeur en lui donnant une meilleure confiance dans le code déjà écrit, de faciliter la maintenance de l'application et même de faciliter l'utilisation des classes et méthodes en donnant un exemple d'utilisation (dans le code des tests).
Dans le cycle de vie de Maven, les tests unitaires sont lancés après la compilation des classes et avant la construction du fichier war (pour notre cas d'application Web). Si un seul des tests détecte une anomalie, le fichier war n'est pas construit.
JUnit est un standard de facto pour l'écriture de tests en Java.
Optionnel : Les frameworks de test ne font pas partie du programme du cours mais, si vous ne connaissez pas déjà JUnit (version 5), vous devriez lire cette présentation rapide pour ajouter au moins un test à cette application.
Vous avez vu dans le cours d'introduction aux applications d'entreprise que ces applications doivent être surveillées pour s'assurer de leur bon fonctionnement et pour repérer à l'avance des indices d'un futur problème. Par exemple pour repérer des tentatives non réussies d'accès dans l'application (tentative de piratage ?), ou des délais anormaux pour répondre aux demandes des utilisateurs (il faut augmenter les capacités de traitement de l'application ?).
Le framework MicroProfile, satellite de Jakarta EE, fourni des API pour cela.
Une surveillance basique est fournie par le logging (journalisation) qui permet d'afficher ou d'enregistrer les étapes dans le déroulement d'une application. Par exemple, vous avez utilisé dans ces TPs les logs de Payara pour vous aider à chercher les bugs dans les applications que vous avez écrites. Java SE fourni une API pour ajouter des logs dans les applications Java.
Optionnel : L'API de Java SE (qui permet aussi d'utiliser d'autres APIs de logging qui fournissent les mêmes services) ne fait pas partie du programme du cours. Cette page vous en donnera une présentation rapide et vous montrera comment ajouter un logger dans l'application.
Vous allez créer une branche Git (menu Team > Branch/Tag > Create Branch...) qui contiendra toutes les parties optionnelles. Vous aurez ainsi 2 branches : la branche principale qui contient les parties non optionnelles et la branche pour les parties optionnelles.
Si vous écrivez quelques parties optionnelles avant de commencer le TP suivant, revenez à la branche principale au début du TP suivant.
Vérifiez bien que vous êtes dans la nouvelle branche avant de commencer à écrire le code pour les fonctionnalités optionnelles ci-dessous. Avec le menu View > "Show versioning labels" vous pouvez faire afficher la branche à la droite du nom du projet, ou bien vous pouvez aller dans le browser de repository (menu Team > Repository > Repository Browser) et voir quelle branche est affichée en caractères gras.
On peut passer d'une branche à une autre avec le menu Team > Checkout > Checkout Revision...
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)
. Evidemment cette méthode doit tenir compte des éventuels tris et champs de sélection. Heureusement, depuis la version 11 PrimeFaces fournit une classe qui fait le plus gros du travail. Aide.
<property name="eclipselink.logging.level" value="FINE"/>
retirer
de l'entité 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.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.).