Plan de ce cours
Présentation de JPA JPA Entité Gestionnaire d’entités (Jakarta Persistence API) Compléments sur les entités : pour Jakarta EE identité, associations, héritage Requêtes, JPQL ITU - Université Côte d’Azur Modification en volume Richard Grin Concurrence Version O 1.37 – 10/3/20 Adaptation à une base préexistante Bibliographie
R. Grin JPA page 2
1 2
JPA
Spécification pour la persistance des objets Java dans une base de données (BD) relationnelle ORM, Object-Relational mapping, fait Présentation de JPA correspondre le monde relationnel et le monde des objets Au-dessus de JDBC ; donc un driver JDBC du SGBD doit être installé sur le serveur d’application ou dans l’application Ce support étudie JPA dans Jakarta EE mais JPA peut aussi être utilisé avec Java SE
R. Grin JPA page 3 R. Grin JPA page 4
3 4
Principales propriétés de JPA Fournisseur de persistance
Des appels simples permettent de gérer la L’utilisation de JPA nécessite un fournisseur persistance, par exemple « persist(objet) » de persistance qui implémente les classes et pour rendre un objet persistant interfaces de l’API Les données de la base de données relationnelle EclipseLink et Hibernate sont des fournisseurs sont traitées avec une vision objet, en utilisant les de persistance classes Java entités
R. Grin JPA page 5 R. Grin JPA page 6
5 6
1 Entité Exemple d’entité – identificateur et attributs Classe dont les instances peuvent être persistantes @Entity Clé primaire Annotée par @Entity public class Departement { dans la base – obligatoire « entité » peut aussi désigner une instance de @Id classe entité @GeneratedValue valeur générée private Long id; automatiquement – optionnel private String nom; Par défaut tous les attributs private String lieu; sont persistants
R. Grin JPA page 7 R. Grin JPA page 8
7 8
Exemple d’entité – constructeurs Exemple d’entité – une association Un département - Autre bout de l’association plusieurs employés dans classe Employe public Departement() { } Obligatoire @OneToMany(mappedBy="dept") private List
R. Grin JPA page 9 R. Grin JPA page 10
9 10
Fichiers de configuration XML persistence.xml
11 12
2 Définition d’une source de données Exemple 1Nom JNDI dans persistence.xml Soit directement dans le serveur d’application @DataSourceDefinition( (pas standardisé) name = "java:app/jdbc/b1", className = "com.mysql.cj.jdbc.MysqlDataSource", Soit dans l’application, portNumber = 3306, de façon standard serverName = "localhost", — par @DataSourceDefinition user = "root", password = "fflkq7dlfj", databaseName = "db1", Nom de la BD — dans un fichier de configuration standard properties= {"characterEncoding=UTF-8", dans le SGBD (web.xml par exemple) "useUnicode=true",…}) ou d’une façon particulière au serveur @Stateless Propriétés d’application (glassfish-resources.xml par public class MonEJB { ... } non standards exemple)
R. Grin JPA page 13 R. Grin JPA page 14
13 14
Exemple 2 Gestionnaire d’entités (EM)
R. Grin JPA page 15 R. Grin JPA page 16
15 16
Contexte de persistance Exemple 1 nom dans persistence.xml ; em.persist(entité) @Stateless optionnel si une seule unité rend entité persistant sera géré par em public class DepartementFacade { Toute modification apportée à entité sera @PersistenceContext(unitName = "employes") enregistrée dans la BD par em au moment du private EntityManager em; EM injecté par le container commit public void create(Dept dept) { L’ensemble des entités gérées par un EM em.persist(dept); s’appelle un contexte de persistance (CP) dept.setLieu("Paris"); enregistré dans la BD ? } Transaction démarrée EM CP ... } au début de la méthode Pourquoi ? et fermée à la fin
R. Grin JPA page 17 R. Grin JPA page 18
17 18
3 Exemple 2 Que fait cette méthode ?
public void augmenter(String poste, int prime) { String requete = "SELECT e FROM Employe e " Pas du SQL, c’est du JPQL ; + " WHERE e.poste = :poste"; Employe est une classe Java Query query = em.createQuery(requete); query.setParameter("poste", poste); Entité List
Oui, car les employés récupérés dans la BD sont ajoutés automatiquement par em dans le CP
R. Grin JPA page 19 R. Grin JPA page 20
19 20
Conditions pour une classe entité Types d’accès à une valeur persistante
Constructeur sans paramètre protected ou JPA peut accéder à la valeur d’un attribut de 2 public façons Attribut qui représente la clé primaire dans la BD par la variable d’instance ; accès « par champ » Pas final par les getters et setters ; Méthodes ou champs persistants pas final accès « par propriété » La classe ne doit pas nécessairement implémenter Serializable mais c’est requis dans certaines circonstances
R. Grin JPA page 21 R. Grin JPA page 22
21 22
Exemple Quel type d’accès choisir ?
Accès par champ : L’accès par propriété oblige à ajouter des getters @Id et des setters pour tous les attributs private int id; Choisir plutôt l’accès par champ Accès par propriété : @Id public int getId() { ... } Accès par champ ou par propriété, quelle est votre préférence ?
R. Grin JPA page 23 R. Grin JPA page 24
23 24
4 Attributs persistants Cycle de vie d’une instance d’entité
Par défaut, tous les attributs d’une entité sont L’instance peut être persistants nouvelle : créée mais pas gérée par un EM Exception : attribut avec un champ gérée par un EM (méthode persist, ou transient (mot-clé Java lié à la sérialisation) instance récupérée dans la BD) ou annoté par @Transient détachée : a une identité dans la BD mais n’est plus gérée par un EM supprimée : gérée par un EM ; données seront supprimées dans la BD au commit (méthode remove)
R. Grin JPA page 25 R. Grin JPA page 26
25 26
Table de la base de données Exemple
Dans les cas simples, une classe une table @Entity nom de la table = nom de la classe public class Departement { instances sauvegardées dans la table noms des colonnes = noms des attributs Departement (ou DEPARTEMENT) Par exemple, la classe Departement @Entity correspond à la table Departement avec les @Table(name = "DEPT") colonnes id, nom, lieu public class Departement { Convention plutôt que configuration pour JPA @Column pour changer le nom ou les attributs d’une colonne dans la table
R. Grin JPA page 27 R. Grin JPA page 28
27 28
Types persistants Types « basic » Types primitifs (int, double,…) ou enveloppes Un attribut persistant peut être d’un des types de type primitifs (Integer,…) suivants : String, BigInteger, BigDecimal « basic » Types de l’API « time » Java 8 (LocalDate,…), « Embeddable » Date et Calendar (de java.util ; doivent être collection d’éléments de type « basic » ou annoté par @Temporal), Date, Time et Embeddable Timestamp (de java.sql ; peu utilisés) Énumérations Tableaux de byte, Byte, char, Character Plus généralement Serializable (sauvegardé comme un tout dans la base) R. Grin JPA page 29 R. Grin JPA page 30
29 30
5 Exemples Classe Embeddable
private LocalDateTime dateOperationBancaire; Classe dont les données sont insérées dans // par défaut le numéro est enregistré dans la BD les lignes de la table d’une entité @Enumerated(EnumType.STRING) private TypeEmploye typeEmploye;
R. Grin JPA page 31 R. Grin JPA page 32
31 32
Exemple Collection d’éléments @Embeddable public class Adresse { Pas besoin d’id Collection d’éléments de type basic ou private int numero; puisque les données embeddable private String rue; sont insérées dans private String ville; celles de Employe Par exemple une collection de numéros de . . . téléphone, enregistrés dans une table à part : } @ElementCollection @Entity private Set
R. Grin JPA page 33 R. Grin JPA page 34
33 34
2 types d’EM dans un serveur d’application
Géré par l’application : créé par l’application à Gestionnaire d’entités partir d’une fabrique d’EM injectée ; doit être fermé par l’application (Entity Manager) Géré par le container : injecté par le serveur d’application ; automatiquement fermé par le container
R. Grin JPA page 35 R. Grin JPA page 36
35 36
6 EM géré par l’application 2 types d’EM géré par le container
@Stateful Les 2 types diffèrent par la relation de l’EM public class DeptManager { avec le CP et la durée de vie du CP par @PersistenceUnit Fabrique EM injectée rapport aux transactions private EntityManagerFactory emf; par container private EntityManager em; EM de portée transaction : le CP ne dure private Departement dept; que le temps de la transaction et il peut être lié à plusieurs EMs Le CP est attaché à la transaction public void init(int deptId) { em = emf.createEntityManager(); EM créé par application EM étendu (ne peut exister que dans un dept = em.find(Departement.class, deptId); EJB stateful) : le CP est lié à un seul EM ; il } EJB stateful pour gérer peut exister après la fin de la transaction ... un seul département Le CP est attaché à l’EJB stateful
R. Grin JPA page 37 R. Grin JPA page 38
37 38
Portée transaction Propagation de contexte @Stateless public class DeptFacade { Avec un EM de portée transaction, à chaque @PersistenceContext EM injecté par le container private EntityManager em; opération JPA, l’EM regarde si un CP est déjà attaché à la transaction public void create(Dept dept) { Si c’est le cas, ce CP est utilisé par l’EM, sinon em.persist(dept); CP ne dure que le temps l’EM demande un nouveau CP au container et } de la transaction l’attache à la transaction public void edit(Dept dept) { Permet à plusieurs EMs d’utiliser le même CP em.merge(dept); pendant une transaction entière } ... } R. Grin JPA page 39 R. Grin JPA page 40
39 40
Limitation d’une transaction EM étendu gérée par le container (CMT) @Stateful Elle ne peut pas englober des entités gérées public class Caddy { par plusieurs unités de persistance (donc des @PersistenceContext(type=EXTENDED) données venant de plusieurs bases de EntityManager em; Même CP conservé données) ... pendant plusieurs transactions Si on veut qu’une transaction englobe } plusieurs unités de persistance, il faut donc utiliser une transaction gérée par l’application Permet d’établir une conversation longue (BMT) entre l’utilisateur et la BD, sans avoir à gérer des entités détachées (le contexte reste ouvert entre les transactions) R. Grin JPA page 41 R. Grin JPA page 42
41 42
7 Opérations en dehors transaction Choix du type d’EM
Opérations permises avec EM étendu : Plus simple de le faire gérer par le container Opérations de lecture : find, refresh, detach (propagation du CP, pas besoin de le fermer) requêtes select Le plus souvent l’EM est de portée transaction Opérations de modification permises : persist, merge, remove Avec EM de portée transaction : Seules les opérations de lecture sont autorisées ; elles retournent des entités détachées Qui peut expliquer pourquoi cette différence entre types d’EM ?
R. Grin JPA page 43 R. Grin JPA page 44
43 44
Méthodes de EntityManager Méthodes de EntityManager
void persist(Object entité) void clear()
R. Grin JPA page 45 R. Grin JPA page 46
45 46
Quand INSERT ? flush() Quand UPDATE ? refresh(entité) Quand DELETE ? Enregistre dans la BD les modifications entité doit être gérée (faire un merge en cas effectuées sur les entités d’un CP de besoin) L’EM lance les commandes SQL pour Données de la BD sont copiées dans l’entité modifier la BD (INSERT, UPDATE ou (qui doit être dans le CP), écrasant les DELETE) éventuelles modifications faites dans cette Automatiquement effectué entité à chaque commit et, Pour avoir la dernière version des données optionnellement, avant enregistrées dans la BD les requêtes flush() = commit ?
R. Grin JPA page 47 R. Grin JPA page 48
47 48
8 Contexte de persistance - cache Entité détachée
Un CP est un cache pour les accès à la BD Une entité gérée par un EM peut être find, et Query qui retourne des entités détachée de son CP (par exemple par la entières, ramènent les données du CP si elles méthode detach, ou si l’EM est fermé) y sont déjà Cette entité détachée peut être modifiée Attention, les données du CP ne tiennent pas compte des modifications faites sans passer Pour enregistrer ces modifications dans la BD par l’EM il est nécessaire de rattacher l’entité à un CP par la méthode merge Peut poser des problèmes ; un refresh peut être la solution Attention au cache de 2ème niveau partagé par tous les EMs d’une même unité de persistance R. Grin JPA page 49 R. Grin JPA page 50
49 50
merge(a) merge – une erreur à ne pas faire
Attention, merge(a) ne met pas a dans le CP On veut ajouter un employé à dept dans la BD : C’est une copie de a qui est mise dans le CP ; em.merge(dept); Quel est le cette copie est retournée par la méthode dept.addEmploye(emp); problème ? Si l’application utilise les données de a après L’ajout du nouvel employé ne sera pas un merge, il faut écrire ce type de code : enregistré dans la BD car l’objet référencé par a = em.merge(a); dept n’est pas géré ; le bon code : dept = em.merge(dept); dept.addEmploye(emp);
R. Grin JPA page 51 R. Grin JPA page 52
51 52
Différence entre persist et merge
persist sert pour les entités dont les données ne sont pas déjà dans la BD : traduit par un INSERT si l’entité a un id qui existe déjà dans la Identité des entités base, une exception est levée Au contraire, merge sert le plus souvent pour les entités qui correspondent à des données déjà dans la BD et il est alors traduit par un UPDATE
R. Grin JPA page 53 R. Grin JPA page 54
53 54
9 Clé primaire Type de la clé primaire
L’attribut annoté @Id correspond à la clé Type primitif Java primaire dans la table associée Classe qui enveloppe un type primitif Ne jamais le modifier dès que l’entité java.lang.String représente des données de la base (java.util.Date) (java.sql.Date)
R. Grin JPA page 55 R. Grin JPA page 56
55 56
Génération automatique de clé Exemple
@Id Pour les clés de type nombre entier @GeneratedValue( @GeneratedValue indique que la clé primaire strategy = GenerationType.SEQUENCE, sera générée par le SGBD generator = "EMP_SEQ") Cette annotation peut avoir un attribut private Long id; strategy qui indique comment la clé sera générée La valeur de l’id est mise automatiquement dans l’entité soit après un persist, soit après un flush
R. Grin JPA page 57 R. Grin JPA page 58
57 58
Clé composite
Pas recommandé, mais une clé primaire peut Faire TP 4 être composée de plusieurs colonnes Peut arriver quand la BD existe déjà, en particulier quand la classe correspond à une table association (association M:N) 2 possibilités : @EmbeddedId et @Embeddable @IdClass
R. Grin JPA page 59 R. Grin JPA page 60
59 60
10 Rappels
Exemples : association entre les départements d’une entreprise et leurs employés Associations les cours et les étudiants Types d’association entre entités : 1-1, 1-N, N-1, M-N Donnez des exemples uni ou bidirectionnelle de chacun de ces types
R. Grin JPA page 61 R. Grin JPA page 62
61 62
Bout propriétaire Exemple
Un des 2 bouts d’une association bidirectionnelle Dans la classe Employe : est dit propriétaire de l’association @ManyToOne private Departement departement; Pour les associations 1 – N, c’est celui qui correspond à la table qui contient la clé Dans la classe Departement : étrangère ; c’est le bout 1 ou N ? @OneToMany(mappedBy = "departement") private Collection
Nom de la colonne clé étrangère : DEPARTEMENT_ID
R. Grin JPA page 63 R. Grin JPA page 64
63 64
Association bidirectionnelle - JPA Exemple
Dans les associations 1-N bidirectionnelle, le bout Le développeur est responsable de la gestion « 1 » peut comporter ce genre de méthode : correcte des 2 bouts de l’association public void ajouterEmploye(Employe e) { Pour faciliter la gestion des 2 bouts, il est Departement d = e.getDept(); commode d’ajouter une méthode qui effectue if (d != null) { tout le travail d.employes.remove(e); } Dans quelle classe this.employes.add(e); est ce code ? e.setDept(this); } Qui veut expliquer ce code ?
R. Grin JPA page 65 R. Grin JPA page 66
65 66
11 Erreur à ne pas faire Configuration
Attention, si on ne modifie que le bout non Si une valeur par défaut ne convient pas, propriétaire (on oublie de modifier le bout ajouter une annotation dans le bout propriétaire) d’une association propriétaire bidirectionnelle, l’association n’est pas Par exemple, pour indiquer le nom de la enregistrée dans la base de données au colonne clé étrangère : moment du commit @ManyToOne @JoinColumn(name="DEPT") private Departement departement; Ce code est dans quelle classe ? Que signifie l’annotation @JoinColumn ?
R. Grin JPA page 67 R. Grin JPA page 68
67 68
Association M:N Exemple - bidirectionnel Dans quelle Traduite par 1 ou 2 collections annotées par @ManyToMany classe ? @ManyToMany private Collection
69 70
Association M:N avec information Classe association portée par l’association Chaque classe qui participe à l’association peut avoir une collection d’objets de la classe Exemple : association (suivant directionnalité) Association entre les employés et les projets ; un employé a une fonction dans chaque 2 possibilités pour la classe association : projet auquel il participe un attribut identificateur (@Id) unique (le Où mettre la fonction dans le modèle objet ? plus simple) Association M:N traduite par une classe un attribut identificateur par classe association participant à l’association Association bidirectionnelle pour le code des transparents suivants
R. Grin JPA page 71 R. Grin JPA page 72
71 72
12 Code classe association (début) Code classe association (fin)
public Participation() { } Obligatoire ? @Entity public class Participation { public Participation(Employe employe, @Id @GeneratedValue Projet projet, String fonction) { private int id; this.employe = employe; @ManyToOne this.projet = projet; private Employe employe; this.fonction = fonction; @ManyToOne } private Projet projet; // Accesseurs pour employe, projet et // fonction private String fonction; ...
R. Grin JPA page 73 R. Grin JPA page 74
73 74
Classes Employe et Projet Pas de persistance par transitivité automatique avec JPA @Entity public class Employe { @Id private int id; Par défaut, JPA n’effectue pas automatiquement @OneToMany(mappedBy="employe", cascade=CascadeType.ALL) la persistance par transitivité : private Collection
R. Grin JPA page 75 R. Grin JPA page 76
75 76
Cohérence des données Cascade
Classe Departement contient une collection Les opérations persist, remove, refresh, d’Employe merge, detach peuvent être transmises par Au moment du commit, si un département est transitivité à l’autre bout d’une association persistant et si un des employés du département Exemple : ne l’est pas, une IllegalStateException est @OneToMany( lancée cascade = { CascadeType.PERSIST, CascadeType.MERGE }, L’attribut cascade des associations peut simplifier mappedBy = "client") le code private Collection
77 78
13 Attribut orphanRemoval Exemple
Pour traduire la « composition » UML @Entity Si une annotation @OneToMany a l’attribut public class Facture { orphanRemoval à true, les entités enlevées de ... la collection seront supprimées de la BD @OneToMany(mappedBy="facture", cascade=CascadeType.ALL, Implique une cascade sur remove orphanRemoval=true) Facilite en particulier la suppression private Collection
R. Grin JPA page 79 R. Grin JPA page 80
79 80
Récupération des entités associées EAGER ou LAZY
JPA laisse le choix de récupérer ou non Lorsqu’une entité est récupérée depuis la BD immédiatement les entités associées (Query ou find), est-ce que les entités associées sont aussi récupérées ? Mode EAGER : les données associées sont récupérées immédiatement Si elles sont récupérées, est-ce que les entités associées à ces entités sont elles aussi Mode LAZY (lazy loading) : les données récupérées ? associées ne sont récupérées que lorsque c’est vraiment nécessaire Le risque est de récupérer un très grand nombre d’entités inutiles
R. Grin JPA page 81 R. Grin JPA page 82
81 82
Récupération tardive Mode de récupération par défaut des entités associées Supposons une association en mode lazy entre un département et ses employés Mode EAGER pour les associations OneToOne On récupère un département et ManyToOne Plus tard on veut avoir le nom d’un employé de Mode LAZY pour les associations OneToMany ce département et ManyToMany Cet employé est récupéré automatiquement par l’EM si l’entité département n’est pas Vous voyez une raison pour ce choix ? détachée Et si le département est détaché ? Il faut faire avant un merge du département
R. Grin JPA page 83 R. Grin JPA page 84
83 84
14 Indiquer le type de récupération des entités associées
L’attribut fetch permet de modifier le comportement par défaut Signification ? @OneToMany(mappedBy = "departement", fetch = FetchType.EAGER) Héritage private Collection
R. Grin JPA page 85 R. Grin JPA page 86
85 86
Stratégies Toutes les classes traduites par une seule table 2 stratégies pour traduire une hiérarchie d’héritage en relationnel : une seule table (SINGLE_TABLE) ; par défaut une table par classe ; les tables sont jointes pour reconstituer les données (JOINED) La stratégie « une table par classe non abstraite » est optionnelle (TABLE_PER_CLASS) Pour différencier les types de personnes R. Grin JPA page 87 R. Grin JPA page 88
87 88
Exemple « 1 table par hiérarchie » Une table par classe Dans la classe @Entity racine de la Colonne @Inheritance(strategy = hiérarchie ; discriminatrice InheritanceType.SINGLE_TABLE) optionnel public abstract class Personne {...}
@Entity « Employe » par défaut @DiscriminatorValue("E") public class Employe extends Personne { ... }
R. Grin JPA page 89 R. Grin JPA page 90
89 90
15 Préservation de l’identité Exemple
@Entity @Inheritance(strategy = InheritanceType.JOINED) public abstract class Personne {...}
Clé étrangère et primaire @Entity @DiscriminatorValue("E") « Employe » par défaut public class Employe extends Personne { ... }
R. Grin JPA page 91 R. Grin JPA page 92
91 92
@Entity public abstract class Personne {...}
@Entity Requêtes – JPQL public class Employe extends Personne { Java Persistence Query Language ... }
Quelle sera la stratégie utilisée pour traduire l’héritage ?
R. Grin JPA page 93 R. Grin JPA page 94
93 94
IMPORTANT Chercher par identité
Cette section concerne les entités retrouvées find retrouve une entité en donnant son en interrogeant la base de données identificateur dans la BD : Departement dept = Rappel : ces entités sont automatiquement em.find(Departement.class, 10); gérées par le gestionnaire d’entités Les modifications apportées à ces entités seront enregistrées au prochain commit
R. Grin JPA page 95 R. Grin JPA page 96
95 96
16 Étapes pour récupérer des données
Il est possible de rechercher des données sur 1. Décrire ce qui est recherché (langage JPQL) des critères plus complexes que la simple 2. Créer une instance de type Query identité 3. Initialiser la requête (paramètres, pagination) 4. Lancer l’exécution de la requête String s = "select e from Employe as e"; Query query = em.createQuery(s); List
cast obligatoire R. Grin JPA page 97 R. Grin JPA page 98
97 98
Exemple plus complexe Exemple plus complexe - chaînage
String q = "select e from Employe as e " String q = "select e from Employe as e " + "where e.departement.numero = :numero"; + "where e.departement.numero = :numero"; Query query = em.createQuery(q); Query query = em.createQuery(q); query.setParameter("numero", 10); query.setParameter("numero", 10) query.setMaxResults(30); .setMaxResults(30); for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) { query.setFirstResult(30 * i); query.setFirstResult(30 * i); List
R. Grin JPA page 99 R. Grin JPA page 100
99 100
Langage JPQL Exemples de requêtes Alias de classe obligatoire Java Persistence Query Language décrit ce qui select e from Employe e est recherché en utilisant le modèle objet (pas select e.nom, e.salaire from Employe e le modèle relationnel) select e from Employe e where e.departement.nom = 'Direction' Les seules classes qui peuvent être utilisées sont les entités et les Embeddable select d.nom, avg(e.salaire) from Departement d join d.employes e group by d.nom having count(d.nom) > 5
R. Grin JPA page 101 R. Grin JPA page 102
101 102
17 Obtenir le résultat de la requête Type d’un élément du résultat
Résultat = une seule valeur ou entité : Object getSingleResult() Le type d’un élément du résultat est Résultat = plusieurs valeurs ou entités : Object si la clause select ne comporte List getResultList() qu’une seule expression On peut aussi récupérer les données dans un Object[] sinon Stream avec la méthode getResultStream() Attention, ne pas utiliser le stream pour faire des choses que le SGBD sait mieux faire, par exemple une sélection
R. Grin JPA page 103 R. Grin JPA page 104
103 104
Exemple Types de requête
texte = "select e.nom, e.salaire " Requête dynamique : texte donné en + " from Employe as e"; paramètre de createQuery query = em.createQuery(texte); Requête nommée : texte statique donné dans Qui peut expliquer ? List
R. Grin JPA page 105 R. Grin JPA page 106
105 106
Exemple de requête nommée Exemples de paramètres
@Entity Query query = em.createQuery( @NamedQuery ( "select e from Employe as e " name = "employe.findNomsEmpsDept", + "where e.nom = :nom"); query = "select e.nom from Employe as e where query.setParameter("nom", "Dupond"); upper(e.departement.nom) = :nomDept" Query query = em.createQuery( ) "select e from Employe as e " public class Employe extends Personne { + "where e.nom = ?1"); query.setParameter(1, "Dupond"); ...
Query q = em.createNamedQuery( "employe.findNomsEmpsDept");
R. Grin JPA page 107 R. Grin JPA page 108
107 108
18 TypedQuery
Quand on connait le type T retourné par une String s = "select e from Employe as e"; requête, il vaut mieux utiliser l’interface TypedQuery
R. Grin JPA page 109 R. Grin JPA page 110
109 110
Clauses d’un select Navigation Il est possible de suivre le chemin select correspondant à une association avec la from JPQL permet les sous-requêtes notation « pointée » et même les sous-requêtes where synchronisées Si e alias pour Employe, group by « e.departement » désigne le département having d’un employé ; l’entité Employe doit avoir un attribut departement order by De même « e.projets » désigne les projets auxquels participe un employé
R. Grin JPA page 111 R. Grin JPA page 112
111 112
Règles pour les Contre-exemple expressions de chemin « e.projets.nom » est interdit car Si une navigation ne donne qu’une seule e.projets est une collection entité (OneToOne ou ManyToOne) on peut Pour avoir les noms des employés du chaîner une navigation à la suite département « Direction » et les noms des select e.nom, projets auxquels ils participent, il faut une e.departement.nom, jointure : e.superieur.departement.nom select e.nom, p.nom from Employe e from Employe e join e.projets p where e.departement.nom = 'Direction'
R. Grin JPA page 113 R. Grin JPA page 114
113 114
19 Jointure Récupération entités associées
Interne (jointure standard join) Le plus simple est la clause join fetch de Externe (left outer join) JPQL (ou le pendant dans l’API « criteria ») Avec récupération de données en mémoire La méthode find (Map passée en dernier (join fetch) ; pour éviter le problème des paramètre), l’interface Query (méthode « N +1 selects » setHint) ou l’annotation @NamedQuery (attribut hints) permettent de donner des « hints » Jointure « à la SQL » : égalité de 2 valeurs, sans tenir compte des associations entre (indications, conseils) entités Par ces hints on peut désigner un graphe d’entités (pas étudié dans ce cours) qui indique quelles entités associées on veut récupérer
R. Grin JPA page 115 R. Grin JPA page 116
115 116
join fetch Problèmes des N + 1 selects
On veut les noms des projets auxquels select e from Employe as e participent les N employés join fetch e.participations On commence par récupérer les employés avec Les participations sont créées dans la mémoire un select (1 select SQL) : centrale en même temps que les employés select e from Employe e (sans fetch join) (mais pas retournées par le select) Pour chaque employé, e.getProjets() (pour L’instruction récupérer les projets de l’employé) déclenche unEmploye.getParticipations() un select (N select SQL pour les N employés) ne nécessitera pas d’accès à la base N + 1 select seront donc nécessaires, alors Permet de résoudre le problème des « N + 1 qu’un seul select SQL (avec jointure externe) selects » suffit à récupérer toutes les informations R. Grin JPA page 117 R. Grin JPA page 118
117 118
Problèmes avec join fetch Graphe d’entités
La requête SQL avec un join fetch fait une Permet d’indiquer quels champs d’une entité, jointure pour récupérer les entités préchargées ou quelles entités associées, sont récupérés Ce traitement peut occasionner des doublons dans la base de données lors d’un find ou d’une requête Pour les éviter, il faut ajouter distinct dans le texte de la requête ou placer le résultat Utile pour indiquer, pour chaque requête, ce dans une collection de type Set qu’on veut charger en mémoire Un graphe d’entités est défini par annotation ou par programmation
R. Grin JPA page 119 R. Grin JPA page 120
119 120
20 select new Flush mode
Pour retourner des instances d’une classe dont Une requête est traduite par une requête SQL le constructeur prend en paramètre des qui ne tient pas nécessairement compte des informations récupérées dans la base de modifications déjà effectuées dans la données transaction Classe doit être désignée par son nom complet Pour en tenir compte, par défaut, un flush est Exemple : effectué par JPA avant chaque requête select new p1.p2.Classe(e.nom, e.salaire) Ce flush peut nuire aux performances ; from Employe e s’il est inutile, modifier ce comportement : query.setFlushMode(FlushModeType.COMMIT) Peut servir à créer des DTO « à la volée »
R. Grin JPA page 121 R. Grin JPA page 122
121 122
Fonctions dans une requête Exemples
Pour les chaînes de caractères : concat, select count(c) from Customer c substring, trim, lower, upper, length, select a.description, locate case type(a) Arithmétique : abs, sqrt, mod, size when Stylo then 'stylo' when Ramette then 'ramette' Temporelles : current_date, current_time, else 'autre type' current_timestamp end Fonctions de regroupement : count, max, min, from Article a avg ; count(distinct expression) case (semblable CASE de SQL)
R. Grin JPA page 123 R. Grin JPA page 124
123 124
Fonction personnalisée Procédure stockée
Une fonction personnalisée définie dans le On peut utiliser une procédure stockée définie SGBD peut être appelée avec function() : dans le SGBD select a from Auteur a La procédure stockée est décrite (noms, WHERE a.id = function('calculer', 1, 2) paramètres, valeur de retour) par @NamedStoredProcedureQuery (comme @NamedQuery) Elle est transformée en StoredProcedureQuery par em.createNamedStoredProcedureQuery(), et exécutée par query.execute()
R. Grin JPA page 125 R. Grin JPA page 126
125 126
21 API « criteria »
Tout ce qui peut être fait avec JPQL peut l’être avec cette API Les requêtes JPQL sont des String qui peuvent contenir des erreurs (par exemple le Opération de modification nom d’une classe qui n’existe pas) en volume L’avantage de l’API « critères » est que les requêtes peuvent être vérifiées à la compilation L’inconvénient est que le code est un peu plus complexe à écrire, et sans doute moins lisible
R. Grin JPA page 127 R. Grin JPA page 128
127 128
Un cas d’utilisation Exemple
Augmenter les 10 000 employés de 5% String ordre = "update Employe e " + Une solution : " set e.salaire = e.salaire * 1.05"; 1. Créer toutes les entités « Employe » en Query q = em.createQuery(ordre); lisant les données dans la base int nbEntitesModif = q.executeUpdate();
2. Modifier le salaire de chaque entité
3. Enregistrer ces modifications dans la base Mauvaise solution ! Pourquoi ? JPQL permet de modifier les données de la base directement, sans créer les entités correspondantes et avec une seule requête SQL R. Grin JPA page 129 R. Grin JPA page 130
129 130
Gestion de la concurrence Par défaut, les problèmes d’accès concurrents sont gérés avec une stratégie optimiste : on pense qu’aucune autre transaction ne modifiera les données sur lesquelles on va Concurrence travailler donc aucun blocage n’est effectué dans la base de données au moment du commit, blocage court et vérifications pour voir si on avait raison d’être optimiste si on avait tort, rollback de la transaction R. Grin JPA page 131 R. Grin JPA page 132
131 132
22 @Version lock(A, mode)
Pour savoir si l’état d’une entité a été modifié Protège une entité contre les accès entre 2 moments différents, un numéro de concurrents pour les cas où la protection version est ajouté à l’entité et enregistré dans offerte par défaut ne suffit pas la base de données Permet en particulier d’effectuer un blocage Exemple : pessimiste (blocage dans la base) @Version private int version; Ce numéro est incrémenté automatiquement par JPA à chaque modification Il ne doit jamais être modifié par l’application
R. Grin JPA page 133 R. Grin JPA page 134
133 134
Variantes avec lock Exemples
Les méthodes find et refresh de Personne p = em.find(Person.class, id, LockModeType.PESSIMISTIC_WRITE); EntityManager ont une variante qui permet em.refresh(p, LockModeType.OPTIMISTIC_FORCE_INCREMENT); de bloquer les données liées aux entités Query q = em.createQuery(...); retrouvées ou rafraichies q.setLockMode(LockModeType.PESSIMISTIC_FORCE_INCREMENT); @NamedQuery(name="person.LockQuery", L’interface Query a la méthode setLockMode query="SELECT p FROM Person p WHERE p.name LIKE :nom", pour bloquer les données récupérées par la lockMode=PESSIMISTIC_READ) requête @NamedQuery a un attribut lockMode
R. Grin JPA page 135 R. Grin JPA page 136
135 136
Comparaison optimiste - pessimiste Concurrence et entité détachée
Une entité est détachée de son EM Un traitement pessimiste peut conduire à des blocages qui prennent du temps et aussi à Elle est modifiée des interblocages Quand elle est rattachée à un EM, l’EM vérifie Si les conflits ne sont pas trop fréquents le au moment du commit que le numéro de traitement optimiste est recommandé version n’a pas changé Sinon un traitement optimiste peut conduire à Si le numéro a changé, une trop d’annulations de transactions et il vaut OptimisticLockException est levée mieux alors utiliser les blocages du traitement pessimiste
R. Grin JPA page 137 R. Grin JPA page 138
137 138
23 Position du problème
Parfois les structures des bases préexistantes ne respectent pas les bons usages ou ne Configuration des tables SQL – correspondent pas aux valeurs par défaut Adaptation du code Java à une supposées par JPA, pour des raisons diverses base préexistante C’est souvent le cas pour ce qui concerne la modélisation des associations entre entités JPA peut s’adapter à presque toutes les situations préexistantes, le plus souvent en utilisant des annotations
R. Grin JPA page 139 R. Grin JPA page 140
139 140
Annotations Définition des colonnes
@Column pour changer le nom Si les tables relationnelles sont créées à @Table pour changer le nom d’une table partir du code Java des entités, la notation @Column permet de donner une définition @JoinColumn permet de changer le nom d’une colonne clé étrangère précise du type SQL utilisé pour un attribut d’une entité On peut même répartir les données d’une entité sur plusieurs tables avec @SecondaryTable
R. Grin JPA page 141 R. Grin JPA page 142
141 142
Exemple Bibliographie
Le type Java BigDecimal correspond par Pro JPA 2 in Java EE 8 de Mike Keith, défaut au type SQL DECIMAL mais sans Merrick Schincariol, Massimo Nardone, chiffres après la virgule édtions Apress @Column permet d’indiquer la définition que l’on veut : @Column(columnDefinition = "DECIMAL(12,2)") private BigDecimal prix;
R. Grin JPA page 143 R. Grin JPA page 144
143 144
24