✍️ Spring Framework 講座【第22回】SQLを直接書く!〜Spring Data JPAの@QueryとJPQL〜

docs

前回、Spring Data JPAでは、メソッド名の命名規則に従うだけで、ほとんどの定型的な検索(CRUD)が自動で実現できることを学びました。これは開発の生産性を劇的に向上させます。

しかし、以下のようなケースでは、メソッド名によるクエリ生成だけでは対応できません。

  1. 複雑すぎる検索条件: findBy...And...Or... のようにメソッド名が長くなり、可読性が低下する。
  2. DB固有の高度なSQL: 複雑な集計関数や特殊な結合(JOIN)など、標準的な命名規則では表現できないSQLを使いたい。

今回は、これらの複雑な検索に対応するため、自分でSQL文を記述する方法である @Query アノテーションの利用方法を学びます。

1. @Query アノテーションとは?

@Query アノテーションは、Repositoryインターフェースのメソッドの上に付けることで、そのメソッドが実行する検索クエリ(SQL文)を直接指定する機能です。

@Query で記述するクエリには、主に以下の2種類があります。

  1. JPQL (Java Persistence Query Language): Javaのエンティティ名やフィールド名をベースに記述するクエリ言語。DBの種類に依存しないため、こちらが推奨されます。
  2. 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つのアノテーションを追加する必要があります。

  1. @Modifying: このクエリがデータの変更(INSERT, UPDATE, DELETE)を行うことをSpringに伝える。
  2. @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のリレーションシップ(関連付け) にご期待ください!

コメント

タイトルとURLをコピーしました