Aller au contenu

Exercice 3 - Utilisation de JPA⚓︎

Nous allons modifier le projet Web gestionnotes, afin d'utiliser l'API JPA.

1. Configuration de JPA⚓︎

Configuration du 📄persistence.xml

Le fichier 📄persistence.xml permet de configurer toute la partie persistance des beans Java.

Il est créé par défaut par Maven, car nous avons choisi l'archetype webapp-jakartaee10. Il est situé dans le dossier 📂src/main/resources/META-INF.

Dans ce fichier 📄persistence.xml :

  1. Le nom de l'unité de persistance (attribut name de la balise persistence-unit) est très important, il faut l'indiquer dans les classes DAO pour indiquer sur quelle base de données les requêtes doivent être effectuées.

    Dans un projet, il peut y avoir plusieurs unités de persistance s'il y a plusieurs base de données auxquelles se connecter. Ici, nous n'avons qu'une, que nous allons appeler GestionNotes (c'est donc cette valeur qu'il faut indiquer).

  2. Nous allons indiquer que nous utilisons l'API JTA pour gérer les transactions. Pour cela, il faut ajouter l'attribut transaction-type="JTA" dans la balise <persistence-unit>.

  3. À l'intérieur de cette balise <persistence-unit>, nous allons ajouter la balise fille <jta-data-source>java:/MySqlGestionNotesJPA</jta-data-source> pour faire le lien avec la datasource créée précédemment (on indique le nom JNDI).
  4. Enfin, on indique la stratégie utilisée au redémarrage du serveur. Ici, nous sommes en mode "développement", nous allons donc supprimer et re-créer la table correspondante à chaque redémarrage du serveur WilFly. Pour cela, à l'intérieur de <persistence-unit>, ajouter une balise <properties>. Cette balise va elle même contenir la balise fille suivante :

    Balise du 📄 persistence.xml
    1
    2
    3
    <properties>
        <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
    </properties>
    

    ⚠️⚠️ Attention, cela signifie qu'à chaque redémarrage de l'application, toutes les tables du schéma sont supprimées et recréées. C'est très pratique en mode développement, puisque des champs peuvent être ajoutés et/ou supprimés régulièrement, mais une fois que les développements sont terminés, il faut bien sûr repasser cette valeur à None. ⚠️⚠️

Finalement, le fichier 📄persistence.xml ressemble à :

XML
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="3.0" xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd">
    <!-- Define Persistence Unit -->
    <persistence-unit name="GestionNotes" transaction-type="JTA"><!--(1)!-->
        <jta-data-source>java:/MySqlGestionNotes</jta-data-source><!--(2)!-->
        <properties>
            <property
                name="jakarta.persistence.schema-generation.database.action"
                value="drop-and-create" /><!--(3)!-->
        </properties>
    </persistence-unit>
</persistence>
  1. Dans les DAOs, on aura besoin du nom de l'unité de persistance pour savoir sur quelle base de données envoyer les requêtes.

    Plus d'infos sur transaction-type ici.
  2. Ici, il faut indiquer le même nom JNDI que celui précisé à la création de la Data Source précédemment.
  3. Cela signifie qu'à chaque démarrage du serveur, les tables correspondantes sont supprimées et recréées. On évitera bien sûr de laisser cette option en environnement de production ...

2. Gestion du modèle - Persistance des beans⚓︎

Pour effectuer le mapping entre les beans et les tables de la base de données, nous n'allons plus écrire des requêtes SQL, mais il suffit d'ajouter des annotations !

