JPAにおける多対多の関係

JPAの多対多の関係

1. 前書き

このチュートリアルでは、deal with many-to-many relationships using JPAへの複数の方法を説明します。

アイデアを提示するために、学生、コース、およびそれらの間のさまざまな関係のモデルを使用します。

簡単にするために、コード例では、多対多の関係に関連する属性とJPA構成のみを示します。

参考文献:

JPAを使用したエンティティクラス名からSQLテーブル名へのマッピング

デフォルトでテーブル名が生成される方法と、その動作をオーバーライドする方法を学びます。

JPA / Hibernateカスケードタイプの概要

JPA / Hibernateカスケードタイプの簡単で実用的な概要。

2. 基本的な多対多

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

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

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

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

たとえば、学生が好きなコースをマークすると、学生はmanyコースを好きになり、many学生は同じコースを好きになります。

image

ご存じのとおり、RDBMSで外部キーとの関係を作成できます。 両側が他方を参照できるはずなので、we need to create a separate table to hold the foreign keys

image

このようなテーブルはjoin tableと呼ばれます。 結合テーブルでは、外部キーの組み合わせがその複合主キーになることに注意してください。

2.2. JPAでの実装

Modeling a many-to-many relationship with POJOsは簡単です。 他の要素を含むinclude a Collection in both classesが必要です。

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

また、関係タイプを構成する必要があります。 したがって、we 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
}

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

所有者側で関係を構成します。この例では、Studentクラスを選択します。

We can do this with the @JoinTable annotation in the Student class.結合テーブルの名前(course_like)と、@JoinColumn注釈付きの外部キーを提供します。 joinColumn属性は関係の所有者側に接続し、inverseJoinColumnは反対側に接続します。

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

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

On the target side, we only have to provide the name of the field, which maps the relationship。 したがって、Courseクラスの@ManyToManyアノテーションのmappedBy属性を設定します。

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

a many-to-many relationship doesn’t have an owner side in the databaseなので、Courseクラスで結合テーブルを構成し、Studentクラスから参照できることに注意してください。

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

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

学生にコースの評価をさせたいとしましょう。 学生は任意の数のコースを評価でき、任意の数の学生が同じコースを評価できます。 したがって、それは多対多の関係でもあります。 もう少し複雑なのは、there 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.

この情報はどこに保存できますか? 学生はコースごとに異なる評価を与えることができるため、Studentエンティティに入れることはできません。 同様に、Courseエンティティに保存することも適切な解決策ではありません。

これは、the relationship itself has an attributeの場合の状況です。

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

image

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

image

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

単純な多対多の関係の実装はかなり簡単でした。 唯一の問題は、エンティティを直接接続したため、そのように関係にプロパティを追加できないことです。 したがって、we had no way to add a property to the relationship itself

DB属性をJPAのクラスフィールドにマップするため、we need to create a new entity class for the relationship.

もちろん、すべてのJPAエンティティには主キーが必要です。 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
}

いくつかのkey requirements, which a composite key class has to fulfillがあることに注意してください:

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

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

  • CourseRatingKeyクラスのインスタンスである@EmbeddedId, to mark the primary keyを使用しました

  • studentフィールドとcourseフィールドを@MapsIdでマークしました

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

この後、前のようにStudentおよびCourseエンティティで逆参照を構成できます。

class Student {

    // ...

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

    // ...
}

class Course {

    // ...

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

    // ...
}

複合キーを使用する別の方法があることに注意してください:@IdClassアノテーション。

3.4. さらなる特徴

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

なぜこれができたのですか? 前のケースでテーブルを詳しく調べてみると、2つの多対1の関係が含まれていることがわかります。 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.

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

さらに、このソリューションには、まだ言及していない追加機能があります。 単純な多対多ソリューションは、2つのエンティティ間の関係を作成します。 したがって、関係をより多くのエンティティに拡張することはできません。 ただし、このソリューションでは、we can model relationships between any number of entity typesという制限はありません。

たとえば、複数の教師がコースを教えることができる場合、学生は特定の教師が特定のコースを教える方法を評価できます。 That way, a rating would be a relationship between three entities: a student, a course, and a teacher.

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

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

学生にコースへの登録を許可したいとします。 また、we need to store the point when a student registered for a specific course。 それに加えて、私たちは彼女がコースで受け取った成績も保存したいと思います。

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

この場合、同じstudent_id-course_idペアを持つmultiple connections between the same student-course pairsまたは複数の行があります。 すべての主キーは一意である必要があるため、以前のソリューションを使用してモデル化することはできません。 したがって、別の主キーを使用する必要があります。

したがって、登録の属性を保持するwe can introduce an entity

image

この場合、他の2つのエンティティ間のthe Registration entity represents the relationship

エンティティであるため、独自の主キーがあります。

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

image

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 registrations;

    // ...
}

class Course {

    // ...

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

    // ...
}

繰り返しますが、前に関係を構成しました。 したがって、JPAに伝える必要があるのは、その構成がどこで見つかるかだけです。

このソリューションを使用して、以前の問題である学生のコースの評価に対処できることに注意してください。 ただし、必要がない限り、専用のプライマリキーを作成するのは奇妙に感じます。 さらに、RDBMSの観点からは、2つの外部キーを組み合わせると完全な複合キーが作成されるため、あまり意味がありません。 その上、そのcomposite key had a clear meaning: which entities we connect in the relationship

それ以外の場合、これら2つの実装の選択は、多くの場合、単に個人的な好みです。

5. 結論

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

JPAでモデル化する3つの方法を見ました。 3つすべてには、次の点で異なる長所と短所があります。

  • コードの明快さ

  • DBの明快さ

  • リレーションシップに属性を割り当てる機能

  • 関係とリンクできるエンティティの数、および

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

いつものように、例は利用可能なover on GitHubです。