CHAPITRE 8 : LES RELATIONS ENTRE ENTITES

Une application reelle manipule des donnees liees : un etudiant appartient a une filiere, suit des cours, obtient des notes. Voyons comment exprimer ces liens.

1. Les quatre cardinalites

RelationExemple
@OneToOneUn etudiant a une carte d'etudiant
@OneToManyUne filiere compte plusieurs etudiants
@ManyToOneUn etudiant appartient a une filiere
@ManyToManyUn etudiant suit plusieurs cours, un cours accueille plusieurs etudiants

Le reflexe pour choisir : placez-vous du cote de l'entite que vous ecrivez et posez la question « combien ? ». Un etudiant a une filiere → @ManyToOne cote etudiant.

2. ManyToOne et OneToMany

@Entity
public class Etudiant {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nom;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "filiere_id")
    private Filiere filiere;
}
@Entity
public class Filiere {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String libelle;

    @OneToMany(mappedBy = "filiere", cascade = CascadeType.ALL)
    private List<Etudiant> etudiants = new ArrayList<>();
}

Le role de mappedBy

mappedBy = "filiere" signifie : « la relation est deja decrite par le champ filiere de l'entite Etudiant ». Sans lui, JPA croirait a deux relations distinctes et creerait une table de liaison inutile.

Le cote qui porte @JoinColumn est le proprietaire : c'est lui qui detient la cle etrangere en base. Retenez-le, c'est determinant pour l'enregistrement.

3. ManyToMany

@Entity
public class Etudiant {

    @ManyToMany
    @JoinTable(
        name = "inscriptions",
        joinColumns = @JoinColumn(name = "etudiant_id"),
        inverseJoinColumns = @JoinColumn(name = "cours_id")
    )
    private Set<Cours> cours = new HashSet<>();
}
@Entity
public class Cours {

    @ManyToMany(mappedBy = "cours")
    private Set<Etudiant> etudiants = new HashSet<>();
}

Utilisez un Set plutot qu'une List : il interdit les doublons, ce qui correspond a la realite (on ne s'inscrit pas deux fois au meme cours).

Quand la liaison porte des donnees

Si l'inscription doit memoriser une date ou une note, @ManyToMany ne suffit plus : creez une entite Inscription avec deux @ManyToOne. C'est le cas le plus frequent en pratique.

4. Le chargement : LAZY ou EAGER

ModeComportementDefaut pour
LAZYCharge seulement a l'acces@OneToMany, @ManyToMany
EAGERCharge immediatement@ManyToOne, @OneToOne

Conseil : mettez tout en LAZY, et chargez explicitement ce dont vous avez besoin. Un EAGER sur une collection peut charger la moitie de la base pour afficher un nom.

5. Le probleme des N+1 requetes

C'est le piege classique de JPA. Ce code semble anodin :

List<Etudiant> etudiants = repository.findAll();   // 1 requete

for (Etudiant e : etudiants) {
    System.out.println(e.getFiliere().getLibelle());  // 1 requete par etudiant
}

Avec 100 etudiants, vous executez 101 requetes. La solution : une jointure explicite.

@Query("SELECT e FROM Etudiant e JOIN FETCH e.filiere")
List<Etudiant> findAllAvecFiliere();

Une seule requete. Activez spring.jpa.show-sql=true et comptez les lignes : c'est le meilleur moyen de reperer le probleme avant qu'il ne devienne visible en production.

6. La cascade

TypeEffet
PERSISTEnregistre aussi les enfants
MERGEMet a jour les enfants
REMOVESupprime les enfants
ALLTout ce qui precede

Attention a REMOVE et ALL : supprimer une filiere supprimerait tous ses etudiants. A reserver aux cas ou l'enfant n'a aucun sens sans son parent.

7. La boucle infinie en JSON

Un etudiant reference sa filiere, qui reference ses etudiants, qui referencent leur filiere… La serialisation JSON tourne alors indefiniment.

@JsonIgnore
@OneToMany(mappedBy = "filiere")
private List<Etudiant> etudiants;

La meilleure solution reste cependant le DTO du chapitre 6 : il ne contient que les champs voulus, et le probleme ne se pose plus du tout.

A retenir

  • Le cote qui porte @JoinColumn detient la cle etrangere
  • mappedBy designe le champ de l'autre cote
  • Preferez LAZY partout, et chargez explicitement
  • Surveillez les N+1 : JOIN FETCH les resout
  • Contre les boucles JSON, le DTO est la vraie reponse

Exercices d'application

Exercice 1. Filiere et etudiantsCreez les entites Filiere et Etudiant avec leur relation. Enregistrez une filiere et trois etudiants, puis verifiez la cle etrangere en base.
Exercice 2. Reperer un N+1Activez show-sql=true, listez des etudiants en affichant leur filiere, et comptez les requetes. Corrigez avec JOIN FETCH et recomptez.
Exercice 3. InscriptionsModelisez l'inscription d'etudiants a des cours avec une date d'inscription. Expliquez pourquoi @ManyToMany ne suffit pas.

Cherchez avant de regarder la solution : c'est en butant sur les difficultes que l'on progresse.

Questions / Réponses

Aucune question. Soyez le premier à poser une question.
★ ★ ★ ★ ★
2 votes. Moyenne 5 sur 5.

Ajouter un commentaire

Anti-spam