Spring Data JPAでの検索クエリ

Spring Data JPAを利用した検索クエリの作成に苦戦したので、
メモを残します。以下、掲載内容です。

- JPAを用いたデータアクセス
- JPQLのクエリ
- Where句の動的条件
- Where句の動的条件＋Select句のDistict

# JPAとは
> Java Persistence API  関係データベースのデータを扱う Java SE および Java EE のアプリケーションを開発するためのJava用フレームワークである。
> 出典:ウィキペディア

自分の解釈では

- Entityでデータ構造、データの関係性を定義
- クエリはJPQL（Java Persistence Query Language）で作ろう

ということだと思います。

# Spring Data
SpringBootのデータアクセスフレームワーク
`Spring Data JDBC`や`Spring Data JPA`など様々なデータアクセスが用意されています。

# Spring Data JPAの利用方法
早速、使っていきたいと思います。
先ずはpom.xmlの指定

```XML:pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
```

データアクセスはEntityとRepositoryを用意してアクセスします。

```java:TUser.java

@Entity
@Table(name = "T_USER ")
public class TUser {

    @Id
    String userId;
    String userNo;
    String userName;
    String email;
    // …
    // + Getter&Setter
}
```

```Java:TUserRepository.java

@Repository
public interface TUserRepository extends JpaRepository<TUser, String>,
        JpaSpecificationExecutor<TUser>, TUserRepositoryCustom {
// @QueryによるJPQLなど
}

```

`JpaRepository`の継承で基本的なデータアクセスが可能です。
`@Query`の指定でJPQLによるクエリも実行可能です。

```Java:TUserRepository.java
<S extends T> S save(S entity);        // 保存
Optional<T> findById(ID primaryKey);   // 単一検索条件
Iterable<T> findAll();                 // 全Select
long count();                          // count
void delete(T entity);                 // delete
boolean existsById(ID primaryKey);     // exists

// JPQL文による検索条件指定のクエリ
@Query("select u from TUser u where u.email= ?1")  
User findByEmail(String email);
```

ここまでだとWhere句に動的な条件を付与できない為、
`JpaSpecificationExecutor`を継承し、
`findAll(Specification)`に検索条件を渡します。
独自の条件を生成する関数を用意し、呼び出し元のサービスクラスから
`findAll()`の引数に指定すると動的条件の付与が可能です。

```java:TUserRepository.java
// 独自条件生成の関数　userNameに値が設定されている場合のみ、条件を生成する。
static Specification<TUser> userNameContains(String userName) {
  return StringUtils.isEmpty(userName) ? null : new Specification<TUser>() {
      @Override
      public Predicate toPredicate(Root<TUser> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        return cb.like(root.get("userName"), "%" + userName + "%");
      }
   };
}

```

```Java:サービスクラス
@Autowired
TUserRepository rep;

public List<TUser> getList(String userName) {
  List<TUser> list = rep.findAll(Specification
    .where(TUserRepository.userNameContains(userName));
    // 複数項目の場合は.and(),.or()でつなげる
}
```

Where句に動的条件を付与しつつ、select句をdistinctする場合は
`TUserRepositoryCustom`という独自のinterfaceを用意し、
`EntiyManager`,`CriteriaBuilder`でクエリを作成します。
XXXReposytory **Custom**とつけるのがルールのようです。

```Java:TUserRepositoryCustomImpl.java
  @Autowired
  private JpaContext context;

  public List<String> findBySpec(Specification<TUser> spec) {
    final EntityManager em = context.getEntityManagerByManagedType(TUser.class);
    // CriteriaBuilderでクエリを作成
    final CriteriaBuilder cb = em.getCriteriaBuilder();
    // CriteriaQueryに設定するObjectを指定　※戻り値になる。
    CriteriaQuery<String> query = cb.createQuery(String.class);
    // FROM句
    Root<TUser> root = query.from(TUser.class);
    // select句
    query.select(root.get("userId")).distinct(true);
    // where句　※外部指定可能にしている
    query.where(spec.toPredicate(root, query, cb));
    final List<String> resultList = em.createQuery(query).getResultList();

    return resultList;
  }
}
```

```Java:サービスクラス
@Autowired
TUserRepository rep;

public List<TUser> getList(String userName) {
  List<String> list = rep.findBySpec(Specification
    .where(TUserRepository.userNameContains(userName));
}
```
# まとめ
最近は複雑なクエリを実行する機会は少ないかもしれませんが、
どうしようもない時も**Spring Data JPA**では手法が用意されていました。

これからは複雑なクエリが生まれないように
シンプルなデータアクセスを意識したDB設計が重要だと感じました。


# 参考
[Spring Data JPA - リファレンスドキュメント](https://spring.pleiades.io/spring-data/jpa/docs/current/reference/html/#reference)
[[Java][Spring Boot] Customクラスを使用してデータベースアクセスする](https://qiita.com/sengoku/items/28b068e4ad8745340879)