Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 22

Mapping Entities and Relationships with JPA Annotations

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons réussi à établir la connexion entre notre application Spring Boot et une base de données MySQL. Ce "pont" est désormais fonctionnel, mais il ne sert à rien si nous ne définissons pas la nature des informations qui vont le traverser.

Aujourd'hui, nous allons nous concentrer sur le cœur du mapping objet-relationnel (ORM) avec JPA. L'objectif est de traduire notre modèle de données conceptuel en classes Java que Hibernate pourra comprendre et transformer en tables et relations dans la base de données. Vous allez apprendre à définir des entités JPA et leurs relations en utilisant les annotations @Entity, @Id, @OneToMany, et @ManyToMany.

Votre expérience avec les ORM comme Eloquent dans Laravel vous sera utile. Les concepts de relations (hasOne, hasMany, etc.) sont très similaires, même si la syntaxe et les mécanismes sous-jacents diffèrent.

1. Qu'est-ce qu'une Entité JPA ?

En JPA, une entité est une simple classe Java (un POJO - Plain Old Java Object) qui est "mappée" à une table dans une base de données. Pour que JPA reconnaisse une classe comme une entité, nous utilisons deux annotations fondamentales :

  • @Entity : Placée au-dessus de la déclaration de la classe, elle signale à JPA que cette classe correspond à une table dans la base de données. Par défaut, le nom de la table sera le nom de la classe (insensible à la casse). On peut le spécifier avec @Table(name = "nom_de_table").
  • @Id : Placée au-dessus d'un attribut de la classe, elle désigne cet attribut comme étant la clé primaire de la table. Chaque entité doit obligatoirement en avoir un.
  • @GeneratedValue : Souvent utilisée avec @Id, cette annotation indique comment la valeur de la clé primaire est générée (par exemple, auto-incrémentation).
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "utilisateurs") // Spécifie le nom de la table
public class Utilisateur {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // AUTO_INCREMENT de MySQL
    private Long id;

    private String nom;
    private String email;

    // Constructeurs, Getters, Setters...
}

Avec cette simple classe, Hibernate sait qu'il doit (potentiellement) créer une table utilisateurs avec une clé primaire id auto-incrémentée et des colonnes nom et email.


2. Le Mapping des Relations

Le véritable pouvoir de JPA réside dans sa capacité à modéliser les relations entre les tables : One-to-One, One-to-Many, et Many-to-Many.

La vidéo suivante, de la chaîne "Coding With Armand", est un excellent guide pratique qui implémente un mini réseau social. Nous allons la suivre pas à pas pour comprendre comment mapper chaque type de relation.

Comprendre le mapping JPA avec Spring Boot - OneToOne, OneToMany, ManyToMany & Self Referencing

Cette vidéo est un cas pratique complet qui couvre exactement les objectifs de notre leçon. L'auteur part d'un diagramme de classes et implémente toutes les relations nécessaires.

Regardez l'intégralité de la vidéo (environ 14 minutes). Elle sert de fil rouge pour cette leçon. Prenez des notes sur la manière dont chaque relation est implémentée, en particulier l'utilisation des annotations que nous allons détailler ci-dessous.

a) Relation @OneToOne (Un-à-Un)

Exemple : Un Utilisateur a un et un seul Profil.

Dans une relation OneToOne, une ligne d'une table A est liée à une seule ligne d'une table B. La question est : où stocker la clé étrangère ? C'est le côté "propriétaire" (owning side) de la relation qui contient la colonne de jointure (@JoinColumn).

Dans l'exemple de la vidéo (de 01:20 à 04:17), la table users contiendra une colonne profil_id. L'entité User est donc propriétaire de la relation.

Côté propriétaire (User) :

// Dans la classe User
@OneToOne
@JoinColumn(name = "profil_id") // Nom de la colonne de la clé étrangère
private Profile profile;

Côté inverse (Profile) :
Pour rendre la relation bidirectionnelle (pouvoir accéder à l'utilisateur depuis le profil), on ajoute une référence dans l'autre classe. L'attribut mappedBy est crucial : il indique à JPA que la gestion de cette relation (la colonne de jointure) est déjà définie dans l'attribut profile de la classe User.

// Dans la classe Profile
@OneToOne(mappedBy = "profile") // "profile" est le nom de l'attribut dans la classe User
private User user;

Sans mappedBy, JPA penserait qu'il s'agit de deux relations unidirectionnelles distinctes et créerait une clé étrangère dans chaque table, ce qui est incorrect.

b) Relations @OneToMany (Un-à-Plusieurs) et @ManyToOne (Plusieurs-à-Un)

Exemple : Un Utilisateur peut écrire plusieurs Post, mais un Post n'est écrit que par un seul Utilisateur.

UML Diagram: One-to-Many Relationship (User-Command)
Ce diagramme UML illustre une relation 1-à-plusieurs classique. Un 'User' a plusieurs 'Command' (commandes), et chaque 'Command' appartient à un seul 'User'. La clé étrangère sera dans la table 'Command'.

C'est la relation la plus courante. La règle est simple : la clé étrangère est toujours du côté "plusieurs" (Many). L'entité Post contiendra donc une colonne user_id. C'est donc le côté @ManyToOne qui est propriétaire de la relation.

La vidéo explique cela très clairement (de 04:17 à 07:01).

Côté "Many" et propriétaire (Post) :

// Dans la classe Post
@ManyToOne
@JoinColumn(name = "user_id") // La clé étrangère est ici
private User user;

Côté "One" et inverse (User) :
Un utilisateur ayant plusieurs posts, on utilise une List ou un Set. On utilise encore mappedBy pour indiquer que la jointure est gérée par l'attribut user de l'entité Post.

