1対多と多対多のリレーションシップ
カスケードタイプとフェッチ戦略を指定して、@OneToMany、@ManyToMany、@JoinTableをマッピングします。
「1対多と多対多のリレーションシップ」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
JPAの関連付けの種類
JPAは、@OneToOne、@OneToMany、@ManyToOne、@ManyToManyの4種類の関連付けをサポートしています。それぞれが、外部キーまたは結合テーブルの制約にマッピングされます。
@ManyToOne: 所有側
多対一の関連(例: 複数の Order が 1 つの User に属する場合)は、@ManyToOne と外部キー用の @JoinColumn でマッピングします。
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}@OneToMany: 逆側
逆側(User が複数の Order を持つ場合)では、@OneToMany(mappedBy = "user") を使用します。mappedBy 属性は、外部キーを保持する所有側のフィールドを指します。
@Entity
public class User {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
}双方向関連の整合性を保つヘルパー
双方向関連では、フラッシュする前にオブジェクトグラフの整合性を保てるよう、ヘルパーメソッドで関連の両側を更新してください。
public void addOrder(Order order) {
orders.add(order);
order.setUser(this); // keep both sides in sync
}
public void removeOrder(Order order) {
orders.remove(order);
order.setUser(null);
}カスケードタイプ
CascadeType.ALL は、親から子へすべての操作(persist、merge、remove、refresh、detach)を伝播します。一般的に安全な選択肢は PERSIST と MERGE です。REMOVE を使用すると、親の削除時に子も削除されます。
@OneToMany(mappedBy = "user",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
private List<Order> orders;orphanRemoval: 子エンティティの自動削除
orphanRemoval = true を設定すると、明示的に entityManager.remove() を呼び出さなくても、親のコレクションから子エンティティを削除したときに、その子エンティティが削除されます。
user.getOrders().remove(order); // triggers DELETE for the orphaned order on flush@ManyToMany と @JoinTable
多対多の関連(例: Student と Course)では、中間テーブルを使用します。所有側で @JoinTable を使って定義し、逆側では mappedBy を使用します。
@Entity
public class Student {
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private Set<Course> courses = new HashSet<>();
}中間テーブルの追加カラム
中間テーブルに追加カラム(例: 受講登録日)が必要な場合は、@ManyToMany を 2 つの @OneToMany / @ManyToOne 関連に置き換え、中間テーブルを表す新しいエンティティを作成します。
@Entity
public class Enrollment {
@ManyToOne Student student;
@ManyToOne Course course;
private LocalDate enrolledAt;
}FetchType: LAZY と EAGER
デフォルトのフェッチ設定では、@ManyToOne と @OneToOne は EAGER(即座に読み込み)で、@OneToMany と @ManyToMany は LAZY(アクセス時に読み込み)です。N+1 クエリを避けるため、コレクションには常に LAZY を使用してください。
// Always override to LAZY:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;N+1 クエリ問題
ループ内で遅延読み込みのコレクションにアクセスすると、エンティティごとに 1 回ずつクエリが実行され、N+1 問題が発生します。JPQL の JOIN FETCH または @EntityGraph を使って、コレクションを 1 回のクエリで読み込んで解決してください。
// N+1 problem:
List<User> users = repo.findAll();
users.forEach(u -> u.getOrders().size()); // 1 + N queries!
// Fix:
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();コレクションの Set と List
@ManyToMany には Set を使用してください(中間テーブルの重複行を避けられます)。順序が重要な場合は @OneToMany に List を使用します。@ManyToMany で List を使用してはいけません。Hibernate がすべての行を削除して再挿入するためです。
確認問題
@OneToMany アノテーションの mappedBy は何を示しますか?
まとめ
所有側(外部キー側)には @ManyToOne と @JoinColumn を使用します。逆側には @OneToMany(mappedBy=...) を使用します。コレクションは常に LAZY にしてください。N+1 問題は JOIN FETCH で解決します。追加カラムがある多対多の関連には、中間エンティティを使用します。
よくある質問
「1対多と多対多のリレーションシップ」レッスンは無料ですか?
はい。「1対多と多対多のリレーションシップ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「1対多と多対多のリレーションシップ」で何を学びますか?
カスケードタイプとフェッチ戦略を指定して、@OneToMany、@ManyToMany、@JoinTableをマッピングします。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「1対多と多対多のリレーションシップ」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。