JPAにおける多対多の関係

1.はじめに

このチュートリアルでは、JPAを使用して多対多の関係を処理する複数の方法を説明します。

アイデアを提示するために、私たちは学生、コース、そしてそれらの間の様々な関係のモデルを使用します。

わかりやすくするために、コード例では、多対多の関係に関連する属性とJPA設定のみを示します。

2.基本多対多

2.1. 多対多関係のモデリング

  • 関係は2種類のエンティティ間の関係です。多対多の関係の場合、両側は反対側の複数のインスタンスに関連付けることができます。

エンティティタイプが互いに関連している可能性があることに注意してください。たとえば、家系図をモデル化すると、すべてのノードが人物になります。したがって、親子関係について話すと、両方の参加者が人物になります。

ただし、単一のエンティティタイプと複数のエンティティタイプの間の関係について説明しても、それほど違いはありません。 2つの異なるエンティティタイプ間の関係について考えるのは簡単なので、私達は私達のケースを説明するためにそれを使用します。

たとえば、生徒が自分の好きなコースにマークを付けると、生徒は 多数 のコースを好きになり、 多数 の学生は同じコースを好きになることができます。

ご存じのとおり、RDBMSでは、外部キーとの関係を作成できます。

両側が他方を参照できるようにする必要があるので、 外部キーを保持するための別のテーブルを作成する必要があります

そのような表は 結合表 と呼ばれます。結合テーブルでは、外部キーの組み合わせがその複合主キーになります。

2.2. JPAでの実装

POJOとの多対多の関係をモデル化するのは簡単です。他の要素を含む Collection を両方のクラスに含める必要があります。

その後、クラスを適切なJPAエンティティにするために、クラスに @ Entity 、主キーに @ Id のマークを付ける必要があります。

また、関係タイプを設定する必要があります。したがって、 コレクションに @ ManyToMany 注釈を付けます。

@Entity
class Student {

    @Id
    Long id;

    @ManyToMany
    Set<Course> likedCourses;

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

@Entity
class Course {

    @Id
    Long id;

    @ManyToMany
    Set<Student> likes;

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

さらに、RDBMSで関係をモデル化する方法を構成する必要があります。

所有者側は、リレーションシップを設定する場所です。この例では、 Student クラスを選択します。

  • Student クラスの @ JoinTable アノテーションを使ってこれを行うことができます。 joinColumn 属性は関係の所有者側に接続し、 inverseJoinColumn はもう一方の側に接続します。

@ManyToMany
@JoinTable(
  name = "course__like",
  joinColumns = @JoinColumn(name = "student__id"),
  inverseJoinColumns = @JoinColumn(name = "course__id"))
Set<Course> likedCourses;

@ JoinTable 、さらには @ JoinColumn を使用する必要はないことに注意してください。JPAがテーブル名と列名を生成します。ただし、JPAが使用する戦略は、常に使用する命名規則と一致するとは限りません。したがって、テーブルとカラムの名前を設定する可能性があります。

  • ターゲット側では、リレーションシップをマッピングするフィールドの名前を提供するだけです。したがって、 Course クラスの @ ManyToMany アノテーションの mappedBy 属性を設定します。

@ManyToMany(mappedBy = "likedCourses")
Set<Student> likes;

多対多のリレーションシップはデータベース内に所有者側を持たないため、 Course クラスで結合テーブルを設定し、それを Student クラスから参照することができます。

3.複合キーを使用した多対多

3.1. 関係属性のモデリング

生徒にコースを評価させたいとしましょう。学生は任意の数のコースを評価でき、任意の数の学生が同じコースを評価できます。そのため、多対多の関係もあります。それをもう少し複雑にしているのは、それが存在するという事実よりも格付けの関係にもっとたくさんあるということです。生徒がコースで与えた評価スコアを保存する必要があります。

この情報はどこに保存できますか?生徒はコースごとに異なる評価を付けることができるため、これを Student エンティティに含めることはできません。

同様に、それを Course エンティティに格納しても、良い解決策にはなりません。

これは、関係自体が属性を持つ場合の状況です。

この例を使用して、属性をリレーションにアタッチすると、ER図では次のようになります。

  • 単純な多対多の関係で行ったのとほぼ同じ方法でモデル化できます。唯一の違いは、結合テーブルに新しい属性を追加することです。

3.2. JPAで複合キーを作成する

単純な多対多関係の実装はかなり簡単でした。唯一の問題は、エンティティを直接接続しているため、そのように関係にプロパティを追加できないことです。

したがって、 関係そのものにプロパティを追加する方法はありません

JPAではDB属性をクラスフィールドにマッピングしているので、リレーションシップの新しいエンティティクラスを作成する必要があります。

もちろん、すべてのJPAエンティティは主キーを必要とします。 ** 主キーは複合キーなので、キーのさまざまな部分を保持する新しいクラスを作成する必要があります。

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

いくつかの 重要な要件があることに注意してください。複合キークラスは を満たす必要があります。

  • @ Embeddable でマークする必要があります

  • java.io.Serializable を実装する必要があります

