D3-TP06 : Projet "Gestion de clients" - Persistence des données avec un ORM - JPA de Payara
Attention: On travaille sur une copie du projet “Gestion de clients” en jakarta EE
ORM
Un ORM (Object-Relational Mapping / mapping objet-relationnel) est un ensemble de bibliothèques permettant de mapper (faire correspondre) les structures de données orientées objet d'un langage de programmation avec les tables d'une base de données relationnelle. L'objectif est de simplifier et d'abstraire les opérations de base de données pour le développeur, en permettant de manipuler les données comme s'il s'agissait d'objets dans le langage de programmation choisi.
L'ORM offre plusieurs avantages significatifs :
- Abstraction des détails de la base de données : Le développeur n'a pas besoin de connaître le langage SQL spécifique au SGBDR utilisé.
- Productivité accrue : Les opérations CRUD (Create, Read, Update, Delete) sont simplifiées, souvent réduites à de simples appels de méthodes.
- Maintenabilité : Le code est plus lisible et plus facile à maintenir, car il reflète directement les structures de données utilisées par l'application.
- Sécurité : Beaucoup d'ORMs intègrent des mécanismes de prévention des injections SQL.
JPA
JPA est un acronyme qui signifie Java Persistence API. C'est évolution de l'approche JDBC classique (“API bas niveau” : classe Connexion, ClientMysql avec Statement, ResultSet etc.) → API standard pour l’utilisation d’un ORM
1-La datasource
Dans Payara il faut une DataSource JDBC jdbc/bdclient pointant vers ta base MariaDB.
Définir la ressource JNDI sur ton serveur
Note: JNDI signifie Java Naming and Directory Interface, cette API permet :
- d’accéder à différents services de nommage ou de répertoire de façon uniforme ;
- d'organiser et rechercher des informations ou des objets par nommage (java naming and directory interface) ;
- de faire des opérations sur des annuaires (java naming and directory interface)
- Aller sur : http://localhost:4848
- Menu : Resources → JDBC → JDBC Connection Pools
- Créer un pool de connexions (ex. : MonPoolMariaDB)
- Pool Name : MonPoolMariaDB
- Resource Type : javax.sql.DataSource
- Database Vendor : MySQL (MariaDB est compatible MySQL)
- Next
- Classname : org.mariadb.jdbc.MariaDbDataSource
- Finish
Ping : OK ! OBLIGATOIRE
Créer la JDBC Resource (source de données JDBC) :
- Menu : Resources → JDBC → JDBC Resources
- Cliquer sur New…
- Configurer :
- JNDI Name : jdbc/bdclient (nom utilisé dans ton code Jakarta EE)
- Pool Name : monPoolMariaDB
- Clique sur OK
2-Configurer la persistence avec JPA
a) Les dépendances JPA
Le projet va inclure :
- Jakarta Persistence (jakarta.persistence-api)
- Une implémentation JPA (ex. : EclipseLink, inclus avec Payara)
- Le driver MariaDB (mariadb-java-client)
- pom.xml
<dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-web-api</artifactId> <version>10.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.mariadb.jdbc</groupId> <artifactId>mariadb-java-client</artifactId> <version>3.3.2</version> </dependency>
b) persistence.xml
Ce fichier indique à JPA comment se connecter à la base de données.
Dans WEB-INF/classes/META-INF/ (dans NETBEANS : Other Sources - src/main/resources-META-INF), créer un fichier (Source) :
- persistence.xml
<!-- Define Persistence Unit --> <persistence-unit name="bdclientPU" transaction-type="JTA"> <jta-data-source>jdbc/bdclient</jta-data-source> <properties> <property name="jakarta.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver"/> <property name="jakarta.persistence.jdbc.url" value="jdbc:mariadb://192.168.100.100/bdclient"/> <property name="jakarta.persistence.jdbc.user" value="adminBDClient"/> <property name="jakarta.persistence.jdbc.password" value="mdpBDClient"/> <!-- Optionnel : génération du schéma --> <property name="jakarta.persistence.schema-generation.database.action" value="none"/> </properties> </persistence-unit>
Les transactions
Rappel :Une transaction est une série d’actions qui doivent toutes se terminer avec succès, sinon toutes les modifications apportées à chaque action sont annulées. Les transactions se terminent par une validation ou une annulation.
Deux modes de gestion de transactions dans JPA :
- JTA (Java Transaction API) – mode container-managed
Utilisé dans les serveurs d’application comme WildFly, Payara, GlassFish, TomEE, etc. Les transactions sont gérées par le conteneur.
Les transactions gérées par conteneur simplifient le développement, car le code de l'EJB (Enterprise JavaBeans) ne marque pas explicitement les limites de la transaction. Le code n’inclut pas les instructions qui commencent et terminent la transaction. Par défaut, si aucune démarcation de transaction n’est spécifiée, les EJB utilisent la démarcation de transaction gérée par conteneur.
En règle générale, le conteneur commence une transaction immédiatement avant le démarrage d’une méthode et valide la transaction juste avant la fermeture de la méthode. Chaque méthode peut être associée à une seule transaction. Les transactions imbriquées ou multiples ne sont pas autorisées dans une méthode.
- RESOURCE_LOCAL – mode application-managed
Utilisé dans les applis standalone (Java SE, Spring Boot sans conteneur JEE). Les transactions sont gérées par le code (ex : getTransaction(), begin(), commit()).
Note: Nous utiliserons le mode conteneur JTA permis avec Payara (voir fichier persistence.xml ci-dessus)
<persistence-unit name="bdclientPU" transaction-type="JTA"> <jta-data-source>jdbc/bdclient</jta-data-source>
L'EJB
EJB (Enterprise Java Bean) est une spécification Java pour créer des composants métier côté serveur, qui permettent de gérer automatiquement:
- La logique métier (ex : traitement de commandes, calculs…)
- La sécurité
- La transaction
- La concurrence
Types principaux d’EJB
- Stateless (@Stateless) :
- Pas d’état entre les appels.
- Idéal pour des services comme “calculer le prix d’une commande”.
- Exemple : un service qui ne se soucie pas de qui l’a appelé avant.
- Stateful (@Stateful) :
- Garde un état pour chaque client.
- Exemple : panier d’achat en session.
- Singleton (@Singleton) :
- Une seule instance partagée pour tous les clients.
- Exemple : compteur global ou cache partagé.
3-Créer l’entité Client utilisable par la JPA
Une entité JPA est très proche d’une classe java Bean (Client), mais avec quelques annotations supplémentaires pour que JPA sache comment la relier à la base de données.
Attention: Pour ne pas changer tout le code, l'entité vas s'appeller Client, renommer pour l'instant la javabean Client en ClientOld pour garder le code visible mais à terme il ne sera plus utilisé
- “New > Entity Class” : création de l'entité à la main, définition des attributs, des annotations (@Entity, @Id, etc.) et de la correspondance avec la table.
- “New > Entity Classes from Database” : NetBeans génère automatiquement les classes d’entités à partir de la base existante (via la connexion JDBC).
Nous allons créer l'entity JPA manuellement pour avoir l'équivalent de la classe Client :
- entité Client
package com.Test.entities; import jakarta.persistence.*; //Annotations pour la JPA @Entity // indique que c’est une entité gérée par JPA @Table(name = "client") // nom de la table SQL dans la bd liée public class Client { //Annotations pour gérer id auto-incrémenté @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // clé primaire auto-incrémentée private int id; private String nom; private String prenom; private String mail; public Client() {} // obligatoire pour JPA (constructeur vide) public Client(int id, String nom, String prenom, String mail) { this.id=id; this.nom = nom; this.prenom = prenom; this.mail = mail; } public Client(String nom, String prenom, String mail) { this.nom = nom; this.prenom = prenom; this.mail = mail; } // Getters, setters, toString() identiques public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public String getPrenom() { return prenom; } public void setPrenom(String prenom) { this.prenom = prenom; } public String getMail() { return mail; } public void setMail(String mail) { this.mail = mail; } @Override public String toString() { return "Client{" + "id=" + id + ", nom=" + nom + ", prenom=" + prenom + ", mail=" + mail + '}'; } }
Comparaison
Élément | Classe Java simple | Classe JPA Entity |
---|---|---|
Utilisation | Objet métier (logique applicative) | Objet persistant (lié à une table SQL) |
Base de données | Aucune relation directe | Mappée à une table |
Annotations | Aucune | @Entity, @Id, @Table, etc. |
Constructeur vide | Optionnel | Obligatoire |
Gestion SQL | Fait à la main (JDBC) | Automatique (via EntityManager) |
4-Créer un DAO
La classe java ClientMysql est un DAO (Data Access Object) en mode JDBC classique (Connection, Statement, ResultSet, etc.)
Passer à JPA va permettre de simplifier le code, tout en gardant la même logique métier (lire tous les clients, créer un client, etc.).
Attention: Version JPA de la classe ClientMysql : la classe ClientJPA, à terme ClientMysql ne sera plus utilisé
- ClientJpa
@Stateless //type EJB, logique métier, à injecter dans les autres classes avec @EJB public class ClientJPA { @PersistenceContext(unitName = "bdclientPU") //en lien avec le fichier persistence.xml : <persistence-unit name="bdclientPU" transaction-type="JTA"> private EntityManager em; // Lecture de tous les clients public List<Client> readAll() { List<Client> lesClients = em.createQuery("SELECT c FROM Client c", Client.class).getResultList(); return lesClients; } // Création d’un client public int create(Client unClient) { em.persist(unClient); // enregistre l’objet en base // Force la synchro avec la base pour récupérer l’ID tout de suite em.flush(); return unClient.getId(); // l’ID est rempli automatiquement par JPA } }
Comparaison
Action | JDBC (ClientMysql) | JPA (ClientJpa) |
---|---|---|
Connexion à la base | Connection, PreparedStatement, ResultSet | EntityManager (géré par JPA) |
Lecture | Boucle while(resultSet.next()) | SELECT c FROM Client c (JPQL) |
Insertion | INSERT INTO … VALUES (…) | em.persist(client) |
Gestion des transactions | Manuelle (implémentée ou pas) | Automatique via em.getTransaction() |
Fermeture | stmt.close(), resultSet.close() | em.close() |
A retenir
Notion | Explication |
---|---|
EntityManagerFactory | Point d’entrée JPA (selon le fichier persistence.xml) |
EntityManager | Objet qui gère les entités (sélection, insertion, etc.)=objet fourni par JPA pour interagir avec la base de données. |
persist() | Dit à JPA d’insérer l’objet dans la table correspondante |
JPQL | Langage de requête orienté objets au lieu de SQL |
exemple : SELECT c FROM Client c | Client est l’entité. | ||
c est un alias qu’on utilise pour faire référence à chaque client. | |||
SELECT c signifie : « prends tous les clients ». | |||
Client.class indique le type de retour attendu : ici, chaque résultat sera un objet Client. |
5-Adaptation de l'application : Création d'un client avec JPA
Modification de la classe NouveauClientForm
Prise en compte du conteneur : modification de la déclaration de la dao qui devient une propriété de la classe
- NouveauClienForm.java
@Stateless //type EJB public class NouveauClientForm { @EJB //pour utiliser la classe ClientJPA définie en @Stateless private ClientJPA dao; private String resultat; public String getResultat() { return resultat; } public void setResultat(String resultat) { this.resultat = resultat; } public int verifierClient(HttpServletRequest request){ int id=-1; String leNom = request.getParameter("nom"); String lePrenom =request.getParameter("prenom"); if ((lePrenom.matches("[A-Za-zÀ-ÖØ-öø-ÿ' -]{1,100}"))&& (leNom.matches("[A-Za-zÀ-ÖØ-öø-ÿ' -]{1,100}"))){ Client cliSaisi = new Client(request.getParameter("nom"),request.getParameter("prenom"),request.getParameter("mail") ); id = dao.create(cliSaisi); System.out.println("Client créé avec id : " + id); } return id; } }
Note: La classe Client utilisée est maintenant l'entité JPA créée précédemment
Modification de la servlet NouveauServlet.java
Prise en compte du conteneur : modification de la déclaration de l'objet NouveauClientForm qui devient une propriété de la classe
@EJB private NouveauClientForm leControle; // injecté par Jakarta EE grâce à @EJB, géré par le contrôleur
Jalon : montrer la création d'un client ainsi modifiée en version JPA