27 Spring Mybatisを利用したデータベース操作[4. 検索、全クリア]
今回はMybatisを用いて検索、全クリア処理を記述していきます。
前提条件
この記事はSpringの最低限の知識が必要になります。
また、なるべく分かりやすく書くつもりですが、この記事の目的は自分の勉強のアウトプットであるため所々説明は省略します。
前回まで
前回はMybatisを利用してデータの更新・削除を行いました。
構築環境
-
各バージョン
Spring Boot ver 2.7.5
mybatis-spring-boot-starter ver 2.2.2
Model Mapper ver 3.1.0
jquery ver 3.6.1
bootstrap ver 5.2.2
webjars-locator ver 0.46
thymeleaf-layout-dialect ver 3.0.0
成果物
検索処理
検索結果あり(ユーザーID:Tokyo@xxx.co.jpの場合)
全クリア処理
今回行うこと
UserListFormのデータ型をString型に変換しなくともInteger型で問題なく作動しました。
なので、以下の注意点
は全て無視してください
注意点
今回性別(gender)
をプルダウンメニューを用いて検索しようとしました。テーブルに定義されている項目gender
はNUMBER型
ですが、検索欄に入力された値を受け取るFormクラスのgender
はString型
にしておく必要があります。
package com.example.form;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;
@Data
public class UserListForm {
private String userId;
private String phoneNumber;
private String userName;
@DateTimeFormat(iso=DateTimeFormat.ISO.DATE)
private Date birthday2;
private Integer age;
/* 数値が入る */
private String gender;
}
理由として、HTMLのプルダウンメニューから選択された項目の値(value)を渡すとき、渡される値はString
型であり、Formクラスのgender
のデータ型をIntegerのままにしておくと画面から送られてきた値をFormクラスに格納することができません。
<!-- 選択された値はString型として渡される。例) 男性を選択 ⇒ "0"が渡される -->
<select class="dropdown form-control" th:field="*{gender}">
<option th:value="2"></option>
<option th:value="0">男性</option>
<option th:value="1">女性</option>
</select>
今回は以下の流れに沿って進めていきます。
手順
- SQLの記述
1. マッパー(UserMapper.java)にメソッドを追加
2. UserMapper.xmlにSQLを記述
1. ifタグ
2. whereタグ
3. 検索条件のポイント
1. 曖昧検索
2. null&空文字
3. 日付比較 - サービスの記述
1. UserService.java
2. UserServiceImpl.java - コントローラー(UserListController.java)の修正
1. 検索処理
2. 全クリア処理 - HTML(list.html)の修正
1. SQLの記述
1. マッパー(UserMapper.java)にメソッドを追加
package com.example.repository;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.example.model.MUser;
@Mapper
public interface UserMapper {
package com.example.repository;
import java.util.List;
import com.example.model.MUser;
@Mapper
public interface UserMapper {
/* ユーザー表示(複数件の場合はリスト型で返す) */
public List<MUser> findAllMUser();
/* 検索条件で一致するユーザーを取得 */
public List<MUser> searchMUser(MUser user);
}
2. UserMapper.xmlにSQLを記述
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- Mapperとxmlのマッピング -->
<mapper namespace="com.example.repository.UserMapper">
<!-- マッピング定義(ユーザー) -->
<resultMap type="com.example.model.MUser" id="muser">
<id column="user_id" property="userId"/>
<result column="phone_number" property="phoneNumber"/>
<result column="postal_number1" property="postalNumber1"/>
<result column="postal_number2" property="postalNumber2"/>
<result column="address" property="address"/>
<result column="user_name" property="userName"/>
<result column="birthday1" property="birthday1"/>
<result column="birthday2" property="birthday2"/>
<result column="age" property="age"/>
<result column="account_icon" property="accountIcon"/>
<result column="gender" property="gender"/>
</resultMap>
<!-- 省略 -->
<!-- ユーザー情報全件取得(初期表示用) -->
<select id="findAllMUser" resultType="MUser">
SELECT * FROM M_USER
</select>
<!-- ユーザー情報取得(検索処理用) -->
<select id="searchMUser" resultType="MUser">
SELECT * FROM M_USER
<where>
<if test="userId != null and userId != ''">
user_id like '%' || #{userId} || '%'
</if>
<if test="phoneNumber != null and phoneNumber != ''">
and phone_number = #{phoneNumber}
</if>
<if test="birthday2 != null">
and TO_CHAR(birthday2, 'yyyy-MM-dd') = TO_CHAR(#{birthday2}, 'yyyy-MM-dd')
</if>
<if test="userName != null and userName != ''">
and user_name like '%' || #{userName} || '%'
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="gender != 2">
and gender = #{gender}
</if>
</where>
</select>
<!-- 省略 -->
</mapper>
ポイントは2つになります。
1. ifタグ
ifタグはif文と使い方は基本的に同じです。ifタグ内にtest属性
を追加し、条件式を書きます。
条件式がtureであればifタグで囲まれたSQLが追加されます。
UserId
の条件式では、nullか空文字で無かった場合、user_id like '%' || #{userId} || '%'
で書かれている曖昧検索を実行します。
2. whereタグ
whereタグ内のifタグが1つでもtureとなればwhere句を追加します。つまり、where句が必ず付くかどうか分からない場合にこのタグを使用します。where句が必ず付くSQLを使用する場合であれば、ifタグのみを使用します。
<!-- ユーザー情報取得(検索処理用) -->
<select id="searchMUser" resultType="MUser">
SELECT * FROM M_USER
<where>
<if test="userId != null and userId != ''">
user_id like '%' || #{userId} || '%'
</if>
<if test="phoneNumber != null and phoneNumber != ''">
and phone_number = #{phoneNumber}
</if>
<if test="birthday2 != null">
and TO_CHAR(birthday2, 'yyyy-MM-dd') = TO_CHAR(#{birthday2}, 'yyyy-MM-dd')
</if>
<if test="userName != null and userName != ''">
and user_name like '%' || #{userName} || '%'
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="gender != 2">
and gender = #{gender}
</if>
</where>
</select>
3. 検索条件のポイント
1. 曖昧検索
曖昧検索は以下のように記述します。入力された値#{userId}
を'%' || || '%'
で囲むことで部分一致検索することが可能です。
user_id like '%' || #{userId} || '%'
2. null&空文字
入力フォームがtype="text"
の場合、何も入力していなくとも空文字が入力されたと判断されたしまい、条件文が正しく処理されない場合があるので、nullと空文字を除く条件式をand
で繋いで表現しています。
<if test="userId != null and userId != ''">
3. 日付比較
日付が一致しているかを判定するために一度日付型を文字列型に変更してから比較しまいた。
もっと上手いやり方があると思いますが現状これで進めます。
TO_CHAR(birthday2, 'yyyy-MM-dd') = TO_CHAR(#{birthday2}, 'yyyy-MM-dd')
2. サービスの記述
次にサービスに内容を記述していきます。
1. UserService.java
package com.example.service;
import java.util.List;
import com.example.model.MUser;
public interface UserService {
/* ユーザー取得 */
public List<MUser> getAllMUser();
/* 検索条件に一致するユーザーの取得 */
public List<MUser> getSearchMUser(MUser user);
}
2. UserServiceImpl.java
package com.example.service.impl;
/* 省略 */
@Service
public class UserServiceImpl implements UserService {
/* レポジトリー(UserMapper.java)のDIを注入 */
@Autowired
private UserMapper mapper;
/* ユーザー情報取得 */
@Override
public List<MUser> getAllMUser() {
return mapper.findAllMUser();
}
/* 検索条件で一致するユーザーを取得 */
@Override
public List<MUser> getSearchMUser(MUser user) {
return mapper.searchMUser(user);
}
}
3. コントローラー(UserListController.java)の修正
package com.example.controller;
import java.util.List;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.form.UserListForm;
import com.example.model.MUser;
import com.example.service.UserService;
@Controller
@RequestMapping("/user")
public class UserListController {
@Autowired
private UserService userservice;
@Autowired
private ModelMapper modelMapper;
/* ユーザー一覧画面を表示 */
@GetMapping("/list")
public String getUserList(@ModelAttribute UserListForm form, Model model) {
// formをMUserクラスに変換
MUser user = modelMapper.map(form, MUser.class);
// ユーザー検索
List<MUser> userList = userservice.getAllMUser(user);
// Modelに登録
model.addAttribute("userList", userList);
return "/user/list";
}
/* ユーザー検索処理 */
@PostMapping(value="/list", params="search")
public String postUserList(@ModelAttribute UserListForm form, Model model) {
// formをMUserクラスに変換
MUser user = modelMapper.map(form, MUser.class);
// ユーザー検索
List<MUser> userList = userservice.getSearchMUser(user);
if (userList.isEmpty() == true) {
// 検索結果がない場合のメッセージを設定
model.addAttribute("NoList", "検索結果がありません");
}
// Modelに登録
model.addAttribute("userList", userList);
// ユーザー一覧画面を表示
return "user/list";
}
/* クリア処理 */
@PostMapping(value="/list", params="clear")
public String clearUserList() {
// 一覧表示画面に戻る
return "redirect:/user/list";
}
}
1. 検索処理
@PostMappingにparams属性
を記述することで同じFormタグでも別処理を行うことが可能です。
Formタグから受け取った内容をUserListForm
に格納し、エンティティクラスであるMUser
にコピーします。
その後、先ほど追加したgetSearchMUser
メソッドを続行します。
また、検索結果が1件も得られない場合は「検索結果がありません」と表示できるよう処理を追加します。
/* ユーザー検索処理 */
@PostMapping(value="/list", params="search")
public String postUserList(@ModelAttribute UserListForm form, Model model) {
// formをMUserクラスに変換
MUser user = modelMapper.map(form, MUser.class);
// ユーザー検索
List<MUser> userList = userservice.getSearchMUser(user);
if (userList.isEmpty() == true) {
// 検索結果がない場合のメッセージを設定
model.addAttribute("NoList", "検索結果がありません");
}
// Modelに登録
model.addAttribute("userList", userList);
// ユーザー一覧画面を表示
return "user/list";
}
2. 全クリア処理
全クリア処理は同じ画面にredirectするだけです。
/* クリア処理 */
@PostMapping(value="/list", params="clear")
public String clearUserList() {
// 一覧表示画面に戻る
return "redirect:/user/list";
}
4. HTML(list.html)の修正
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<head>
<title>ユーザー一覧</title>
<!-- 個別CSS読込 -->
<link rel="stylesheet" th:href="@{/css/user/list.css}">
</head>
<body>
<div layout:fragment="content">
<div class="header border-bottom">
<h1 class="h2">ユーザ一覧画面</h1>
</div>
<!-- 検索 -->
<div class="mb-4">
<form id="user-search-form" method="post" th:action="@{/user/list}"
class="form-inline d-flex my-3" th:object="${userListForm}">
<div class="form-group d-flex align-items-center">
<label for="userId" class="mr-2 search-label">ユーザーID</label>
<input type ="text" class="form-control" th:field="*{userId}"/>
</div>
<div class="form-group d-flex align-items-center">
<label for="phoneNumber" class="mr-2 search-label">電話番号</label>
<input type="text" class="form-control" th:field="*{phoneNumber}">
</div>
<div class="form-group d-flex align-items-center">
<label for="userName" class="mr-2 search-label">ユーザー名</label>
<input type="text" class="form-control" th:field="*{userName}">
</div>
<div class="form-group d-flex align-items-center">
<label for="birthday2" class="mr-2 search-label">誕生日</label>
<input type="date" class="form-control" th:field="*{birthday2}">
</div>
<div class="form-group d-flex align-items-center">
<label for="age" class="mr-2 search-label">年齢</label>
<input type="number" class="form-control" th:field="*{age}">
</div>
<div class="form-group d-flex align-items-center">
<label for="gender" class="mr-2 search-label">性別</label>
<select class="dropdown form-control" th:field="*{gender}">
<option th:value="2"></option>
<option th:value="0">男性</option>
<option th:value="1">女性</option>
</select>
</div>
<button class="btn btn-primary mx-1" type="submit" name="search">検索</button>
<button class="btn btn-primary" type="submit" name="clear">クリア</button>
</form>
</div>
<!-- 一覧表示 -->
<div>
<table class="table table-striped table-bordered table-hover table-primary">
<thead class="thead-light">
<tr>
<th class="th-width">ユーザーID</th>
<th class="th-width">電話番号</th>
<th class="th-width">郵便番号</th>
<th class="th-width">住所</th>
<th class="th-width">ユーザー名</th>
<th class="th-width">パスワード</th>
<th class="th-width">誕生日</th>
<th class="th-width">年齢</th>
<th class="th-width">性別</th>
<th class="th-width">詳細</th>
</tr>
</thead>
<tbody>
<tr class="align-middle" th:each="item : ${userList}">
<td th:text="${item.userId}"></td>
<td th:text="${item.phoneNumber}"></td>
<td th:text="${item.postalNumber1} == null ? '登録されていません' : |${item.postalNumber1} - ${item.postalNumber2}|"></td>
<td th:text="${item.address} == null ? '登録されていません' : ${item.address}"></td>
<td th:text="${item.userName}"></td>
<td th:text="${item.password}"></td>
<td th:text="${item.birthday2} == null ? '登録されていません' : ${#dates.format(item.birthday2, 'YYYY/MM/dd')}"></td>
<td th:text="${item.age} == null ? '登録されていません' : ${item.age}"></td>
<div th:switch="${item.gender}">
<td th:case= 0 th:text='男性'></td>
<td th:case= 1 th:text='女性'></td>
<td th:case= "*" th:text='登録されていません'></td>
</div>
<td>
<a class="btn btn-primary" th:href="@{'/user/detail/' + ${item.userId}}">
詳細
</a>
</td>
</tr>
</tbody>
</table>
<p th:if="${NoList}" th:text="${NoList}"></p>
</div>
</div>
</body>
</html>
検索部分は以下になります。
buttonタグのname属性
を追加することにより、同じFormタグ内でも処理を分けることができます
<!-- 検索 -->
<div class="mb-4">
<form id="user-search-form" method="post" th:action="@{/user/list}"
class="form-inline d-flex my-3" th:object="${userListForm}">
<div class="form-group d-flex align-items-center">
<label for="userId" class="mr-2 search-label">ユーザーID</label>
<input type ="text" class="form-control" th:field="*{userId}"/>
</div>
<div class="form-group d-flex align-items-center">
<label for="phoneNumber" class="mr-2 search-label">電話番号</label>
<input type="text" class="form-control" th:field="*{phoneNumber}">
</div>
<div class="form-group d-flex align-items-center">
<label for="userName" class="mr-2 search-label">ユーザー名</label>
<input type="text" class="form-control" th:field="*{userName}">
</div>
<div class="form-group d-flex align-items-center">
<label for="birthday2" class="mr-2 search-label">誕生日</label>
<input type="date" class="form-control" th:field="*{birthday2}">
</div>
<div class="form-group d-flex align-items-center">
<label for="age" class="mr-2 search-label">年齢</label>
<input type="number" class="form-control" th:field="*{age}">
</div>
<div class="form-group d-flex align-items-center">
<label for="gender" class="mr-2 search-label">性別</label>
<select class="dropdown form-control" th:field="*{gender}">
<option th:value="2"></option>
<option th:value="0">男性</option>
<option th:value="1">女性</option>
</select>
</div>
<button class="btn btn-primary mx-1" type="submit" name="search">検索</button>
<button class="btn btn-primary" type="submit" name="clear">クリア</button>
</form>
</div>
最後に
今回は検索、全クリア処理を記述しました。
ポイントとしてはデータの型を合わせることです。
暗黙的に変換される場合もありますが、そうでない場合例外発生の原因にもなるので気をつけましょう。