Viele-zu-viele-Beziehung in JPA

1. Einleitung

In diesem Tutorial werden verschiedene Möglichkeiten gezeigt, wie Sie mit JPA mit vielen-zu-vielen-Beziehungen umgehen können .

Um die Ideen zu präsentieren, verwenden wir ein Modell von Studenten, Kursen und verschiedenen Beziehungen zwischen ihnen.

Der Einfachheit halber werden in den Codebeispielen nur die Attribute und die JPA-Konfiguration angezeigt, die sich auf die Viele-zu-Viele-Beziehungen beziehen.

2. Grundlegende Viele-zu-Viele

2.1. Modellierung einer Viele-zu-Viele-Beziehung

Eine Beziehung ist eine Verbindung zwischen zwei Arten von Entitäten. Im Falle einer Viele-zu-Viele-Beziehung können sich beide Seiten auf mehrere Instanzen der anderen Seite beziehen.

Beachten Sie, dass Entitätstypen möglicherweise in einer Beziehung zu sich selbst stehen. Wenn wir beispielsweise Stammbäume modellieren: Jeder Knoten ist eine Person. Wenn wir also über die Eltern-Kind-Beziehung sprechen, sind beide Teilnehmer eine Person.

Es macht jedoch keinen solchen Unterschied, ob es sich um eine Beziehung zwischen einzelnen oder mehreren Entitätstypen handelt. Da es einfacher ist, über Beziehungen zwischen zwei verschiedenen Entitätstypen nachzudenken, werden wir dies verwenden, um unsere Fälle zu veranschaulichen.

Wenn die Schüler beispielsweise die Kurse markieren, die ihnen gefallen: Ein Schüler kann viele Kurse mögen , und viele Schüler können denselben Kurs mögen:

Wie wir wissen, können wir in RDBMS Beziehungen zu Fremdschlüsseln erstellen. Da beide Seiten auf die andere verweisen können sollten, müssen wir eine separate Tabelle erstellen, in der die Fremdschlüssel gespeichert sind :

Eine solche Tabelle wird als Join-Tabelle bezeichnet . Beachten Sie, dass in einer Join-Tabelle die Kombination der Fremdschlüssel der zusammengesetzte Primärschlüssel ist.

2.2. Implementierung in JPA

Das Modellieren einer Viele-zu-Viele-Beziehung zu POJOs ist einfach. Wir sollten eine Sammlung in beide Klassen aufnehmen , die die Elemente der anderen enthält.

Danach müssen wir die Klasse mit @Entity und den Primärschlüssel mit @Id markieren , damit sie zu richtigen JPA-Entitäten werden.

Außerdem sollten wir den Beziehungstyp konfigurieren. Daher kennzeichnen wir die Sammlungen mit @ManyToMany- Anmerkungen:

@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 }

Außerdem müssen wir konfigurieren, wie die Beziehung im RDBMS modelliert wird.

Auf der Besitzerseite konfigurieren wir die Beziehung. In diesem Beispiel wählen wir die Schülerklasse aus .

Wir können dies mit der Annotation @JoinTable in der Student- Klasse tun . Wir geben den Namen der Join-Tabelle ( kursähnlich ) und die Fremdschlüssel mit den @ JoinColumn- Annotationen an. Das joinColumn- Attribut stellt eine Verbindung zur Eigentümerseite der Beziehung und das inverseJoinColumn zur anderen Seite her:

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

Beachten Sie, dass die Verwendung von @JoinTable oder sogar @JoinColumn nicht erforderlich ist: JPA generiert die Tabellen- und Spaltennamen für uns. Die von JPA verwendete Strategie entspricht jedoch nicht immer den von uns verwendeten Namenskonventionen. Daher die Möglichkeit, Tabellen- und Spaltennamen zu konfigurieren.

Auf der Zielseite müssen wir nur den Namen des Feldes angeben, das die Beziehung abbildet . Daher setzen wir das mappedBy Attribut der @ManyToMany Anmerkung im Kurs Klasse:

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

Beachten Sie, dass wir , da eine Viele-zu-Viele-Beziehung keine Besitzerseite in der Datenbank hat , die Join-Tabelle in der Kursklasse konfigurieren und aus der Student- Klasse darauf verweisen können .

3. Viele-zu-Viele mit einem zusammengesetzten Schlüssel

3.1. Modellierung von Beziehungsattributen

Angenommen, wir möchten, dass die Schüler die Kurse bewerten. Ein Student kann eine beliebige Anzahl von Kursen bewerten, und eine beliebige Anzahl von Studenten kann denselben Kurs bewerten. Daher ist es auch eine Viele-zu-Viele-Beziehung. Was es etwas komplizierter macht, ist, dass die Ratingbeziehung mehr beinhaltet als die Tatsache, dass sie existiert. Wir müssen die Bewertung speichern, die der Student im Kurs abgegeben hat.

Wo können wir diese Informationen speichern? Wir können es nicht in die Studentenentität einfügen, da ein Student verschiedenen Kursen unterschiedliche Bewertungen geben kann. Ebenso wäre es keine gute Lösung , es in der Kursentität zu speichern .

Dies ist eine Situation, in der die Beziehung selbst ein Attribut hat .

In diesem Beispiel sieht das Anhängen eines Attributs an eine Beziehung in einem ER-Diagramm folgendermaßen aus:

