Spring @Queryの使い方入門!SQLを直書きする基本テクニック
新人
「Spring JPAでデータを取得する方法って、基本的にはfindByでできますよね?でも、もっと細かい条件で検索したいときって、どうすればいいんでしょうか?」
先輩
「確かにfindByは便利だけど、条件が複雑になると使いにくくなるよ。そんなときは@Queryを使うんだ。」
新人
「@Queryって何ですか?Springのアノテーションなんですか?」
先輩
「その通り。Spring Data JPAに用意されているアノテーションで、SQLやJPQLを直接書けるようになるんだ。」
新人
「なるほど……それなら、findByでは表現できない複雑な検索も可能になりそうですね!」
先輩
「その通り。今回は基本的な使い方から、開発環境の前提も含めて一緒に確認していこう。」
1. @Queryとは何か?Spring Data JPAでの役割
Spring Data JPAでは、リポジトリインターフェースに対してfindByやfindAllなどの命名規則に基づいたメソッドを書くことで、自動的にSQLクエリを生成してくれる仕組みがあります。
この機能はとても便利ですが、場合によっては細かい条件や複雑な結合(JOIN)処理、ネストされた検索など、標準の命名メソッドでは対応しきれないことがあります。
そういったときに登場するのが@Queryです。
@Queryアノテーションは、Spring Data JPAが用意している機能で、リポジトリメソッドに直接JPQL(Java Persistence Query Language)またはネイティブSQL(nativeQuery)を記述することができます。
これにより、自由度の高いデータ取得が可能になり、システムの要求に応じた柔軟なクエリ設計が実現します。
たとえば、以下のような用途に向いています:
- 複数のテーブルを結合(JOIN)してデータを取得したい
- GROUP BYやHAVINGを使った集計処理を行いたい
- 日付や数値の範囲指定、部分一致検索などを組み合わせたい
- テーブルの一部カラムのみを取得したい
つまり、標準機能でカバーできない処理を補完できるのが@Queryの大きな役割です。
2. なぜ@Queryを使うのか?findByの限界とは
Spring JPAのfindByメソッドは、簡単にSQLのWHERE句を組み立ててくれる便利な機能です。たとえば、次のように書けば自動的にSELECT * FROM users WHERE name = ?というクエリを実行してくれます。
User findByName(String name);
しかし、この方法には明確な限界があります。
● 限界1:複雑なAND/OR条件の表現が困難
たとえば「名前が'田中'で、かつ年齢が30歳以上、かつ住所が東京」など、複数条件を含んだ複雑な検索では、メソッド名が長くなりすぎてしまいます。
● 限界2:JOINなどの高度なSQL構文が使えない
別テーブルの情報を参照したい場合、JOIN句が必要になりますが、findByメソッドだけでは対応できません。
● 限界3:GROUP BYやORDER BYなどが柔軟に書けない
並び替えや集計を伴うクエリをメソッド名で表現するのは、現実的ではありません。
こうした場合に@Queryを使えば、以下のように直接SQLやJPQLを書いて、意図した処理を明確に表現できます。
@Query("SELECT u FROM User u WHERE u.name = :name AND u.age >= :age")
List<User> findByNameAndAge(@Param("name") String name, @Param("age") int age);
このように、@Queryを使うことでメソッド名に依存せず、意図通りの処理が簡潔に記述できます。
結果的に、メンテナンス性も高まり、開発の自由度が大きく広がるのです。
3. 簡単なJPQLクエリの実装例(SELECT文)
それでは、実際に@Queryを使ったJPQL(Java Persistence Query Language)の基本的な記述方法を見ていきましょう。JPQLは、エンティティクラスとそのフィールドを使ってクエリを書く方法で、SQLとは異なり、テーブル名やカラム名ではなくJavaのクラスとプロパティ名を記述します。
たとえば、Userというエンティティがあった場合、次のように記述することで、名前が一致するユーザー情報を取得できます。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findByName(@Param("name") String name);
}
このJPQLでは、Userというエンティティの別名をuとし、u.nameという形でプロパティにアクセスしています。SQLとは異なり、テーブル名ではなくエンティティ名を使う点に注意してください。
このような書き方に慣れておくことで、Spring @Query JPQLによる検索の柔軟性を活かすことができます。また、WHERE句の条件は、実際のSQLと同じようにANDやORを組み合わせて記述することも可能です。
4. ネイティブSQL(nativeQuery = true)の使い方と違い
Spring Data JPAでは、JPQLだけでなく、ネイティブSQL(実際のデータベースに対するSQL文)を記述することも可能です。これを使うには、@QueryにnativeQuery = trueという属性を追加します。
ネイティブSQLでは、JPQLとは異なり、エンティティ名やプロパティではなく、実際のテーブル名やカラム名をそのまま使用します。以下はその例です。
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE age >= :age", nativeQuery = true)
List<User> findByMinAge(@Param("age") int age);
}
このように、nativeQuery = trueを指定することで、生のSQLを記述できるため、データベース固有の関数や構文を活用することが可能になります。たとえば、PostgreSQLやMySQLなどのベンダー依存の処理にも対応できます。
ただし、注意点として、ネイティブSQLではマッピング対象のエンティティやフィールドに対応した正確なテーブル構造と一致していないと、実行時エラーになる可能性があります。
また、SQL文そのものが間違っていた場合も、エラーメッセージは実際のデータベースから返されるため、JPQLよりもトラブルシューティングに時間がかかる場合があります。
このように、nativeQuery 違いを理解することで、JPQLとネイティブSQLの使い分けができるようになります。基本的には、汎用的な処理はJPQLで、特殊な構文や高速化が求められる場合にはネイティブSQLを使うとよいでしょう。
5. パラメータを使った動的クエリの書き方(@Paramの使い方)
Spring JPAで@Queryを使う際には、SQLやJPQLに変数をバインドして、動的なクエリを構築することがよくあります。このときに使うのが@Paramアノテーションです。
@Paramを使うことで、クエリ文内のプレースホルダ(:変数名)とメソッド引数を結びつけ、実行時に安全に値をバインドすることができます。これはSQLインジェクションなどのセキュリティリスクを防ぐ上でも非常に重要です。
● JPQLでの@Paramの使用例
@Query("SELECT u FROM User u WHERE u.email = :email")
User findByEmail(@Param("email") String email);
このように、:emailというプレースホルダと、@Param("email")で渡された引数が一致するように記述します。変数名と@Paramの値が一致しないと実行時エラーになります。
● ネイティブSQLでの@Param使用例
@Query(value = "SELECT * FROM users WHERE name LIKE %:keyword%", nativeQuery = true)
List<User> searchByKeyword(@Param("keyword") String keyword);
ネイティブSQLでも同様に@Paramを使って動的に値を挿入できますが、LIKE句などに使う場合は文字列の連結やプレースホルダの位置に注意が必要です。
● 複数パラメータの例
@Query("SELECT u FROM User u WHERE u.name = :name AND u.age >= :age")
List<User> search(@Param("name") String name, @Param("age") int age);
このように複数のパラメータを使う場合でも、それぞれ@Paramで明示的にマッピングする必要があります。順番ではなく名前でマッピングするため、見通しがよく、安全性も高まります。
特に初心者が間違えやすいのが、:paramと@Param("param")の名前が一致していないケースです。IDEの補完機能やコンパイルエラーでは検出されにくいため、細心の注意が必要です。
このように、@Paramを正しく使うことで、Spring @Query JPQLやnativeQueryでの動的クエリ構築が安全かつスムーズに行えるようになります。
6. @Queryを使う際の注意点(エラー・型の不一致・非対応構文)
@Queryは非常に便利ですが、使用時にはいくつか注意しなければならないポイントがあります。初心者がつまずきやすい部分でもあるため、事前に理解しておくことでエラーを未然に防ぐことができます。
● エンティティ名とテーブル名の混同
JPQLでは、SQLと異なり、テーブル名ではなくエンティティクラス名を使います。この違いを知らずにSQL文のように書いてしまうと、文法エラーになります。
● 型の不一致による例外
クエリで取得するデータの型と、戻り値で指定した型が一致していないと、実行時にClassCastExceptionが発生します。特にSELECT u.nameなど、単一カラムを取得する場合は注意が必要です。
● サポートされていないSQL構文の使用
JPQLでは一部のSQL構文(LIMITやOFFSETなど)がサポートされていません。このような構文を使いたい場合は、nativeQuery = trueを指定してネイティブSQLに切り替えましょう。
● LIKE句でのワイルドカード指定
LIKEを使った部分一致検索では、ワイルドカード(%)の位置にも注意が必要です。特に動的文字列との結合では、CONCATや||など、DBに応じた記述が必要です。
これらの注意点を押さえておけば、Spring データベース クエリにおけるトラブルを大幅に回避することができ、より安定したアプリケーション開発につながります。
7. 実践的な使い方(画面から検索値を受け取ってクエリに反映)
ここでは、画面からユーザーが入力した検索キーワードを受け取り、@Queryを使ってデータベース検索を行う一連の流れを紹介します。
検索フォームにはThymeleafを使用し、Thymeleaf SQL 検索の典型的な例として、名前の部分一致検索を実装します。
● HTML側(Thymeleafテンプレート)
<form th:action="@{/users/search}" method="get">
<input type="text" name="keyword" placeholder="名前で検索">
<button type="submit">検索</button>
</form>
<table>
<tr>
<th>ID</th>
<th>名前</th>
<th>メールアドレス</th>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
</tr>
</table>
このテンプレートは、検索キーワードをGETリクエストとして/users/searchに送信し、その結果をテーブルに一覧表示します。
● Repository(@Queryを使った検索メソッド)
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword%")
List<User> findByNameLike(@Param("keyword") String keyword);
}
このようにLIKEを使って部分一致検索を実現しています。@Paramでバインドされたkeywordをクエリ内に埋め込んでいます。
● Controller(@Controllerを使った受け取り処理)
@Controller
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/users/search")
public String searchUsers(@RequestParam("keyword") String keyword, Model model) {
List<User> users = userRepository.findByNameLike(keyword);
model.addAttribute("users", users);
return "user-list";
}
}
この@Controllerでは、GETパラメータとして受け取ったkeywordをUserRepositoryの検索メソッドに渡し、取得したデータをModelにセットして画面へ返しています。
このような構成により、ユーザーからの入力をもとに@Queryによる検索処理を動的に実行できます。まさにSpring データベース クエリを活用した実用的なアプローチです。
8. @Queryのメリット・今後学ぶべきカスタムクエリの応用
@Queryは、Spring Data JPAにおいて、自由度の高い検索処理を実現するための強力な手段です。findByのようなメソッド名クエリでは表現できない複雑な条件にも対応でき、開発の効率と可読性を大幅に向上させます。
この記事では、以下の内容を確認してきました:
- JPQLとネイティブSQLの基本的な違い
- 動的なクエリ構築における
@Paramの活用方法 - Thymeleafとの連携による画面入力からの検索処理
- クエリ使用時の注意点(構文・型・エラー)
今後は、さらに一歩進んだカスタムクエリとして、DTOでの結果マッピングや、複数テーブルを結合したJOINクエリ、ページング・ソートなども学んでいくとよいでしょう。
実践的なアプリケーション開発においては、@Queryの理解と応用が不可欠です。ぜひ基本を押さえた上で、実務に役立つカスタムクエリをどんどん取り入れてみてください。