-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path01_java_jpa_L_intro.qmd
More file actions
494 lines (372 loc) · 21.6 KB
/
Copy path01_java_jpa_L_intro.qmd
File metadata and controls
494 lines (372 loc) · 21.6 KB
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
---
title: Introduction à Jakarta Persistence API (JPA)
description: Mapping relationnel/Objet en java avec Jakarta Persistence
image: _quarto-utils/MyMedia/images/optimized/taiki-ishikawa-PUXFKuVf_84-unsplash_java-400.webp
jupyter: java
tools:
- docker
- java
- maven
programs:
- M1-InfoMath [DEV-ADV]
- CNAM-I [PO43]
categories:
- Lecture
- Containers
- Java
- JPA
- Persistence
- ORM
provide_notes: true
provide_slides: true
execute:
echo: false
output: true
---
{{< include ./_init.qmd >}}
## Magics
```{java}
%list
```
## Exemples
Les exemples présentés sont disponibles dans l'entrepôt: [https://github.com/ebpro/sample-hellojpa/](https://github.com/ebpro/sample-hellojpa/)
Un exemple complet de base est disponible dans l'entrepôt: [https://github.com/ebpro/minimal-jpa/](https://github.com/ebpro/minimal-jpa/)
## Introduction à Jakarta Persistence API (JPA)
::: {.content-visible when-profile="slides"}
- Jakarta Persistence API (JPA) est une spécification Java pour la gestion de la persistance des données dans les applications Jakarta EE.
- Objectif : faciliter le mapping entre les objets Java et les tables de base de données relationnelles.
- Simplifie les opérations CRUD (Create, Read, Update, Delete) sur les objets persistants.
- Avantages : portabilité des applications, abstraction de la couche de persistance, réduction de la dépendance au code SQL spécifique au fournisseur de base de données.
- Exemple : utilisation de JPA avec des outils populaires tels que Hibernate, EclipseLink, ou OpenJPA pour simplifier le développement d'applications Jakarta EE.
:::
::: {.content-visible when-profile="notes"}
Le Mapping Relationnel/Object (ORM) est une approche déclarative visant à établir un lien entre un modèle Orienté Objet, tel qu'un programme Java, et un modèle relationnel, matérialisé par des tables dans une base de données relationnelle. Cette démarche peut comprendre la création du schéma relationnel à partir du programme Java et l'automatisation des fonctions essentielles pour la gestion de la persistance des données. Cela améliore la portabilité des applications par une abstraction de la couche de persistance et donc une réduction de la dépendance au code SQL spécifique au fournisseur de base de données.
:::
## Spécification
::: {.content-visible when-profile="slides"}
- JPA : spécification Java pour la liaison entre le modèle orienté objet de Java et le modèle relationnel (ORM).
- Version actuelle : [JPA 3.1](https://jakarta.ee/specifications/persistence/3.1/jakarta-persistence-spec-3.1.html).
- Repose sur JDBC (Java Database Connectivity): génère automatiquement les requêtes SQL et leur mise en oeuvre.
- Offre une solution normalisée et robuste pour la gestion des données dans les applications Java.
:::
::: {.content-visible when-profile="notes"}
Pour Java, Jakara Persistance API (JPA) est une [spécification (actuellement en version 3.1)](https://jakarta.ee/specifications/persistence/3.1/jakarta-persistence-spec-3.1.html) concernant la liaison entre le modèle orienté objet de Java et le modèle relationnel (Object Relational Mapping - ORM). JPA s'appuie sur [JDBC (Java Database Connectivity)](https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/). JPA fournit une solution robuste et normalisée pour la gestion des interactions entre une application Java et une base de données.
Plusieurs implantations de JPA existent dont l'implantation de référence [EclipseLink](https://www.eclipse.org/eclipselink) et une autre très utilisée [Hibernate](https://hibernate.org/). Cette introduction utilise Hibernate mais en respectant le standard.
:::
## En pratique
Il faut donc ajouter les éléments suivant au `pom.xml` de Maven.
``` xml
<!-- L'API Standard -->
<!-- a priori inclue par transitivité avec l'une des suivantes -->
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.3.1.Final</version>
</dependency>
<!-- L'implantation de référence -->
<!--
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>4.0.1</version>
</dependency>
-->
```
## JPA possède trois composantes principales
- La description des "entités" persistentes.
- L'interaction de base avec la persistence via les les opérations CRUD (Create, Read, Update, Delete).
- L'interrogation native ou via un langage dédié.
Pour utiliser JPA, il faut une base de données relationnelle. Nous utiliserons ici [PostgreSQL](https://hub.docker.com/_/postgres) via l'image Docker officielle.
```{java}
//| output: false
//| echo: false
//| vscode: {languageId: java}
// INIT DATABASE PROPERTIES
//System.setProperty("jakarta.persistence.jdbc.url","jdbc:postgresql://db/notebook-db");
//System.setProperty("jakarta.persistence.jdbc.user","dba");
//ystem.setProperty("jakarta.persistence.jdbc.password","secretsecret");
System.setProperty("db.username","dba");
System.setProperty("db.password","secretsecret");
System.setProperty("db.name","notebook-db");
System.setProperty("db.url","jdbc:postgresql://dind/notebook-db");
System.setProperty("jdbc.url", "jdbc:postgresql://dind/notebook-db");
System.setProperty("jdbc.user", "dba");
System.setProperty("jdbc.password", "secretsecret");
```
Les exemples sont disponibles sur ce [repository](https://github.com/ebpro/sample-hellojpa) GitHub.
## Les Entités dans Jakarta Persistence API (JPA)
::: {.content-visible when-profile="slides"}
- **Entité :** Une classe dont les instances doivent être persistantes.
- **Critères pour être une entité :**
- Annotée avec [\@Entity](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entity).
- Au moins un attribut annoté avec [\@Id](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/id).
- Doit avoir au moins un constructeur sans paramètre (au plus `protected`).
- **Association avec une relation :**
- La classe est associée à une relation du même nom.
- Cette relation possède une clé primaire, atomique ou composite.
:::
::: {.content-visible when-profile="notes"}
Avec JPA une classe dont les instances doivent être persistantes est appelée une entité. Techniquement, une classe est une entité si elle annotée avec [\@Entity](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entity), qu'un ou plusieurs de ses attributs sont annotés avec [\@Id](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/id) et qu'elle a au moins un constructeur sans paramètre (au plus `protected`). La classe sera alors associée à une relation du même nom, ayant une clé primaire (atomique ou composite)
:::
::: {.content-visible when-profile="slides"}
## Premier exemple d'entité
:::
::: columns
::: {.column width="50%"}
La classe suivante est donc entité JPA :
```{java}
%%javasrcClassByName fr.univtln.bruno.demos.jpa.hello.samples.ex_simple.Customer
/home/jovyan/work/examples/github/ebpro/sample-hellojpa/src/main/java/fr/univtln/bruno/demos/jpa/hello/samples/ex_simple/Customer.java
```
:::
::: {.column width="50%"}
associée à la relation :
```{java}
%%rdbmsSchema ex_simple
customer;
```
et à la table :
```{java}
%%tableSchema --ddl --sample=3
ex_simple.customer
```
:::
:::
::: {.content-visible when-profile="slides"}
## Exemple étendu d'entité
:::
Le mapping peut être contrôllé finement par un ensemble d'annotations. La classe suivante illustre celles de base.
::: columns
::: {.column width="80%"}
```{java}
%%javasrcClassByName fr.univtln.bruno.demos.jpa.hello.samples.ex_entity.Customer
/home/jovyan/work/examples/github/ebpro/sample-hellojpa/src/main/java/fr/univtln/bruno/demos/jpa/hello/samples/ex_entity/Customer.java
```
:::
::: {.column width="20%"}
```{java}
%%rdbmsSchema public
customer;
```
:::
:::
::: {.content-visible when-profile="slides"}
## La table correspondante
:::
```{java}
%tableSchema customer --ddl --sample=3
```
::: {.content-visible when-profile="slides"}
## Annotations de base pour la Persistance
:::
::: {.content-visible when-profile="slides"}
- **\@Entity**: Indique une classe entité.
- **\@Id**: Définit la clé primaire.
- **\@GeneratedValue**: Contrôle la génération de clés.
- **\@Table**: Contrôle le mapping de la table.
- **\@Column**: Contrôle les colonnes.
- **\@Basic**: Pour les attributs simples.
- **\@Transient**: Non persistant.
- **\@Lob**: Pour les grands objets.
- **\@Version**: Verrouillage optimiste.
:::
::: {.content-visible when-profile="notes"}
[\@Entity](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entity) indique qu'une classe est une entité et permet de contrôler son nom (par défaut celui de la classe).
[\@Id](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/id) est obligatoire et défini le ou les attributs qui compose la clé primaire.
[\@GeneratedValue](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/generatedvalue) contrôle la méthode utilisée pour générer la clé lors de l'insertion (automatique pour s'adapater à la base de données utilisée, identité pour MySQL, séquence pour ceux qui le supporte ou Table qui est portable mais avec les moins bonnes performances et récement UUID).
[\@Table](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/table) contrôle finement le mapping au niveau de la table associée (nom spécifique, schema, catalogue, contraintes et [index](https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/)).
[\@Column](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/column) contrôle finement la colonne associée au membre comme son nom ou ses contraintes (null, insertion ou mise à jour autorisés ou non ; unicité ; ...).
[\@Basic](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/basic) est l'annotation pour tout ce qui peut etre représenté directement comme un attribut : les primitifs (et leurs classes d'enveloppement), les chaînes de caractères, les types temporels, les octets ou tableaux d'octets, les énumérés et tout ce qui implante l'interface Serialisable. Cette annotation est optionnelle et permet de d'indiquer si l'attribut peut être nul dans la base (`optional`) et s'il doit être récupéré au changement de classe (par défaut, [fetch](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/fetchtype)=EAGER) ou uniquement lors de l'accès au membre (fetch=LAZY).
[\@Transient](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/transient) permet d'indiquer qu'un membre ne doit pas être persistant (s'il porte le modificateur `transient` c'est aussi le cas).
[\@Lob](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/lob) permet d'associer des grands objets binaires ou textes (BLOB et CLOB).
[\@Version](https://jakarta.ee/specifications/persistence/3.1/jakarta-persistence-spec-3.1#a2059) définit un attribut utiliser pour assurer le verrouillage optimiste.
:::
## Le gestionnaire d'entités
::: {.content-visible when-profile="slides"}
- Utilisation de l'[entity manager](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entitymanager) (EM) pour les opérations CRUD et les requêtes.
- Configuration des unités de persistance dans `META-INF/persistence.xml`.
- Schéma: <https://jakarta.ee/xml/ns/persistence>.
:::
::: {.content-visible when-profile="notes"}
Pour gérer les entité JPA s'appuie sur l'[entity manager](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entitymanager) (EM). Il fournit les opération CRUD de base et une interface pour exécuter des requêtes. L'EM s'appuie sur des unités de persistances (des connexions à une base de données) qui sont définie dans un fichier XML `META-INF/persistence.xml` à la racine du classpath (donc dans `src/main/resources` avec Maven). Son schéma est défini dans le standard [https://jakarta.ee/xml/ns/persistence](%5Bhttps://jakarta.ee/xml/ns/persistence).
:::
::: {.content-visible when-profile="slides"}
## Exemple de persistence.xml
:::
::: {.content-visible when-profile="notes"}
### Exemple de persistence.xml
Un exemple de base est donné ci-dessous :
:::
``` {.xml include="/home/jovyan/work/examples/github/ebpro/sample-hellojpa/src/main/resources/META-INF/persistence.xml"}
```
::: {.content-visible when-profile="slides"}
## EM et persistence d'une entité
- Utilisation de l'EntityManagerFactory
- Obtention d'un EntityManager via l'[EntityManagerFactory](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entitymanagerfactory), en spécifiant le nom de la persistence unit.
- La génération automatique du schéma a lieu lors de la première utilisation.
- Opérations avec l'EntityManager
- Commencer une transaction avec `begin`, persister une instance avec `persist`, terminer avec `commit` ou `rollback`.
- L'EM met automatiquement à jour l'entity avec l'ID généré par la base de données.
- L'EM : utilisé localement et fermé après utilisation (`AutoCloseable`).
:::
::: {.content-visible when-profile="notes"}
### Création de l'EM et persistence d'une entité
Pour obtenir un EM, on utilise l'[EntityManagerFactory](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entitymanagerfactory) (EMF) en indiquant le nom d'une persistence unit. La génération automatique éventuelle du schéma a lieu lors de la première utilisation de l'EMF.
A partir de l'entity manager, il est alors possible de commencer une transaction (`begin`), de rendre persistante une instance (`persist`) et de terminer la transaction (`commit` ou `rollback`). On note la mise à jour automatique de l'entity avec l'ID généré par la base de données. L'entity manager est prévu pour une utilisation locale et généralement brève, il doit être fermé après utilisation (`autocloseable`).
:::
``` java
try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("hellojpa-pu"))
{
try (EntityManager entityManager = emf.createEntityManager()) {
}
}
```
::: {.content-visible when-profile="slides"}
## Usage avec un Singleton EMF
:::
::: {.content-visible when-profile="notes"}
La création d'un EMF est couteuse, on utilise donc généralement un singleton pour gérer l'EMF.
:::
```{java}
//| output: true
//| echo: true
//| user_expressions: []
//| vscode: {languageId: java}
EntityManagerFactory emf = DatabaseManager.getEntityManagerFactory();
Logger log = LoggerFactory.getLogger("notebook");
```
ce singleton retourne l'instance de l'EMF qui n'est qu'à la première demande.
```{java}
//| output: true
//| echo: true
//| vscode: {languageId: java}
import fr.univtln.bruno.demos.jpa.hello.samples.ex_simple.*;
try (EntityManager entityManager = emf.createEntityManager();) {
entityManager.getTransaction().begin();
Customer customer = Customer.of("pierre.durand@ici.fr");
log.info("BEFORE PERSIST "+customer.toString());
entityManager.persist(customer);
log.info("AFTER PERSIST: "+customer.toString());
entityManager.getTransaction().commit();
}
```
::: {.content-visible when-profile="notes"}
A partir du moment ou une entité est liée au contexte de persistence (en venant d'être crée ou récupérée), la mise à jour dans la base est implicite (pas de fonction dédiée de l'entity manager). La transaction n'est nécessaire que pour les modifications.
:::
::: {.content-visible when-profile="slides"}
## Mise à jour
- Mise à jour implicite
- Transaction nécessaire pour les mises à jour
:::
```{java}
//| output: true
//| echo: true
//| vscode: {languageId: java}
long newId;
Customer customer = Customer.of("marie.dupond@la.fr");
try (EntityManager entityManager = emf.createEntityManager();) {
entityManager.getTransaction().begin();
entityManager.persist(customer);
customer.setName("Marie Dupond"); // <1>
entityManager.getTransaction().commit();
log.info("AFTER COMMIT: "+customer.toString());
}
newId=customer.getId();
```
::: {.content-visible when-profile="slides"}
## Find by Id
- recherche par ID [`find(Class<T> entityClass, Object primaryKey)`](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entitymanager)
:::
::: {.content-visible when-profile="notes"}
L'entity manager propose une méthode de recherche par ID [`find(Class<T> entityClass, Object primaryKey)`](https://jakarta.ee/specifications/persistence/3.1/apidocs/jakarta.persistence/jakarta/persistence/entitymanager) qui prend en paramètre la classe de l'entité recherchée et la valeur de la clé.
:::
```{java}
//| output: true
//| echo: true
//| vscode: {languageId: java}
try (EntityManager entityManager = emf.createEntityManager();) {
Optional<Customer> customer = Optional.ofNullable(entityManager.find(Customer.class, newId));
if (customer.isEmpty())
log.info("Id %d not in database.".formatted(newId));
else {
log.info("Search cust. Id %d: %s".formatted(newId,customer));
}
}
```
::: {.content-visible when-profile="slides"}
## Suppression
- **remove** supprimer une entité de la base de données.
:::
::: {.content-visible when-profile="notes"}
L'entity manager propose une méthode `remove` pour supprimer une entité de la base de données.
:::
```{java}
//| output: true
//| echo: true
//| vscode: {languageId: java}
try (EntityManager entityManager = emf.createEntityManager();) {
Optional<Customer> customer = Optional.ofNullable(entityManager.find(Customer.class, newId));
if (customer.isEmpty())
log.error("Id %d not in database.".formatted(newId));
else {
log.info("Removed Id %d from the database.".formatted(newId));
entityManager.getTransaction().begin();
entityManager.remove(customer.get());
entityManager.getTransaction().commit();
}
}
```
::: {.content-visible when-profile="slides"}
## Fonctions supplémentaires de l'EntityManager
- **merge**: Attache un objet au contexte de persistence (par son ID).
- **refresh**: Annule les modifications en cours de transaction sur une entité.
- **detach**: Indique à l'EM de ne plus gérer une entité.
:::
::: {.content-visible when-profile="notes"}
`merge` attache un objet au contexte de persistence (par son id), `refresh` annule les modifications en cours de transanction sur une entité et `detach` indique à l'EM de ne plus gérer une entité.
:::
```{java}
//| output: false
//| echo: false
//| vscode: {languageId: java}
import fr.univtln.bruno.demos.jpa.hello.samples.ex_simple.*;
import fr.univtln.bruno.demos.jpa.hello.DatabaseManager;
long newId;
try (EntityManager entityManager = emf.createEntityManager();) {
entityManager.getTransaction().begin();
Customer customer = Customer.of("pierre.durand@ici.fr");
entityManager.persist(customer);
entityManager.getTransaction().commit();
newId = customer.getId();
}
```
```{java}
//| output: true
//| echo: true
//| vscode: {languageId: java}
Customer customer = Customer.of("???");
customer.setId(newId);
log.info("BEFORE MERGE "+customer.toString());
try (EntityManager entityManager = emf.createEntityManager();) {
entityManager.getTransaction().begin();
customer = entityManager.merge(customer);
entityManager.refresh(customer);
entityManager.getTransaction().commit();
}
log.info("AFTER MERGE+REFRESH "+customer.toString());
```
## Conseils pour l'utilisation de JPA avec Lombok
::: {.content-visible when-profile="slides"}
- **Attention avec `@EqualsAndHashCode`**: Vérifier les implications avec les relations Hibernate, voir https://thorben-janssen.com/ultimate-guide-to-implementing-equals-and-hashcode-with-hibernate/
- Éviter d'utiliser `@Data`: Contrôler précisément les méthodes générées pour éviter des comportements inattendus.
- Exclure les attributs "Lazy" de `@ToString`: Prévenir les problèmes de chargement paresseux inattendus lors de l'affichage.
- Ajouter `@NoArgsConstructor(access= AccessLevel.PROTECTED)`: Utiliser en conjonction avec `@Builder` ou `@AllArgsConstructor` pour garantir des comportements de construction cohérents.
:::
::: {.content-visible when-profile="notes"}
Lorsque vous utilisez Lombok avec JPA (Java Persistence API), quelques précautions sont nécessaires pour assurer un fonctionnement harmonieux de votre application. Évitez l'utilisation de l'annotation `@Data`, car elle peut générer des méthodes indésirables dans le contexte de la persistance des données. De plus, faites attention avec `@EqualsAndHashCode`, car elle peut avoir des implications inattendues sur les relations Hibernate. Lorsque vous utilisez `@ToString`, assurez-vous d'exclure les attributs "Lazy" pour éviter les problèmes liés au chargement paresseux lors de l'affichage des objets. Enfin, pour garantir une construction cohérente des entités, ajoutez `@NoArgsConstructor(access= AccessLevel.PROTECTED)` en conjonction avec `@Builder` ou `@AllArgsConstructor`. En suivant ces bonnes pratiques, vous pouvez optimiser l'utilisation de Lombok avec JPA tout en évitant les écueils potentiels.
:::