Spring Data JPA でのクエリー実装方法まとめ

Spring Data JPA でクエリーを実装する方法をざっくりまとめてみた。

# JPAのクエリー実装方法

先ずはJPAのクエリー実装方法を見てみる。
JPAでは EntityManage を使用してクエリーを構築／実行するが、主な実装方法は以下の通り。

* ネイティブSQL
* JPQL
* CriteriaAPI
* 名前付きクエリー
* JPAプロバイダの機能を直接使用する

## ネイティブSQL

EntityManager#createNativeQuery(String)を使用する。

```java
        List<User> results = entityManager
            .createNativeQuery("select * from user where name = :name", User.class)
            .setParameter("name", "きい太")
            .getResultList();
```

## JPQL

EntityManager#createQuery(String)を使用する。

```java
        List<User> results = entityManager
                .createQuery("from User where name = :name", User.class)
                .setParameter("name", "きい太")
                .getResultList();
```

## CriteriaAPI

EntityManager#createQuery(CriteriaQuery)を使用する。

```java
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> root = query.from(User.class);
        query.where(cb.equal(root.get("name"), "きい太"));
        List<User> results = entityManager
                .createQuery(query)
                .getResultList();
```

## 名前付きクエリー

`@NamedQuery` 又は `@NamedNativeQuery` を使用してEntityクラスに名前付きクエリーを定義しておく事ができる。
定義した名前付きクエリーはEntityManager#createNamedQuery(String)で使用する。

```java:User.java
@Entity
@NamedQuery(name = "findByName", query = "from User where name = :name")
public class User {
}
```
```java
        List<User> results = entityManager
                .createNamedQuery("findByName", User.class)
                .setParameter("name", "きい太")
                .getResultList();
```

## JPAプロバイダの機能を直接使用する

EntityManager#unwrap()でJPAプロバイダ(Hibernate,EclipseLinkeなど)のオブジェクトを取得して実装する。

```java:Hibernateの場合
        Session session = entityManager.unwrap(Session.class);
        :
```

# Spring Data JPA のクエリー実装方法

Spring Data JPA では Repositoryインターフェイスにメソッドを定義することでクエリーが作成される。

```java:UserRepository.java
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    public User findByName(String name);
    public User findByEmail(String email);
    :
```

Repositoryのスーパーインターフェイスには以下が用意されている。

* org.springframework.data.repository.CrudRepository
   * org.springframework.data.repository.PagingAndSortingRepository
      * org.springframework.data.jpa.repository.JpaRepository
* org.springframework.data.jpa.repository.JpaSpecificationExecutor

JpaRepositoryには標準的なCRUD操作を行うメソッドが用意されている。
通常はJpaRepositoryだけ使用すれば良いが、後述のSpecificationを使用する場合はJpaSpecificationExecutorも併用する。

標準のメソッド以外のクエリーを作るには、Repositoryインターフェイスにクエリーメソッドを追加する。
クエリーメソッドの実装方法は以下の通り。

* 命名規約に従ったメソッド名での自動実装
* `@Query`アノテーションでのクエリー指定
* リポジトリ実装クラスでクエリーを実装する
* Specificationでの実装

## 命名規約に従ったメソッド名での自動実装

決められたパターンでメソッド名を定義すると、クエリーを指定しなくても自動実装される。

```java:UserRepository.java
    public List<User> findByNameContainsOrderByIdAsc(String name);
```

```sql:実行されるJPQL
select u
from User u
where u.name like %:name%
order by u.id asc
```

## `@Query`アノテーションでのクエリー指定

複雑なクエリーや、命名規約ではメソッド名が長くなってしまう場合などには、`@Query`アノテーションでJPQL又はネイティブSQLを指定して任意のメソッド名でクエリーを定義する事ができる。

```java:UserRepository.java
    @Query("select u from User u where u.name like %:name% order by u.id asc")
    public List<User> findUsers(@Param("name") String name);
```

## 名前付きクエリーの指定

`@Query`のname属性でEntityクラスに定義した名前付きクエリーと結び付ける事ができる。

```java:User.java
@Entity
@NamedQuery(name = "findUsers", query = "select u from User u where u.name like %:name% order by u.id asc")
public class User {
```
```java:UserRepository.java
    @Query(name = "findUsers")
    public List<User> findByNamedQuery(@Param("name") String name);
```

が、せっかくRepositoryにクエリーをまとめてるのにEntityクラスにクエリーを書くという事はないので、Spring Data JPA で名前付きクエリーを使う際はプロパティファイルを使用する。

## プロパティファイルに名前付きクエリーを定義する

クエリー名はデフォルトでは`Entity名.メソッド名`となるが、`@Query`のname属性で変更可能。

```properties:resources/META-INF/jpa-named-queries.properties
User.findAllOrderByName=select u from User u order by u.name
User.findAllOrderByEmailDesc=select u from User u order by u.email desc
```
```java:UserRepository.java
    public List<User> findAllOrderByName();
    @Query(name = "User.findAllOrderByEmailDesc")
    public List<User> findAllOrderByEmail();
```

## リポジトリ実装クラスでクエリーを実装する

動的クエリーの組み立てやJPAプロバイダの機能を使用する場合など、特殊なクエリーが必要な場合等には自分でクエリーメソッドを実装する事ができる。

```UserRepository.java
public interface UserRepository extends 
        JpaRepository<User, Long>,
        JpaSpecificationExecutor<User>,
        UserRepositoryCustom {
```
```UserRepositoryCustom.java
public interface UserRepositoryCustom {
    public List<User> findUsers(String name, String email);
}
```
```UserRepositoryImpl.java
public class UserRepositoryImpl implements UserRepositoryCustom {
    @Autowired
    EntityManager entityManager;
    public List<User> findUsers(String name, String email) {
        //ここにクエリーを実装する
    }
}
```

## Specification

Specificationは何らかの検索条件を表すインターフェイスで、CriteriaAPIを使用して検索条件を実装する。

```java:UserSpecifications.java
    public Specification<User> nameContains(String name) {
        return StringUtils.isEmpty(name) ? null : (root, query, cb) -> {
            return cb.like(root.get("name"), "%" + name + "%");
        };
    }
    public Specification<User> emailContains(String email) {
        return StringUtils.isEmpty(email) ? null : (root, query, cb) -> {
            return cb.like(root.get("email"), "%" + email + "%");
        };
    }
```
```java:呼び出す箇所
    List<User> results = userRepository.findAll(Specifications
        .where(nameContains(name))
        .and(emailContains(email))
    );
```