カスタムリポジトリー

カスタムリポジトリー

カスタムリポジトリーの使用に適した例

前回の単元で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">&emsp;</label>
			<label>年齢:<input type="text" size="3" name="ageLower">~<input type="text" size="3" name="ageUpper">&emsp;</label>
			<input type="submit">
		</div>
	</form>
	<h1>employeesテーブル全件</h1>
//略

「/emp/all」にアクセスし、フォームから送信して検索機能を確認してみましょう。

(例)名前に「田」を入力して検索した例

今回紹介したカスタムリポジトリー以外にも、SpringでのDB処理には様々な実装方法があります。
気になる方はぜひ調べてみてください!
参考:Spring Data JPA でのクエリー実装方法まとめ – Qiita

練習問題

問1:マニュアルのサンプルを改変して、codeカラムのあいまい検索も追加してください。(名前、年齢の範囲、コードの3種の検索にする)

タイトルとURLをコピーしました