Relation plusieurs à plusieurs dans JPA

Relation plusieurs à plusieurs dans JPA

1. introduction

Dans ce didacticiel, nous allons voir plusieurs façons dedeal with many-to-many relationships using JPA.

Pour présenter les idées, nous utiliserons un modèle d'étudiants, de cours et de diverses relations entre eux.

Par souci de simplicité, dans les exemples de code, nous ne montrerons que les attributs et la configuration JPA liés aux relations plusieurs-à-plusieurs.

Lectures complémentaires:

Mappage des noms de classe d'entité en noms de table SQL avec JPA

Découvrez comment les noms de table sont générés par défaut et comment remplacer ce comportement.

Read more

Présentation des types JPA / Hibernate Cascade

Un aperçu rapide et pratique des types de cascade JPA / Hibernate.

Read more

2. De base plusieurs à plusieurs

2.1. Modéliser une relation plusieurs à plusieurs

Une relation est une connexion entre deux types d'entités. Dans le cas d'une relation plusieurs-à-plusieurs, les deux côtés peuvent se rapporter à plusieurs instances de l'autre côté.

Notez qu'il est possible que les types d'entités soient en relation avec eux-mêmes. Par exemple, lorsque nous modélisons des arbres généalogiques: chaque nœud est une personne. Par conséquent, si nous parlons de la relation parent-enfant, les deux participants seront une personne.

Cependant, cela ne fait pas une telle différence que nous parlions d'une relation entre un ou plusieurs types d'entités. Comme il est plus facile de penser aux relations entre deux types d'entités différents, nous allons l'utiliser pour illustrer nos cas.

Par exemple, lorsque les étudiants notent les cours qu'ils aiment: un étudiant peut aimer les coursmany et les étudiantsmany peuvent aimer le même cours:

image

Comme nous le savons, dans les SGBDR, nous pouvons créer des relations avec des clés étrangères. Puisque les deux côtés doivent pouvoir référencer l'autre,we need to create a separate table to hold the foreign keys:

image

Une telle table est appelée unjoin table. Notez que dans une table de jointure, la combinaison des clés étrangères sera sa clé primaire composite.

2.2. Mise en œuvre dans l'APP

Modeling a many-to-many relationship with POJOs est facile. Nous devrionsinclude a Collection in both classes, qui contient les éléments des autres.

Après cela, nous devons marquer la classe avec@Entity et la clé primaire avec@Id pour en faire des entités JPA appropriées.

En outre, nous devrions configurer le type de relation. D'où les annotationswe mark the collections with @ManyToMany:

@Entity
class Student {

    @Id
    Long id;

    @ManyToMany
    Set likedCourses;

    // additional properties
    // standard constructors, getters, and setters
}

@Entity
class Course {

    @Id
    Long id;

    @ManyToMany
    Set likes;

    // additional properties
    // standard constructors, getters, and setters
}

De plus, nous devons configurer comment modéliser la relation dans le SGBDR.

Le côté propriétaire est l'endroit où nous configurons la relation, qui pour cet exemple, nous allons choisir la classeStudent.

We can do this with the @JoinTable annotation in the Student class. Nous fournissons le nom de la table de jointure (course_like), et les clés étrangères avec les annotations@JoinColumn. L'attributjoinColumn se connectera au côté propriétaire de la relation et lesinverseJoinColumn à l'autre côté:

@ManyToMany
@JoinTable(
  name = "course_like",
  joinColumns = @JoinColumn(name = "student_id"),
  inverseJoinColumns = @JoinColumn(name = "course_id"))
Set likedCourses;

Notez que l’utilisation de@JoinTable ou même de@JoinColumn n’est pas obligatoire: JPA générera les noms de table et de colonne pour nous. Cependant, la stratégie utilisée par JPA ne correspond pas toujours aux conventions de dénomination que nous utilisons. D'où la possibilité de configurer les noms de table et de colonne.

On the target side, we only have to provide the name of the field, which maps the relationship. Par conséquent, nous définissons l'attributmappedBy de l'annotation@ManyToMany dans la classeCourse:

@ManyToMany(mappedBy = "likedCourses")
Set likes;

Notez que depuisa many-to-many relationship doesn’t have an owner side in the database, nous pourrions configurer la table de jointure dans la classeCourse et la référencer à partir de la classeStudent.

