SpringのJPQLと@Queryの使い方を完全ガイド!ネイティブSQLとの違いも解説
生徒
「SpringでSQLっぽいことをしたいときって、どうやって書けばいいんですか?」
先生
「Spring Data JPAでは、JPQLというクエリ言語や@Queryアノテーションを使って柔軟にデータを取得できますよ。」
生徒
「SQLとは違うんですか?ネイティブSQLとの違いも知りたいです!」
先生
「では、JPQLとネイティブSQLの違い、@Queryの使い方、使い分けについて一緒に見ていきましょう。」
1. JPQLとは?SQLとの違いを解説
「1. JPQLとは?SQLとの違いを解説」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
JPQL(Java Persistence Query Language)は、Javaのエンティティクラスを操作するためのクエリ言語です。SQLと構文は似ていますが、テーブル名ではなくエンティティ名やフィールド名を使います。
たとえば、ユーザーをID順に取得するJPQLは以下のようになります。
@Query("SELECT u FROM User u ORDER BY u.id ASC")
List<User> findAllUsers();
このように、Userというのはテーブルではなくエンティティ名を指しています。u.idもカラムではなく、エンティティのプロパティ名です。
2. @QueryアノテーションでJPQLを使う方法
Spring Data JPAでは、@Queryアノテーションを使うことで、メソッド名に依存しないクエリを自由に記述できます。
例えば、「名前でユーザーを検索する」場合は以下のように記述します。
@Query("SELECT u FROM User u WHERE u.name = :name")
User findByName(@Param("name") String name);
:nameはパラメータで、@Paramで指定することで値をバインドできます。JPQLでは型安全なエンティティベースの記述ができ、リファクタにも強く、保守性も高いのが特長です。
3. ネイティブSQLの使い方と@QueryのnativeQuery属性
JPQLではできない複雑なSQLやデータベース固有の関数を使いたいときには、ネイティブSQL(生のSQL)を使うことができます。@QueryにnativeQuery = trueを指定することで可能です。
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmail(String email);
この場合、テーブル名はusersのように実際のデータベースのスキーマ名を使用します。エイリアスもSQLの書き方と同様に使えます。
4. JPQLとネイティブSQLの使い分けポイント
「4. JPQLとネイティブSQLの使い分けポイント」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
JPQLとネイティブSQLは、使い分けが重要です。以下のような基準で選ぶと良いでしょう。
- JPQLを使う場面:基本的な検索・更新・削除、エンティティベースで完結する処理
- ネイティブSQLを使う場面:データベースの特有の構文、複雑な結合や関数、パフォーマンスチューニング
例えば、PostgreSQLのjsonbやMySQLのLIMITなどはJPQLでは扱いづらいため、ネイティブSQLが必要です。
5. JPQLでのJOIN文の書き方と注意点
JPQLでもJOINを使ってテーブル(エンティティ)間のリレーションを扱うことができます。
@Query("SELECT o FROM Order o JOIN o.customer c WHERE c.name = :name")
List<Order> findOrdersByCustomerName(@Param("name") String name);
このように、JOINではエンティティのプロパティをたどる形で記述します。SQLのようにテーブル名を直書きすることはありません。
6. DTOを使ったJPQLクエリの書き方
JPQLでは、DTO(データ転送オブジェクト)を使って必要なフィールドだけを取り出すこともできます。クエリ内でnewを使ってDTOを生成します。
@Query("SELECT new com.example.dto.UserDto(u.name, u.email) FROM User u")
List<UserDto> findUserDtos();
この形式は、パフォーマンスを意識した設計にも有効で、必要なカラムだけを選択してデータ転送量を最適化できます。
7. @Modifyingと@Queryの組み合わせで更新系JPQLを書く
「7. @Modifyingと@Queryの組み合わせで更新系JPQLを書く」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
更新系クエリ(UPDATE/DELETE)は@Modifyingアノテーションを併用して書きます。トランザクション制御も必要なので注意しましょう。
@Transactional
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
void updateUserStatus(@Param("id") Long id, @Param("status") String status);
@Transactionalをメソッドに付けることで、更新クエリがトランザクション内で安全に実行されます。
8. @Queryを使うときのよくあるエラーと対処法
JPQL/ネイティブSQLの@Queryでありがちなエラーには以下のようなものがあります。
- エンティティ名とテーブル名の混同(JPQLではエンティティ名)
- ネイティブSQLなのに
nativeQuery = trueを付け忘れ - 引数の名前と
:paramの不一致
こうしたエラーはIDEの補完やユニットテストで事前に気づくようにすると、開発効率が上がります。
まとめ
SpringのJPQLと@Queryを理解すると、Spring Data JPAでのデータ取得や検索処理をより柔軟に書けるようになります。通常、Spring Data JPAではfindByNameやfindByEmailのようにメソッド名から自動でクエリを作成できますが、条件が複雑になったり、JOINを使ったり、DTOへ必要な値だけを取得したりしたい場合には、@Queryを使ったJPQLの記述が役立ちます。JPQLはSQLに似た書き方ですが、テーブル名やカラム名ではなく、Javaのエンティティ名とフィールド名を使う点が大きな違いです。
JPQLを使うと、データベースの物理的なテーブル構造に直接依存しすぎず、エンティティ中心の考え方で検索処理を書けます。たとえばUserエンティティのnameやemailを条件にした検索では、usersテーブルやuser_nameカラムではなく、Userやu.nameのようにJavaのクラスとプロパティを使います。このため、Spring BootやSpring Data JPAでエンティティ設計を中心に開発している場合、JPQLは読みやすく保守しやすいクエリになります。
一方で、ネイティブSQLは実際のテーブル名やカラム名を使って書くため、データベース固有の機能を使いたい場合に向いています。PostgreSQL、MySQL、Oracle、SQL Serverなど、それぞれのデータベースが持つ独自関数や高度なSQLを使いたい場合は、@QueryにnativeQuery=trueを指定します。ただし、ネイティブSQLはデータベース依存が強くなるため、基本的な検索やエンティティ中心の処理では、まずJPQLを検討するとよいです。
JPQLと@Queryの基本を整理
@Queryは、Repositoryのメソッドに直接クエリを指定できるアノテーションです。メソッド名だけでは表現しにくい検索条件や並び替えを、JPQLとして明示的に書けます。JPQLではエンティティ名とフィールド名を使うため、Javaコードと対応関係を確認しながら書くことが大切です。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u ORDER BY u.id ASC")
List<User> findAllUsers();
}
この例では、Userエンティティをidの昇順で取得しています。SELECT u FROM User uのUserはテーブル名ではなくエンティティ名です。u.idもデータベースのカラム名ではなく、Userクラスのidフィールドを指しています。初心者は、JPQLではデータベースではなくJavaのエンティティを見る、と覚えると理解しやすいです。
名前付きパラメータで条件検索する
@Queryでは、:nameのような名前付きパラメータを使って、メソッドの引数をクエリに渡せます。@Paramの名前とJPQL内のパラメータ名を一致させることが重要です。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name = :name")
User findByName(@Param("name") String name);
}
このコードでは、引数nameの値がJPQLの:nameに渡されます。@Param("name")とクエリ内の:nameが一致していないとエラーになるため、文字の打ち間違いに注意しましょう。検索条件が複数ある場合も、名前付きパラメータを使うと読みやすくなります。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name = :name AND u.email = :email")
User findByNameAndEmail(@Param("name") String name, @Param("email") String email);
}
このように、複数条件でもJPQLを使えば分かりやすく書けます。メソッド名が長くなりすぎる場合や、条件が複雑な場合は@Queryを使うと保守しやすくなります。
JOINを使った関連エンティティの検索
JPQLでは、エンティティ同士の関連をたどってJOINできます。SQLのようにテーブル名と結合条件を直接書くのではなく、エンティティの関連フィールドを使って書くのが特徴です。
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o JOIN o.customer c WHERE c.name = :name")
List<Order> findOrdersByCustomerName(@Param("name") String name);
}
この例では、Orderエンティティからcustomerフィールドをたどり、Customerのnameを条件に注文情報を取得しています。JOIN o.customer cのように、エンティティのプロパティを使うため、Java側の関連定義を理解しておくことが重要です。
DTOで必要な値だけを取得する
一覧画面や検索結果では、エンティティ全体ではなく、名前とメールアドレスだけなど、必要な項目だけを取得したい場合があります。そのようなときは、JPQLでDTOを直接生成できます。
public class UserDto {
private String name;
private String email;
public UserDto(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT new com.example.dto.UserDto(u.name, u.email) FROM User u")
List<UserDto> findUserDtos();
}
DTOを使うと、画面表示に必要なデータだけを取得できるため、不要なデータの取得を減らせます。ただし、JPQL内でnewを使う場合は、DTOのパッケージ名を含めた完全なクラス名と、コンストラクタの引数順に注意しましょう。
ネイティブSQLを使う場面
JPQLでは表現しにくいSQLや、データベース固有の関数を使いたい場合は、ネイティブSQLを使います。@QueryのvalueにSQLを記述し、nativeQuery=trueを指定します。
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);
}
ネイティブSQLでは、usersのような実際のテーブル名を使います。JPQLと違って、エンティティ名ではありません。SQLの知識をそのまま使える反面、データベースのテーブル構造や方言に依存しやすくなるため、使いどころを見極めることが大切です。
更新系クエリではModifyingとTransactionalを使う
@QueryでUPDATEやDELETEを実行する場合は、@Modifyingを付けます。また、更新処理はトランザクションの中で行う必要があるため、@Transactionalも併用します。
public interface UserRepository extends JpaRepository<User, Long> {
@Transactional
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
void updateUserStatus(@Param("id") Long id, @Param("status") String status);
}
SELECTのような取得系クエリと違い、UPDATEやDELETEはデータベースの内容を変更します。そのため、@Modifyingを付け忘れると正しく実行できません。さらに、トランザクション管理も必要になるため、更新系の@Queryでは@Transactionalの有無も確認しましょう。
よくあるエラーと確認ポイント
JPQLと@Queryで初心者がつまずきやすいのは、エンティティ名とテーブル名の混同です。JPQLではUserやOrderのようなエンティティ名を使いますが、ネイティブSQLではusersやordersのようなテーブル名を使います。また、JPQLではフィールド名を使うため、データベースのカラム名をそのまま書くとエラーになることがあります。
もう一つ多いのが、@Paramの名前とJPQL内のパラメータ名の不一致です。@Param("name")と書いたなら、JPQL側も:nameにする必要があります。小さなスペルミスでも実行時エラーにつながるため、Repositoryのテストを書いて早めに確認することが大切です。
Spring、Spring Boot、Spring Data JPA、JPQL、@Query、ネイティブSQL、nativeQuery、Repository、JpaRepository、Entity、DTO、JOIN、@Param、@Modifying、@Transactional、エンティティ名、テーブル名、フィールド名、SQL、データベース検索、更新クエリ、削除クエリ、パフォーマンス、保守性といったキーワードは、JPAの検索処理を学ぶうえで重要です。これらを関連づけて理解すると、単純なfindByだけでは対応しにくい検索や更新処理も実装しやすくなります。
初心者は、まずJPQLでSELECT文を書き、エンティティ名とフィールド名を使う感覚を身につけるとよいです。次に@Paramで条件を渡す検索、JOINを使った関連エンティティの検索、DTOで必要な項目だけを取得する方法へ進むと理解しやすくなります。最後に、ネイティブSQLと@Modifyingを学ぶと、より実務的なデータアクセス処理に対応できるようになります。
JPQLと@Queryは、Spring Data JPAの検索処理を柔軟にするための強力な機能です。基本的な検索はメソッド名クエリで十分ですが、複雑な条件やJOIN、DTO取得、更新処理が必要な場合は@Queryが役立ちます。JPQLとネイティブSQLの違いを理解し、目的に合わせて使い分けることで、読みやすく保守しやすいSpringアプリケーションを作っていきましょう。
生徒
JPQLはSQLに似ていますが、テーブル名ではなくエンティティ名を使うことが分かりました。
先生
その通りです。JPQLでは、データベースのテーブルではなくJavaのエンティティを対象にして書きます。
生徒
@Queryを使うと、Repositoryのメソッドに自由な検索条件を書けるのですね。
先生
はい。メソッド名だけでは長くなりすぎる検索や、JOINを使う検索に向いています。
生徒
:nameのようなパラメータは、@Paramの名前と一致させる必要があるのですね。
先生
はい。名前が一致していないと、実行時にエラーになることがあります。
生徒
ネイティブSQLを使う場合は、nativeQuery=trueを指定して、実際のテーブル名やカラム名を書くのですね。
先生
その理解で大丈夫です。データベース固有のSQLを使いたい場合に便利です。
生徒
DTOを使うと、必要な項目だけを取得できるので、一覧画面などで便利そうです。
先生
はい。不要なデータを取得しない設計にできるため、画面表示用のデータ取得でよく使われます。
生徒
UPDATEやDELETEを書くときは、@Modifyingと@Transactionalが必要なのですね。
先生
その通りです。取得系と更新系では必要なアノテーションが違うため、忘れずに確認しましょう。
生徒
JPQLとネイティブSQLの違いを意識して使い分けることが大切だと分かりました。
先生
はい。基本はJPQL、データベース固有の処理や複雑なSQLが必要なときはネイティブSQLという考え方で、保守しやすいRepositoryを作っていきましょう。
Spring FrameworkやThymeleafを使った Webアプリ開発の全体像をやさしく理解したい人には、 この入門書が定番です。
Spring Framework超入門をAmazonで見る※ Amazon広告リンク