JPA — *Javaオブジェクトでデータベースを操作する*
JPA — *Javaオブジェクトでデータベースを操作する*
🎯 このレッスンを読み終えたら
このレッスンを最後まで読み終えると、以下の3つを自信を持って実践できるようになります。
- ▸✅ JpaRepositoryを継承するだけでコード0行でCRUDを実装
- ▸✅ N+1問題 → @EntityGraphまたはJOIN FETCHで解決
- ▸✅ LazyローディングとEagerローディングの違い + 実務では必ずLazyを選ぶ理由
学習目標をチェックリストとして手元に置き、すべてに答えられるようになったらレッスンを閉じてください。
JPAとは何か — *SQLを書かずにDBを使う*
要点を一言で
JPA (Java Persistence API) = JavaオブジェクトをDBテーブルに自動マッピングする標準仕様。SQLを直接書かず、Javaメソッドを呼ぶだけでCRUDが可能。
旧来の方法 — JDBCの苦労
その都度接続管理・SQL記述・結果マッピングをすべて手動で行う必要があります。500テーブルあれば数万行にも及ぶ繰り返しコードに。
新しい方法 — JPAの魔法
SQLを一切書かずJavaオブジェクトだけを扱えば、DBが自動的に同期されます。「オブジェクトで作業し、DBは忘れる」 — JPAの核心思想。
HibernateとJPA
- ▸JPA — 標準仕様(インターフェース)。javax.persistenceパッケージ
- ▸Hibernate — 実装。最も人気(シェア95%超)。EclipseLink・OpenJPAも存在
Spring Data JPA = SpringがJPAをさらに使いやすくした抽象化レイヤー。実務の標準スタック。
Entityマッピング — オブジェクトとテーブルの対応付け
リレーションマッピング — 外部キーをオブジェクト参照に変換
DBの外部キー関係をJavaオブジェクト参照に変換。order.getUser().getName()のように自然にナビゲートできます。
永続化コンテキスト — JPAの秘密兵器
JPAは永続化コンテキスト(Persistence Context)という一次キャッシュを持ちます。1トランザクション内では同じエンティティのDB問い合わせは1回だけ。
ダーティチェッキング(Dirty Checking) — JPAが開始時点と終了時点のオブジェクトを比較し、変更されたフィールドだけをUPDATE。setterを呼ぶだけで自動的に保存されます。
まとめ
- ▸JPA = Javaオブジェクト ↔ DBテーブルの自動マッピング標準
- ▸Hibernate = 最も人気の実装
- ▸Spring Data JPA = Springがさらに使いやすくした抽象化レイヤー
- ▸アノテーションでマッピング、リレーションはオブジェクト参照、変更は自動検知
Spring Data JpaRepository — *クエリの自動生成*
要点を一言で
JpaRepositoryインターフェースを定義するだけで基本的なCRUDメソッドが自動で作成されます。さらにメソッド名を見るだけでクエリを推測して自動生成してくれます。
基本CRUD — すぐに使える
この1行だけで以下のメソッドすべてが使えます:
クエリを1行も書かずにフルCRUDが可能。まるで魔法のようです。
クエリメソッド — 名前がそのままクエリ
ルール: find・exists・count・deleteで始まり + By + フィールド名・条件キーワード。Springがパースしてクエリを生成してくれます。
@Query — 複雑なクエリ
命名規則で表現できない場合は直接記述:
JPQL(JPA Query Language)— SQLに似ていますがテーブル名ではなくエンティティ名を使用。DB非依存です。
ネイティブSQLも利用可能:
QueryDSL — 動的クエリの標準
@Queryにも限界があります — 条件が動的な場合(検索フィルターなど)はQueryDSLの出番:
型安全でコンパイル時に検証。Spring Data JPAとともに実務の標準です。
ページング — 自動サポート
Pageableを引数に受け取るだけでLIMIT・OFFSET処理が自動。ページング情報も自動で返されます。
まとめ
- ▸
JpaRepositoryを継承するだけで基本CRUDが無料 - ▸メソッド名でクエリが自動生成
- ▸複雑な場合は
@QueryまたはQueryDSL - ▸ページング・ソートもPageableの1行で完結
N+1問題 — *JPAで最もよくある落とし穴*
問題の状況
ユーザーが100人なら合計101回のクエリ。1万人なら1万1回。ページのレスポンスタイムが5秒から50秒へと急増します。
これがN+1問題。JPAの遅延ローディング(Lazy Loading)が意図と異なる動作をする典型的な落とし穴です。
なぜ発生するのか
JPAは関連オブジェクトをデフォルトでLazyにロードします。user.getOrders()を初めて呼び出した瞬間に追加クエリが発行されます。一度にすべて取得すると不要なデータが多くなる可能性があるため、必要になったときだけ取得する設計です。
ただしループ内で呼び出すと毎回新しいクエリが → N+1。
解決策1 — Fetch Join
JOINで一度にすべて取得します。クエリ1回で完了。
生成されるSQL:
解決策2 — @EntityGraph
特定のメソッドにのみEagerローディングを適用。Fetch Joinよりもスッキリ書けます。
解決策3 — @BatchSize
関連データをN+1ではなくバッチ単位でまとめて取得します。
ユーザー1000人 → 10クエリ(100件ずつ)。完全な解決ではありませんが大幅な改善になります。
コレクション2つのFetch Join — 危険
コレクションを2つ同時にFetch Joinするとデータがデカルト積(M × N)で爆発します。対策:
- ▸1つだけFetch Join + 残りはBatchSize
- ▸またはDTOプロジェクションで必要なフィールドだけ取得
DTOプロジェクション — さらなる最適化
エンティティ全体ではなく必要なフィールドだけ取得:
エンティティマッピングとLazyローディングを完全に回避。最も高速な選択肢です。
診断 — ログで確認
またはより強力なP6Spy — 実際のパラメータ値まで表示してくれます。すべてのクエリを目で確認しながら、N+1の発生を即座に検知できます。
まとめ
- ▸N+1はLazyローディング + ループの組み合わせが原因
- ▸解決策: Fetch Join > @EntityGraph > @BatchSize > DTOプロジェクション
- ▸show-sqlで常にクエリをトレース
- ▸コレクション2つのFetch Joinは危険 — 1つに留める
🤖 AIへの質問例
このレッスンの概念を理解すれば、AIに具体的な指示を出せるようになります。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。
- ▸「このfindAll + ループがN+1になっているので@EntityGraphで解決して」
- ▸「このEntityをUserResponse DTOに変換するstaticファクトリメソッドを追加して」
- ▸「JpaRepositoryにfindByEmailAndActiveメソッドのシグネチャを追加して」
なぜトークンが減るのか
概念を知らないままだと、AIの回答を受け取っても「それは何ですか?」と再度質問しなければなりません。その「再質問」がトークンを消費します。概念を一度理解しておけば、会話が一度で完結します。