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の指定
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
データアクセスはEntityとRepositoryを用意してアクセスします。
@Entity
@Table(name = "T_USER ")
public class TUser {
@Id
String userId;
String userNo;
String userName;
String email;
// …
// + Getter&Setter
}
@Repository
public interface TUserRepository extends JpaRepository<TUser, String>,
JpaSpecificationExecutor<TUser>, TUserRepositoryCustom {
// @QueryによるJPQLなど
}
JpaRepository
の継承で基本的なデータアクセスが可能です。
@Query
の指定でJPQLによるクエリも実行可能です。
<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()
の引数に指定すると動的条件の付与が可能です。
// 独自条件生成の関数 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 + "%");
}
};
}
@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とつけるのがルールのようです。
@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;
}
}
@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 - リファレンスドキュメント
[Java][Spring Boot] Customクラスを使用してデータベースアクセスする