Javaでの効率的なSQL操作方法：JPAからCriteriaまで最適な方法を選択する優先順位を解説！

# 1.本記事の内容

JavaのSpringBootでRepositoryクラスのメソッドを作成する際、選択肢が多くて迷うことはないでしょうか?
本記事では、JPAのデフォルトメソッドやメソッド名による簡単なクエリ生成から、SpecificationやCriteriaを使用した柔軟で高度な実装方法までを網羅し、目的に応じた最適な手法とその優先順位を解説します。

# 2.優先順位一覧
以下は、クエリ生成手法を開発工数や難易度などの観点から優先順位付けした一覧です。

| 優先順位 |作成手法      |    説明    |
|:-----------------|------------------|:------------------|
| 1|JPAのデフォルトメソッド |シンプルなCRUD操作に最適|
| 2|メソッド名によるクエリ生成|簡潔かつ自動生成。少し複雑な条件検索にも対応|
| 3|JPQL + `@Query`|柔軟なカスタムクエリが必要な場合|
| 4|ネイティブクエリ + `@Query` |データベース固有の最適化が必要な場合|
| 5|Specification|動的な条件を柔軟に扱いたい場合|
| 6|Criteria|高度なクエリ構築が必要、最大限の柔軟性を求める場合|

# 3.各作成方法の解説

それぞれの手法について、次章で具体的な特徴や活用例を簡単に解説します。
目的やシチュエーションに応じて、どの方法を選べばよいかを確認してみてください。

## 3.1 JPAのデフォルトメソッド

JPAでは、JpaRepositoryを継承することで、基本的なCRUD操作がすぐに利用できるデフォルトメソッドが提供されます。特に、標準的な操作で十分な場合には、これらのメソッドを活用することで効率的に開発を進められます。
提供されるメソッドは、findAll, findById, existsById, save, deleteById などです。

https://spring.pleiades.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html

実装例：
以下のようにJpaRepositoryを継承したインターフェースを作成するだけで、デフォルトメソッドが利用可能になります。
```java
public interface ItemRepository extends JpaRepository<Item, String> {
}
```


## 3.2 JPAのメソッド名によるクエリ生成

JPAでは、メソッドを宣言するだけで、メソッド名の規約に従い手軽に実装することができます。
特に、3.1ではカバーできない比較的単純な検索ロジックを実装する際に非常に有効です。
条件が増えすぎるとメソッド名が長くなり可読性が低下するため3.3での作成をおすすめします。

実装例：
以下のようにメソッドを宣言するだけでクエリの生成ができます。

```java
public interface ItemRepository extends JpaRepository<Item, String> {
    long countByPrice(Integer price);
    List<Item> findByPriceLessThan(Integer price);
    List<Item> findByFragranceThemeIdAndFragranceImageId(String fragranceThemeId, String fragranceImageId);
}
```

## 3.3 JPQL + `@Query`
`@Query`を使用して、JPQLを直接記述することでカスタムクエリを実行できます。
JPQLを使うことで、データベースに依存しない形で柔軟なクエリを定義可能です。特に、複数の条件や集約関数を含むクエリを実装したい場合に適しています。

実装例：
以下のように`@Query`でメソッドを宣言することでクエリの生成ができます。


```java
public interface ItemRepository extends JpaRepository<Item, String> {
    @Query("SELECT i.itemId, i.price FROM Item i GROUP BY i.itemId, i.price")
    List<Object[]> findItemIdAndPrice();

    @Query("SELECT i FROM Item i WHERE i.fragranceThemeId = :fragranceThemeId AND i.price BETWEEN :minPrice AND :maxPrice")
  List<Item> findByfragranceThemeIdAndPriceRange(
      @Param("fragranceThemeId") String fragranceThemeId, 
      @Param("minPrice") Integer minPrice, 
      @Param("maxPrice") Integer maxPrice);
}
```


## 3.4 ネイティブクエリ + `@Query`
ネイティブクエリを使用すると、JPQLでは実現できないデータベース固有の最適化や機能を活用することが可能です。

