今回も引き続き、Spring BootのWEB画面上でDB更新項目がNULL更新できるパターンの実装について述べる。ここでは、具体的なサンプルプログラムのソースコードを共有する。
前提条件
下記記事を参照のこと。
作成したサンプルプログラムの内容
作成したサンプルプログラムの構成は以下の通り。
なお、上図の赤枠は、前提条件に記載したソースコードと比較し、変更になったソースコードを示す。赤枠のソースコードについては今後記載する。
まず、DemoForm・UserDataに、項目「memo」を追加する対応を行った。そのソースコードは以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | package com.example.demo; import lombok.Data; import javax.validation.constraints.NotEmpty; import java.util.LinkedHashMap; import java.util.Map; /** * Formオブジェクトのクラス */ @Data public class DemoForm { /** ID */ private String id; /** 名前 */ @NotEmpty private String name; /** 生年月日_年 */ private String birthYear; /** 生年月日_月 */ private String birthMonth; /** 生年月日_日 */ private String birthDay; /** 性別 */ @NotEmpty private String sex; /** メモ */ private String memo; /** 確認チェック */ @NotEmpty private String checked; /** 性別(文字列) */ private String sex_value; /** 生年月日_月の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; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package com.example.demo; import lombok.Data; /** * ユーザーデータテーブル(user_data)アクセス用エンティティ */ @Data public class UserData { /** ID */ private long id; /** 名前 */ private String name; /** 生年月日_年 */ private int birthY; /** 生年月日_月 */ private int birthM; /** 生年月日_日 */ private int birthD; /** 性別 */ private String sex; /** メモ */ private String memo; /** 性別(文字列) */ private String sex_value; } |
次に、サービスクラスにmemoをDemoForm, UserData間で受け渡す修正を行った。そのソースコードは以下の通りで、「getDemoForm」「getUserData」メソッドを変更している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | 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.setMemo(userData.getMemo()); 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.setMemo(demoForm.getMemo()); 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; } } } |
さらに、SQLのxmlファイルを変更した。「memo」を追加・更新するcreate,updateメソッドでは、jdbcType指定を追加している。また、select句にjdbcType指定を追加するため、「userDataResultMap」というresultMapを定義し、それをfindBySearchForm,findByIdメソッドで利用するようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | <?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"> <resultMap id="userDataResultMap" type="com.example.demo.UserData" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="name" property="name" jdbcType="VARCHAR" /> <result column="birthY" property="birthY" jdbcType="VARCHAR" /> <result column="birthM" property="birthM" jdbcType="VARCHAR" /> <result column="birthD" property="birthD" jdbcType="VARCHAR" /> <result column="sex" property="sex" jdbcType="VARCHAR" /> <result column="memo" property="memo" jdbcType="VARCHAR" /> <result column="sex_value" property="sex_value" jdbcType="VARCHAR" /> </resultMap> <select id="findBySearchForm" parameterType="com.example.demo.SearchForm" resultMap="userDataResultMap"> SELECT u.id, u.name, u.birth_year as birthY, u.birth_month as birthM , u.birth_day as birthD, u.sex, u.memo, m.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') <= 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') <= #{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" resultMap="userDataResultMap"> SELECT id, name, birth_year as birthY, birth_month as birthM , birth_day as birthD, sex, memo 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, memo ) VALUES (#{id}, #{name}, #{birthY}, #{birthM} , #{birthD}, #{sex}, #{memo,jdbcType=VARCHAR}) </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}, memo = #{memo,jdbcType=VARCHAR} WHERE id = #{id} </update> <select id="findMaxId" resultType="long"> SELECT NVL(max(id), 0) FROM USER_DATA </select> </mapper> |
また、変更したHTMLファイルは以下の通り。「input.html」ではメモ欄のテキストエリアを追加し、「confirm.html」「confirm_delete.html」ではメモ欄の表示を追加している。さらに、表示形式を整えるため、tableタグを利用するように修正している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | <!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>入力画面</title> </head> <body> <p>下記必要事項を記載の上、「確認」ボタンを押下してください。</p><br/> <form method="post" th:action="@{/confirm}" th:object="${demoForm}"> <table border="0"> <tr> <td align="left" valign="top">名前:</td> <td> <input type="text" th:value="*{name}" th:field="*{name}" th:errorclass="fieldError" /> <span th:if="*{#fields.hasErrors('name')}" th:errors="*{name}" class="errorMessage"></span> </td> </tr> <tr> <td align="left" valign="top">生年月日:</td> <td> <input type="text" th:value="*{birthYear}" size="4" maxlength="4" th:field="*{birthYear}" th:errorclass="fieldError" />年 <select th:field="*{birthMonth}" th:errorclass="fieldError"> <option value="">---</option> <option th:each="item : *{getMonthItems()}" th:value="${item.key}" th:text="${item.value}"/> </select>月 <select th:field="*{birthDay}" th:errorclass="fieldError"> <option value="">---</option> <option th:each="item : *{getDayItems()}" th:value="${item.key}" th:text="${item.value}"/> </select>日 <span th:if="*{#fields.hasErrors('birthYear')}" th:errors="*{birthYear}" class="errorMessage"></span> <span th:if="*{#fields.hasErrors('birthMonth')}" th:errors="*{birthMonth}" class="errorMessage"></span> <span th:if="*{#fields.hasErrors('birthDay')}" th:errors="*{birthDay}" class="errorMessage"></span> </td> </tr> <tr> <td align="left" valign="top">性別:</td> <td> <for th:each="item : *{getSexItems()}"> <input type="radio" name="sex" th:value="${item.key}" th:text="${item.value}" th:field="*{sex}" th:errorclass="fieldError" /> </for> <span th:if="*{#fields.hasErrors('sex')}" th:errors="*{sex}" class="errorMessage"></span> </td> </tr> <tr> <td align="left" valign="top">メモ:</td> <td> <textarea rows="6" cols="40" th:value="*{memo}" th:field="*{memo}"></textarea> </td> </tr> <tr> <td align="left" valign="top">入力確認:</td> <td> <input type="checkbox" name="checked" th:value="確認済" th:field="*{checked}" th:errorclass="fieldError" /> <span th:if="*{#fields.hasErrors('checked')}" th:errors="*{checked}" class="errorMessage"></span> </td> </tr> </table> <br/><br/> <input type="submit" name="next" value="確認" /> <input type="submit" name="back" value="戻る" /> </form> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | <!DOCTYPE html> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>確認画面</title> </head> <body> <p>入力内容を確認し、問題なければ「送信」ボタンを押下してください。</p> <form method="post" th:action="@{/send}" th:object="${demoForm}"> <table border="0"> <tr> <td align="left" valign="top">名前: </td> <td> <span th:text="*{name}"> ここに名前が表示されます </span> </td> </tr> <tr> <td align="left" valign="top">生年月日: </td> <td> <span th:text="*{birthYear} + '年' + *{getMonthItems().get('__*{birthMonth}__')} + '月' + *{getDayItems().get('__*{birthDay}__')} + '日'"> ここに生年月日が表示されます </span> </td> </tr> <tr> <td align="left" valign="top">性別: </td> <td> <span th:text="*{getSexItems().get('__*{sex}__')}"> ここに性別が表示されます </span> </td> </tr> <tr> <td align="left" valign="top">メモ: </td> <td> <th:block th:if="*{memo}"> <th:block th:each="memoStr, memoStat : *{memo.split('\r\n|\r|\n', -1)}"> <th:block th:text="${memoStr}"/> <br th:if="${!memoStat.last}"/> </th:block> </th:block> </td> </tr> <tr> <td align="left" valign="top">確認チェック: </td> <td> <span th:text="*{checked}"> ここに確認チェック内容が表示されます </span> </td> </tr> </table> <br/><br/> <input type="submit" name="next" value="送信" /> <input type="submit" name="back" value="戻る" /> </form> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | <!DOCTYPE html> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>削除確認画面</title> </head> <body> <p>下記内容を削除してよろしいでしょうか?問題なければ「送信」ボタンを押下してください。</p> <form method="post" th:action="@{/delete}" th:object="${demoForm}"> <table border="0"> <tr> <td align="left" valign="top">名前: </td> <td> <span th:text="*{name}"> ここに名前が表示されます </span> </td> </tr> <tr> <td align="left" valign="top">生年月日: </td> <td> <span th:text="*{birthYear} + '年' + *{getMonthItems().get('__*{birthMonth}__')} + '月' + *{getDayItems().get('__*{birthDay}__')} + '日'"> ここに生年月日が表示されます </span> </td> </tr> <tr> <td align="left" valign="top">性別: </td> <td> <span th:text="*{getSexItems().get('__*{sex}__')}"> ここに性別が表示されます </span> </td> </tr> <tr> <td align="left" valign="top">メモ: </td> <td> <th:block th:if="*{memo}"> <th:block th:each="memoStr, memoStat : *{memo.split('\r\n|\r|\n', -1)}"> <th:block th:text="${memoStr}"/> <br th:if="${!memoStat.last}"/> </th:block> </th:block> </td> </tr> </table> <br/><br/> <input type="submit" name="next" value="送信" /> <input type="submit" name="back" value="戻る" /> </form> </body> </html> |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-null-update-2/demo
要点まとめ
- NULL更新するDB項目がある場合は、jdbcTypeの指定が必要になる。
- select句にjdbcType指定を追加するには、resultMapを定義し、そこでjdbcTypeを指定する。