Spring BootでMySQL接続してSpring DATA JPAであいまい検索風

検索欄が1つのときのあいまい検索を実装してみました。検索するテーブルは1つですけど...それではいってみましょー。

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

⇧  上記サイト様の説明にありますように、

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

といろいろな方法がありますが、今回は「CriteriaAPI」ですかね。 

Spring Bootでプロジェクトの作成

何はともあれ、プロジェクトを作っていきましょう。

Eclipseを起動し、「ファイル(F)」>「新規(N)」>「Spring スターター・プロジェクト」を選択。

f:id:ts0818:20170917231829j:plain

「名前」を入力し、「次へ(N)>」をクリック。

f:id:ts0818:20170917231954j:plain

「コア」>「Lombok」、「SQL」>「JPA」、「SQL」>「MySQL」、「テンプレート・エンジン」>「Thymeleaf」、「Web」>「Web」を選択し、「次へ(N)>」をクリック。

f:id:ts0818:20170917232038j:plain

「完了(F)」をクリック。

f:id:ts0818:20170917232321j:plain

 

ファイルを作成

こんな感じの構成になるようにフォルダ、ファイルを作成していきます。

f:id:ts0818:20170918110502j:plain

まずは、フォルダ作成。「src/main/java」直下のフォルダを選択した状態で右クリックし、「新規(W)」>「フォルダー」を選択。

f:id:ts0818:20170918090335j:plain

「フォルダー名(N):」を入力し、「完了(F)」をクリック。

f:id:ts0818:20170918090511j:plain

同じ流れで、フォルダを作成し、「controller」「entity」「repository」「service」の4つのフォルダを用意します。

フォルダを作り終わったら、まずは「controller」ふぁおるだを選択し、「新規(W)」>「クラス」を選択します。

f:id:ts0818:20170918090313j:plain

「名前(M):」を入力し、「完了(F)」。

f:id:ts0818:20170918090833j:plain

続いて、「entity」フォルダーを選択した状態で右クリックし、「新規(W)」>「クラス」を選択します。

f:id:ts0818:20170918090917j:plain

「名前(M):」を入力し、「インターフェイス(I):」の「追加(A)...」をクリック。

f:id:ts0818:20170918091019j:plain

「一致する項目(M):」から「Serializable - java.io」を選択し、「OK」。

f:id:ts0818:20170918091129j:plain

インターフェースが追加されているのを確認し、「完了(F)」。

f:id:ts0818:20170918091242j:plain

続いて、「repository」フォルダを選択した状態で右クリックし、「新規(W)」>「インターフェース」をクリック。

f:id:ts0818:20170918091348j:plain

「名前(M):」を入力し、「拡張インターフェイス(I):」の「追加(A)....」を選択します。

f:id:ts0818:20170918110415j:plain

「一致する項目(M):」から「JpaRepository」「JpaSpecificationExcutor」を選択します。(「Ctrl」キーを押しながらクリックで複数選択できます。)

f:id:ts0818:20170918091549j:plain

「インターフェース」が追加されたのを確認し、「完了(F)」。(今回は、Specificationというものを使うので、「JpaSpecificationExcutor」というものも追加しています。)コード上でエラーが出た状態になりますが、後で編集していきます。

f:id:ts0818:20170918110438j:plain

続いて、「service」フォルダを選択した状態で右クリックし、「新規(W)」>「クラス」を選択。

f:id:ts0818:20170918092011j:plain

「名前(M):」を入力し、「完了(F)」。

f:id:ts0818:20170918092129j:plain

今回、Specificationを利用するので、「service」フォルダにもう1つファイルを作成します。

f:id:ts0818:20170918105341j:plain

「名前(M):」を入力し、「完了(F)」。

f:id:ts0818:20170918105355j:plain

 

Javaファイルができたので、画面表示側を担うHTMLファイルを作っていきます。場所が変わって、「src/main/resources」>「templates」を選択した状態で右クリックし、「新規(W)」>「その他(O)...」を選択します。

f:id:ts0818:20170918092426j:plain

「Web」>「HTMLファイル」を選択し「次へ(N)>」。

f:id:ts0818:20170918092113j:plain

「ファイル名(M):」を入力し、「次へ(N)」。

f:id:ts0818:20170918092652j:plain

「完了(F)」をクリック。

f:id:ts0818:20170918092753j:plain

 

データベースを用意

ファイルの編集の前に、データベースを用意します。コマンドプロンプトMySQLに接続(ログイン)します。

mysql -u root -p

f:id:ts0818:20170918093226j:plain

データベース一覧

SHOW DATABASES;

f:id:ts0818:20170918094246j:plain

今回は既に作成していた、「sample」データベースを使用することにします。

USE sample;
SHOW TABLES

