カスタムリポジトリー
カスタムリポジトリーの使用に適した例
前回の単元でJPQLについて学習しましたが、前回の内容だけではまだまだ様々なパターンに対応できません。
例えば、複数の検索項目があって、入力したテキストボックスについてのみ検索させたい例では、
「入力していない項目は検索条件に含めない」といったように動的に実行クエリを変えたい場合があります。
具体例で見ていきましょう。
「名前のあいまい検索」と「年齢の範囲検索」を備えたフォームだとします。
「名前のみ入力」「年齢の下限のみ入力」「年齢の下限上限を入力」「すべて入力」・・など入力パターンは様々に考えられます。


もちろん、入力項目に応じて実行するメソッドを用意し、if文で場合分けすることもできますが、
「名前のみ入力」:findByNameContaining(String name)
「年齢の下限のみ入力」:findByAgeGreaterThanEqual(int ageLower)
「年齢の下限上限を入力」:findByAgeBetween(int ageLower, int ageUpper)
・・・
条件分岐の数だけメソッドを用意することになってしまいます。労力もかかりますし、効率的とは言えないですよね。
このような場合、カスタムリポジトリーを活用することで、1つのメソッドに処理をまとめることができます。
実際に作成していきましょう。
カスタムリポジトリーのサンプル実装
前回まで使用していたEmployeeRepositoryに対して実装していきます。
Customインターフェースの作成
EmployeeRepositoryを拡張するためにインターフェースを作成します。
こちらもインターフェースですので、具体的なメソッドの記述はせず、抽象メソッドを定義するものです。
【com.cmps.spring.repository.custom】パッケージを作成し、
EmployeeRepositoryCustomインターフェースを作成します。’○○Repository’+’Custom’で命名します。
【com/cmps/spring/repository/custom/EmployeeRepositoryCustom.java】
package com.cmps.spring.repository.custom;
import java.util.List;
import com.cmps.spring.entity.Employee;
public interface EmployeeRepositoryCustom {
/**
* 従業員テーブルからの動的な複数条件AND検索
*
* @param name String 名前の検索キーワード
* @param ageLower String 年齢の下限
* @param ageUpper String 年齢の上限
* @return List<Employee>
*/
public List<Employee> search(String name, Integer ageLower, Integer ageUpper);
}
Customインターフェースの実装クラスの作成
JpaRepositoryを継承したEmployeeRepositroyでは、メソッドの名前から解決され処理が自動実装されましたが、カスタムリポジトリーでは自分でメソッドの処理内容を記述する必要があります。すなわち、インターフェースの実装クラスを作成します。
【com.cmps.spring.repository.custom.impl】パッケージを作成し、EmployeeRepositoryImplクラスを作成します。
‘○○Repository’+’Impl’で命名します。ImplはImplementsから来ており、Springでは慣習的にこの命名をよく用います。
【com/cmps/spring/repository/custom/impl/EmployeeRepositoryImpl.java】
package com.cmps.spring.repository.custom.impl;
import java.util.List;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import com.cmps.spring.entity.Employee;
import com.cmps.spring.repository.custom.EmployeeRepositoryCustom;
public class EmployeeRepositoryImpl implements EmployeeRepositoryCustom {
// Entityを利用するために必要な機能を提供する
@PersistenceContext
private EntityManager entityManager;
@Override
public List<Employee> search(String name, Integer ageLower, Integer ageUpper) {
//StringBuilderでSQL文を連結する
StringBuilder builder = new StringBuilder();
builder.append("SELECT em FROM Employee em WHERE 1=1");
// 各項目が入力されているかどうかの真偽値を収めた変数
boolean nameExists = !"".equals(name) && name != null;
boolean ageLowerExists = ageLower != null;
boolean ageUpperExists = ageUpper != null;
//各カラムがブランクではなかった場合、sql変数にappendする
if (nameExists) {
builder.append(" AND em.name LIKE :name");
}
if (ageLowerExists) {
builder.append(" AND em.age >= :ageLower");
}
if (ageUpperExists) {
builder.append(" AND em.age <= :ageUpper");
}
// 整列
builder.append(" order by em.id");
/*
QueryはSQLでデータを問い合わせるためのクエリ文に相当する機能を持つ
entityManagerのcreateQueryメソッドを使用する
sql変数を引数に渡す
*/
Query query = entityManager.createQuery(builder.toString());
// 各変数に値をセットする
if (nameExists) query.setParameter("name", "%" + name + "%");
if (ageLowerExists) {
query.setParameter("ageLower", ageLower);
}
if (ageUpperExists) {
query.setParameter("ageUpper", ageUpper);
}
return query.getResultList();
}
}
全体の流れとしては、「各入力項目が入力されているか判定し、入力されてるものだけJPQL文に付け加える。最終的にクエリを実行し、結果のListを得る」という流れです。
細かく見ていくと、
・private EntityManager entityManager; ここではEntityManagerを使用します。EntityManagerとは、EntityとDBを紐づける仲介者であり、実はSpring Data JPAの処理の中枢を担うインターフェースです。今回は具体的に実行クエリを自分で準備したいため、こういった場合にはEntityManagerに依頼する必要があります。
Entityを管理するための領域をPersistenceContext(永続コンテキスト)と言い、EntityManagerを使用する上では、紐づけ(依存性注入)のために@PersistenceContextアノテーションを使用します。
・StringBuilderクラスは文字列を作成するためのクラスです。appendメソッドを使用すると、指定した文字列を末尾に連結させることができます。
ここではJPQLを文字列として作成するために使用しています。各項目に対して、入力されているか判定、入力されていればJPQL文に付け加えています。
なお、最初に WHERE 1=1としているのは、appendする文字列を項目問わず[AND 条件]を後ろに付け加える形に記述を揃えるためです。1=1は常に真ですので、上記の通り成立します。
・Queryは、クエリの実行を制御するために使用されるインターフェースです。
EntityManagerのcreateQuery()メソッドにJPQL文字列を引数に渡すと、JPQLを実行するためのQueryインスタンスを生成します。
QueryインスタンスのsetParameter()メソッドで、JPQLのプレースホルダーに値をセットできます。
QueryインスタンスのgetResultList()メソッドを使用すると、SELECT クエリを実行し、クエリの結果を型なしリストとして返します。
Query (Jakarta EE 8 Specification API) – Javadoc
RepositoryインターフェースにCustomインターフェースを継承させる
ここまで処理を見てきましたが、元のEmployeeRepositroyから処理を実行するには、RepositoryインターフェースにCustomインターフェースを継承させる必要があります。
extendsの後ろに追記します。
【com/cmps/spring/repository/EmployeeRepository.java】に追記
import com.cmps.spring.repository.custom.EmployeeRepositoryCustom;////追記
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer>, EmployeeRepositoryCustom {////追記
//略
}
具体的な処理はImplクラスに記載しているので、EmployeeRepositroy自身にはメソッドを記述する必要はありません。
以上の記述で、EmployeeRepositoryにsearchメソッドが定義できました!
Controller、Viewでの確認サンプル
入力内容を受け取るためのFormクラス
【com/cmps/spring/form/employee/SearchForm.java】を作成
package com.cmps.spring.form.employee;
import org.hibernate.validator.constraints.Range;
import lombok.Data;
@Data
public class SearchForm {
// 名前
private String name;
// 年齢 下限
private Integer ageLower;
// 年齢 上限
private Integer ageUpper;
}
searchメソッドを実行するメソッドを記述
【com/cmps/spring/controller/EmployeeController.java】に追記
/**
* employeesテーブルから名前、年齢で検索した結果を表示する
*
* @param model Model
* @param form SearchForm
* @return
*/
@GetMapping("search")
public String search(Model model, SearchForm form) {
// 検索処理
List<Employee> employees = employeeRepository.search(form.getName(), form.getAgeLower(), form.getAgeUpper());
model.addAttribute("employees", employees);
return "employee/list";
}
検索を実行するためのフォームをViewに追加する
【src/main/resources/templates/employee/list.html】に追記(body開始タグとh1タグの間)
//略
<body>
<form method="GET" th:action="@{search}">
<div>
<label>名前:<input type="text" size="12" name="name"> </label>
<label>年齢:<input type="text" size="3" name="ageLower">~<input type="text" size="3" name="ageUpper"> </label>
<input type="submit">
</div>
</form>
<h1>employeesテーブル全件</h1>
//略
「/emp/all」にアクセスし、フォームから送信して検索機能を確認してみましょう。
(例)名前に「田」を入力して検索した例

今回紹介したカスタムリポジトリー以外にも、SpringでのDB処理には様々な実装方法があります。
気になる方はぜひ調べてみてください!
参考:Spring Data JPA でのクエリー実装方法まとめ – Qiita
練習問題
問1:マニュアルのサンプルを改変して、codeカラムのあいまい検索も追加してください。(名前、年齢の範囲、コードの3種の検索にする)