  • hashcode() の実装を提供する必要があります。

equals() メソッド ** どのフィールドもそれ自体エンティティになることはできません

3.3. JPAで複合キーを使用する

この複合キークラスを使用して、結合テーブルをモデル化するエンティティクラスを作成できます。

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

このコードは通常のエンティティの実装と非常によく似ています。ただし、主な違いがいくつかあります。

  • 主キーをマークするために** @ EmbeddedId を使用しました。これはインスタンスです。

CourseRatingKey クラスの説明 student course のフィールドを @ MapsId でマークしました**

@ MapsId は、これらのフィールドをキーの一部に結び付けることを意味します。それらは、多対1の関係の外部キーです。これは、前述したように、複合キーではエンティティを持てないためです。

この後、前と同じように Student エンティティと Course エンティティの逆参照を設定できます。

class Student {

   //...

    @OneToMany(mappedBy = "student")
    Set<CourseRating> ratings;

   //...
}

class Course {

   //...

    @OneToMany(mappedBy = "course")
    Set<CourseRating> ratings;

   //...
}

複合キーを使用する別の方法があることに注意してください:https://www.baeldung.com/hibernate-identifiers[ @IdClass ]アノテーション。

3.4. さらなる特徴

  • Student クラスと Course クラスへの関係を @ ManyToOne として設定しました。これは、新しいエンティティを使用して、多対多の関係を2つの多対1の関係に構造的に分解したためです。

なぜ我々はこれをすることができましたか?前のケースでテーブルを詳しく調べると、2つの多対1の関係が含まれていることがわかります。 ** 言い換えれば、RDBMSには多対多の関係はありません。結合テーブルを使用して作成した構造体を多対多の関係と呼びます。

それに加えて、多対多の関係について話すのがより明確です。それが私たちの意図だからです。一方、結合テーブルは単なる実装の詳細です。あまり気にしない。

さらに、このソリューションにはまだ言及していない追加機能があります。

単純な多対多ソリューションは、2つのエンティティ間に関係を作成します。したがって、この関係を他の事業体に拡大することはできません。

ただし、このソリューションでは、この制限はありません。 任意の数のエンティティタイプ間の関係をモデル化できます

例えば、複数の教師がコースを教えることができるとき、学生は特定の教師が特定のコースを教える方法を評価することができます。そのように、評価は3つのエンティティ、学生、コース、そして教師の間の関係になります。

4.新しいエンティティとの多対多

4.1. 関係属性のモデリング

生徒にコースへの登録を許可したいとしましょう。また、 学生が特定のコースに登録したときのポイントを保存する必要があります 。それに加えて、私たちは彼女がコースで受けた学年も保存したいと思います。

理想的な世界では、複合キーを持つエンティティがある場合、以前のソリューションでこれを解決できます。しかし、私たちの世界は理想からかけ離れているため、学生は最初の試行で必ずしもコースを修了するとは限りません。

この場合、 同一の生徒と生徒のペアの間に 複数の接続、または同じ student id-course id のペアを持つ複数の行があります。すべての主キーは一意である必要があるため、これまでのソリューションを使用してモデル化することはできません。そのため、別の主キーを使用する必要があります。

したがって、 登録の属性を保持するエンティティ を導入できます。

この場合、 Registrationエンティティは他の2つのエンティティ間の関係 を表します。

これはエンティティなので、独自の主キーを持ちます。

前のソリューションでは、2つの外部キーから作成した複合主キーがありました。現在、2つの外部キーは主キーの一部にはなりません。

4.2. JPAでの実装

coure registration__は通常のテーブルになったので、それをモデル化した単純な古いJPAエンティティを作成できます。

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

また、 Student クラスと Course クラスの関係を設定する必要があります。

class Student {

   //...

    @OneToMany(mappedBy = "student")
    Set<CourseRegistration> registrations;

   //...
}

class Course {

   //...

    @OneToMany(mappedBy = "courses")
    Set<CourseRegistration> registrations;

   //...
}

繰り返しますが、前に関係を設定しました。したがって、JPAにその設定をどこで見つけることができるかを伝えるだけで済みます。

以前の問題に対処するためにこの解決策を使用できることに注意してください。

学生評価コース。ただし、必要がない限り、専用の主キーを作成するのは変です。さらに、RDBMSの観点からは、2つの外部キーを組み合わせることで完全な複合キーが作成されるため、意味がありません。その上、その** 複合キーは明確な意味を持っていました:

どのエンティティをリレーションシップ** で接続します。

そうでなければ、これら2つの実装の間の選択は、しばしば単に個人的な好みです。

5.まとめ

このチュートリアルでは、多対多の関係とは何か、またJPAを使用してRDBMSでそれをどのようにモデル化できるかを説明しました。

JPAでそれをモデル化する方法は3つあります。それに関しては、3つすべてに異なる長所と短所があります。

  • コードの明快さ

  • DBの明確さ

  • 関係に属性を割り当てる能力

  • 何人の実体を関係と結びつけることができるか

  • 同じエンティティ間の複数接続のサポート

いつものように、例はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-jpa[GitHubで動く]で利用可能です。