🎯 Spring Framework 講座【第25回】必要なデータだけを取得!〜プロジェクションによる部分データ取得〜

docs

前回、N+1問題を解決するために、JOIN FETCHを使って関連データを一度に取得する方法を学びました。これはパフォーマンスチューニングの強力なテクニックです。

しかし、データを大量に取得する際に、もう一つ考慮すべき点があります。それは「本当にそのEntityの全フィールドが必要か?」という点です。

例えば、ユーザーのリストを表示する際、パスワードや機密情報を含む全フィールドを取得する必要はありません。「IDと名前とメールアドレス」だけで十分なケースがほとんどです。

今回は、このように必要なフィールドだけを取得し、メモリ使用量やネットワーク転送量を削減する手法、**プロジェクション(Projection)**について学びます。

1. プロジェクションとは?

**プロジェクション(Projection)**とは、DBからEntityの全フィールドではなく、必要なフィールドだけを抽出して取得することを指します。

Spring Data JPAでは、プロジェクションを実現するために、以下の2つの主要な方法を提供しています。

  1. インターフェースベースプロジェクション(Interface-based Projection)
  2. クラスベースプロジェクション(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. まとめ:プロジェクションの重要性

プロジェクションは、アプリケーションの健全性を高めるための重要なテクニックです。

  1. セキュリティ: 不要な機密データ(パスワードなど)が、意図せずService層やController層に流出するのを防ぎます。
  2. パフォーマンス: DBからのデータ転送量、JPAによるマッピング処理、メモリ使用量を最小限に抑え、特に大規模な検索において効果を発揮します。
  3. 責務の分離: Controller層はプレゼンテーションに必要なデータ(DTO/プロジェクション)だけを扱い、Entityの変更に影響されにくくなります。

✅ 本日のまとめ

  • プロジェクションは、Entityの全フィールドではなく、必要なフィールドだけを取得する手法であり、パフォーマンス向上とセキュリティ強化に役立つ。
  • インターフェースベースプロジェクションは、取得したいフィールドのGetterを持つインターフェースを定義するだけで実現でき、最もシンプルである(推奨)。
  • クラスベースプロジェクションは、結果を不変なDTOとして扱いたい場合に利用し、JPQLの new 構文コンストラクタを通じてマッピングする。

🔔 次回予告

これで、JPAを使ったデータアクセス(CRUD、カスタム検索、リレーション、プロジェクション)の主要なテクニックは網羅しました。

しかし、これらのDB操作がシステム内で正しく実行されるためには、トランザクションという仕組みが必要です。次回は、業務処理の信頼性を保証するための重要な概念である「トランザクション管理」について学びます。

次回:【第26回】業務の信頼性を保証!〜トランザクション管理の基本〜 にご期待ください!

コメント

タイトルとURLをコピーしました