実装例：
以下のように@QueryアノテーションにnativeQuery = trueを指定することでネイティブクエリを利用できます。
現在の日付を取得するOracleのSYSDATEという関数を用いた例です。JPQLでは標準で提供されていませんが、ネイティブクエリであれば簡単に使用できます。


```java
public interface ItemRepository extends JpaRepository<Item, String> {
    @Query(value = "SELECT * FROM items WHERE created_at > SYSDATE - 7", nativeQuery = true)
    List<Item> findItemsCreatedWithinSevenDays();
}
```


## 3.5 Specification
Specificationは、Spring Data JPAで動的なクエリを生成するためのインターフェースで、条件を組み合わせて複雑なクエリを動的に構築する際に使用します。Specificationを使用することで、AND、OR条件や、条件が追加されることでクエリが動的に変更されるような場合に便利です。

実装例：
以下のように①で条件式を3つ追加したSpecificationクラスを実装しています。
②でRepositoryクラスにSpecificationのインターフェースを追加して、Serviceクラスで③のように利用できます。

①Specificationクラス：
```java
public class ItemSpecification {

      public Specification<Item> findAllSpecification(
      String itemNameJpn, String itemNameEng, Integer price) {
      
      return Specification.where(byItemNameJpn(itemNameJpn).and(byItemNameEng(itemNameEng)).and(byPrice(price)));
  }

  private Specification<Item> byItemNameJpn(String itemNameJpn) {
    return (root, query, builder) -> {
      if (itemNameJpn != null && !itemNameJpn.isEmpty()) {
            return builder.like(root.get("itemNameJpn"), "%" + itemNameJpn + "%");
        }
        return null;
    };
  }

  private Specification<Item> byItemNameEng(String itemNameEng) {
    return (root, query, builder) -> {
        if (itemNameEng != null && !itemNameEng.isEmpty()) {
            return builder.like(root.get("itemNameEng"), "%" + itemNameEng + "%");
        }
        return null;
    };  
  }

  private Specification<Item> byPrice(Double price) {
    return (root, query, builder) -> {
        if (price != null) {
            return builder.lessThan(root.get("price"), price);
        }
        return null;
    };
  } 
}
```

②Repositoryクラス：
```java
public interface ItemRepository extends JpaRepository<Item, String>, JpaSpecificationExecutor<Item> {}
```

③Serviceクラス：
```java
List<Item> itemList = itemRepository.findAll(itemSpecification.findAllSpecification(itemNameJpn, itemNameEng, price));
```

## 3.6 Criteria
Criteria は、JPQLをJavaのオブジェクトベースで構築する手法です。条件を動的に組み立てる必要がある場合や、非常に複雑なクエリを実装する際に使用します。SQLライクなクエリをJavaコード内で安全に記述できる点が大きな利点です。

実装例：
STEP1. EntityManager を利用する
STEP2. CriteriaBuilder を取得する
STEP3. CriteriaQuery を作成する


```java
@Repository
public class ItemsCriteriaRepository {
  // STEP1
  @Autowired private EntityManager entityManager;

  public List<ClientInfoForClientsSearch> getClients(
      String itemNameJpn, String itemNameEng, Integer price) {
      // STEP2
      final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
      // STEP3
      CriteriaQuery<Item> query = cb.createQuery(Item.class);
      Root<Item> root = query.from(Item.class);
      }

      // 条件を動的に追加
      if (name != null && !name.isEmpty()) {
          predicates.add(cb.equal(root.get("name"), name));
      }
      if (minPrice != null) {
          predicates.add(cb.greaterThanOrEqualTo(root.get("price"), minPrice));
      }
      if (maxPrice != null) {
          predicates.add(cb.lessThanOrEqualTo(root.get("price"), maxPrice));
      }

      // クエリにWHERE句とソートを追加
      query.select(root)
           .where(predicates.toArray(new Predicate[0]))
           .orderBy(cb.asc(root.get("itemId"))); // IDで昇順にソート

      return entityManager.createQuery(query).getResultList();
}
```

# 4.おわりに

本記事では、Spring Boot と JPA を使ったリポジトリクラスの実装方法について、いくつかのクエリ生成手法を紹介しました。
もし他により良い方法があれば、ぜひ共有いただけると嬉しいです。

最後まで読んでいただきありがとうございました。