Spring Boot DB連携

Spring BootのWEB画面上でデータ検索機能を追加してみた(ソースコード編)

今回も引き続き、Spring BootのWEB画面上でデータ検索機能を追加した実装について述べる。ここでは、具体的なサンプルプログラムのソースコードを共有する。

前提条件

下記記事を参照のこと。

Spring BootのWEB画面上でデータ検索機能を追加してみた(完成イメージと前提条件)今回は、Spring BootのWEB画面上でデータ検索機能を追加してみたので、そのサンプルプログラムを共有する。 検索機能を行う...

作成したサンプルプログラムの内容

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上図の赤枠は、前提条件に記載したソースコードと比較し、変更になったソースコードを示す。赤枠のソースコードについては今後記載する。

まず、検索用のFormオブジェクトは下記の通り。名前・生年月日(From,To)・性別の項目とプルダウンを設定している。

package com.example.demo;

import lombok.Data;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Formオブジェクトのクラス
 */
@Data
public class SearchForm {

    /** 検索用名前 */
    private String searchName;

    /** 生年月日_年_from */
    private String fromBirthYear;

    /** 生年月日_月_from */
    private String fromBirthMonth;

    /** 生年月日_日_from */
    private String fromBirthDay;

    /** 生年月日_年_to */
    private String toBirthYear;

    /** 生年月日_月_to */
    private String toBirthMonth;

    /** 生年月日_日_to */
    private String toBirthDay;

    /** 検索用性別 */
    private String searchSex;

    /** 生年月日_月のMapオブジェクト */
    public Map<String,String> getMonthItems(){
        Map<String, String> monthMap = new LinkedHashMap<String, String>();
        for(int i = 1; i <= 12; i++){
            monthMap.put(String.valueOf(i), String.valueOf(i));
        }
        return monthMap;
    }

    /** 生年月日_日のMapオブジェクト */
    public Map<String,String> getDayItems(){
        Map<String, String> dayMap = new LinkedHashMap<String, String>();
        for(int i = 1; i <= 31; i++){
            dayMap.put(String.valueOf(i), String.valueOf(i));
        }
        return dayMap;
    }

    /** 性別のMapオブジェクト */
    public Map<String,String> getSexItems(){
        Map<String, String> sexMap = new LinkedHashMap<String, String>();
        sexMap.put("1", "男");
        sexMap.put("2", "女");
        return sexMap;
    }
}
「EaseUS Todo Backup」は様々な形でバックアップ取得が行える便利ツールだったパソコン内のデータを、ファイル/パーティション/ディスク等の様々な単位でバックアップしたり、バックアップのスケジュール設定や暗号化設定も...

次に、データベースにアクセスするMapperクラスは以下の通り。全件検索するfindAllメソッドを、検索条件指定により検索するfindBySearchFormメソッドに変更している。

package com.example.demo;

import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;

@Mapper
public interface UserDataMapper {

    /**
     * ユーザーデータテーブル(user_data)から検索条件に合うデータを取得する
     * @param searchForm 検索用Formオブジェクト
     * @return ユーザーデータテーブル(user_data)の検索条件に合うデータ
     */
    Collection<UserData> findBySearchForm(SearchForm searchForm);

    /**
     * 指定したIDをもつユーザーデータテーブル(user_data)のデータを取得する
     * @param id ID
     * @return ユーザーデータテーブル(user_data)の指定したIDのデータ
     */
    UserData findById(Long id);

    /**
     * 指定したIDをもつユーザーデータテーブル(user_data)のデータを削除する
     * @param id ID
     */
    void deleteById(Long id);

    /**
     * 指定したユーザーデータテーブル(user_data)のデータを追加する
     * @param userData ユーザーデータテーブル(user_data)の追加データ
     */
    void create(UserData userData);

    /**
     * 指定したユーザーデータテーブル(user_data)のデータを更新する
     * @param userData ユーザーデータテーブル(user_data)の更新データ
     */
    void update(UserData userData);

    /**
     * ユーザーデータテーブル(user_data)の最大値IDを取得する
     * @return ユーザーデータテーブル(user_data)の最大値ID
     */
    long findMaxId();
}
freelance hubを利用して10万件を超える案件情報からJava Spring案件を検索してみたfreelance hubは、レバテックフリーランスやフリエン(furien)を始めとした多くのフリーランスエージェントの案件をまとめて...

