検索機能を実装しよう

このページでは検索機能を追加していきます。

Mapper

まずはマッパーの設定を追加します。

MemberMapper.java

findByNameLike メソッドを追加します。

@Mapper
public interface MemberMapper {
    List<Member> all();
    List<Member> findByNameLike(String words);
}

members テーブルの name 列に対して引数の文字列で LIKE 検索を発行するメソッドです。

MemberMapper.xml

XML には findByNameLike メソッドに対応する SQL を定義します。

<mapper namespace="com.example.search.mappers.MemberMapper">
    <!-- 中略 -->
    <select id="findByNameLike" resultMap="memberResultMap" parameterType="String">
        SELECT * FROM members WHERE name LIKE '%' || #{words} || '%'
    </select>
</mapper>

all メソッドと異なるのは、findByNameLike メソッドが引数を取ることです。

parameterType に引数の型を記述します。今回は画面で言うと検索窓に入力した文字列が渡されるので String です。

そして、SQL 文では #{引数名} で引数を参照できます。引数名は Mapper インターフェース側で定義した引数名に一致します。

(ちなみに || は SQL の機能で文字列結合を行います。最終的に LIKE '%あいう%' という形で LIKE 検索を行いたいのでこのように記述しています。)

Controller

コントローラーには /api/members/api/members/{words} にあたる処理を追加する必要があります。

既存の MemberController に追記してもいいのですが、あまり長くなると読みづらいので Ajax 用の処理は別のコントローラークラスに分けることにします。

com.example.search.controllersMemberApiController.java を追加してください。

package com.example.search.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.search.domains.Member;
import com.example.search.mappers.MemberMapper;

@Controller
public class MemberApiController {

    private final MemberMapper memberMapper;

    @Autowired
    public MemberApiController(MemberMapper memberMapper) {
        this.memberMapper = memberMapper;
    }

    @GetMapping("/api/members")
    @ResponseBody
    public List<Member> all() {
        List<Member> members = memberMapper.all();
        return members;
    }

    @GetMapping("/api/members/{words}")
    @ResponseBody
    public List<Member> find(@PathVariable String words) {
        List<Member> members = memberMapper.findByNameLike(words);
        return members;
    }
}

ポイント1:URL のパス部分の値を取得する

/api/members/あいう のような URL にリクエストが来る想定ですので、この「あいう」の部分の文字列を取得する必要があります。

まず、@GetMapping の引数の記述を /api/members/{words} というように、取得したい部分を {} で囲みます。

次に、メソッドの引数に @PathVariable アノテーションを付与します。こうすれば {} で囲った部分を引数として受け取ることができます。

ポイント2:JSON レスポンスを返却する

HTML を返却する場合はテンプレート名の文字列を返却しました。

JSON を返却する場合は、まずメソッドに @ResponseBody アノテーションを付与します。

その上で、オブジェクトをそのまま返してしまいます。そうすると自動的に JSON に変換されてレスポンスメッセージが生成されます。

JavaScript ファイル

src/main/resource/static に app.js を追加してください。

// 検索フォームの値が変わるたびに呼ばれる関数
function search(event) {
  // 検索フォームに入力された値をイベントオブジェクトから取得する
  const words = event.target.value;

  // メンバー一覧を表現する要素を取得する
  const memberList = document.getElementById('memberList');

  // APIのURLを作成する
  const url = '/api/members/' + words;

  // JavaScriptからサーバと通信するためのオブジェクト
  const xhr = new XMLHttpRequest();

  // リクエストを初期化する
  xhr.open('GET', url, true);

  // load(通信完了)イベントに関数を登録する
  // この関数(第2引数)は登録された時点では実行されない
  // あるイベントが発生したタイミングで実行されるこのような関数を「イベントリスナー」と呼ぶ
  xhr.addEventListener('load', function() {
    // サーバからのレスポンス(文字列)をJSONに変換する
    const members = JSON.parse(xhr.responseText);
    // メンバー一覧を書き換える
    let html = '';
    members.forEach(function(member) {
      html += '<li class="list-group-item">' + member.name + '</li>';
    });
    memberList.innerHTML = html;
  });

  // リクエスト発行
  xhr.send();
}

// HTMLの読み込みが完了したタイミングで実行される関数を登録する
window.onload = function() {
  // 検索フォーム要素を取得する
  const wordsInput = document.getElementById('words');
  // inputイベントに上で定義したsearch関数を登録する
  wordsInput.addEventListener('input', search, false);
}

処理の内容をコメントに記載しました。

テンプレートファイル

index.htmlapp.js の読み込みを追記してください。

  <script src="/app.js"></script>
</body>
</html>

アプリケーションを再起動する

アプリケーションを再起動して機能を確かめてみましょう。