// Dans la classe User
@OneToMany(mappedBy = "user")
private List<Post> posts;

c) Relation @ManyToMany (Plusieurs-à-Plusieurs)

Exemple : Un Utilisateur peut appartenir à plusieurs Group, et un Group peut contenir plusieurs Utilisateurs.

Comme vous le savez de votre expérience avec MySQL, cette relation nécessite une table intermédiaire (ou table de jointure). JPA gère la création et la gestion de cette table pour nous.

Many-to-Many Relationship in JPA and Database
Cette image montre parfaitement comment l'annotation `@ManyToMany` dans le code Java se traduit par une table de jointure (`EntityA_EntityB`) dans la base de données pour lier `EntityA` et `EntityB`.

La vidéo (de 07:01 à 10:01) montre comment utiliser @JoinTable pour configurer cette table.

Côté propriétaire (choisi arbitrairement, ici User) :

// Dans la classe User
@ManyToMany
@JoinTable(
    name = "user_groups", // Nom de la table de jointure
    joinColumns = @JoinColumn(name = "user_id"), // Clé étrangère pour l'entité User
    inverseJoinColumns = @JoinColumn(name = "group_id") // Clé étrangère pour l'entité Group
)
private Set<Group> groups; // Un Set est souvent préféré pour éviter les doublons

Côté inverse (Group) :
Comme toujours, on utilise mappedBy pour désigner l'autre côté comme propriétaire de la relation.

// Dans la classe Group
@ManyToMany(mappedBy = "groups")
private Set<User> users;

3. Un point sur les relations bidirectionnelles et le FetchType

Vous avez remarqué l'importance de mappedBy pour les relations bidirectionnelles. Pour approfondir ce concept et en introduire un nouveau, très important pour les performances, je vous propose un extrait d'une vidéo plus théorique.

Mapping des associations et de l'héritage avec JPA

Le professeur Mohamed Youssfi explique en détail le fonctionnement de mappedBy et introduit la notion de stratégies de chargement (FetchType). Son analogie pour LAZY vs EAGER est particulièrement éclairante.

Regardez les deux extraits suivants : De 09:33 à 11:33 : Concentrez-vous sur l'explication du rôle de mappedBy dans une relation bidirectionnelle OneToMany/ManyToOne. Il clarifie pourquoi il est essentiel pour dire à JPA qu'il s'agit d'une seule et même relation. De 11:33 à 20:14 : Écoutez attentivement la distinction entre FetchType.LAZY et FetchType.EAGER. C'est un concept fondamental en ORM. L'analogie avec les courses (aller chercher uniquement le pain, ou anticiper et prendre aussi le fromage et la confiture) est une excellente façon de le mémoriser.

Pour résumer ce point crucial :

  • FetchType.EAGER (avide) : Quand on charge une entité (ex: User), JPA charge immédiatement toutes ses relations associées (ex: sa List<Post>). Simple, mais peut être très lourd et inefficace si on n'a pas besoin des données associées.
  • FetchType.LAZY (paresseux) : Quand on charge une entité, JPA ne charge pas ses relations associées. Elles ne seront chargées de la base de données que si on y accède explicitement dans le code (ex: user.getPosts()). C'est beaucoup plus performant.

Règle générale de Spring/JPA :

  • Les relations *-to-One (@ManyToOne, @OneToOne) sont EAGER par défaut.
  • Les relations *-to-Many (@OneToMany, @ManyToMany) sont LAZY par défaut.

Il est souvent recommandé de mettre toutes les relations en LAZY explicitement (@OneToMany(fetch = FetchType.LAZY)) et de ne passer en EAGER que si c'est absolument nécessaire, pour un meilleur contrôle des performances.


4. Synthèse et ressources écrites

Pour consolider vos connaissances, vous pouvez parcourir ces deux articles qui résument bien les concepts que nous venons de voir, avec des exemples de code.

4. Mise en relation d'entités

Cet article de blog par José Paumard est une référence technique concise sur le sujet. Il est bien structuré par type de relation.

Parcourez les sections 4.1 à 4.6. Comparez les exemples de code avec ce que vous avez vu dans la vidéo. Notez la distinction claire entre les cas unidirectionnels et bidirectionnels pour chaque type de relation.

Les relations avec JPA — Documentation Java ORM / Spring

Cette documentation offre une autre perspective, avec des exemples clairs et des explications sur l'utilisation de @JoinColumn et @JoinTable.

Lisez les sections sur les relations 1:1, n:1, 1:n, n:n et les relations bi-directionnelles. Cela vous donnera une bonne vue d'ensemble et renforcera votre compréhension.


Conclusion

Aujourd'hui, vous avez appris à modéliser la structure de vos données directement dans votre code Java. C'est une compétence centrale pour tout développeur Spring.

Points clés à retenir :

  • Une classe devient une entité persistante avec l'annotation @Entity et doit avoir une clé primaire marquée par @Id.
  • Les relations entre entités sont définies par les annotations @OneToOne, @ManyToOne / @OneToMany, et @ManyToMany.
  • Dans une relation bidirectionnelle, un seul côté est propriétaire (celui qui définit la clé étrangère ou la table de jointure). L'autre côté doit utiliser l'attribut mappedBy pour le signaler.
  • La stratégie de chargement (FetchType) a un impact majeur sur les performances. LAZY est généralement préférable pour les collections.

Nous avons maintenant des entités, mais nous n'avons aucun moyen de les sauvegarder ou de les récupérer. Dans la prochaine leçon, nous allons combler ce manque en créant des interfaces Repository. Vous découvrirez comment Spring Data JPA vous permet d'effectuer des opérations CRUD (Create, Read, Update, Delete) complètes sans écrire la moindre ligne de SQL.