f:id:ts0818:20170918094357j:plain

「member」というテーブルを作成します。(複数形「members」にしたほうが良かったかな...)

CREATE TABLE member(
  id int(11) NOT NULL auto_increment,
  last_name VARCHAR(20) NOT NULL,
  first_name VARCHAR(20) NOT NULL,
  last_name_kana VARCHAR(30) NOT NULL,
  first_name_kana VARCHAR(30) NOT NULL,
  email VARCHAR(255) NOT NULL,
  tel VARCHAR(12) NOT NULL,
  address_level01 VARCHAR(30) NOT NULL,
  address_level02 VARCHAR(30) NOT NULL,
  address_line01 VARCHAR(50) NOT NULL,
  address_line02 VARCHAR(50) NOT NULL,
  PRIMARY KEY(id)
);

f:id:ts0818:20170918094546j:plain

データを入れておきます。

INSERT INTO member (last_name, first_name, last_name_kana, first_name_kana, email, tel, address_level01, address_level02, address_line01, address_line02) VALUES('佐藤', '健', 'サトウ', 'タケル', 'takeru@gmail.com', '00087260000', '東京都', '第九地区', 'ドンジャラ市0-22-39', 'ライオンズコーポ102');
INSERT INTO member (last_name, first_name, last_name_kana, first_name_kana, email, tel, address_level01, address_level02, address_line01, address_line02) VALUES('鈴木', 'イチロー', 'スズキ', 'イチロー', 'ichiro@gmail.com', '01041567800', '大阪府', 'ゴメス市', 'セバスティック州2-12-32', '松庵202');
INSERT INTO member (last_name, first_name, last_name_kana, first_name_kana, email, tel, address_level01, address_level02, address_line01, address_line02) VALUES('高田', '善衛', 'タカダ', 'ゼンエイ', 'zennei@gmail.com', '12039860210', '沖縄県', 'ウーマ区', '救世観音市8-21-7', 'シーサーマンション101');

f:id:ts0818:20170918100221j:plain

MySQLのユーザー一覧を確認。

SELECT Host, User FROM mysql.user;

f:id:ts0818:20170918100714j:plain

ユーザーを追加してみます。 

CREATE USER 'member_admin'@'localhost' IDENTIFIED BY 'Password$';

「member_admin」ユーザーが「sample」データベースを利用できるように権限の設定を行います。

GRANT ALL PRIVILEGES ON sample.* TO 'member_admin'@'localhost' IDENTIFIED BY 'Password$';

f:id:ts0818:20170918102246j:plain

ユーザーも追加されています。

Eclipse側で、今回のプロジェクトの「src/main/resources」>「application.properties」 ファイルにデータベースの設定を記述していきます。(自分で、YAMLファイルを作っていれば、そちらに記述していっても問題ないと思われます。)

テックノート – Spring BootでMySQLに接続してJPAを使う方法

 ⇧  yamlファイルでの接続設定は上記サイト様を参考にしてください

 

spring.datasource.url=jdbc:mysql://localhost:3306/sample
spring.datasource.username=member_admin
spring.datasource.password=Password$
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

保存して、データベースの接続設定などが準備できました。

ファイルの編集

それでは、作成していたJavaファイルのほうから編集していきます。

まずは、entityクラスである「Member.java」から。 

package com.example.demo.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name="member")
public class Member implements Serializable {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name="id", nullable=false)
	private Integer id;

	@Column(name="last_name", nullable=false)
	private String lastName;

	@Column(name="first_name", nullable=false)
	private String firstName;

	@Column(name="last_name_kana", nullable=false)
	private String lastNameKana;

	@Column(name="first_name_kana", nullable=false)
	private String firstNameKana;

	@Column(name="email", nullable=false)
	private String email;

	@Column(name="tel", nullable=false)
	private String tel;

	@Column(name="address_level01", nullable=false)
	private String addressLevel01;

	@Column(name="address_level02", nullable=false)
	private String addressLevel02;

	@Column(name="address_line01", nullable=false)
	private String addressLine01;

	@Column(name="address_line02", nullable=false)
	private String addressLine02;
}
    

続いて、repositoryインターフェース「MemberRepository.java」を編集。

package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import com.example.demo.entity.Member;

@Repository
public interface MemberSpecification extends JpaRepository<Member, Integer>, JpaSpecificationExecutor<Member> {

}

続いて、serviceクラス「MemberSpecification.java」を編集。

package com.example.demo.service;

import org.springframework.data.jpa.domain.Specification;

import com.example.demo.entity.Member;

@Service
public class SpecificationService {

