La façon la plus efficace de mapper une relation @OneToMany avec JPA et Hibernate
J’ai choisi d’ouvrir ce billet avec cette citation car je suis un fan de Linus Torvalds.😉
C’est mon tout premier article. Dans celui-ci, je vais couvrir tous les cas possibles d’association d’entités One-to-Many/Many-to-One
. Les Many-to-Many
et One-to-One
restants seront couverts dans les prochains articles.
J’espère que cela aidera définitivement tous les newbies qui veulent apprendre jpa/hibernate, S’il vous plaît lire la pièce entière 😛
NOTE:
J’ai couvert ici tous les cas possibles de mappage
One-to-Many/Many-to-One
. Parmi tous , l’association bidirectionnelle `@OneToMany` est la meilleure façon de mapper une relation de base de données de un à plusieurs.
L’association hibernate classée en One-to-One
, One-to-Many/Many-to-One
et Many-to-Many
.
- Le sens d’une relation peut être bidirectionnel ou unidirectionnel.
- Une relation bidirectionnelle a à la fois un côté propriétaire et un côté inverse.
- Une relation unidirectionnelle a seulement un côté propriétaire. Le côté propriétaire d’une relation détermine comment le temps d’exécution de Persistence effectue les mises à jour de la relation dans la base de données.
- Unidirectionnelle est une relation où un côté ne connaît pas la relation.
- Dans une relation unidirectionnelle, seule une entité a un champ ou une propriété de relation qui fait référence à l’autre. Par exemple, Line Item aurait un champ de relation qui identifie Product, mais Product n’aurait pas de champ de relation ou de propriété pour Line Item. En d’autres termes, Line Item connaît Product, mais Product ne sait pas quelles instances de Line Item s’y réfèrent.
Relations bidirectionnelles:
- La relation bidirectionnelle fournit un accès de navigation dans les deux sens, de sorte que vous pouvez accéder à l’autre côté sans requêtes explicites.
- Dans une relation bidirectionnelle, chaque entité a un champ de relation ou une propriété qui fait référence à l’autre entité. Grâce au champ ou à la propriété de relation, le code d’une classe d’entité peut accéder à son objet lié. Si une entité possède un champ de relation, on dit que l’entité « connaît » son objet lié. Par exemple, si Commande sait quelles instances de Poste de ligne elle possède et si Poste de ligne sait à quelle Commande elle appartient, ils ont une relation bidirectionnelle.
Les relations bidirectionnelles doivent suivre ces règles.
- Le côté inverse d’une relation bidirectionnelle doit faire référence à son côté propriétaire(Entité qui contient la clé étrangère) en utilisant l’élément
mappedBy
de l’annotation@OneToOne
,@OneToMany
ou@ManyToMany
. L’élémentmappedBy
désigne la propriété ou le champ de l’entité qui est le propriétaire de la relation. - Le côté multiple des relations bidirectionnelles
@ManyToOne
ne doit pas définir l’élémentmappedBy
. Le côté many est toujours le côté propriétaire de la relation. - Pour les relations bidirectionnelles
@OneToOne
, le côté propriétaire correspond au côté qui contient @JoinColumn c’est-à-dire la clé étrangère correspondante. - Pour les relations bidirectionnelles
@ManyToMany
, chaque côté peut être le côté propriétaire.
@Relation OneToMany avec JPA et Hibernate
En termes simples, le mapping one-to-many signifie qu’une ligne d’une table est mappée à plusieurs lignes d’une autre table.
Quand utiliser le mapping un à plusieurs
Utiliser le mapping un à plusieurs pour créer une relation 1…N entre des entités ou des objets.
Nous devons écrire deux entités à savoir .
Company
etBranch
de telle sorte que plusieurs succursales puissent être associées à une seule entreprise, mais qu’une seule succursale ne puisse être partagée entre deux ou plusieurs entreprises.
Solutions de mappage un à plusieurs d’Hibernate:
- Mappage un à plusieurs avec association de clé étrangère
- Mappage un à plusieurs avec table de jointure
Ce problème peut être résolu de deux manières différentes.
La première consiste à avoir une colonne de clé étrangère dans la table de branche c’est-à-dire
company_id
. Cette colonne fera référence à la clé primaire de la table Company. De cette façon, aucune branche ne peut être associée à plusieurs sociétés.La deuxième approche consiste à avoir une table de jointure commune disons
Company_Branch,
Cette table aura deux colonnes c’est-à-direcompany_id
qui sera une clé étrangère se référant à la clé primaire de la table Société et de mêmebranch_id
qui sera une clé étrangère se référant à la clé primaire de la table Branche.
Si @OneToMany/@ManyToOne n’a pas d’association miroir @ManyToOne/@OneToMany respectivement du côté enfant alors, l’association @OneToMany/@ManyToOne est unidirectionnelle.
@OneToMany Relation unidirectionnelle
Dans cette approche, une seule entité sera responsable de la réalisation de la relation et de son maintien. Soit la société déclare la relation comme un à plusieurs, soit la branche déclare la relation de son extrémité comme plusieurs à un.
CAS 1 : (Mapping avec association de clé étrangère)
Si nous utilisons seulement @OneToMany
alors il y aura 3 tables. Comme company
, branch
et company_branch.
CAS 4 : (Unidirectionnel @OneToMany avec @JoinColumn)(Mapping avec association de clé étrangère)
Si nous utilisons @OneToMany
avec @JoinColumn
alors il y aura 2 tables. Comme la société, la branche
Dans l’entité ci-dessus à l’intérieur de @JoinColumn
, le nom fait référence au nom de la colonne de la clé étrangère qui est companyId c’est-à-dire company_id
et rference.c’est-à-dire company_id
et rferencedColumnName indique la clé primaire c’est-à-dire id
de l’entité (Company) à laquelle la clé étrangère companyId
fait référence.
CAS 5 : (Mapping avec association de clé étrangère)
Si nous utilisons @ManyToOne
avec @JoinColumn
alors il y aura 2 tables. Telles que compagnie,branche.
L’annotation @JoinColumn
aide Hibernate à comprendre qu’il y a une colonne company_id
Clé étrangère dans la table branche qui définit cette association.
Branch aura la clé étrangère company_id
, donc c’est le côté propriétaire.
Maintenant, si nous persistons 1 société et 2 branche(s):
lors de la suppression de la première entrée de la collection enfant:
company.getBranches().remove(0);
Hibernate exécute deux instructions au lieu d’une :
- D’abord il rendra le champ de la clé étrangère à null(pour rompre l’association avec le parent) puis il supprimera l’enregistrement.
Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;
CAS 6 : (Mapping avec table de jointure)
Maintenant, considérons la relation one-to-many
où Person(id,name)
s’associent à plusieurs Veichle(id,name,number)
et plusieurs véhicules peuvent appartenir à la même personne.
Un jour, alors qu’il était sur l’autoroute, le shérif Carlos a trouvé quelques véhicules abandonnés, très probablement volés. Maintenant le shérif doit mettre à jour les détails des véhicules(numéro, nom) dans leur base de données mais le problème principal est, il n’y a pas de propriétaire pour ces véhicules, donc le champ person_id(foreign key )
restera nul.
Maintenant le shérif enregistre deux véhicules volés dans la db comme suit:
Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);
Hibernate va exécuter les instructions SQL suivantes:
insert into vehicle (id, name, person_id) values (1, "ford", null); insert into vehicle (id, name, person_id) values (2, "gmc", null);id |name|person_id|
-----|----|---------|
1 |ford|NULL |
2 |benz|NULL |
---------------------
Cette stratégie ci-dessus nous obligera à mettre des valeurs nulles dans la colonne pour gérer les relations optionnelles.
Typiquement, nous pensons à des relations many-to-many
lorsque nous considérons un join table
, mais, l’utilisation d’une table de jointure, dans ce cas, peut nous aider à éliminer ces valeurs nulles:
Cette approche utilise une table de jointure pour stocker les associations entre les entités Branch et Company. L’annotation @JoinTable
a été utilisée pour faire cette association.
Dans cet exemple, nous avons appliqué @JoinTable
sur le côté véhicule(Many Side).
Voyons à quoi ressemblera le schéma de la base de données :
Le code ci-dessus générera 3 tables telles que person(id,name)
, vehicle(id,name)
et vehicle_person(vehicle_id,person_id)
. Ici, vehicle_person
tiendra la relation de clé étrangère aux deux entités Personne et Véhicule.
Ainsi, lorsque le shérif enregistre les détails du véhicule, aucune valeur nulle ne doit être persistée dans la table du véhicule parce que nous gardons l’association de clé étrangère dans les tables vehicle_person
et non dans la table du véhicule.
Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);
Hibernate va exécuter les instructions SQL suivantes :
insert into vehicle (id, name) values (1, "ford"); insert into vehicle (id, name) values (2, "gmc");id |name|
-----|----|
1 |ford|
2 |benz|
-----------
Cet exemple ci-dessus montre, comment nous nous sommes débarrassés de l’insertion de valeur nulle.
@OneToMany Relation bidirectionnelle
- L’association bidirectionnelle
@OneToMany
nécessite également une association@ManyToOne
du côté enfant. Bien que le modèle de domaine expose deux côtés pour naviguer dans cette association, en coulisse, la base de données relationnelle n’a qu’une seule clé étrangère pour cette relation. - Toute association bidirectionnelle doit avoir un seul côté propriétaire (le côté enfant), l’autre étant appelé le côté inverse (ou le côté
mappedBy
). - Si nous utilisons le
@OneToMany
avec l’attributmappedBy
défini, alors nous avons une association bidirectionnelle, ce qui signifie que nous devons avoir une association@ManyToOne
du côté enfant que lemappedBy
référence. - L’élément
mappedBy
définit une relation bidirectionnelle. Cet attribut vous permet de référencer les entités associées des deux côtés.
La meilleure façon de mapper une association @OneToMany
est de compter sur le côté @ManyToOne
pour propager tous les changements d’état de l’entité.
Si nous persistons 2 Branche(s)
Hibernate génère une seule instruction SQL pour chaque entité Branche persistée:
insert into company (name, id) values ("company1",1);
insert into branch (company_id, name, id) values (1,"branch1",1); insert into branch (company_id, name, id) values (1,"branch2",2);
Si nous supprimons une Branche :
Company company = entityManager.find( Company.class, 1L ); Branch branch = company.getBranches().get(0); company.removeBranches(branch);
Il n’y a qu’une seule instruction SQL de suppression qui est exécutée:
delete from Branch where id = 1
Donc, l’association bidirectionnelle @OneToMany est la meilleure façon de mapper une relation de base de données de un à plusieurs lorsque nous avons vraiment besoin de la collection du côté parent de l’association.
@JoinColumn
Spécifie une colonne pour joindre une association d’entités ou une collection d’éléments. L’annotation@JoinColumn
indique que cette entité est le propriétaire de la relation. C’est-à-dire que la table correspondante a une colonne avec une clé étrangère à la table référencée.Dans l’exemple ci-dessus, l’entité propriétaire Branche, a une colonne de jonction nommée
company_id
qui a une clé étrangère à l’entité non propriétaire Société.Chaque fois qu’une association bidirectionnelle est formée, le développeur d’applications doit s’assurer que les deux côtés sont en synchronisation à tout moment. Les
addBranches()
etremoveBranches()
sont des méthodes utilitaires qui synchronisent les deux extrémités chaque fois qu’un élément enfant (c’est-à-dire Branch) est ajouté ou retiré.Contrairement à l’association unidirectionnelle
@OneToMany
, l’association bidirectionnelle est beaucoup plus efficace lors de la gestion de l’état de persistance de la collection.Chaque retrait d’élément ne nécessite qu’une seule mise à jour (dans laquelle la colonne de clé étrangère est définie à NULL) et si le cycle de vie de l’entité enfant est lié à son parent propriétaire de sorte que l’enfant ne peut pas exister sans son parent, alors nous pouvons annoter l’association avec l’attribut orphan-removal et la dissociation de l’enfant déclenchera une déclaration de suppression sur la ligne de table enfant réelle également.
NOTE:
- Dans l’exemple bidirectionnel ci-dessus, le terme Parent et Enfant dans Entité/OO/Modèle( c’est-à-dire dans la classe java) se réfère respectivement au côté Non-gagnant/Inverse et au côté Propriétaire dans SQL.
Dans le point de vue java, la Société est le Parent et la branche est l’enfant ici. Puisqu’une branche ne peut pas exister sans parent.
En point de vue SQL Branch est le côté propriétaire et Company est le côté non propriétaire(Inverse). Puisqu’il y a 1 société pour N branches, chaque branche contient une clé étrangère à la société à laquelle elle appartient.Cela signifie que la branche « possède » (ou contient littéralement) la connexion (information). C’est exactement le contraire du monde OO/modèle.
Relation bidirectionnelle @OneToMany(Mapping avec table de jointure)
Depuis CASE 6
nous avons un mapping unidirectionnel avec @JoinTable
, donc si nous ajoutons l’attribut mappedBy
à l’entité Personne alors la relation deviendra bidirectionnelle.
SOMMAIRE
Il y a plusieurs choses à noter sur le mappage susmentionné :
- L’association
@ManyToOne
utiliseFetchType.LAZY
car, sinon, nous retomberions dans le fetching EAGER qui est mauvais pour les performances. - Les associations bidirectionnelles doivent toujours être mises à jour des deux côtés, c’est pourquoi le côté Parent doit contenir la combo
addChild
etremoveChild
(Parent side Company contient deux méthodes utilitairesaddBranches
etremoveBranches
). Ces méthodes garantissent que nous synchronisons toujours les deux côtés de l’association, pour éviter les problèmes de corruption d’objets ou de données relationnelles. - L’entité enfant, Branch, implémente les méthodes equals et hashCode. Puisque nous ne pouvons pas compter sur un identifiant naturel pour les contrôles d’égalité, nous devons utiliser l’identifiant de l’entité à la place. Cependant, nous devons le faire correctement afin que l’égalité soit cohérente dans toutes les transitions d’état de l’entité. Parce que nous nous appuyons sur l’égalité pour le removeBranches, c’est une bonne pratique de surcharger equals et hashCode pour l’entité enfant dans une association bidirectionnelle.
- L’association @OneToMany est par définition une association parent, même si c’est une association unidirectionnelle ou bidirectionnelle. Seul le côté parent d’une association a un sens pour cascader ses transitions d’état d’entité vers les enfants.