検索欄が1つのときのあいまい検索を実装してみました。検索するテーブルは1つですけど...それではいってみましょー。
・Spring Data JPA でのクエリー実装方法まとめ - Qiita
⇧ 上記サイト様の説明にありますように、
といろいろな方法がありますが、今回は「CriteriaAPI」ですかね。
Spring Bootでプロジェクトの作成
何はともあれ、プロジェクトを作っていきましょう。
Eclipseを起動し、「ファイル(F)」>「新規(N)」>「Spring スターター・プロジェクト」を選択。
「名前」を入力し、「次へ(N)>」をクリック。
「コア」>「Lombok」、「SQL」>「JPA」、「SQL」>「MySQL」、「テンプレート・エンジン」>「Thymeleaf」、「Web」>「Web」を選択し、「次へ(N)>」をクリック。
「完了(F)」をクリック。
ファイルを作成
こんな感じの構成になるようにフォルダ、ファイルを作成していきます。
まずは、フォルダ作成。「src/main/java」直下のフォルダを選択した状態で右クリックし、「新規(W)」>「フォルダー」を選択。
「フォルダー名(N):」を入力し、「完了(F)」をクリック。
同じ流れで、フォルダを作成し、「controller」「entity」「repository」「service」の4つのフォルダを用意します。
フォルダを作り終わったら、まずは「controller」ふぁおるだを選択し、「新規(W)」>「クラス」を選択します。
「名前(M):」を入力し、「完了(F)」。
続いて、「entity」フォルダーを選択した状態で右クリックし、「新規(W)」>「クラス」を選択します。
「名前(M):」を入力し、「インターフェイス(I):」の「追加(A)...」をクリック。
「一致する項目(M):」から「Serializable - java.io」を選択し、「OK」。
インターフェースが追加されているのを確認し、「完了(F)」。
続いて、「repository」フォルダを選択した状態で右クリックし、「新規(W)」>「インターフェース」をクリック。
「名前(M):」を入力し、「拡張インターフェイス(I):」の「追加(A)....」を選択します。
「一致する項目(M):」から「JpaRepository」「JpaSpecificationExcutor」を選択します。(「Ctrl」キーを押しながらクリックで複数選択できます。)
「インターフェース」が追加されたのを確認し、「完了(F)」。(今回は、Specificationというものを使うので、「JpaSpecificationExcutor」というものも追加しています。)コード上でエラーが出た状態になりますが、後で編集していきます。
続いて、「service」フォルダを選択した状態で右クリックし、「新規(W)」>「クラス」を選択。
「名前(M):」を入力し、「完了(F)」。
今回、Specificationを利用するので、「service」フォルダにもう1つファイルを作成します。
「名前(M):」を入力し、「完了(F)」。
Javaファイルができたので、画面表示側を担うHTMLファイルを作っていきます。場所が変わって、「src/main/resources」>「templates」を選択した状態で右クリックし、「新規(W)」>「その他(O)...」を選択します。
「Web」>「HTMLファイル」を選択し「次へ(N)>」。
「ファイル名(M):」を入力し、「次へ(N)」。
「完了(F)」をクリック。
データベースを用意
ファイルの編集の前に、データベースを用意します。コマンドプロンプトで MySQLに接続(ログイン)します。
mysql -u root -p
データベース一覧
SHOW DATABASES;
今回は既に作成していた、「sample」データベースを使用することにします。
USE sample; SHOW TABLES
「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) );
データを入れておきます。
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');
MySQLのユーザー一覧を確認。
SELECT Host, User FROM mysql.user;
ユーザーを追加してみます。
CREATE USER 'member_admin'@'localhost' IDENTIFIED BY 'Password$';
「member_admin」ユーザーが「sample」データベースを利用できるように権限の設定を行います。
GRANT ALL PRIVILEGES ON sample.* TO 'member_admin'@'localhost' IDENTIFIED BY 'Password$';
ユーザーも追加されています。
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 アプリケーション」を選択。
ブラウザで表示。「http://localhost:8080」にアクセス。
「沖縄」で検索。
「スズキ」で検索。
「区」で検索。
何も入力しないで検索すると全部取得されます。
ただ、複数キーワード検索はできてません。
検索ムズイっす....。
・Spring Data JPA の Specificationでらくらく動的クエリー - Qiita
・Spring Data JPA Tutorial: Creating Database Queries With the JPA Criteria API