@Queryを使ったカスタムクエリの作成を完全解説!Spring Data JPAでの使い方と基礎知識
新人
「Spring Data JPAって便利ですよね。でも、どうして自分でSQLを書かないといけないときがあるんですか?」
先輩
「いいところに気づいたね。Spring Data JPAは自動でSQLを作ってくれるけど、複雑な検索になると自分でSQLを書く必要が出てくるんだ。そのときに使うのが@Queryだよ。」
新人
「なるほど。じゃあその@Queryの使い方を教えてください!」
先輩
「よし、ではまず@Queryとは何かから説明していこう!」
1. @Queryとは何か?基本的な意味と役割
@Queryは、Spring Data JPAでカスタムクエリを記述するためのアノテーションです。Spring Data JPAでは、リポジトリインターフェースにメソッドを定義するだけでSQL(正確にはJPQL)が自動生成されますが、複雑な検索条件が必要な場合や、テーブルを結合したい場合などは自分でクエリを書く必要があります。そのときに@Queryを使うことで、SQLのような書き方で自由にクエリを定義できます。
たとえば、「名前に特定のキーワードが含まれているデータを検索したい」というような柔軟な条件検索も、@Queryを使えば実現できます。
2. Spring Data JPAで@Queryが必要になる理由
Spring Data JPAは、メソッド名からSQLを推測してくれる便利な仕組みがあります。たとえばfindByNameやfindByEmailといったメソッドを定義するだけで、Springが自動的にSELECT文を作ってくれます。
しかし、次のようなケースでは@Queryが必要になります。
- 複数のテーブルを結合して検索したいとき
- LIKE句やGROUP BYなどのSQL構文を使いたいとき
- サブクエリやネイティブSQLを使いたいとき
このように、Spring Data JPAの自動生成では対応できないような複雑な条件や結合処理には、明示的にクエリを書く必要があります。これを実現するのが@Queryアノテーションというわけです。
3. 基本的な@Queryの使い方(SELECT文)
では、実際に@Queryを使ってカスタムクエリを定義する方法を見てみましょう。ここでは、ユーザー名で検索する例を紹介します。まずはエンティティクラスとリポジトリのコードです。
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// ゲッター・セッターは省略
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findByNameCustom(@Param("name") String name);
}
上記のように、@Queryを使ってJPQL(Java Persistence Query Language)でSELECT文を記述しています。JPQLでは、SQLのようにテーブル名ではなくエンティティ名を使い、カラム名ではなくプロパティ名を使います。
:nameの部分がパラメータで、メソッドの引数に@Paramをつけて渡す仕組みになっています。
コントローラクラスでこのカスタムクエリを呼び出す場合の例も見てみましょう。
@Controller
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/users")
public String getUsers(Model model) {
List<User> users = userRepository.findByNameCustom("田中");
model.addAttribute("users", users);
return "userList";
}
}
@Controllerクラスでは、リポジトリを@Autowiredで注入し、カスタムクエリメソッドfindByNameCustomを使って検索しています。そして取得したデータをModelにセットして、HTMLページに渡しています。
4. パラメータの指定方法(?1 と :name の違いと使い方)
@Queryでは、クエリ内にパラメータを指定する方法として、「位置指定」と「名前指定」の2種類があります。それぞれに特徴があるため、使い分けが大切です。
まず、「位置指定」は?1のように数字で表します。これはメソッド引数の順番を示していて、1番目の引数を?1、2番目を?2と記述します。下記は位置指定の例です。
@Query("SELECT u FROM User u WHERE u.name = ?1")
List<User> findByNamePosition(String name);
次に「名前指定」は、:nameのようにパラメータ名を使います。これに対応して、引数に@Param("name")を付ける必要があります。こちらの方が読みやすく、引数の意味が明確になるので、初心者にもおすすめです。
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findByNameNamed(@Param("name") String name);
基本的には、名前付きパラメータのほうが直感的で管理しやすいため、Spring公式でも名前指定の使用が推奨されています。
5. 複数条件での@Query記述例(AND / OR)
@Queryでは、SQLと同様にANDやORを使って複数条件の検索を行うことができます。たとえば、「名前が特定の文字列で、かつメールアドレスが一致するユーザーを探す」といった条件です。
以下は、名前とメールアドレスの両方に一致するユーザーを検索する例です(AND条件)。
@Query("SELECT u FROM User u WHERE u.name = :name AND u.email = :email")
List<User> findByNameAndEmail(@Param("name") String name, @Param("email") String email);
一方、名前かメールアドレスのどちらかに一致するユーザーを取得したい場合は、OR条件を使います。
@Query("SELECT u FROM User u WHERE u.name = :name OR u.email = :email")
List<User> findByNameOrEmail(@Param("name") String name, @Param("email") String email);
このように@Queryでは、SQLライクな表現で複雑な条件検索を実現できるため、Spring Data JPAで柔軟なデータ取得が可能になります。特に、複数のカラムを条件にしたい場合や、OR検索をしたいときに非常に有効です。
6. @Controllerで@Queryを使った検索結果の表示方法
実際に@Queryでデータ取得した結果を、Springの@Controllerを使って画面に表示するにはどうすればよいでしょうか。ここでは、検索フォームで指定されたユーザー名とメールアドレスを受け取り、その条件に一致するユーザーを検索して表示する流れを紹介します。
まず、HTMLから送信されるリクエストを処理する@GetMappingメソッドを定義します。今回は、nameとemailをURLパラメータで受け取る想定です。
@Controller
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/search")
public String searchUsers(@RequestParam("name") String name,
@RequestParam("email") String email,
Model model) {
List<User> users = userRepository.findByNameAndEmail(name, email);
model.addAttribute("users", users);
return "userList";
}
}
@RequestParamでフォームから渡されたnameとemailを受け取り、それらを@Queryで定義したリポジトリメソッドに渡して検索を行います。そしてModelにユーザー一覧をセットし、ビューであるuserList.htmlに渡します。
このように、@Queryを使った検索処理は、@Controllerとの組み合わせによって画面にデータを表示するところまで簡単に実装できます。Spring ControllerとJPQLによる検索処理の連携は、非常に強力で汎用性が高いため、初心者のうちにしっかり理解しておくことが大切です。
7. よくあるエラーとその対処法(構文ミス、型不一致など)
@Queryを使ったカスタムクエリでは、JPQLの記述ミスやデータ型の不一致などによって実行時にエラーが発生することがあります。ここでは、初心者が特につまずきやすい代表的なエラーとその対処法を紹介します。
① JPQL構文エラー
JPQLでは、エンティティ名やプロパティ名を使う必要があります。SQLと同じようにテーブル名やカラム名を書いてしまうと、org.hibernate.hql.internal.ast.QuerySyntaxExceptionのようなエラーが発生します。
// 間違った書き方(SQLっぽい)
@Query("SELECT * FROM users WHERE name = :name")
// 正しい書き方(JPQL)
@Query("SELECT u FROM User u WHERE u.name = :name")
JPQLでは「テーブル名ではなくエンティティ名」、「カラム名ではなくプロパティ名」を使うのが基本です。
② 型の不一致エラー
メソッドの戻り値とクエリの結果の型が一致していない場合も、実行時にエラーになります。たとえば、List<User>を返すメソッドで、クエリがCOUNTを返しているとClassCastExceptionが発生します。
// クエリの結果はlong型
@Query("SELECT COUNT(u) FROM User u")
long countAllUsers();
このように、クエリの結果の型とメソッドの戻り値が一致していることを必ず確認しましょう。
③ パラメータの指定ミス
名前付きパラメータを使う場合、@Paramの指定がない、または名前が一致していないとエラーになります。
// エラーになる例(@Paramが足りない)
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findByName(String name); // @Param("name") が必要
このようなミスを防ぐためには、@Paramの記述を忘れないようにし、名前も一致しているか常に確認することが重要です。
8. カスタムクエリとネイティブSQLの違いと使い分け
@Queryでは、JPQL(Java Persistence Query Language)を使ってクエリを記述するのが基本ですが、nativeQuery = trueを使えばネイティブSQLも利用できます。ここでは、JPQLとネイティブSQLの違いと使い分けについて解説します。
① JPQLの特徴
JPQLはエンティティをベースにクエリを記述する言語です。データベースに依存せず、移植性が高いというメリットがあります。また、HibernateなどのJPA実装によってクエリが最適化される可能性があります。
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword%")
List<User> searchByName(@Param("keyword") String keyword);
② ネイティブSQLの特徴
ネイティブSQLは、実際のデータベースのテーブル名やSQL構文をそのまま使います。そのため、データベース固有の機能やパフォーマンスチューニングが必要な場合に適しています。
@Query(value = "SELECT * FROM users WHERE name LIKE %:keyword%", nativeQuery = true)
List<User> nativeSearch(@Param("keyword") String keyword);
ただし、ネイティブSQLはJPAの抽象化レイヤーをバイパスするため、エンティティのマッピングが反映されなかったり、データベースの種類によって動かなくなる可能性があります。
③ 使い分けのポイント
- 簡単な検索やテーブル結合 → JPQL(移植性と保守性が高い)
- 集計関数、複雑なJOIN、DB固有の構文 → ネイティブSQL(柔軟性と性能重視)
@QueryのnativeQuery = trueは便利ですが、使いすぎると移植性やメンテナンス性が下がるため、できる限りJPQLで対応できないかをまず検討しましょう。
9. @Queryを使いこなすための次の学習ステップ
@Queryの基本をマスターしたら、次はより実践的な内容へとステップアップしていきましょう。Spring Data JPAでは、@Query以外にも便利なアノテーションや機能が多数用意されています。以下に、今後学ぶべき内容を紹介します。
① @Modifying と UPDATE / DELETE クエリ
@QueryはSELECTだけでなく、更新や削除にも使えます。ただしその場合は@Modifyingを併用する必要があります。これにより、UPDATEやDELETEのカスタムクエリを安全に実行できます。
② ページネーション(Pageable)との連携
大量のデータを取得する場面では、すべてを一度に取得せず、ページごとに分割して取得する機能(ページネーション)が重要です。Spring Data JPAではPageableと組み合わせることで、簡単にページング処理が実現できます。
③ ネストされたプロパティの検索
リレーションを持つエンティティ同士の検索も、@Queryで実現可能です。例えば、ユーザーが所属する部署名で検索する場合など、関連エンティティを活用する方法も学んでいくと良いでしょう。
④ 動的クエリの構築(Specifications)
条件によってクエリを動的に変えたい場合には、Spring Data JPAのSpecifications機能が有効です。@Queryでは対応しづらい柔軟な検索処理を、プログラム的に組み立てることができます。
これらを学んでいくことで、Springの@Controllerと@Queryを組み合わせたWebアプリケーション開発において、より柔軟で強力なデータ取得ロジックを実装できるようになります。
まずは今回の内容を実際にPleiades環境で試しながら、少しずつ応用へ進んでいきましょう。
まとめ
@Query を中心に、Spring Data JPA のカスタムクエリをどのように扱うかを振り返ってみると、単純な findBy~ 形式で自動生成されるクエリでは対応しきれない複雑な検索処理を、より柔軟に表現できるという点が大きな特徴として浮かび上がります。とくに、複数の条件を組み合わせた検索や、テーブル結合が必要になる場面では、アプリケーション全体の処理に対して自然で読みやすい構造を保ちつつ、確実にデータベースへアクセスできる仕組みが求められます。そうした中で、@Query が提供する JPQL の表現力は非常に頼もしく、Spring MVC 全体のアーキテクチャとの相性もよいため、Web アプリケーションを構築するうえで避けて通れない重要な知識と言えます。
記事の中で最も印象的なのは、「エンティティを基準にしてクエリを書く」という JPQL の特徴です。SQL に慣れた人ほど、ついテーブル名を書きたくなるものですが、JPQL ではエンティティ名、つまり Java クラスそのものが検索の対象になります。この考え方は、Spring Data JPA を使用する上で必須であり、ドメインモデルとデータベースの構造が密接に結びついていることを自然に実感させてくれます。
また、@Query のパラメータ指定では「位置指定(?1)」と「名前指定(:name)」の両方を扱いましたが、実際に開発していくと名前付きパラメータのほうが圧倒的に扱いやすく、保守性も高くなります。コードを後から見返したときにも意図が読み取りやすいため、プロジェクトが大きくなるほどメリットが大きく感じられます。そして、複数条件での AND / OR の使い分け、Controller との連携、Model に値を渡して画面表示へつなげるところまでひと通り確認したことで、Spring MVC のフロー全体に溶け込む形で @Query を扱えることが理解できたはずです。
さらに、よくあるエラーにも触れましたが、特に JPQL の構文エラーは初心者が一度は経験するポイントです。「エンティティ名ではなくテーブル名を書いてしまう」「プロパティ名ではなくカラム名を誤って書く」「@Param の指定を忘れる」など、ちょっとした違いで実行時に問題が発生します。こうしたポイントを事前に知っておくことは、今後の開発で大きな時間短縮につながります。慣れれば自然とミスが減っていくので、まずはゆっくりと正しい JPQL の書き方を身につけていくことが大切です。
一方で、ネイティブ SQL を使う場面についても触れました。nativeQuery = true を必要以上に多用するのは推奨されませんが、データベース固有の構文を使いたい場合、複雑すぎて JPQL では表現しづらい場合には非常に強力な選択肢となります。JPQL の抽象度と移植性をうまく活かしつつ、必要に応じてネイティブ SQL を組み合わせることで、堅牢で柔軟なデータアクセス層を構築できるでしょう。
■ @Query のまとめコード例
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% OR u.email LIKE %:keyword%")
List<User> searchByKeyword(@Param("keyword") String keyword);
@Query(value = "SELECT * FROM users WHERE email LIKE %:mail%", nativeQuery = true)
List<User> nativeMailSearch(@Param("mail") String mail);
}
上のコード例では、JPQL で名前 OR メールアドレスの部分一致を検索するメソッドと、ネイティブ SQL を使ったメール検索の例を挙げています。どちらも実際の開発でよく登場するパターンで、JPQL とネイティブ SQL をどのように使い分けるかのよい参考になるはずです。
今回学んだ内容をふり返ると、クエリの書き方ひとつで取得できるデータの幅が大きく変わり、アプリケーションの機能性にも直結していくということがはっきりと感じられます。Spring MVC と組み合わせたときの流れも理解できたので、次は UPDATE や DELETE を扱う @Modifying、さらにはページング、 Specifications を使った動的検索など、より高度な機能へ進んでみると学びがさらに深まります。
生徒:「@Query って思ったより奥が深いんですね。SQL と JPQL の違いもやっと腑に落ちました。」
先生:「そうだね。最初は混乱しやすいけれど、慣れてくるとエンティティで考えるほうが自然になってくるよ。Spring が提供している仕組みを理解するうえでも大事な部分だね。」
生徒:「AND や OR の条件を書けるのも便利ですね。業務アプリだと複雑な検索って絶対必要になりますし。」
先生:「まさにその通り。複雑な条件を安全に書けるのが @Query の強みなんだ。Controller と連携すれば、画面にデータを渡すところまで自然に流れるよ。」
生徒:「ネイティブ SQL も使えるのは意外でした。でも使いすぎると移植性が下がるっていう点も気をつけたいですね。」
先生:「そこを理解して使い分けられると、開発者として一段上に進めるよ。今回の内容をもとに、次は @Modifying やページングにも挑戦してみよう。」