Springの@Queryアノテーションを徹底解説!初心者でもわかるSpring Bootのカスタムクエリ
生徒
「Springフレームワークで@Queryアノテーションを見かけたんですけど、どういう使い方をするんですか?」
先生
「@Queryアノテーションは、リポジトリにカスタムなSQLクエリを直接書いてデータベース操作を行うためのものだよ。」
生徒
「標準のCRUDメソッドではできないような複雑なクエリを使いたいときに便利なんですね?」
先生
「その通りだね。さっそく@Queryアノテーションの基本的な使い方を見ていこう!」
1. @Queryアノテーションとは?
Spring Data JPAの@Queryは、リポジトリインターフェースのメソッドに
カスタムクエリを直接書ける仕組みです。メソッド名から自動生成される
findBy...だけでは表現しづらい条件や結合を、明示的なクエリとして定義できます。
クエリ言語は基本的にエンティティ名とプロパティ名を使うJPQLで、
実テーブルやカラム名を使うネイティブSQLも指定可能です(後の章で詳しく扱います)。
使いどころのイメージを掴むために、初心者向けの短い例を見てみましょう。
たとえば「有効な顧客だけを取得したい」という要件があるとします。
メソッド名だけで書くと長くなりがちですが、@Queryならクエリを一目で把握できます。
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
// JPQL:エンティティ名(Customer)とプロパティ名(active)を使う
@Query("SELECT c FROM Customer c WHERE c.active = true")
List<Customer> findActiveCustomers();
}
上記はとてもシンプルですが、Spring Boot + Spring Data JPAで
@Queryを使う感覚をつかむのに十分です。JPQLなので、データベースに依存せず、
エンティティ(Customer)とそのプロパティ(active)で条件を記述します。
「どこに何を書くのか?」が明確になり、読みやすく保守しやすいリポジトリを作れます。
まずは「必要な条件をJPQLで素直に書く」ことから始めるとスムーズです。
2. @Queryの基本的な使い方
それでは実際に@Queryを使ってみましょう。以下の例では、特定の価格以上の商品を検索するクエリを作成します。
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
// 特定の価格以上の商品を検索するカスタムクエリ
@Query("SELECT p FROM Product p WHERE p.price > :price")
List<Product> findProductsByMinPrice(@Param("price") double price);
}
この例では、@Queryアノテーションを使って、priceというパラメータを渡し、指定された価格以上の商品を取得しています。
3. ネイティブSQLクエリを使う方法
@Queryアノテーションでは、JPQL(Java Persistence Query Language)だけでなく、ネイティブSQLも使用できます。nativeQuery=trueを指定することで、
実際のデータベースのSQL文を直接記述することが可能です。
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
// ネイティブSQLクエリを使用して顧客を検索
@Query(value = "SELECT * FROM customers WHERE email = :email", nativeQuery = true)
Customer findCustomerByEmail(@Param("email") String email);
}
上記の例では、データベースの顧客テーブルから特定のメールアドレスの顧客情報を取得するためにネイティブクエリを使用しています。
4. @Queryと@Modifyingの組み合わせ
データの更新や削除を行う場合には、@Modifyingアノテーションを@Queryと組み合わせて使用します。
例えば、特定の商品の在庫数を一括で更新するクエリを以下のように記述します。
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.annotation.Transactional;
@Repository
public interface InventoryRepository extends JpaRepository<Inventory, Long> {
// 商品の在庫数を一括更新する
@Modifying
@Transactional
@Query("UPDATE Inventory i SET i.stock = i.stock - :quantity WHERE i.id = :id")
int updateStock(@Param("id") Long productId, @Param("quantity") int quantity);
}
このように、@Modifyingと@Transactionalを併用することで、更新系のクエリもシンプルに書けます。
5. @Queryアノテーションの注意点
@Queryを使用する際には、いくつかの注意点があります。まず、パラメータ名を間違えるとクエリ実行時にエラーが発生します。
また、JPQLではクラス名やフィールド名を使用するため、テーブルやカラム名ではない点に注意しましょう。
さらに、nativeQuery=trueを使用する場合、データベース固有のSQL構文に依存するため、データベースの移行時に問題が発生することがあります。
そのため、できるだけJPQLを使うことが推奨されます。
生徒
「@Queryアノテーションについて理解できました!これを使えば、より柔軟にデータベースと連携できますね。」
先生
「その通り!標準のメソッドでは足りない部分を@Queryで補えるから、うまく活用していこう。」
6. パラメータバインディングの基本(LIKE・IN・BETWEEN)
@Queryアノテーションでは、@Paramで受け取った値をJPQLに安全に埋め込みます。初心者がつまずきやすいのがワイルドカードの扱いです。
部分一致はCONCATで確実に書き、複数条件はIN、期間検索はBETWEENを使います(Spring Boot + Spring Data JPAの定番パターン)。
// 名前にキーワードを含む商品を検索(部分一致:LIKE)
@Query("SELECT p FROM Product p WHERE LOWER(p.name) LIKE LOWER(CONCAT('%', :keyword, '%'))")
List<Product> searchByNameLike(@Param("keyword") String keyword);
// IDの集合に含まれる商品をまとめて取得(IN 句)
@Query("SELECT p FROM Product p WHERE p.id IN :ids")
List<Product> findByIds(@Param("ids") List<Long> ids);
// 期間で注文を検索(BETWEEN)
@Query("SELECT o FROM Order o WHERE o.createdAt BETWEEN :from AND :to")
List<Order> findOrdersBetween(@Param("from") LocalDateTime from,
@Param("to") LocalDateTime to);
SEOポイント:Spring Data JPAの@Query パラメータバインディングは、LIKE・IN・BETWEENの書き方を覚えると カスタムクエリの表現力が一気に上がります。
7. ページング・ソートを@Queryで実装する(Pageable / Sort / countQuery)
大量データはPageableでページング、Sortで並び替え。Page<T>を返すメソッドでは、複雑なJPQLやネイティブSQLの場合
countQueryの指定が重要です(総件数の高速取得でパフォーマンス最適化)。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
// JPQL + Pageable(カテゴリで絞り、ページング)
@Query(
value = "SELECT p FROM Product p WHERE p.category = :category",
countQuery = "SELECT COUNT(p) FROM Product p WHERE p.category = :category"
)
Page<Product> findByCategory(@Param("category") String category, Pageable pageable);
// Sortだけを引数で受けて並び替え
@Query("SELECT c FROM Customer c WHERE c.status = :status")
List<Customer> findByStatus(@Param("status") String status, Sort sort);
SEOポイント:Spring Bootの@Query ページングとソートは、Pageable+countQueryのセットが基本。 カスタムクエリでもスケーラブルなデータ取得が可能です。
8. DTO/プロジェクションで必要な項目だけ返す(高速化の定石)
一覧画面などで全カラムは不要なことが多いです。select new(コンストラクタ式)やインターフェース・プロジェクションを使うと、
軽量なDTOだけを返せて描画が高速化します(Spring Data JPA プロジェクションの鉄板)。
// DTOコンストラクタ式(JPQL)
@Query("SELECT new com.example.dto.ProductSummary(p.id, p.name, p.price) " +
"FROM Product p WHERE p.price > :minPrice")
List<ProductSummary> findSummary(@Param("minPrice") BigDecimal minPrice);
// インターフェース・プロジェクション(ネイティブでも可)
public interface ProductView {
Long getId();
String getName();
BigDecimal getPrice();
}
@Query(value = "SELECT id, name, price FROM products WHERE price > :minPrice", nativeQuery = true)
List<ProductView> findViewNative(@Param("minPrice") BigDecimal minPrice);
SEOポイント:@Query DTO・プロジェクションでカスタムクエリ最適化。 返却データを必要最小限にしてネットワークとメモリを節約できます。
9. N+1問題を避けるフェッチ戦略(FETCH JOIN / DISTINCT / EntityGraph)
コレクションの遅延読み込みで発生するN+1問題は表示性能を大きく落とします。@QueryでJOIN FETCHを使い、
必要な関連を一括ロードしましょう。重複行はDISTINCTで抑止するのが定石です。
// 注文とその明細を一括で取得(N+1回避)
// DISTINCTを付けて重複エンティティの復元を安定化
@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items WHERE o.customer.id = :customerId")
List<Order> findOrdersWithItems(@Param("customerId") Long customerId);
// 参考:@EntityGraph 併用パターン(@Queryと使い分け)
@EntityGraph(attributePaths = {"items", "customer"})
@Query("SELECT o FROM Order o WHERE o.id = :id")
Optional<Order> findByIdWithRelations(@Param("id") Long id);
SEOポイント:Spring Data JPA @QueryでのFETCH JOINはN+1問題対策の基本。 DISTINCTと@EntityGraphを使い分けてカスタムクエリのパフォーマンスを最大化します。
まとめ
今回は、Springフレームワークの@Queryアノテーションについて詳しく解説しました。初心者でも理解できるように、基本的な使い方から、ネイティブSQLの使用方法、データの更新や削除のための@Modifyingアノテーションとの組み合わせまでカバーしましたね。
まず、@Queryアノテーションは、Spring Data JPAを使ってリポジトリ層にカスタムクエリを簡単に書ける非常に便利な機能です。findByメソッドのような標準メソッドでは実現できない複雑な検索条件や特定の条件に基づいたデータ取得が必要な場合に大いに役立ちます。
特にネイティブSQLを使うことで、データベースのパフォーマンスを最大限に引き出すことができるため、データベースごとに異なる最適化を行いたいときには効果的です。ただし、データベース移行時の互換性には注意が必要なので、基本的にはJPQLを使い、どうしても必要な場合にネイティブクエリを検討する方が良いでしょう。
また、データの更新には@Modifyingと@Transactionalを併用することで、安全にトランザクションを管理しながら効率的なデータ更新が可能です。特に大規模なシステムや高頻度なデータ変更が必要な場面ではこの機能を上手く活用することが重要です。
さらに、パラメータ名の指定ミスやJPQLとSQLの違いなど、いくつかの注意点についても解説しました。これらのポイントを押さえることで、より安全で効果的なクエリを作成することができます。
最後に、@Queryアノテーションを活用することで、開発の柔軟性が増し、コードの可読性も向上します。Spring Bootアプリケーションを開発する際には、是非@Queryを活用してみてください。
生徒
「今回学んだ@Queryアノテーションについて、色々な使い方があるんですね。標準のメソッドだけでなく、より柔軟にクエリが書けるのはとても便利だと思いました。」
先生
「そうだね。実際のプロジェクトでは、@Queryを使うことで複雑なデータ取得が可能になるよ。例えば、検索機能の実装やデータ分析に活用できるんだ。」
生徒
「それに、ネイティブSQLも使えるのは驚きました。データベースに依存した最適化ができるので、パフォーマンスを意識したいときに便利ですね。」
先生
「その通り。けれど、基本的にはJPQLを使ってデータベースに依存しない書き方を心掛けよう。ただ、どうしても必要なときにはネイティブSQLをうまく使い分けると良いよ。」
生徒
「次はデータ更新の@Modifyingと@Transactionalを使った高度な操作も学んでみたいです!」
先生
「いい意欲だね!それじゃあ、次回は@Modifyingを使ったバッチ処理や複数レコードの更新について学んでいこう!」