Wir können es fast genauso modellieren wie mit der einfachen Viele-zu-Viele-Beziehung. Der einzige Unterschied besteht darin, dass wir der Join-Tabelle ein neues Attribut hinzufügen:

3.2. Erstellen eines zusammengesetzten Schlüssels in JPA

Die Implementierung einer einfachen Viele-zu-Viele-Beziehung war ziemlich einfach. Das einzige Problem ist, dass wir einer Beziehung auf diese Weise keine Eigenschaft hinzufügen können, da wir die Entitäten direkt verbunden haben. Daher hatten wir keine Möglichkeit, der Beziehung selbst eine Eigenschaft hinzuzufügen .

Da wir Klassenattribute in JPA DB-Attribute zuordnen, müssen wir eine neue Entitätsklasse für die Beziehung erstellen.

Of course, every JPA entity needs a primary key. 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 }

Note, that there're some key requirements, which a composite key class has to fulfill:

  • We have to mark it with @Embeddable
  • It has to implement java.io.Serializable
  • We need to provide an implementation of the hashcode() and equals() methods
  • None of the fields can be an entity themselves

3.3. Using a Composite Key in JPA

Using this composite key class, we can create the entity class, which models the join table:

@Entity class CourseRating { @EmbeddedId CourseRatingKey id; @ManyToOne @MapsId("studentId") @JoinColumn(name = "student_id") Student student; @ManyToOne @MapsId("courseId") @JoinColumn(name = "course_id") Course course; int rating; // standard constructors, getters, and setters }

This code is very similar to a regular entity implementation. However, we have some key differences:

  • we used @EmbeddedId, to mark the primary key, which is an instance of the CourseRatingKey class
  • we marked the student and course fields with @MapsId

@MapsId means that we tie those fields to a part of the key, and they're the foreign keys of a many-to-one relationship. We need it, because as we mentioned above, in the composite key we can't have entities.

After this, we can configure the inverse references in the Student and Course entities like before:

class Student { // ... @OneToMany(mappedBy = "student") Set ratings; // ... } class Course { // ... @OneToMany(mappedBy = "course") Set ratings; // ... }

Note, that there's an alternative way to use composite keys: the @IdClass annotation.

3.4. Further Characteristics

We configured the relationships to the Student and Course classes as @ManyToOne. We could do this because with the new entity we structurally decomposed the many-to-many relationship to two many-to-one relationships.

Why were we able to do this? If we inspect the tables closely in the previous case, we can see, that it contained two many-to-one relationships. 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.

Besides, it's more clear if we talk about many-to-many relationships, because that's our intention. Meanwhile, a join table is just an implementation detail; we don't really care about it.

Moreover, this solution has an additional feature we didn't mention yet. The simple many-to-many solution creates a relationship between two entities. Therefore, we cannot expand the relationship to more entities. However, in this solution we don't have this limit: we can model relationships between any number of entity types.

For example, when multiple teachers can teach a course, students can rate how a specific teacher teaches a specific course. That way, a rating would be a relationship between three entities: a student, a course, and a teacher.

4. Many-to-Many With a New Entity

4.1. Modeling Relationship Attributes

Let's say we want to let students register to courses. Also, we need to store the point when a student registered for a specific course. On top of that, we also want to store what grade she received in the course.

In an ideal world, we could solve this with the previous solution, when we had an entity with a composite key. However, our world is far from ideal and students don't always accomplish a course on the first try.

In this case, there're multiple connections between the same student-course pairs, or multiple rows, with the same student_id-course_id pairs. We can't model it using any of the previous solutions because all primary keys must be unique. Therefore, we need to use a separate primary key.

Therefore, we can introduce an entity, which will hold the attributes of the registration:

In this case, the Registration entity represents the relationship between the other two entities.

Since it's an entity, it'll have its own primary key.

Note, that in the previous solution we had a composite primary key, which we created from the two foreign keys. Now, the two foreign keys won't be the part of the primary key:

4.2. Implementation in JPA

Since the coure_registration became a regular table, we can create a plain old JPA entity modeling it:

@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 }

Also, we need to configure the relationships in the Student and Course classes:

class Student { // ... @OneToMany(mappedBy = "student") Set registrations; // ... } class Course { // ... @OneToMany(mappedBy = "courses") Set registrations; // ... }

Again, we configured the relationship before. Hence, we only need to tell JPA, where can it find that configuration.

Note, that we could use this solution to address the previous problem: students rating courses. However, it feels weird to create a dedicated primary key unless we have to. Moreover, from an RDBMS perspective, it doesn't make much sense, since combining the two foreign keys made a perfect composite key. Besides, that composite key had a clear meaning: which entities we connect in the relationship.

Otherwise, the choice between these two implementations is often simply personal preference.

5. Conclusion

In this tutorial, we saw what a many-to-many relationship is and how can we model it in an RDBMS using JPA.

Wir haben drei Möglichkeiten gesehen, es in JPA zu modellieren. Alle drei haben unterschiedliche Vor- und Nachteile, wenn es um Folgendes geht:

  • Klarheit des Codes
  • DB Klarheit
  • Fähigkeit, der Beziehung Attribute zuzuweisen
  • Wie viele Entitäten können wir mit der Beziehung verknüpfen, und
  • Unterstützung für mehrere Verbindungen zwischen denselben Entitäten

Wie üblich sind die Beispiele auf GitHub verfügbar.