Réponds à voix haute ou par écrit avant d’ouvrir la réponse : c’est l’effort de rappel qui fait retenir. Marque ensuite la question « Je savais » ou « À revoir », puis filtre sur « À revoir » pour la prochaine séance. Les marques restent dans ce navigateur.
Rappel actif
Restitue de mémoire, sans support.
Sans regarder : donne le FetchType par défaut des quatre associations, puis la règle qui permet de le retrouver sans l’apprendre par cœur.
Association Par défaut @OneToOneEAGER @ManyToOneEAGER @OneToManyLAZY @ManyToManyLAZY Règle : ce qui se termine par One (une seule entité) est EAGER, ce qui se termine par Many (une collection, potentiellement volumineuse) est LAZY.
Cite les six valeurs de
CascadeTypeet l’opération que chacune propage du parent vers les entités associées. Que se passe-t-il si on ne précise rien ?CascadeType Propage PERSISTla sauvegarde MERGEla mise à jour REMOVEla suppression REFRESHle rechargement depuis la base DETACHle détachement du contexte de persistance ALLtoutes les précédentes Sans
cascade, aucune opération n’est cascadée.Pour chaque mapping du cours, dis ce qui apparaît en base : table de jointure ou colonne clé étrangère, et dans quelle table.
OneToOne (uni et bi) ; OneToMany unidirectionnelle Entreprise vers Equipe ; ManyToOne unidirectionnelle Equipe vers Entreprise ; OneToMany / ManyToOne bidirectionnelle ; ManyToMany (uni et bi).
Mapping Résultat en base OneToOne Projet / ProjetDetail clé étrangère dans projet, vers le détailOneToMany uni (Entreprise vers Equipe) table de jointure entreprise_equipesManyToOne uni (Equipe vers Entreprise) colonne entreprise_id_entreprisedansequipeOneToMany / ManyToOne bidirectionnelle colonne entreprise_id_entreprisedansequipe, pas de table de jointureManyToMany uni ou bi table de jointure equipe_projets, la même dans les deux sens
Distingue
Deux notions proches : dis ce qui les sépare vraiment.
Entreprise / Equipe : la OneToMany unidirectionnelle et la bidirectionnelle donnent des tables différentes. Lesquelles, et qu’est-ce qui, dans le code, explique cette différence ?
- Unidirectionnelle (
@OneToManyseul dansEntreprise) : une table de jointureentreprise_equipes. - Bidirectionnelle (
@ManyToOnedansEquipe+@OneToMany(mappedBy = "entreprise")dansEntreprise) : une simple colonneentreprise_id_entreprisedansequipe.
C’est
mappedByqui fait la différence : il dit à JPA que la collection d’Entrepriseest déjà décrite par l’attributentreprisede l’autre côté. JPA réutilise alors la clé étrangère deequipeau lieu de créer une table pour la@OneToMany.- Unidirectionnelle (
Dans une OneToOne bidirectionnelle Projet / ProjetDetail, quel côté porte
mappedByen Java, et quelle table porte la clé étrangère en base ? Pourquoi ce ne sont pas les mêmes ?- En Java, c’est l’enfant (
ProjetDetail) qui porte@OneToOne(mappedBy = "projetDetail"). - En base, c’est le parent (
projet) qui porte la clé étrangère vers le détail.
mappedByveut dire « cette relation est déjà gérée par l’attributprojetDetailde l’autre classe » : le côté avecmappedByest donc justement celui qui n’a pas la colonne. Celui qui la possède est l’autre.- En Java, c’est l’enfant (
CascadeType.REMOVEetorphanRemovalsuppriment tous deux des enfants. Quel événement déclenche chacun ?CascadeType.REMOVE: on supprime le parent, la suppression est propagée aux entités associées.orphanRemoval: le parent reste, mais un enfant est retiré de sa collection : il n’est plus référencé, il est supprimé. Exemple : retirer l’équipe DevOps de la collection d’une entreprise la supprime de la base.
LAZY et EAGER : compare-les sur trois critères (moment du chargement, performances, cas d’usage).
LAZY EAGER Chargement Seulement quand la donnée est demandée Automatique, avec l’entité principale Performances Meilleures, moins de mémoire Accès immédiat, mais peut charger l’inutile Cas d’usage Collections volumineuses Relations toujours nécessaires Unidirectionnel et bidirectionnel : est-ce une propriété de la base de données ou du code Java ? Appuie-toi sur la ManyToMany Equipe / Projet.
C’est d’abord une propriété du code Java : qui connaît qui, et dans quel sens on peut naviguer (
equipe.getProjets(), et en bidirectionnel aussiprojet.getEquipes()).La ManyToMany le montre bien : uni ou bi, la base contient la même table de jointure
equipe_projets. Seule la classeProjetchange, en gagnant@ManyToMany(mappedBy = "projets").
Explique pourquoi
Le mécanisme derrière la règle.
Pourquoi une ManyToMany produit-elle toujours une table de jointure, alors qu’une ManyToOne se contente d’une colonne ?
En ManyToOne, chaque équipe a une seule entreprise : une colonne dans
equipesuffit à stocker cet unique identifiant.En ManyToMany, une équipe a plusieurs projets et un projet plusieurs équipes. Une colonne ne contient qu’une valeur, aucun des deux côtés ne peut donc porter la relation. Il faut une table où chaque ligne est un couple (
equipe_id_equipe,projets_id_projet).Pourquoi les associations vers une collection sont-elles LAZY par défaut, et celles vers une seule entité EAGER ?
Une collection peut être volumineuse (une entreprise et toutes ses équipes, toutes leurs données) : la charger à chaque lecture coûterait de la mémoire pour des données souvent inutiles. Une seule entité associée coûte peu, et on en a souvent besoin tout de suite : la charger d’emblée facilite l’accès.
Dans
@OneToMany(mappedBy = "entreprise"), à quoi correspond exactement la chaîne"entreprise"? Pourquoi pas"entreprise_id_entreprise"?C’est le nom de l’attribut Java de l’autre côté de la relation (le champ
Entreprise entreprise;de la classeEquipe), pas un nom de colonne ni de table.entreprise_id_entrepriseest la colonne que JPA génère en base.mappedBytravaille au niveau des objets : il désigne l’attribut qui possède déjà la relation.Pourquoi, dans une OneToMany / ManyToOne, est-ce la table du côté « Many » (
equipe) qui porte la clé étrangère ?Chaque ligne de
equipen’a qu’une entreprise : elle peut stocker son identifiant dans une colonne. Une ligne d’entreprise, elle, a plusieurs équipes, qu’une seule colonne ne pourrait pas contenir. La clé étrangère va donc naturellement du côté où la cardinalité vaut 1 vers l’autre.
Cas pratique
Choisis et justifie avec les critères du cours.
AutoLoc : une
Agencepossède plusieursVehicule, un véhicule appartient à une seule agence. On veut naviguer dans les deux sens et éviter toute table de jointure. Écris les deux attributs d’association.// Vehicule @ManyToOne private Agence agence; // Agence @OneToMany(mappedBy = "agence") private Set<Vehicule> vehicules;mappedByva sur la@OneToManyet nomme l’attributagencedeVehicule. Résultat : une colonneagence_id_agencedansvehicule, pas de table de jointure.Une page liste les 200 entreprises par nom, et chaque entreprise a des centaines d’équipes que la page n’affiche pas. Quel FetchType veux-tu sur
Entreprise.equipes, et dois-tu écrire quelque chose pour l’obtenir ?LAZY : les équipes ne seront chargées que si on les demande, ce qui évite de monter en mémoire des dizaines de milliers d’équipes inutiles.
Rien à écrire :
@OneToManyest LAZY par défaut. Ajouterfetch = FetchType.EAGERserait précisément l’erreur à éviter ici.Règle métier : quand on retire une équipe de la liste d’une entreprise, l’équipe doit disparaître de la base. Mais si on supprime l’entreprise, on doit aussi supprimer toutes ses équipes. Quels réglages faut-il, et pourquoi un seul ne suffit pas ?
orphanRemoval = truesur@OneToMany: couvre le retrait d’une équipe de la collection, parent toujours présent.CascadeType.REMOVE(ouALL) : couvre la suppression de l’entreprise elle-même.
Ce sont deux événements différents : la suppression du parent et le retrait d’un enfant. Chaque réglage n’en couvre qu’un.
On crée un
Projetavec unProjetDetailneuf, puis on sauvegarde uniquement le projet. On veut que le détail soit enregistré du même coup. Qu’ajoutes-tu, et sur quel attribut ?Une cascade de sauvegarde sur l’attribut du parent :
@OneToOne(cascade = CascadeType.PERSIST)(ouALL) surProjet.projetDetail. Sans cascade, aucune opération n’est propagée et le détail neuf ne serait pas sauvegardé avec le projet.
Trouve l'erreur
Chaque affirmation contient au moins une faute.
« Avec cette configuration, supprimer l’équipe Web ne supprime que l’équipe : les projets sont partagés avec d’autres équipes, ils restent. »
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) private Set<Projet> projets;REMOVEpropage la suppression : supprimer l’équipe supprime automatiquement les projets associés, y compris ceux sur lesquels d’autres équipes travaillent encore. En ManyToMany, les enfants sont partagés par définition : y propagerREMOVEest dangereux.« Dans
Equipe, on a mis@ManyToOne(cascade = CascadeType.ALL) Entreprise entreprise;. Supprimer une équipe obsolète est donc sans risque. »ALLinclutREMOVE, et ici la cascade part de l’équipe vers son entreprise. Supprimer une équipe propage la suppression à l’entreprise à laquelle elle appartient. Une cascade va du parent vers l’enfant : la placer sur le côté « Many » inverse ce sens.« Pour la bidirectionnelle Entreprise / Equipe, je mets
mappedBysur le@ManyToOnedeEquipe. »Le cours place
mappedBysur l’annotation@OneToMany, dansEntreprise:@OneToMany(mappedBy = "entreprise"). Le@ManyToOnedeEquipereste nu, c’est lui qui porte la relation, donc la clé étrangère.« Comme la relation Equipe vers Projet est unidirectionnelle, il est impossible en SQL de retrouver les équipes d’un projet. »
Le sens n’existe qu’en Java :
Projetn’a pas d’attributequipes. En base, la table de jointureequipe_projetscontient les couples des deux identifiants, et une requête SQL peut très bien partir d’un projet pour trouver ses équipes.« Une ManyToMany bidirectionnelle crée deux tables de jointure,
equipe_projetsetprojet_equipes, une par sens. »Grâce à
mappedBy = "projets"côtéProjet, la même tableequipe_projetssert dans les deux sens. Il n’y a qu’une relation, décrite une fois et vue des deux côtés.
Lis le code
Relie le code aux concepts.
Quelles tables et quelles colonnes de liaison ce code génère-t-il ?
@Entity public class Entreprise { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long idEntreprise; @OneToMany(cascade = CascadeType.ALL) private Set<Equipe> equipes; } @Entity public class Equipe { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long idEquipe; }Trois tables :
entreprise,equipe, et la table de jointureentreprise_equipesavecentreprise_id_entrepriseetequipes_id_equipe.C’est une OneToMany unidirectionnelle : pas de
mappedBy,Equipene connaît pas l’entreprise, doncequipen’a pas de colonne versentreprise.On modifie l’exemple précédent :
Equipegagne@ManyToOne Entreprise entreprise;et la collection devient@OneToMany(cascade = CascadeType.ALL, mappedBy = "entreprise"). Qu’est-ce qui change en base ?La table de jointure
entreprise_equipesdisparaît. À la place,equipeporte une colonneentreprise_id_entreprise(nullable). L’association est devenue bidirectionnelle, portée par le@ManyToOne.Avec ce mapping, que se passe-t-il en base quand on (1) sauvegarde une équipe neuve qui contient deux projets neufs, (2) met à jour le nom d’un de ces projets via l’équipe, (3) supprime l’équipe ?
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) private Set<Projet> projets;- PERSIST : l’équipe et les deux projets sont sauvegardés automatiquement.
- Pas de
MERGE: la mise à jour n’est pas propagée par la cascade aux projets associés. - REMOVE : l’équipe et les projets associés sont supprimés.
Écris de mémoire la ManyToMany bidirectionnelle entre
Equipe(côté propriétaire) etProjet: les deux attributs d’association avec leurs annotations.// Equipe @ManyToMany(cascade = CascadeType.ALL) private Set<Projet> projets; // Projet @ManyToMany(mappedBy = "projets", cascade = CascadeType.ALL) private Set<Equipe> equipes;mappedBy = "projets"est le nom de l’attribut dansEquipe. Une seule table de jointure :equipe_projets.
Vrai ou faux, justifie
La justification compte plus que le verdict.
« Sans attribut
cascade, sauvegarder uneEntreprisesauvegarde aussi les nouvelles équipes de sa collection. »Faux. Si
CascadeTypen’est pas précisé, aucune opération n’est cascadée : les équipes neuves doivent être sauvegardées séparément.« OneToMany bidirectionnelle et ManyToOne bidirectionnelle désignent la même association. »
Vrai. C’est la même relation vue depuis chaque bout :
@OneToMany(mappedBy=...)d’un côté,@ManyToOnede l’autre, une seule colonne clé étrangère côté « Many ». Le cours les traite ensemble.« LAZY est toujours le meilleur choix, puisqu’il améliore les performances. »
Faux. LAZY évite de charger des données inutiles, mais pour une relation toujours nécessaire, EAGER donne un accès immédiat. Avec LAZY, chaque accès déclenche un chargement à part. Le bon choix dépend de l’usage réel de la relation.
«
orphanRemovalne sert à rien si on a déjàCascadeType.ALL. »Faux.
ALLcontientREMOVE, qui n’agit que quand on supprime le parent. Retirer un enfant de la collection, parent conservé, ne supprime l’enfant qu’avecorphanRemoval.
Classe
Range chaque élément et explique les cas limites.
Pour chaque besoin, donne le
CascadeTypeminimal : (a) sauvegarder les adresses neuves d’un client avec lui ; (b) annuler les modifications locales en relisant la base ; (c) propager une modification d’un objet détaché ; (d) retirer les entités associées du contexte de persistance ; (e) supprimer les lignes de commande avec la commande.(a)
PERSIST; (b)REFRESH; (c)MERGE; (d)DETACH; (e)REMOVE.Classe ces associations selon le FetchType que tu choisirais, et dis si c’est déjà le défaut :
Equipe.entreprise(toujours affichée avec l’équipe) ;Entreprise.equipes(centaines d’équipes, rarement affichées) ;Projet.projetDetail(affiché seulement sur la page de détail) ;Equipe.projets(affichés seulement à la demande).Association Choix Défaut ? Equipe.entrepriseEAGER Oui (ManyToOne) Entreprise.equipesLAZY Oui (OneToMany) Projet.projetDetailLAZY Non : OneToOne est EAGER par défaut, il faut fetch = FetchType.LAZYEquipe.projetsLAZY Oui (ManyToMany)
Et si...
Prévois la conséquence.
Et si on charge une entreprise (équipes en LAZY), puis qu’on appelle
entreprise.getEquipes()? Que se passe-t-il, et à quel moment ?Au chargement de l’entreprise, ses équipes ne sont pas lues. C’est l’appel à
getEquipes(), une demande explicite, qui déclenche leur chargement depuis la base, à ce moment-là.Et si, dans la bidirectionnelle Entreprise / Equipe, on oublie
mappedBysur la@OneToManyen gardant le@ManyToOnecôtéEquipe?JPA voit alors deux associations indépendantes au lieu d’une : la
@OneToManysansmappedByse mappe comme une unidirectionnelle (table de jointureentreprise_equipes), et le@ManyToOneajoute sa colonneentreprise_id_entreprisedansequipe. La même information est stockée à deux endroits, qui peuvent diverger.Et si on passe en EAGER toutes les associations d’
Entreprisepuis deEquipe, et qu’on liste toutes les entreprises ?Chaque entreprise charge toutes ses équipes, et chaque équipe tous ses projets : la simple liste des noms monte en mémoire une grande partie de la base. C’est exactement l’inconvénient d’EAGER, charger des données inutiles, multiplié par chaque niveau d’association.