前回、N+1問題を解決するために、JOIN FETCHを使って関連データを一度に取得する方法を学びました。これはパフォーマンスチューニングの強力なテクニックです。
しかし、データを大量に取得する際に、もう一つ考慮すべき点があります。それは「本当にそのEntityの全フィールドが必要か?」という点です。
例えば、ユーザーのリストを表示する際、パスワードや機密情報を含む全フィールドを取得する必要はありません。「IDと名前とメールアドレス」だけで十分なケースがほとんどです。
今回は、このように必要なフィールドだけを取得し、メモリ使用量やネットワーク転送量を削減する手法、**プロジェクション(Projection)**について学びます。
1. プロジェクションとは?
**プロジェクション(Projection)**とは、DBからEntityの全フィールドではなく、必要なフィールドだけを抽出して取得することを指します。
Spring Data JPAでは、プロジェクションを実現するために、以下の2つの主要な方法を提供しています。
- インターフェースベースプロジェクション(Interface-based Projection)
- クラスベースプロジェクション(Class-based Projection/DTO)
2. インターフェースベースプロジェクション(推奨)
最もシンプルで推奨される方法です。取得したいフィールド名に対応するGetterメソッドを持つインターフェースを定義するだけで、Spring Data JPAが自動でそのインターフェースを満たすオブジェクトを生成してくれます。
2-1. プロジェクション用インターフェースの定義
例えば、Item Entityから「名前」と「価格」だけを取得したい場合を考えます。
Java
// ItemInfo.java: プロジェクション用のインターフェース
public interface ItemInfo {
// 取得したいフィールドに対応するGetterを定義する
// フィールド名は get の後に続く名前に対応(例: getName -> Item.name)
String getName();
int getPrice();
// 関連データのフィールドも指定可能 (例: Item.category.name)
String getCategoryName();
}
2-2. Repositoryでの利用
Repositoryインターフェースの検索メソッドの戻り値の型として、定義したプロジェクション用インターフェースを指定します。
Java
// ItemRepository.java
import java.util.List;
public interface ItemRepository extends JpaRepository<Item, Long> {
// List<ItemInfo> を戻り値に指定
// Spring Data JPAは、Item Entityからnameとpriceだけを抽出して返す
List<ItemInfo> findByName(String name);
}
Service層などでこの findByName メソッドを呼び出すと、返されるリストには**Item Entity全体ではなく**、ItemInfo インターフェースを満たすオブジェクトだけが含まれます。
3. クラスベースプロジェクション(DTO)
結果をImmutable(不変)な**DTO(Data Transfer Object)**として扱いたい場合や、フィールドに複雑なロジックを加えたい場合に利用します。
この方法では、取得したいフィールドを持つ専用のクラス(DTO)を作成し、そのDTOのコンストラクタを通じて結果をマッピングします。
3-1. プロジェクション用DTOの定義
DTOは、すべてのフィールドを引数に取るコンストラクタを持つ必要があります。
Java
// ItemSummary.java: クラスベースプロジェクション用のDTO
import lombok.Value;
// @Value はLombokのアノテーションで、finalフィールドと、全ての引数を持つコンストラクタを生成
@Value
public class ItemSummary {
private Long id;
private String name;
// このコンストラクタで結果を受け取る
public ItemSummary(Long id, String name) {
this.id = id;
this.name = name;
}
}
3-2. Repositoryでの利用(JPQL必須)
クラスベースプロジェクションを使う場合、Spring Data JPAの自動クエリ生成機能は使えず、@Query アノテーションとJPQLを使って明示的にマッピングを指示する必要があります。
Java
// ItemRepository.java
import org.springframework.data.jpa.repository.Query;
public interface ItemRepository extends JpaRepository<Item, Long> {
// JPQLで結果をDTOにマッピングすることを明示的に指定
// 'new' キーワードの後に、DTOの完全修飾名と、コンストラクタの引数となるフィールドを指定する
@Query("SELECT new com.example.demo.dto.ItemSummary(i.id, i.name) FROM Item i WHERE i.price < :maxPrice")
List<ItemSummary> findSummaryByPriceLessThan(int maxPrice);
}
この方法の利点は、結果が不変なDTOとして返されるため、Service層やController層で安全にデータを利用できることです。
4. まとめ:プロジェクションの重要性
プロジェクションは、アプリケーションの健全性を高めるための重要なテクニックです。
- セキュリティ: 不要な機密データ(パスワードなど)が、意図せずService層やController層に流出するのを防ぎます。
- パフォーマンス: DBからのデータ転送量、JPAによるマッピング処理、メモリ使用量を最小限に抑え、特に大規模な検索において効果を発揮します。
- 責務の分離: Controller層はプレゼンテーションに必要なデータ(DTO/プロジェクション)だけを扱い、Entityの変更に影響されにくくなります。
✅ 本日のまとめ
- プロジェクションは、Entityの全フィールドではなく、必要なフィールドだけを取得する手法であり、パフォーマンス向上とセキュリティ強化に役立つ。
- インターフェースベースプロジェクションは、取得したいフィールドのGetterを持つインターフェースを定義するだけで実現でき、最もシンプルである(推奨)。
- クラスベースプロジェクションは、結果を不変なDTOとして扱いたい場合に利用し、JPQLの new 構文とコンストラクタを通じてマッピングする。
🔔 次回予告
これで、JPAを使ったデータアクセス(CRUD、カスタム検索、リレーション、プロジェクション)の主要なテクニックは網羅しました。
しかし、これらのDB操作がシステム内で正しく実行されるためには、トランザクションという仕組みが必要です。次回は、業務処理の信頼性を保証するための重要な概念である「トランザクション管理」について学びます。
次回:【第26回】業務の信頼性を保証!〜トランザクション管理の基本〜 にご期待ください!


コメント