3. Plusieurs à plusieurs à l'aide d'une clé composée

3.1. Attributs de relation de modélisation

Disons que nous voulons permettre aux étudiants d'évaluer les cours. Un étudiant peut évaluer n’importe quel nombre de cours et n’importe quel nombre d’étudiants peut évaluer le même cours. C’est donc aussi une relation plusieurs-à-plusieurs. Ce qui le rend un peu plus compliqué, c'est quethere is more to the rating relationship than the fact that it exists. We need to store the rating score the student gave on the course.

Où pouvons-nous stocker cette information? Nous ne pouvons pas le mettre dans l'entitéStudent car un étudiant peut attribuer des notes différentes à différents cours. De même, le stocker dans l’entitéCourse ne serait pas non plus une bonne solution.

C'est une situation oùthe relationship itself has an attribute.

En utilisant cet exemple, attacher un attribut à une relation ressemble à ceci dans un diagramme ER:

image

Nous pouvons le modéliser presque de la même manière que nous l'avons fait avec la simple relation plusieurs-à-plusieurs. La seule différence est que nous attachons un nouvel attribut à la table de jointure:

image

3.2. Création d'une clé composite dans JPA

La mise en place d'une simple relation plusieurs-à-plusieurs était plutôt simple. Le seul problème est que nous ne pouvons pas ajouter une propriété à une relation de cette façon, car nous avons directement connecté les entités. Par conséquent,we had no way to add a property to the relationship itself.

Puisque nous mappons les attributs DB aux champs de classe dans JPA,we need to create a new entity class for the relationship.

Bien entendu, chaque entité JPA a besoin d'une clé primaire. Because our primary key is a composite key, we have to create a new class, which will hold the different parts of the key:

@Embeddable
class CourseRatingKey implements Serializable {

    @Column(name = "student_id")
    Long studentId;

    @Column(name = "course_id")
    Long courseId;

    // standard constructors, getters, and setters
    // hashcode and equals implementation
}

Notez qu'il existe deskey requirements, which a composite key class has to fulfill:

  • Nous devons le marquer avec@Embeddable

  • Il doit implémenterjava.io.Serializable

  • Nous devons fournir une implémentation des méthodeshashcode() etequals()

  • Aucun des champs ne peut être une entité elle-même

3.3. Utilisation d'une clé composite dans JPA

En utilisant cette classe de clé composite, nous pouvons créer la classe d'entité, qui modélise la table de jointure:

@Entity
class CourseRating {

    @EmbeddedId
    CourseRatingKey id;

    @ManyToOne
    @MapsId("student_id")
    @JoinColumn(name = "student_id")
    Student student;

    @ManyToOne
    @MapsId("course_id")
    @JoinColumn(name = "course_id")
    Course course;

    int rating;

    // standard constructors, getters, and setters
}

Ce code est très similaire à une implémentation d'entité normale. Cependant, nous avons quelques différences clés:

  • nous avons utilisé@EmbeddedId, to mark the primary key, qui est une instance de la classeCourseRatingKey

  • nous avons marqué les champsstudent etcourse avec@MapsId

@MapsId signifie que nous lions ces champs à une partie de la clé, et ce sont les clés étrangères d'une relation plusieurs-à-un. Nous en avons besoin, car comme nous l'avons mentionné ci-dessus, dans la clé composite, nous ne pouvons pas avoir d'entités.

Après cela, nous pouvons configurer les références inverses dans les entitésStudent etCourse comme avant:

class Student {

    // ...

    @OneToMany(mappedBy = "student")
    Set ratings;

    // ...
}

class Course {

    // ...

    @OneToMany(mappedBy = "course")
    Set ratings;

    // ...
}

Notez qu'il existe une autre façon d'utiliser les clés composites: l'annotation@IdClass.

3.4. Autres caractéristiques

Nous avons configuré les relations avec les classesStudent etCourse en tant que@ManyToOne. Nous pourrions le faire parce qu'avec la nouvelle entité, nous avons décomposé structurellement la relation plusieurs-à-plusieurs en deux relations plusieurs-à-un.

Pourquoi avons-nous pu faire cela? Si nous examinons les tables de près dans le cas précédent, nous pouvons voir qu’elles contenaient deux relations plusieurs-à-un. In other words, there isn’t any many-to-many relationship in an RDBMS. We call the structures we create with join tables many-to-many relationships because that’s what we model.

