カテゴリ: Spring 更新日: 2025/11/05

Springの@OneToManyと@ManyToOneの基本を解説!初心者向けリレーション・FetchType・N+1問題対策ガイド

リレーション基礎:@OneToMany/@ManyToOne と N+1対策・FetchTypeの選び方
リレーション基礎:@OneToMany/@ManyToOne と N+1対策・FetchTypeの選び方

先生と生徒の会話形式で理解しよう

生徒

「Springでリレーションってどうやって使うんですか?エンティティ同士を結びつける方法が知りたいです!」

先生

「リレーションには@OneToMany@ManyToOneを使います。正しく使わないと、N+1問題などパフォーマンスの落とし穴もありますよ。」

生徒

「N+1って聞いたことあるけどよくわかりません。FetchTypeの選び方も気になります!」

先生

「では、Springのリレーション設定とFetchTypeの違い、N+1問題の対策方法について一緒に学んでいきましょう。」

1. @OneToManyと@ManyToOneの関係を理解しよう

1. @OneToManyと@ManyToOneの関係を理解しよう
1. @OneToManyと@ManyToOneの関係を理解しよう

Spring Data JPAでは、エンティティ間のリレーション(関連付け)を@OneToMany@ManyToOneで定義します。例えば「ユーザー」と「投稿」の関係で考えてみましょう。

一人のユーザーは複数の投稿を持てます。これがOneToManyです。一方、投稿は一人のユーザーに属します。これがManyToOneです。

以下のようにエンティティを定義します。


@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private List<Post> posts;
}

@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
}

2. mappedByとJoinColumnの違いを解説

2. mappedByとJoinColumnの違いを解説
2. mappedByとJoinColumnの違いを解説

mappedByは、リレーションの所有者ではない側(逆側)で使います。リレーションの主導権を相手側に任せる形です。

一方、@JoinColumnはリレーションの所有者側で使われ、外部キーを指定します。つまり、実際にテーブルにカラムが作られるのは@JoinColumnを定義した側です。

3. FetchType.LAZYとFetchType.EAGERの違いとは?

3. FetchType.LAZYとFetchType.EAGERの違いとは?
3. FetchType.LAZYとFetchType.EAGERの違いとは?

FetchTypeはデータ取得のタイミングを制御する重要な設定です。

  • FetchType.LAZY:必要になるまで関連エンティティを読み込まない(遅延読み込み)
  • FetchType.EAGER:親エンティティを取得すると同時に、関連エンティティも全て取得する(即時読み込み)

初心者はLAZYを基本として使い、必要に応じて明示的に取得するスタイルをおすすめします。

4. N+1問題とは?Springでよくある落とし穴

4. N+1問題とは?Springでよくある落とし穴
4. N+1問題とは?Springでよくある落とし穴

N+1問題とは、1つの親エンティティに対して、子エンティティが複数あるとき、個別にSQLが発行されてしまう問題です。

例えば、10人のユーザーがいて、それぞれの投稿を取得する場合、本来なら1回のJOINで済むところが、ユーザー取得の1回+投稿取得10回=合計11回のSQLが発行されてしまうことがあります。

これがパフォーマンスを大きく低下させる原因になります。

5. N+1問題を解決するには?JOIN FETCHの活用

5. N+1問題を解決するには?JOIN FETCHの活用
5. N+1問題を解決するには?JOIN FETCHの活用

JPQLでJOIN FETCHを使うことで、N+1問題を回避できます。例えば、以下のように書きます。


@Query("SELECT u FROM User u JOIN FETCH u.posts")
List<User> findAllWithPosts();

JOIN FETCHを使うことで、ユーザーと投稿を一気に取得でき、SQLの発行回数が最小限になります。

6. 実務でのFetchTypeの選び方と注意点

6. 実務でのFetchTypeの選び方と注意点
6. 実務でのFetchTypeの選び方と注意点

FetchType.EAGERは便利に見えますが、すべての関連データを毎回取得してしまい、メモリや処理速度に悪影響を及ぼす可能性があります。特にリスト型のリレーションに対しては注意が必要です。

LAZYを基本として、必要な場面でJOIN FETCHEntityGraphを使って明示的にデータを取得するのが実務での王道パターンです。

7. JSONシリアライズ時の無限ループに注意

7. JSONシリアライズ時の無限ループに注意
7. JSONシリアライズ時の無限ループに注意

@OneToMany@ManyToOneを双方向に設定したまま@RestControllerなどでJSONレスポンスを返すと、無限ループになってしまうことがあります。

これを防ぐには、以下のように@JsonManagedReference@JsonBackReferenceを使うのが一般的です。


@JsonManagedReference
@OneToMany(mappedBy = "user")
private List<Post> posts;

@JsonBackReference
@ManyToOne
@JoinColumn(name = "user_id")
private User user;

8. リレーションの方向性を意識した設計のコツ

8. リレーションの方向性を意識した設計のコツ
8. リレーションの方向性を意識した設計のコツ

双方向リレーションは便利ですが、すべてのエンティティに設定すると複雑になります。必要な場面に絞って使うのが設計のコツです。

例えば、投稿からユーザーを参照するだけで十分なら、@ManyToOneだけでよく、@OneToManyは省略してもかまいません。

リレーション設計は、パフォーマンスや保守性に影響するため、用途に応じた設計が重要です。

カテゴリの一覧へ
新着記事
Javaのラムダ式で戻り値とvoidの使い方を解説!returnの書き方も完全理解
Javaのメソッド参照とコンストラクタ参照の使い方を完全ガイド!初心者向けに::とClass::newを解説
Javaのラムダ式の書き方を徹底解説!アロー演算子->の基本と使い方
Thymeleafのth:eachの使い方!ループ回数やindexなどの繰り返し処理を学ぼう
人気記事
No.1
Java&Spring記事人気No1
Spring BootとJavaの互換性一覧!3.5/3.4/3.3はJava 21・17に対応してる?
No.2
Java&Spring記事人気No2
Spring Boot JPA入門:エンティティ/リポジトリの基本と作り方
No.3
Java&Spring記事人気No3
Javaの@Validアノテーションを徹底解説!初心者でもわかる入力値検証の基本
No.4
Java&Spring記事人気No4
Springの@Repositoryアノテーションの使い方を徹底解説!初心者でもわかるSpringフレームワークのデータアクセス