SpringのDTOとプロジェクション最適化を完全ガイド!インターフェイスとクラスベースの違いとは?
生徒
「Springでデータを取得するときに、エンティティじゃなくてDTOを返す方法ってあるんですか?」
先生
「はい、Spring Data JPAではDTOを使ったプロジェクションが可能で、パフォーマンス最適化にもつながります。」
生徒
「クラスで受け取る方法と、インターフェイスで受け取る方法があるって聞いたんですが、違いがよく分かりません…」
先生
「それじゃあ、DTOとプロジェクションの基本から、クラスベース・インターフェイスベースの違い、最適な使い分けまで一緒に学んでいきましょう。」
1. DTOとプロジェクションとは?Springにおける意味
DTO(Data Transfer Object)は、クライアントに返却するデータを最適化・整形するためのオブジェクトです。Springではエンティティをそのまま返すよりも、DTOを使って必要なデータだけを選んで返す方が安全でパフォーマンスも良くなります。
プロジェクションとは、エンティティの中から必要な項目だけを取り出して別の形式で返す仕組みです。Spring Data JPAではこのプロジェクションを活用してDTOにデータをマッピングすることができます。
2. クラスベースのDTOでデータを返す方法
まずは、コンストラクタを持つDTOクラスを定義し、JPQLでそのクラスにデータを詰める方法です。
public class UserDto {
private String name;
private String email;
public UserDto(String name, String email) {
this.name = name;
this.email = email;
}
// getter省略
}
このDTOに対して、以下のようなリポジトリメソッドを用意します。
@Query("SELECT new com.example.demo.dto.UserDto(u.name, u.email) FROM User u")
List<UserDto> findAllUserDtos();
この方法の特徴は、型安全で補完も効くこと、バリデーションを追加しやすいことです。
3. インターフェイスベースのプロジェクションとは?
Spring Data JPAでは、DTOクラスを作らずとも、インターフェイスに対してデータをマッピングすることができます。これはインターフェイスベースのプロジェクションと呼ばれます。
public interface UserNameOnly {
String getName();
}
このように定義したインターフェイスに対し、リポジトリでは以下のように記述します。
List<UserNameOnly> findBy();
フィールド名に一致するゲッターがあれば、自動的にプロジェクションしてくれる仕組みです。クエリを明示しなくても使えるのが便利ですが、動的な整形には不向きです。
4. ネストしたプロジェクションも可能
インターフェイスプロジェクションでは、関連エンティティを含めたネスト構造も対応できます。例えば、Userの中に所属する部署名も表示したい場合、以下のようにします。
public interface UserWithDepartment {
String getName();
DepartmentInfo getDepartment();
interface DepartmentInfo {
String getName();
}
}
このようにネストしたインターフェイスを定義することで、JOIN FETCHのような処理を省略して柔軟にデータを取得することが可能です。
5. SpringでDTO/プロジェクションを使うメリット
DTOやプロジェクションを使うことには以下のようなメリットがあります。
- エンティティの情報を隠蔽できる(セキュリティ・構造の独立)
- APIレスポンスの整形がしやすい(REST API設計)
- データ転送量が減り、パフォーマンスが向上
- クライアントにとって分かりやすい構造で返せる
6. インターフェイスとクラスベースの使い分け方
では、どちらのプロジェクションを選べばよいのでしょうか?以下のように使い分けるのが基本です。
- インターフェイスベース:簡単な取得だけで十分、クエリ定義を省略したい場合
- クラスベース:複雑な処理・整形・バリデーションが必要な場合
特にフロントエンド向けAPIでは、JSON構造に応じてクラスベースのDTOで制御する方が管理しやすく、保守性にも優れます。
7. @ValueやSpELによるフィールド整形も可能
DTOに直接クエリ結果をマッピングするだけでなく、文字列を整形したり、フィールドを加工したいときは、Spring Expression Language(SpEL)を使う方法もあります。
@Query("SELECT new com.example.dto.UserDto(CONCAT(u.name, 'さん'), u.email) FROM User u")
List<UserDto> findDecoratedUsers();
このようにCONCATやSUBSTRING関数を使って、取得時に装飾された値を返すこともできます。
8. Projectionの注意点と落とし穴
インターフェイスベースのプロジェクションは、フィールド名に厳密に依存するため、エンティティ側のプロパティ名が変わると動作しなくなるリスクがあります。また、JOINが必要な場合や複雑な整形処理には不向きです。
クラスベースDTOは柔軟ですが、コンストラクタの定義やクエリ記述が必要になるため、開発コストがやや高くなります。シーンに応じた使い分けが成功のカギです。