De plus, c’est plus clair si nous parlons de relations plusieurs-à-plusieurs, car c’est notre intention. Pendant ce temps, une table de jointure n'est qu'un détail d'implémentation; nous ne nous en soucions pas vraiment.

De plus, cette solution possède une fonctionnalité supplémentaire que nous n’avons pas encore mentionnée. La solution simple plusieurs à plusieurs crée une relation entre deux entités. Par conséquent, nous ne pouvons pas élargir la relation à plus d’entités. Cependant, dans cette solution, nous n’avons pas cette limite:we can model relationships between any number of entity types.

Par exemple, lorsque plusieurs enseignants peuvent enseigner un cours, les étudiants peuvent évaluer comment un enseignant spécifique enseigne un cours spécifique. That way, a rating would be a relationship between three entities: a student, a course, and a teacher.

4. Plusieurs à plusieurs avec une nouvelle entité

4.1. Attributs de relation de modélisation

Disons que nous voulons permettre aux étudiants de s'inscrire aux cours. En outre,we need to store the point when a student registered for a specific course. En plus de cela, nous voulons aussi stocker quelle note elle a reçue pendant le cours.

Dans un monde idéal, nous pourrions résoudre ce problème avec la solution précédente, lorsque nous avions une entité avec une clé composite. Cependant, notre monde est loin d’être idéal et les étudiants ne suivent pas toujours un cours du premier coup.

Dans ce cas, il existemultiple connections between the same student-course pairs, ou plusieurs lignes, avec les mêmes pairesstudent_id-course_id. Nous ne pouvons pas le modéliser en utilisant l'une des solutions précédentes, car toutes les clés primaires doivent être uniques. Par conséquent, nous devons utiliser une clé primaire distincte.

Par conséquent,we can introduce an entity, qui contiendra les attributs de l'enregistrement:

image

Dans ce cas,the Registration entity represents the relationship entre les deux autres entités.

Puisqu'il s'agit d'une entité, elle aura sa propre clé primaire.

Notez que dans la solution précédente, nous avions une clé primaire composite, créée à partir des deux clés étrangères. Désormais, les deux clés étrangères ne feront plus partie de la clé primaire:

image

4.2. Mise en œuvre dans l'APP

Puisque lescoure_registration sont devenus une table régulière, nous pouvons créer une ancienne entité JPA la modélisant:

@Entity
class CourseRegistration {

    @Id
    Long id;

    @ManyToOne
    @JoinColumn(name = "student_id")
    Student student;

    @ManyToOne
    @JoinColumn(name = "course_id")
    Course course;

    LocalDateTime registeredAt;

    int grade;

    // additional properties
    // standard constructors, getters, and setters
}

De plus, nous devons configurer les relations dans les classesStudent etCourse:

class Student {

    // ...

    @OneToMany(mappedBy = "student")
    Set registrations;

    // ...
}

class Course {

    // ...

    @OneToMany(mappedBy = "courses")
    Set registrations;

    // ...
}

Encore une fois, nous avons configuré la relation avant. Par conséquent, nous devons seulement dire à JPA, où peut-il trouver cette configuration.

Notez que nous pourrions utiliser cette solution pour résoudre le problème précédent: les étudiants évaluant les cours. Cependant, il est étrange de créer une clé primaire dédiée à moins que nous ne soyons obligés de le faire. De plus, du point de vue du SGBDR, cela n’a pas beaucoup de sens, car la combinaison des deux clés étrangères a fait une clé composite parfaite. De plus, cecomposite key had a clear meaning: which entities we connect in the relationship.

Sinon, le choix entre ces deux implémentations est souvent une simple préférence personnelle.

5. Conclusion

Dans ce didacticiel, nous avons vu ce qu’est une relation plusieurs à plusieurs et comment la modéliser dans un SGBDR à l’aide de JPA.

Nous avons vu trois façons de le modéliser dans JPA. Tous trois présentent des avantages et des inconvénients différents en ce qui concerne:

  • la clarté du code

  • Clarté DB

  • possibilité d'attribuer des attributs à la relation

  • combien d'entités pouvons-nous associer à la relation, et

  • prise en charge de plusieurs connexions entre les mêmes entités

Comme d'habitude, les exemples sont disponiblesover on GitHub.