d3:tp06

Voir cette page sous forme de diaporama.

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

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

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)

  1. Menu : Resources → JDBC → JDBC Connection Pools
  2. Créer un pool de connexions (ex. : MonPoolMariaDB)
    1. Pool Name : MonPoolMariaDB
    2. Resource Type : javax.sql.DataSource
    3. Database Vendor : MySQL (MariaDB est compatible MySQL)
    4. Next
    5. Classname : org.mariadb.jdbc.MariaDbDataSource
    6. Propriétés additionnelles :
    7. Finish

Ping : OK ! OBLIGATOIRE

Créer la JDBC Resource (source de données JDBC) :

  1. Menu : Resources → JDBC → JDBC Resources
  2. Cliquer sur New…
  3. Configurer :
    1. JNDI Name : jdbc/bdclient (nom utilisé dans ton code Jakarta EE)
    2. Pool Name : monPoolMariaDB
    3. Clique sur OK

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>

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>

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

  1. Stateless (@Stateless) :
    1. Pas d’état entre les appels.
    2. Idéal pour des services comme “calculer le prix d’une commande”.
    3. Exemple : un service qui ne se soucie pas de qui l’a appelé avant.
  2. Stateful (@Stateful) :
    1. Garde un état pour chaque client.
    2. Exemple : panier d’achat en session.
  3. Singleton (@Singleton) :
    1. Une seule instance partagée pour tous les clients.
    2. Exemple : compteur global ou cache partagé.

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é

Au lieu d’une classe java Client, on fait une Entity :

  • “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éesAucune relation directeMappée à une table
Annotations Aucune @Entity, @Id, @Table, etc.
Constructeur videOptionnelObligatoire
Gestion SQL Fait à la main (JDBC)Automatique (via EntityManager)

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.

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

  • d3/tp06.txt
  • Dernière modification : 2025/10/13 14:52
  • de dthevenot