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ément mappedBy 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ément mappedBy. 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 et Branch 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:

  1. Mappage un à plusieurs avec association de clé étrangère
  2. 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-à-dire company_id qui sera une clé étrangère se référant à la clé primaire de la table Société et de même branch_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.

NOTE:
Dans l’exemple ci-dessus, j’ai utilisé des termes comme cascade, orphanRemoval, fetch et targetEntity, que j’expliquerai dans mon prochain post.

La table company_branch aura deux clés étrangères company_id et branch_id.

Maintenant, si nous persistons une société et deux branche(s) :

Hibernate va exécuter les instructions SQL suivantes :

  • L’association @OneToMany est par définition une association parent(non propriétaire), même si elle est 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.
  • Lors de la persistance de l’entité Company, la cascade propagera l’opération de persistance aux enfants de la branche sous-jacente également. Lors de la suppression d’une Branch de la collection de branches, la rangée d’association est supprimée de la table de liens, et l’attribut orphanRemoval déclenchera également une suppression de branche.

Les associations unidirectionnelles ne sont pas très efficaces lorsqu’il s’agit de supprimer des entités enfants. Dans cet exemple particulier, lors du vidage du contexte de persistance, Hibernate supprime toutes les entrées enfant de la base de données et réinsère celles qui sont encore trouvées dans le contexte de persistance en mémoire.

En revanche, une association bidirectionnelle @OneToMany est beaucoup plus efficace car l’entité enfant contrôle l’association.

CAS 2 : (Mapping avec association de clé étrangère)

Si nous utilisons seulement @ManyToOne alors il y aura 2 tables. Comme la société, la branche.

Ce code ci-dessus va générer 2 tables Company(company_id,name) et Branch(branch_id,name,company_company_id). Ici, Branch est le côté propriétaire puisqu’il a l’association de clé étrangère.

CAS 3 : (Mapping avec association de clé étrangère)

Si nous utilisons à la fois @ManyToOne et @OneToMany, alors cela créera 3 tables Company(id,name), Branch(id,name,company_id), Company_Branch(company_id,branch_id)

Ce mapping ci-dessous peut sembler bidirectionnel mais il ne l’est pas. Il ne définit pas une relation bidirectionnelle, mais deux relations unidirectionnelles distinctes.

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-manyPerson(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’attribut mappedBy défini, alors nous avons une association bidirectionnelle, ce qui signifie que nous devons avoir une association @ManyToOne du côté enfant que le mappedBy 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() et removeBranches() 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 utilise FetchType.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 et removeChild(Parent side Company contient deux méthodes utilitaires addBranches et removeBranches). 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.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.