また、Mapperクラスに対応するXMLファイルは以下の通り。findBySearchFormメソッド内のifタグにより、条件分岐を利用した動的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 namespace="com.example.demo.UserDataMapper">
    <select id="findBySearchForm" parameterType="com.example.demo.SearchForm" 
            resultType="com.example.demo.UserData">
        SELECT u.id, u.name, u.birth_year as birthY, u.birth_month as birthM
               , u.birth_day as birthD, u.sex as sex, m.sex_value as sex_value
        FROM USER_DATA u, M_SEX m
        WHERE u.sex = m.sex_cd
        <if test="searchName != null and searchName != ''">
            AND u.name like '%' || #{searchName} || '%'
        </if>
        <if test="fromBirthYear != null and fromBirthYear != ''">
            AND #{fromBirthYear} || lpad(#{fromBirthMonth}, 2, '0') 
                    || lpad(#{fromBirthDay}, 2, '0')
               &lt;= u.birth_year || lpad(u.birth_month, 2, '0') 
                    || lpad(u.birth_day, 2, '0')
        </if>
        <if test="toBirthYear != null and toBirthYear != ''">
            AND u.birth_year || lpad(u.birth_month, 2, '0') 
                    || lpad(u.birth_day, 2, '0')
               &lt;= #{toBirthYear} || lpad(#{toBirthMonth}, 2, '0') 
                    || lpad(#{toBirthDay}, 2, '0')
        </if>
        <if test="searchSex != null and searchSex != ''">
            AND u.sex = #{searchSex}
        </if>
        ORDER BY u.id
    </select>
    <select id="findById" resultType="com.example.demo.UserData">
        SELECT id, name, birth_year as birthY
               , birth_month as birthM , birth_day as birthD, sex
        FROM USER_DATA WHERE id = #{id}
    </select>
    <delete id="deleteById" parameterType="java.lang.Long">
        DELETE FROM USER_DATA WHERE id = #{id}
    </delete>
    <insert id="create" parameterType="com.example.demo.UserData">
        INSERT INTO USER_DATA ( id, name, birth_year, birth_month, birth_day, sex )
        VALUES (#{id}, #{name}, #{birthY}, #{birthM}, #{birthD}, #{sex})
    </insert>
    <update id="update" parameterType="com.example.demo.UserData">
        UPDATE USER_DATA SET name = #{name}, birth_year = #{birthY}
            , birth_month = #{birthM}, birth_day = #{birthD}, sex = #{sex}
        WHERE id = #{id}
    </update>
    <select id="findMaxId" resultType="long">
        SELECT NVL(max(id), 0) FROM USER_DATA
    </select>
</mapper>
「HD Video Converter Factory Pro」は動画の形式変換や編集・録画等を行える便利ツールだった動画の形式変換や編集・録画等を行える便利ツールの一つに、「HD Video Converter Factory Pro」があります。ここ...

さらに、検索画面「search.html」の内容は以下の通り。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <link th:href="@{/demo.css}" rel="stylesheet" type="text/css" />
    <title>index page</title>
</head>
<body>
    <p>検索条件を指定し、「検索」ボタンを押下してください。</p><br/>
    <form method="post" th:action="@{/search}" th:object="${searchForm}">
        <span th:if="*{#fields.hasErrors('fromBirthYear')}"
              th:errors="*{fromBirthYear}" class="errorMessage"></span>
        <span th:if="*{#fields.hasErrors('toBirthYear')}"
              th:errors="*{toBirthYear}" class="errorMessage"></span>
        <table border="1" cellpadding="5">
            <tr>
                <th>名前</th>
                <td><input type="text" th:value="*{searchName}" th:field="*{searchName}" /></td>
            </tr>
            <tr>
                <th>生年月日</th>
                <td><input type="text" th:value="*{fromBirthYear}" size="4"
                           maxlength="4" th:field="*{fromBirthYear}" th:errorclass="fieldError" />年
                    <select th:field="*{fromBirthMonth}" th:errorclass="fieldError">
                        <option value=""></option>
                        <option th:each="item : *{getMonthItems()}"
                                th:value="${item.key}" th:text="${item.value}"/>
                    </select>月
                    <select th:field="*{fromBirthDay}" th:errorclass="fieldError">
                        <option value=""></option>
                        <option th:each="item : *{getDayItems()}"
                                th:value="${item.key}" th:text="${item.value}"/>
                    </select>日~
                    <input type="text" th:value="*{toBirthYear}" size="4"
                           maxlength="4" th:field="*{toBirthYear}" th:errorclass="fieldError" />年
                    <select th:field="*{toBirthMonth}" th:errorclass="fieldError">
                        <option value=""></option>
                        <option th:each="item : *{getMonthItems()}"
                                th:value="${item.key}" th:text="${item.value}"/>
                    </select>月
                    <select th:field="*{toBirthDay}" th:errorclass="fieldError">
                        <option value=""></option>
                        <option th:each="item : *{getDayItems()}"
                                th:value="${item.key}" th:text="${item.value}"/>
                    </select>日
                </td>
            </tr>
            <tr>
                <th>性別</th>
                <td>
                    <select th:field="*{searchSex}">
                        <option value=""></option>
                        <option th:each="item : *{getSexItems()}"
                                th:value="${item.key}" th:text="${item.value}"/>
                    </select>
                </td>
            </tr>
        </table>
        <br/><br/>
        <input type="submit" value="検索" /><br/><br/>
        <input type="button" value="閉じる" onclick="window.close();" />
    </form>
</body>
</html>



その他、検索用Formのチェック処理を行うメソッド「checkSearchForm」をユーティリティクラスに追加している。

package com.example.demo;

import java.time.LocalDate;
import java.time.chrono.JapaneseChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.util.Locale;

public class DateCheckUtil {

    /** 日付のフォーマット */
    private final static String dateFormat = "uuuuMMdd";

    /**
     * 日付チェック処理を行う
     * @param year 年
     * @param month 月
     * @param day 日
     * @return 判定結果(1:年が空、2:月が空、3:日が空、4:年月日が不正、
     *                  5:年月日が未来日、0:正常)
     */
    public static int checkDate(String year, String month, String day){
        if(isEmpty(year)){
            return 1;
        }
        if(isEmpty(month)){
            return 2;
        }
        if(isEmpty(day)){
            return 3;
        }
        String dateStr = year + addZero(month) + addZero(day);
        if(!isCorrectDate(dateStr, dateFormat)){
            return 4;
        }
        if(isFutureDate(dateStr, dateFormat)){
            return 5;
        }
        return 0;
    }

    /**
     * 検索用Formオブジェクトのチェック処理行う
     * @param searchForm 検索用Formオブジェクト
     * @return 判定結果(1:生年月日_fromが不正、2:生年月日_toが不正、
     *                  3:生年月日_from>生年月日_to、0:正常)
     */
    public static int checkSearchForm(SearchForm searchForm){
        //生年月日_fromが不正な場合
        if(!checkSearchFormBirthday(searchForm.getFromBirthYear()
                , searchForm.getFromBirthMonth(), searchForm.getFromBirthDay())){
            return 1;
        }
        //生年月日_toが不正な場合
        if(!checkSearchFormBirthday(searchForm.getToBirthYear()
                , searchForm.getToBirthMonth(), searchForm.getToBirthDay())){
            return 2;
        }
        //生年月日_from>生年月日_toの場合
        if(!isEmpty(searchForm.getFromBirthYear()) 
                     && !isEmpty(searchForm.getToBirthYear())){
            String fromBirthDay = searchForm.getFromBirthYear()
                    + addZero(searchForm.getFromBirthMonth()) 
                    + addZero(searchForm.getFromBirthDay());
            String toBirthDay = searchForm.getToBirthYear()
                    + addZero(searchForm.getToBirthMonth()) 
                    + addZero(searchForm.getToBirthDay());
            if(fromBirthDay.compareTo(toBirthDay) > 0){
                return 3;
            }
        }
        //正常な場合
        return 0;
    }

    /**
     * 検索用Form内の日付をチェックする
     * @param year 年
     * @param month 月
     * @param day 日
     * @return 日付チェック結果
     */
    private static boolean checkSearchFormBirthday(
                               String year, String month, String day){
        //年・月・日が全て未指定の場合はチェックOKとする
        if(isEmpty(year) && isEmpty(month) && isEmpty(day)){
            return true;
        }
        //年・月・日が全て指定されている場合は、日付が正しい場合にチェックOKとする
        if(!isEmpty(year) && !isEmpty(month) && !isEmpty(day)){
            String dateStr = year + addZero(month) + addZero(day);
            if(isCorrectDate(dateStr, dateFormat)){
                return true;
            }
            return false;
        }
        //年・月・日が指定あり/指定なしで混在している場合はチェックNGとする
        return false;
    }

    /**
     * DateTimeFormatterを利用して日付チェックを行う
     * @param dateStr チェック対象文字列
     * @param dateFormat 日付フォーマット
     * @return 日付チェック結果
     */
    private static boolean isCorrectDate(String dateStr, String dateFormat){
        if(isEmpty(dateStr) || isEmpty(dateFormat)){
            return false;
        }
        //日付と時刻を厳密に解決するスタイルで、DateTimeFormatterオブジェクトを作成
        DateTimeFormatter df = DateTimeFormatter.ofPattern(dateFormat)
                .withResolverStyle(ResolverStyle.STRICT);
        try{
            //チェック対象文字列をLocalDate型の日付に変換できれば、チェックOKとする
            LocalDate.parse(dateStr, df);
            return true;
        }catch(Exception e){
            return false;
        }
    }

    /**
     * 日付の文字列が未来日かどうかを判定する
     * @param dateStr チェック対象文字列
     * @param dateFormat 日付フォーマット
     * @return 判定結果
     */
    private static boolean isFutureDate(String dateStr, String dateFormat){
        if(!isCorrectDate(dateStr, dateFormat)){
            return false;
        }
        LocalDate dateStrDate = convertStrToLocalDate(dateStr, dateFormat);
        LocalDate now = LocalDate.now();
        if(dateStrDate.isAfter(now)){
            return true;
        }
        return false;
    }

    /**
     * 日付の文字列を日付型に変換した結果を返す
     * @param dateStr 日付の文字列
     * @param dateFormat 日付のフォーマット
     * @return 変換後の文字列
     */
    private static LocalDate convertStrToLocalDate(String dateStr, String dateFormat){
        if(isEmpty(dateStr) || isEmpty(dateFormat)){
            return null;
        }
        //日付と時刻を厳密に解決するスタイルで、暦体系は和暦体系で、
        //DateTimeFormatterオブジェクトを作成
        DateTimeFormatter df = DateTimeFormatter.ofPattern(dateFormat, Locale.JAPAN)
                .withChronology(JapaneseChronology.INSTANCE)
                .withResolverStyle(ResolverStyle.STRICT);
        //日付の文字列をLocalDate型に変換して返却
        return LocalDate.parse(dateStr, df);
    }

    /**
     * 数値文字列が1桁の場合、頭に0を付けて返す
     * @param intNum 数値文字列
     * @return 変換後数値文字列
     */
    private static String addZero(String intNum){
        if(isEmpty(intNum)){
            return intNum;
        }
        if(intNum.length() == 1){
            return "0" + intNum;
        }
        return intNum;
    }

    /**
     * 引数の文字列がnull、空文字かどうかを判定する
     * @param str チェック対象文字列
     * @return 文字列チェック結果
     */
    public static boolean isEmpty(String str){
        if(str == null || "".equals(str)){
            return true;
        }
        return false;
    }
}



さらに、コントローラクラスの内容は以下の通り。searchメソッドに、検索用Formのチェック処理・検索処理を追加している。

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.support.SessionStatus;
import java.util.ArrayList;
import java.util.List;

@Controller
@SessionAttributes(types = {DemoForm.class, SearchForm.class})
public class DemoController {

    /**
     * Demoサービスクラスへのアクセス
     */
    @Autowired
    private DemoService demoService;

    /**
     * ユーザーデータテーブル(user_data)のデータを取得して返却する
     * @return ユーザーデータリスト
     */
    @ModelAttribute("demoFormList")
    public List<DemoForm> userDataList(){
        List<DemoForm> demoFormList = new ArrayList<>();
        return demoFormList;
    }

    /**
     * 追加・更新用Formオブジェクトを初期化して返却する
     * @return 追加・更新用Formオブジェクト
     */
    @ModelAttribute("demoForm")
    public DemoForm createDemoForm(){
        DemoForm demoForm = new DemoForm();
        return demoForm;
    }

    /**
     * 検索用Formオブジェクトを初期化して返却する
     * @return 検索用Formオブジェクト
     */
    @ModelAttribute("searchForm")
    public SearchForm createSearchForm(){
        SearchForm searchForm = new SearchForm();
        return searchForm;
    }

    /**
     * 初期表示(検索)画面に遷移する
     * @return 検索画面へのパス
     */
    @RequestMapping("/")
    public String index(){
        return "search";
    }

    /**
     * 検索処理を行い、一覧画面に遷移する
     * @param searchForm 検索用Formオブジェクト
     * @param model Modelオブジェクト
     * @param result バインド結果
     * @return 一覧画面へのパス
     */
    @PostMapping("/search")
    public String search(SearchForm searchForm, Model model, BindingResult result){
        //検索用Formオブジェクトのチェック処理
        String returnVal = demoService.checkSearchForm(searchForm, result);
        if(returnVal != null){
            return returnVal;
        }
        //ユーザーデータリストを取得
        List<DemoForm> demoFormList = demoService.demoFormList(searchForm);
        //ユーザーデータリストを更新
        model.addAttribute("demoFormList", demoFormList);
        return "list";
    }

    /**
     * 更新処理を行う画面に遷移する
     * @param id 更新対象のID
     * @param model Modelオブジェクト
     * @return 入力・更新画面へのパス
     */
    @GetMapping("/update")
    public String update(@RequestParam("id") String id, Model model){
        //更新対象のユーザーデータを取得
        DemoForm demoForm = demoService.findById(id);
        //ユーザーデータを更新
        model.addAttribute("demoForm", demoForm);
        return "input";
    }

    /**
     * 削除確認画面に遷移する
     * @param id 更新対象のID
     * @param model Modelオブジェクト
     * @return 削除確認画面へのパス
     */
    @GetMapping("/delete_confirm")
    public String delete_confirm(@RequestParam("id") String id, Model model){
        //削除対象のユーザーデータを取得
        DemoForm demoForm = demoService.findById(id);
        //ユーザーデータを更新
        model.addAttribute("demoForm", demoForm);
        return "confirm_delete";
    }

    /**
     * 削除処理を行う
     * @param demoForm 追加・更新用Formオブジェクト
     * @return 一覧画面の表示処理
     */
    @PostMapping(value = "/delete", params = "next")
    public String delete(DemoForm demoForm){
        //指定したユーザーデータを削除
        demoService.deleteById(demoForm.getId());
        //一覧画面に遷移
        return "redirect:/to_index";
    }

    /**
     * 削除完了後に一覧画面に戻る
     * @param searchForm 検索用Formオブジェクト
     * @param model Modelオブジェクト
     * @return 一覧画面
     */
    @GetMapping("/to_index")
    public String toIndex(SearchForm searchForm, Model model){
        //一覧画面に遷移
        //ユーザーデータリストを取得
        List<DemoForm> demoFormList = demoService.demoFormList(searchForm);
        //ユーザーデータリストを更新
        model.addAttribute("demoFormList", demoFormList);
        return "list";
    }

    /**
     * 削除確認画面から一覧画面に戻る
     * @param model Modelオブジェクト
     * @param searchForm 検索用Formオブジェクト
     * @return 一覧画面
     */
    @PostMapping(value = "/delete", params = "back")
    public String confirmDeleteBack(Model model, SearchForm searchForm){
        //一覧画面に遷移
        //ユーザーデータリストを取得
        List<DemoForm> demoFormList = demoService.demoFormList(searchForm);
        //ユーザーデータリストを更新
        model.addAttribute("demoFormList", demoFormList);
        return "list";
    }

    /**
     * 追加処理を行う画面に遷移する
     * @param model Modelオブジェクト
     * @return 入力・更新画面へのパス
     */
    @PostMapping(value = "/add", params = "next")
    public String add(Model model){
        model.addAttribute("demoForm", new DemoForm());
        return "input";
    }

    /**
     * 追加処理を行う画面から検索画面に戻る
     * @return 検索画面へのパス
     */
    @PostMapping(value = "/add", params = "back")
    public String addBack(){
        return "search";
    }

    /**
     * エラーチェックを行い、エラーが無ければ確認画面に遷移し、
     * エラーがあれば入力画面のままとする
     * @param demoForm Formオブジェクト
     * @param result バインド結果
     * @return 確認画面または入力画面へのパス
     */
    @PostMapping(value = "/confirm", params = "next")
    public String confirm(@Validated DemoForm demoForm, BindingResult result){
        //チェック処理を行い、画面遷移する
        return demoService.checkForm(demoForm, result, "confirm");
    }

    /**
     * 一覧画面に戻る
     * @param model Modelオブジェクト
     * @param searchForm 検索用Formオブジェクト
     * @return 一覧画面の表示処理
     */
    @PostMapping(value = "/confirm", params = "back")
    public String confirmBack(Model model, SearchForm searchForm){
        //ユーザーデータリストを取得
        List<DemoForm> demoFormList = demoService.demoFormList(searchForm);
        //ユーザーデータリストを更新
        model.addAttribute("demoFormList", demoFormList);
        return "list";
    }

    /**
     * 完了画面に遷移する
     * @param demoForm 追加・更新用Formオブジェクト
     * @param result バインド結果
     * @return 完了画面
     */
    @PostMapping(value = "/send", params = "next")
    public String send(@Validated DemoForm demoForm, BindingResult result){
        //チェック処理を行い、エラーがなければ、更新・追加処理を行う
        String normalPath = "redirect:/complete";
        String checkPath = demoService.checkForm(demoForm, result, "redirect:/complete");
        if(normalPath.equals(checkPath)){
            demoService.createOrUpdate(demoForm);
        }
        return checkPath;
    }

    /**
     * 完了画面に遷移する
     * @param sessionStatus セッションステータス
     * @return 完了画面
     */
    @GetMapping("/complete")
    public String complete(SessionStatus sessionStatus){
        //セッションオブジェクトを破棄
        sessionStatus.setComplete();
        return "complete";
    }

    /**
     * 入力画面に戻る
     * @return 入力画面
     */
    @PostMapping(value = "/send", params = "back")
    public String sendBack(){
        return "input";
    }

}

https://www.purin-it.com/doctor-homenet

また、サービスクラスとその実装クラスは以下の通り。検索処理を呼び出すdemoFormListメソッドと、検索用Formのチェック処理を呼び出すcheckSearchFormメソッドを追加している。

package com.example.demo;

import org.springframework.validation.BindingResult;
import java.util.List;

public interface DemoService {

    /**
     * ユーザーデータリストを取得
     * @param searchForm 検索用Formオブジェクト
     * @return ユーザーデータリスト
     */
    List<DemoForm> demoFormList(SearchForm searchForm);

    /**
     * 引数のIDに対応するユーザーデータを取得
     * @param id ID
     * @return ユーザーデータ
     */
    DemoForm findById(String id);

    /**
     * 引数のIDに対応するユーザーデータを削除
     * @param id ID
     */
    void deleteById(String id);

    /**
     * 引数のユーザーデータがあれば更新し、無ければ削除
     * @param demoForm 追加・更新用Formオブジェクト
     */
    void createOrUpdate(DemoForm demoForm);

    /**
     * 追加・更新用Formオブジェクトのチェック処理を行い、画面遷移先を返す
     * @param demoForm 追加・更新用Formオブジェクト
     * @param result バインド結果
     * @param normalPath 正常時の画面遷移先
     * @return 画面遷移先
     */
    String checkForm(DemoForm demoForm, BindingResult result, String normalPath);

    /**
     * 検索用Formオブジェクトのチェック処理を行い、画面遷移先を返す
     * @param searchForm 検索用Formオブジェクト
     * @param result バインド結果
     * @return 画面遷移先
     */
    String checkSearchForm(SearchForm searchForm, BindingResult result);
}



package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Service
public class DemoServiceImpl implements DemoService{

    /**
     * ユーザーデータテーブル(user_data)へアクセスするマッパー
     */
    @Autowired
    private UserDataMapper mapper;

    /**
     * {@inheritDoc}
     */
    @Override
    public List<DemoForm> demoFormList(SearchForm searchForm) {
        List<DemoForm> demoFormList = new ArrayList<>();
        //ユーザーデータテーブル(user_data)から検索条件に合うデータを取得する
        Collection<UserData> userDataList = mapper.findBySearchForm(searchForm);
        for (UserData userData : userDataList) {
            demoFormList.add(getDemoForm(userData));
        }
        return demoFormList;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DemoForm findById(String id) {
        Long longId = stringToLong(id);
        UserData userData = mapper.findById(longId);
        return getDemoForm(userData);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(readOnly = false)
    public void deleteById(String id){
        Long longId = stringToLong(id);
        mapper.deleteById(longId);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(readOnly = false)
    public void createOrUpdate(DemoForm demoForm){
        //更新・追加処理を行うエンティティを生成
        UserData userData = getUserData(demoForm);
        //追加・更新処理
        if(demoForm.getId() == null){
            userData.setId(mapper.findMaxId() + 1);
            mapper.create(userData);
        }else{
            mapper.update(userData);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String checkForm(DemoForm demoForm, BindingResult result, String normalPath){
        //formオブジェクトのチェック処理を行う
        if(result.hasErrors()){
            //エラーがある場合は、入力画面のままとする
            return "input";
        }
        //生年月日の日付チェック処理を行う
        //エラーがある場合は、エラーメッセージ・エラーフィールドの設定を行い、
        //入力画面のままとする
        int checkDate = DateCheckUtil.checkDate(demoForm.getBirthYear()
                , demoForm.getBirthMonth(), demoForm.getBirthDay());
        switch(checkDate){
            case 1:
                //生年月日_年が空文字の場合のエラー処理
                result.rejectValue("birthYear", "validation.date-empty"
                        , new String[]{"生年月日_年"}, "");
                return "input";
            case 2:
                //生年月日_月が空文字の場合のエラー処理
                result.rejectValue("birthMonth", "validation.date-empty"
                        , new String[]{"生年月日_月"}, "");
                return "input";
            case 3:
                //生年月日_日が空文字の場合のエラー処理
                result.rejectValue("birthDay", "validation.date-empty"
                        , new String[]{"生年月日_日"}, "");
                return "input";
            case 4:
                //生年月日の日付が不正な場合のエラー処理
                result.rejectValue("birthYear", "validation.date-invalidate");
                //生年月日_月・生年月日_日は、エラーフィールドの設定を行い、
                //メッセージを空文字に設定している
                result.rejectValue("birthMonth", "validation.empty-msg");
                result.rejectValue("birthDay", "validation.empty-msg");
                return "input";
            case 5:
                //生年月日の日付が未来日の場合のエラー処理
                result.rejectValue("birthYear", "validation.date-future");
                //生年月日_月・生年月日_日は、エラーフィールドの設定を行い、
                //メッセージを空文字に設定している
                result.rejectValue("birthMonth", "validation.empty-msg");
                result.rejectValue("birthDay", "validation.empty-msg");
                return "input";
            default:
                //性別が不正に書き換えられていないかチェックする
                if(!demoForm.getSexItems().keySet().contains(demoForm.getSex())){
                    result.rejectValue("sex", "validation.sex-invalidate");
                    return "input";
                }
                //エラーチェックに問題が無いので、正常時の画面遷移先に遷移
                return normalPath;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String checkSearchForm(SearchForm searchForm, BindingResult result){
        int checkDate =DateCheckUtil.checkSearchForm(searchForm);
        switch (checkDate){
            case 1:
                //生年月日_fromが不正な場合のエラー処理
                result.rejectValue("fromBirthYear", "validation.date-invalidate-from");
                result.rejectValue("fromBirthMonth", "validation.empty-msg");
                result.rejectValue("fromBirthDay", "validation.empty-msg");
                return "search";
            case 2:
                //生年月日_toが不正な場合のエラー処理
                result.rejectValue("toBirthYear", "validation.date-invalidate-to");
                result.rejectValue("toBirthMonth", "validation.empty-msg");
                result.rejectValue("toBirthDay", "validation.empty-msg");
                return "search";
            case 3:
                //生年月日_from>生年月日_toの場合のエラー処理
                result.rejectValue("fromBirthYear", "validation.date-invalidate-from-to");
                result.rejectValue("fromBirthMonth", "validation.empty-msg");
                result.rejectValue("fromBirthDay", "validation.empty-msg");
                result.rejectValue("toBirthYear", "validation.empty-msg");
                result.rejectValue("toBirthMonth", "validation.empty-msg");
                result.rejectValue("toBirthDay", "validation.empty-msg");
                return "search";
            default:
                //正常な場合はnullを返却
                return null;
        }
    }

    /**
     * DemoFormオブジェクトに引数のユーザーデータの各値を設定する
     * @param userData ユーザーデータ
     * @return DemoFormオブジェクト
     */
    private DemoForm getDemoForm(UserData userData){
        if(userData == null){
            return null;
        }
        DemoForm demoForm = new DemoForm();
        demoForm.setId(String.valueOf(userData.getId()));
        demoForm.setName(userData.getName());
        demoForm.setBirthYear(String.valueOf(userData.getBirthY()));
        demoForm.setBirthMonth(String.valueOf(userData.getBirthM()));
        demoForm.setBirthDay(String.valueOf(userData.getBirthD()));
        demoForm.setSex(userData.getSex());
        demoForm.setSex_value(userData.getSex_value());
        return demoForm;
    }

    /**
     * UserDataオブジェクトに引数のフォームの各値を設定する
     * @param demoForm DemoFormオブジェクト
     * @return ユーザーデータ
     */
    private UserData getUserData(DemoForm demoForm){
        UserData userData = new UserData();
        if(!DateCheckUtil.isEmpty(demoForm.getId())){
            userData.setId(Long.valueOf(demoForm.getId()));
        }
        userData.setName(demoForm.getName());
        userData.setBirthY(Integer.valueOf(demoForm.getBirthYear()));
        userData.setBirthM(Integer.valueOf(demoForm.getBirthMonth()));
        userData.setBirthD(Integer.valueOf(demoForm.getBirthDay()));
        userData.setSex(demoForm.getSex());
        userData.setSex_value(demoForm.getSex_value());
        return userData;
    }

    /**
     * 引数の文字列をLong型に変換する
     * @param id ID
     * @return Long型のID
     */
    private Long stringToLong(String id){
        try{
            return Long.parseLong(id);
        }catch(NumberFormatException ex){
            return null;
        }
    }

}



さらに、検索用Formのエラーメッセージを下記プロパティファイルに追加している。

#メッセージ
validation.date-empty={0}を入力してください。
validation.date-invalidate=生年月日が存在しない日付になっています。
validation.date-future=生年月日が未来の日付になっています。
validation.empty-msg=
javax.validation.constraints.NotEmpty.message={0}を入力してください。
validation.sex-invalidate=性別に不正な値が入っています。
validation.date-invalidate-from=生年月日(From)の日付が不正です。
validation.date-invalidate-to=生年月日(To)の日付が不正です。
validation.date-invalidate-from-to=生年月日(From)が生年月日(To)より大きくなっています。

#フィールド名
name=名前
sex=性別
checked=確認チェック



また、一覧画面から検索画面に戻れるよう、下記HTMLファイルのsubmitタグの内容を変更している。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index page</title>
</head>
<body>
    ユーザーデータテーブル(user_data)の全データ<br/><br/>

    <table border="1" cellpadding="5">
        <tr>
            <th>ID</th>
            <th>名前</th>
            <th>生年月日</th>
            <th>性別</th>
            <th></th>
            <th></th>
        </tr>
        <tr th:each="obj : ${demoFormList}">
            <td th:text="${obj.id}"></td>
            <td th:text="${obj.name}"></td>
            <td th:text="|${obj.birthYear}年 ${obj.birthMonth}月 ${obj.birthDay}日|"></td>
            <td th:text="${obj.sex_value}"></td>
            <td><a href="/update" th:href="@{/update(id=${'__${obj.id}__'})}">更新</a></td>
            <td><a href="/delete_confirm" th:href="@{/delete_confirm(id=${'__${obj.id}__'})}">削除</a></td>
        </tr>
    </table>
    <br/><br/>
    <form method="post" th:action="@{/add}">
        <input type="submit" name="next" value="データ追加" /><br/><br/>
        <input type="submit" name="back" value="戻る" />
    </form>
</body>
</html>

また、完了画面に表示するボタン名を変更している。

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>完了画面</title>
</head>
<body>
   お申し込みが完了しました。<br/><br/>

    <form method="post" th:action="@{/}">
        <input type="submit" value="一覧に戻る" />
    </form>
</body>
</html>

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-search-2/demo

要点まとめ

  • 検索機能を行うSQL文のような動的SQL文を生成するには、MyBatis提供のifタグによる条件分岐を利用すると便利である。