	/**
	 *  あいまい検索
	 * @param String searchTerm
	 * @return Specification<member> Predicate
	 */
	public static Specification<member> FuzzySearch(String searchTerm) {
		// ラムダ式で記述すると、引数のデータ型の指定が省略できる
		return (root, query, cb) -> {
			String containsLikePattern = getContainsLikePattern(searchTerm);
			return cb.or(
					cb.like(cb.lower(root.get("lastName")), containsLikePattern),
					cb.like(cb.lower(root.get("firstName")), containsLikePattern),
					cb.like(cb.lower(root.get("lastNameKana")), containsLikePattern),
					cb.like(cb.lower(root.get("firstNameKana")), containsLikePattern),
					cb.like(cb.lower(root.get("email")), containsLikePattern),
					cb.like(cb.lower(root.get("tel")), containsLikePattern),
					cb.like(cb.lower(root.get("addressLevel01")), containsLikePattern),
					cb.like(cb.lower(root.get("addressLevel02")), containsLikePattern),
					cb.like(cb.lower(root.get("addressLine01")), containsLikePattern),
					cb.like(cb.lower(root.get("addressLine02")), containsLikePattern)
			);
		};
	}

	/**
	 *  検索文字列が入力されたとき、されなかったときで調整
	 * @param String searchTerm
	 * @return String 調整された文字列
	 */
	private static String getContainsLikePattern(String searchTerm) {
		if(searchTerm == null || searchTerm.isEmpty()) {
			return "%";
		} else {
			return "%" + searchTerm.toLowerCase() + "%";
		}
	}

}

実際に処理を行う「MemberService.java」を編集

package com.example.demo.service;

import static com.example.demo.service.SpecificationService.*;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specifications;
import org.springframework.stereotype.Service;

import com.example.demo.entity.Member;
import com.example.demo.repository.MemberRepository;

@Service
public class MemberService {

	@Autowired
	MemberRepository repository;

	public List<member> findMembers(String target) {
		return repository.findAll(Specifications
				.where(fuzzySearch(target)));
	}
}

「MemberController.java」でデータの受け取りや画面遷移を記述。

package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.entity.Member;
import com.example.demo.service.MemberService;

@Controller
public class MemberController {

	@Autowired
	MemberService service;

	// 初期表示
	@RequestMapping(value="/", method=RequestMethod.GET)
	public String index() {
		return "index";
	}

	// 検索処理
	@RequestMapping(value="/", method=RequestMethod.POST )
	public ModelAndView search(ModelAndView mov, @RequestParam(name="search", required=false) String search) {
		mov.setViewName("index");
		List<member> members = service.findMembers(search);
		mov.addObject("members", members);
		return mov;
	}

}

最後に、index.htmlを編集

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>あいまい検索</title>
</head>
<body>
  <form th:action="@{/}" method="POST">
    <input type="text" name="search" value="" />
    <input type="submit" value="検索" />
  </form>

  <table border="1" th:if="${members !=null}">
    <thead>
      <tr>
        <th>ID</th><th>名前</th><th>Email</th><th>TEL</th><th>住所</th>
      </tr>
    </thead>
    <tbody>
	    <tr th:each="member : ${members}">
	      <td th:text="${member.id}"></td>
	      <td>
	        <div>
	          <span th:text="${member.lastNameKana}"></span>
	          <span th:text="${member.firstNameKana}"></span>
	        </div>
	        <div>
	          <span th:text="${member.lastName}"></span>
	          <span th:text="${member.firstName}"></span>
	        </div>
	      </td>
	      <td>
	          <span th:text="${member.email}"></span>
	      </td>
	      <td>
	          <span th:text="${member.tel}"></span>
	      </td>
	      <td>
	        <span th:text="${member.addressLevel01}"></span>
	        <span th:text="${member.addressLevel02}"></span>
	        <span th:text="${member.addressLine01}"></span>
	        <span th:text="${member.addressLine02}"></span>
	      </td>
	    </tr>
    </tbody>
  </table>
</body>
</html>

サーバーを起動。プロジェクトを選択した状態で右クリックし、「実行(R)」>「Spring Boot アプリケーション」を選択。

f:id:ts0818:20170918123530j:plain

ブラウザで表示。「http://localhost:8080」にアクセス。

f:id:ts0818:20170918123718j:plain

「沖縄」で検索。

f:id:ts0818:20170918123734j:plain

「スズキ」で検索。

f:id:ts0818:20170918123904j:plain

「区」で検索。

f:id:ts0818:20170918124905j:plain

何も入力しないで検索すると全部取得されます。

f:id:ts0818:20170918123829j:plain

ただ、複数キーワード検索はできてません。

f:id:ts0818:20170918124057j:plain

 

検索ムズイっす....。 

 

Spring Data JPA の Specificationでらくらく動的クエリー - Qiita

Spring Data JPA Tutorial: Creating Database Queries With the JPA Criteria API