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
:
-
Le nom de l'unité de persistance (attribut
name
de la balisepersistence-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). -
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>
. - À 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). -
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 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>
- 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 surtransaction-type
ici. - Ici, il faut indiquer le même nom JNDI que celui précisé à la création de la Data Source précédemment.
- 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
:
-
Pour
NoteBean
:☕ Code Java - Entité NoteBean 1 2
@Entity//(1)! public class NoteBean implements Serializable {//(2)!
- Indique qu'il s'agit d'un bean JPA.
- 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;
- Indique qu'il s'agit de la clef primaire.
- Cela correspond exactement au
auto_increment
que nous avions mis sur le champid
de la tableNOTE
dans l'exercice 1 de ce TD. - Permet de préciser que le nom de la colonne qui correspond. Si on ne met rien, la colonne s'appellera
idNote
, et pasID_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'appelleNoteBean
. 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 {
-
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.
-
Il faut maintenant faire de même pour le bean
StudentBean
, lié avec la tableSTUDENT_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
) :
-
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 balisepersistence-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;
- C'est cet attribut qui permet de créer la dépendance vers la couche
-
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
, ... -
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 |
|
- Nécessaire pour que les différents objets soient correctement instanciés.
-
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 :
-
Supprime le constructeur que nous avions créé précédemment :
☕ Code Java - Classe NoteBusinessImpl - Constructeurpublic NoteBusinessImpl() { this.noteDao = new NoteDAOImpl(); this.studentDAO = new StudentDAOImpl(); }
-
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;
-
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 queNoteBusinessImpl
) :☕ 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 :
- Supprime la méthode
init
. -
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 :
@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
!