前回、Spring Data JPAでは、メソッド名の命名規則に従うだけで、ほとんどの定型的な検索(CRUD)が自動で実現できることを学びました。これは開発の生産性を劇的に向上させます。
しかし、以下のようなケースでは、メソッド名によるクエリ生成だけでは対応できません。
- 複雑すぎる検索条件:
findBy...And...Or...のようにメソッド名が長くなり、可読性が低下する。 - DB固有の高度なSQL: 複雑な集計関数や特殊な結合(JOIN)など、標準的な命名規則では表現できないSQLを使いたい。
今回は、これらの複雑な検索に対応するため、自分でSQL文を記述する方法である @Query アノテーションの利用方法を学びます。
1. @Query アノテーションとは?
@Query アノテーションは、Repositoryインターフェースのメソッドの上に付けることで、そのメソッドが実行する検索クエリ(SQL文)を直接指定する機能です。
@Query で記述するクエリには、主に以下の2種類があります。
- JPQL (Java Persistence Query Language): Javaのエンティティ名やフィールド名をベースに記述するクエリ言語。DBの種類に依存しないため、こちらが推奨されます。
- Native SQL: MySQLやPostgreSQLなど、特定のDBに合わせた生のSQL(Native SQL)を記述する方法。
2. JPQLによるカスタム検索(推奨)
JPQLは、SQLと非常によく似ていますが、「テーブル名」の代わりにエンティティ名を、「カラム名」の代わりにエンティティのフィールド名を使います。
2-1. JPQLの基本
例えば、「価格が特定の額以下の商品を検索する」というJPQLは以下のようになります。
Java
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface ItemRepository extends JpaRepository<Item, Long> {
// JPQLの記述:
// SELECT i FROM Item i => Itemエンティティ(テーブル)からiという別名で選択
// WHERE i.price <= :maxPrice => i の price フィールドが引数のmaxPrice以下
@Query("SELECT i FROM Item i WHERE i.price <= :maxPrice")
List<Item> findByPriceLessThanEqual(@Param("maxPrice") int maxPrice);
// @Param アノテーションで、引数名とJPQL内のパラメーター(:maxPrice)を紐づける
}
2-2. 複雑な結合(JOIN)
JPQLを使えば、メソッド名では表現が困難だったエンティティ間の結合(JOIN)も簡単に記述できます。
Java
// ItemエンティティとCategoryエンティティが関連付けられているとする
@Query("SELECT i FROM Item i JOIN i.category c WHERE c.name = :categoryName")
List<Item> findItemsByCategoryName(@Param("categoryName") String categoryName);
3. Native SQLによるカスタム検索
JPQLでは対応できない、DB固有の関数(例:特定の全文検索関数)を使いたい場合は、Native SQL(生SQL)を記述します。
この場合、@Query アノテーションに nativeQuery = true を設定します。
Java
public interface ItemRepository extends JpaRepository<Item, Long> {
// Native SQLの記述
@Query(value = "SELECT * FROM items WHERE item_name LIKE %:keyword%",
nativeQuery = true)
List<Item> searchItemsByNativeSql(@Param("keyword") String keyword);
}
注意: Native SQLを使うと、DBの種類を変更した場合(MySQLからPostgreSQLなど)にSQL文の書き直しが必要になり、DB依存性が高まります。特別な理由がない限り、JPQLの使用が推奨されます。
4. 更新・削除クエリの実行
@Query はデータの検索(SELECT)だけでなく、データの更新(UPDATE)や削除(DELETE)にも使えます。ただし、データ変更を行うクエリを実行する場合、以下の2つのアノテーションを追加する必要があります。
@Modifying: このクエリがデータの変更(INSERT, UPDATE, DELETE)を行うことをSpringに伝える。@Transactional: データ変更の整合性を保つため、この操作をトランザクション内で実行するように指示する(第26回で詳述)。
Java
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.annotation.Transactional;
public interface ItemRepository extends JpaRepository<Item, Long> {
// 在庫を一括で増やす処理(UPDATEクエリ)
@Modifying
@Transactional // 実行をトランザクション内で行う
@Query("UPDATE Item i SET i.stock = i.stock + :amount WHERE i.id = :itemId")
int updateStockAmount(@Param("itemId") Long itemId, @Param("amount") int amount);
// 戻り値の int は、更新されたレコードの件数を表します。
}
5. まとめ:@Queryによる柔軟性の確保
Spring Data JPAは、メソッド名による自動クエリ生成を基本としながらも、@Query アノテーションを提供することで、複雑な業務要件やDB固有の機能に対応できる柔軟性を確保しています。
開発戦略としては、「まずはメソッド名で試み、無理ならJPQLを使い、どうしても無理な場合のみNative SQLを使う」という順序がベストプラクティスです。
✅ 本日のまとめ
@Queryアノテーションは、Repositoryで複雑な検索やデータ変更クエリを直接記述するために使用する。- JPQL (Java Persistence Query Language) は、エンティティ名をベースに記述するため、DB依存性が低く推奨される。
- Native SQL は、DB固有の機能を使いたい場合に
nativeQuery = trueを付けて利用するが、DB依存性が高まる。 - 更新・削除クエリには、
@Modifyingと@Transactionalアノテーションが必須である。
🔔 次回予告
ここまでで、JPAを使った単一のテーブル操作はマスターしました。しかし、業務システムでは、複数のテーブルが関連付けられています(例: ユーザーと注文、商品とカテゴリ)。
次回は、JPAにおいて、これらのテーブル間の関連(リレーションシップ)をJavaのオブジェクトとして表現する方法、すなわち @OneToOne や @OneToMany などのマッピング方法を学びます。
次回:【第23回】JPAのリレーションシップ(関連付け) にご期待ください!


コメント