Sur chaque bean (il y en a deux ici, NoteBean et StudentBean) on ajoute l'annotation @Entity :

  1. Pour NoteBean :

    ☕ Code Java - Entité NoteBean
    1
    2
    @Entity//(1)!
    public class NoteBean implements Serializable {//(2)!
    
    1. Indique qu'il s'agit d'un bean JPA.
    2. Et oui, c'est un bean, il ne faut donc pas oublier que la classe doit implémenter java.io.Serializable !

    Comme la nouvelle erreur l'indique, il faut préciser quelle est la clef primaire. Sur la propriété idNote, on indique donc cela. On en profite également pour préciser que la valeur est auto incrémentée (ce qui n'est pas une obligation) :

    ☕ Code Java - Entité NoteBean
    1
    2
    3
    4
    @Id //(1)!
    @GeneratedValue(strategy = GenerationType.IDENTITY) //(2)!
    @Column(name = "ID_NOTE") //(3)!
    private Integer idNote;
    
    1. Indique qu'il s'agit de la clef primaire.
    2. Cela correspond exactement au auto_increment que nous avions mis sur le champ id de la table NOTE dans l'exercice 1 de ce TD.
    3. Permet de préciser que le nom de la colonne qui correspond. Si on ne met rien, la colonne s'appellera idNote, et pas ID_NOTE !
    Changement du nom de la table

    Par défaut, le nom du bean doit être le même que le nom de la table en base. De même, les noms des propriétés de la classe doivent être les mêmes que les noms de champs de la table.

    Si ce n'est pas le cas, il est bien sûr possible de le préciser dans les annotations. Ici par exemple, la table s'appelle NOTE, alors que le bean s'appelle NoteBean. Pour que le lien (le mapping) puisse se faire correctement, il faut le préciser dans l'annotation :

    ☕ Code Java - Entité NoteBean
    1
    2
    3
    @Entity
    @Table(name = "NOTE_JPA")//(1)!
    public class NoteBean implements Serializable {
    
    1. Il n'y a que cette ligne à rajouter ici.

      On créé une autre table, NOTE_JPA, pour la distinguer de celle créée précédemment.

    Ce site liste les différentes options possibles.

  2. Il faut maintenant faire de même pour le bean StudentBean, lié avec la table STUDENT_JPA.

Vérification en base de données

Il est maintenant temps de tester. Pour cela, on démarre le serveur WildFly (si ce n'est pas déjà fait), puis on publie le projet (gestionnotes) sur le serveur.

On vérifie que les tables NOTE_JPA et STUDENT_JPA ont bien été créées en BDD (et vide), avec les champs indiqués.

À faire à chaque modification

Tout comme précédemment, après chaque modification du code, il faut ré-exécuter le script Maven (mvn clean install), mais il n'y a maintenant plus besoin de redémarrer le serveur.

La publication du WAR est automatique dès qu'il est modifié.

3. Création de la couche DAO⚓︎

Nous allons créer une nouvelle implémentation de cette couche (c'est-à-dire une nouvelle classe implémentant l'interface NoteDAO et une nouvelle classe implémentant l'interface SutdentDAO) :

  1. On crée un objet EntityManager.

    • C'est cet attribut qui permet de créer la dépendance vers la couche JPA.
    • Sur cet objet, on précise le nom de l'unité de persistance (celle indiquée dans l'attribut name de la balise persistence-unit dans le 📄persistence.xml), c'est-à-dire qu'on précise à quelle base de données ce DAO doit se connecter.

      Pour cela, il suffit d'ajouter l'annotation suivante :

      ☕ Code Java - Attribut EntityManager dans chaque DAO
      @PersistenceContext(unitName = "GestionNotes")
      private EntityManager em;
      
  2. Cet objet gère pour nous les requêtes en BDD, avec le langage HQL (pour Hibernate Query Language) - appelé également JPAQL (pour Java Persistence API Query Language) - et l'utilisation des méthodes find, persist, createQuery, ...

  3. Enfin, afin que les différents objets puissent être correctement instanciés, il faut ajouter l'annotation @Stateless sur la classe.

    Nous préciserons cela un peu plus tard, mais nous sommes en fait en train de définir des EJBs.

Voici maintenant à quoi ressemble la classe NoteDAOImplJPA :

☕ Code Java - Classe NoteDAOImplJPA
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Stateless //(1)!
public class NoteDAOImplJPA implements NoteDAO {

    // L'objet EntityManager qui va permettre d'effectuer les requêtes en BDD.
    @PersistenceContext(unitName = "GestionNotes")
    private EntityManager em;

    @SuppressWarnings("unchecked") //(2)!
    @Override
    public List<NoteBean> getNotesList() {
        // Exemple de requête HQL (ou JPAQL).
        Query requete = em.createNativeQuery("select * from NOTE_JPA", NoteBean.class);
        return requete.getResultList();
    }

    @Override
    public void updateNote(NoteBean note) {
        // TODO Auto-generated method stub
    }

    @Override
    public void insertNote(NoteBean note) {
        // Insertion d'un enregistrement en BDD.
        em.persist(note);
    }
}
  1. Nécessaire pour que les différents objets soient correctement instanciés.
  2. On utilise cette annotation, sinon un warning est levé.

    De manière générale, on évite de laisser des warnings dans un projet informatique !

Implémentation de StudentDAO

Il faut maintenant faire la même chose pour l'implémentation du DAO StudentDAO, en créant la classe StudentDAOImplJPA.

Principe ouvert/fermé

Nous venons de créer une nouvelle implémentation des DAO, mais nous n'avons effectué aucune modification sur les interfaces NoteDAO et StudentDAO.

Nous en reparlerons un peu plus tard, mais nous avons ici utilisé un principe très important en programmation : le principe ouvert/fermé.

4. Modification de la couche métier⚓︎

Il n'y a qu'une seule modification à effectuer dans cette couche, c'est la façon dont la dépendance vers la couche DAO est effectuée.

Pour cela :

  1. Supprime le constructeur que nous avions créé précédemment :

    ☕ Code Java - Classe NoteBusinessImpl - Constructeur
    public NoteBusinessImpl() {
        this.noteDao = new NoteDAOImpl();
        this.studentDAO = new StudentDAOImpl();
    }
    
  2. Pour que l'instanciation des deux DAOs se fasse, on ajoute l'annotation @Inject sur les attributs correspondants :

    ☕ Code Java - Classe NoteBusinessImpl - Attributs
    @Inject
    private NoteDAO noteDao;
    @Inject
    private StudentDAO studentDAO;
    
  3. Enfin, de même que sur les implémentations des DAOs, il faut rajouter l'annotation @Stateless sur les implémentations des businness (ici il n'y a que NoteBusinessImpl) :

    ☕ Code Java - Classe NoteBusinessImpl
    @Stateless
    public class NoteBusinessImpl implements NoteBusiness {
    

5. Modification de la couche présentation⚓︎

Ici aussi, il n'y a qu'une seule modification à apporter : la façon dont la dépendance vers la couche métier est créée.

Dans les différentes Servlets créées :

  1. Supprime la méthode init.
  2. Pour que l'instanciation de la couche métier se fasse correctement, ajoute l'annotation @Inject

    ☕ Code Java - Servlets - Attributs
    @Inject
    private NoteBusiness noteBusiness;
    

6. Tests⚓︎

Vérification

Vérifie maintenant que cette nouvelle version de l'application est bien fonctionnelle.

Contrairement à Tomcat où il fallait redémarrer le serveur à chaque modification, avec WildFly, il suffit d'exécuter le script Maven (mvn clean install).

Création d'étudiants

Pour tester que l'application est bien fonctionnelle, on peut créer la Servlet suivante :

☕ Code Java
@WebServlet(name = "fillDBServlet", urlPatterns = { "/fillDB" })
public class FillDBServlet extends HttpServlet {

    @Inject
    private NoteBusiness noteBusiness;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        StudentBean studentA = new StudentBean();
        studentA.setFirstName("Alice");
        studentA.setName("A");
        StudentBean studentB = new StudentBean();
        studentB.setFirstName("Bob");
        studentB.setName("B");

        noteBusiness.insertStudent(studentA);
        noteBusiness.insertStudent(studentB);
    }
}

Il suffit alors d'exécuter l'URL http://localhost:8080/gestionnotes/fillDB et de vérifier que les étudiants Alice A et Bob B sont bien présents dans la table STUDENT_JPA !