Objectifs du chapitre
- Comprendre les différents types d’associations entre entités dans JPA.
- Implémenter des associations OneToOne, OneToMany, ManyToOne et ManyToMany.
- Configurer les stratégies CascadeType et FetchType selon les besoins de l’application.
- Générer et vérifier les relations entre tables dans la base de données à partir des entités JPA.
1. Le mapping objet-relationnel des associations
Les associations illustrées dans ce chapitre s’appuient sur un diagramme de classes commun : Entreprise, Equipe, Projet et ProjetDetail.
| Type d’association | Cardinalité | Variantes |
|---|---|---|
| One-to-One | 1:1 | Unidirectionnelle / Bidirectionnelle |
| One-to-Many | 1:N | Unidirectionnelle / Bidirectionnelle |
| Many-to-One | N:1 | Unidirectionnelle / Bidirectionnelle |
| Many-to-Many | N:M | Unidirectionnelle / Bidirectionnelle |
2. One To One
Un Projet a un seul détail (une seule ligne dans la table ProjetDetail), et le détail d’un projet est lié à un seul Projet.
Unidirectionnelle
Le Projet connaît le détail (il contient un attribut de type ProjetDetail), alors que le détail n’a aucune information sur le Projet auquel il est associé.
@Entity
public class Projet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProjet;
private String sujetProjet;
@OneToOne
private ProjetDetail projetDetail;
}
@Entity
public class ProjetDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProjetDetail;
private String description;
private String technologie;
}
Bidirectionnelle
C’est l’attribut mappedBy qui crée le caractère bidirectionnel de la relation et qui permet de définir les deux bouts de l’association (parent / enfant). Au niveau des entités Java, c’est l’enfant qui porte mappedBy. En base de données, c’est le parent qui porte la clé étrangère qui pointe vers l’enfant.
@Entity
public class Projet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProjet;
private String sujetProjet;
@OneToOne
private ProjetDetail projetDetail;
}
@Entity
public class ProjetDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProjetDetail;
private String description;
private String technologie;
@OneToOne(mappedBy = "projetDetail")
private Projet projet;
}
3. One To Many / Many To One
Une Entreprise peut avoir plusieurs Equipe (Développement web, Développement mobile, DevOps, Test…). Une équipe n’est liée qu’à une seule entreprise.
Unidirectionnelle (Entreprise vers Equipe)
L’entreprise connaît les équipes, l’équipe n’a aucune information sur l’entreprise à laquelle elle appartient :
@Entity
public class Entreprise {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEntreprise;
private String nomEntreprise;
private String adresseEntreprise;
@OneToMany(cascade = CascadeType.ALL)
private Set<Equipe> equipes;
}
@Entity
public class Equipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEquipe;
private String nomEquipe;
private String specialite;
}
Résultat attendu en base : une table de jointure entreprise_equipes est créée, avec les colonnes entreprise_id_entreprise et equipes_id_equipe.
Unidirectionnelle (Equipe vers Entreprise, Many To One)
Chaque équipe porte l’information de l’entreprise (attribut Entreprise dans l’entité Equipe, clé étrangère dans la table equipe), l’entreprise n’a aucune information sur ses équipes :
@Entity
public class Equipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEquipe;
private String nomEquipe;
private String specialite;
@ManyToOne(cascade = CascadeType.ALL)
Entreprise entreprise;
}
@Entity
public class Entreprise {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEntreprise;
private String nomEntreprise;
private String adresseEntreprise;
}
Résultat attendu : la table equipe porte directement la colonne entreprise_id_entreprise.
Bidirectionnelle (Many To One = One To Many bidirectionnelle)
L’entreprise connaît ses équipes, chaque équipe connaît aussi l’entreprise associée. L’attribut mappedBy est défini sur l’annotation @OneToMany, toujours au niveau de l’entité qui a la cardinalité la plus faible :
@Entity
public class Equipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEquipe;
private String nomEquipe;
private String specialite;
@ManyToOne
Entreprise entreprise;
}
@Entity
public class Entreprise {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEntreprise;
private String nomEntreprise;
private String adresseEntreprise;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "entreprise")
private Set<Equipe> equipes;
}
Résultat attendu : la table equipe porte la colonne entreprise_id_entreprise (nullable), sans table de jointure supplémentaire.
4. Many To Many
Une équipe peut travailler sur plusieurs projets, et un même projet peut être lié à plusieurs équipes.
Unidirectionnelle
L’équipe a plusieurs projets et les connaît ; le projet n’a aucune information sur les équipes auxquelles il est associé :
@Entity
public class Projet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProjet;
private String sujetProjet;
}
@Entity
public class Equipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEquipe;
private String nomEquipe;
private String specialite;
@ManyToMany(cascade = CascadeType.ALL)
private Set<Projet> projets;
}
Résultat attendu : une table de jointure equipe_projets est créée, avec equipe_id_equipe et projets_id_projet.
Bidirectionnelle
L’équipe a plusieurs projets et les connaît ; chaque projet est associé à plusieurs équipes et peut accéder à leurs attributs :
@Entity
public class Projet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProjet;
private String sujetProjet;
@ManyToMany(mappedBy = "projets", cascade = CascadeType.ALL)
private Set<Equipe> equipes;
}
@Entity
public class Equipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEquipe;
private String nomEquipe;
private String specialite;
@ManyToMany(cascade = CascadeType.ALL)
private Set<Projet> projets;
}
La même table de jointure equipe_projets sert dans les deux sens.
5. La stratégie CascadeType
L’attribut CascadeType permet de propager automatiquement certaines opérations effectuées sur une entité parent vers les entités qui lui sont associées. Par défaut, si CascadeType n’est pas précisé, aucune opération n’est cascadée.
| CascadeType | Effet |
|---|---|
PERSIST | Sauvegarde les entités associées. |
MERGE | Met à jour les entités associées. |
REMOVE | Supprime les entités associées. |
REFRESH | Recharge depuis la base. |
DETACH | Détache les entités du contexte de persistance. |
ALL | Toutes les opérations précédentes. |
Exemple, une équipe en cascade PERSIST et REMOVE seulement :
@Entity
public class Equipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idEquipe;
private String nomEquipe;
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
private Set<Projet> projets;
}
Résultat : l’ajout d’une équipe ajoute automatiquement les projets associés, et la suppression d’une équipe supprime automatiquement les projets associés.
6. La stratégie FetchType
L’attribut FetchType définit la manière dont les entités associées sont chargées lors de la récupération d’une entité depuis la base de données.
FetchType.EAGER: les entités associées sont chargées automatiquement avec l’entité principale.FetchType.LAZY: les entités associées ne sont chargées que lorsqu’elles sont explicitement demandées.
| Association | FetchType par défaut |
|---|---|
OneToOne | EAGER |
ManyToOne | EAGER |
OneToMany | LAZY |
ManyToMany | LAZY |
| FetchType.LAZY | FetchType.EAGER | |
|---|---|---|
| Chargement | Uniquement lorsque les données sont demandées. | Automatique, avec l’entité principale. |
| Performances | Meilleures, réduit la consommation mémoire. | Accès immédiat aux données, mais peut charger des données inutiles. |
| Cas d’usage | Collections volumineuses. | Relations toujours nécessaires. |
LAZY optimise les performances, EAGER facilite l’accès aux données lorsque celles-ci sont systématiquement nécessaires.
7. Suppression des entités orphelines (orphanRemoval)
orphanRemoval permet de supprimer automatiquement une entité enfant lorsqu’elle n’est plus référencée par son parent : si l’équipe DevOps est retirée de la collection d’équipes d’une entreprise, elle est automatiquement supprimée de la base de données.