1. 概要

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

学生、コース、およびそれらの間のさまざまな関係のモデルを使用します。

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

2. 基本的な多対多

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

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

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

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

好きなコースにマークを付ける学生の例を見てみましょう。

学生は多くのコースを好きになることができ、多くの学生は同じコースを好きになることができます。

ご存知のように、RDBMSでは外部キーとの関係を作成できます。 両側が他方を参照できるはずなので、外部キーを保持するために別のテーブルを作成する必要があります

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

2.2. JPAでの実装

POJOとの多対多の関係のモデリングは簡単です。 両方のクラスにコレクションを含める必要があります。これには他の要素が含まれています。

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

また、関係タイプを構成する必要があります。 したがって、コレクションに@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アノテーションを使用して実行できます。結合テーブルの名前( course_like )と、外部キーを@JoinColumnで指定します。 注釈。 joinColumn 属性は関係の所有者側に接続し、reverseJoinColumnは反対側に接続します。

@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 クラスで結合テーブルを構成し、 学生クラス。

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

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

学生にコースを評価させたいとしましょう。 学生は任意の数のコースを評価でき、任意の数の学生が同じコースを評価できます。 したがって、それは多対多の関係でもあります。

この例をもう少し複雑にしているのは、 格付け関係には、それが存在するという事実以上のものがあります。 学生がコースで与えた評価スコアを保存する必要があります。

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

これは、関係自体に属性がある場合の状況です。

この例を使用すると、関係に属性をアタッチすると、ER図では次のようになります。

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

3.2. JPAでの複合キーの作成

単純な多対多の関係の実装はかなり簡単でした。 唯一の問題は、エンティティを直接接続しているため、その方法でリレーションシップにプロパティを追加できないことです。 したがって、関係自体にプロパティを追加する方法はありませんでした。

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

もちろん、すべての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("studentId")
    @JoinColumn(name = "student_id")
    Student student;

    @ManyToOne
    @MapsId("courseId")
    @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;

    // ...
}

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

3.4. さらなる特徴

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

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

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

さらに、このソリューションには、まだ言及していない追加機能があります。 単純な多対多のソリューションは、2つのエンティティ間の関係を作成します。 したがって、関係をより多くのエンティティに拡張することはできません。 ただし、このソリューションにはこの制限はありません。任意の数のエンティティタイプ間の関係をモデル化できます。

たとえば、複数の教師がコースを教えることができる場合、学生は特定の教師が特定のコースをどのように教えるかを評価できます。 このように、評価は、学生、コース、教師の3つのエンティティ間の関係になります。

4. 新しいエンティティを持つ多対多

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

学生にコースへの登録を許可したいとします。 また、特定のコースに登録したときのポイントを保存する必要があります。さらに、そのコースで取得した成績を保存したいと思います。

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

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

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

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

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

前のソリューションでは、2つの外部キーから作成した複合主キーがあったことを思い出してください。

これで、2つの外部キーは主キーの一部になりません。

4.2. JPAでの実装

course_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 = "course")
    Set<CourseRegistration> registrations;

    // ...
}

繰り返しになりますが、以前に関係を構成したので、JPAにその構成を見つけることができる場所を指示するだけで済みます。

このソリューションを使用して、学生がコースを評価するという以前の問題に対処することもできます。 ただし、必要がない限り、専用の主キーを作成するのは奇妙に感じます。

さらに、RDBMSの観点からは、2つの外部キーを組み合わせることで完全な複合キーが作成されるため、あまり意味がありません。 その上、その複合キーには明確な意味がありました:関係でどのエンティティを接続するか。

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

5. 結論

この記事では、多対多の関係とは何か、そしてJPAを使用してRDBMSでそれをモデル化する方法を説明しました。

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

  • コードの明確さ
  • DBの明確さ
  • 関係に属性を割り当てる機能
  • 関係にリンクできるエンティティの数
  • 同じエンティティ間の複数の接続のサポート

いつものように、例はGitHubから入手できます。