Contents
編集画面のサンプル
画面デモ
このサンプルコードで、次のような画面を作成します。
編集するだけの簡易な画面になります。
ディレクトリ構成
project-root/src ├── main │ ├── java │ │ └── com │ │ └── springhack │ │ └── okozukaisystem │ │ ├── OkozukaiSystemApplication.java │ │ ├── business │ │ │ └── ChildrenService.java │ │ ├── domain │ │ │ └── Child.java │ │ ├── integration │ │ │ ├── entity │ │ │ │ └── ChildrenEntity.java │ │ │ └── mapper │ │ │ └── ChildrenMapper.java │ │ └── presentation │ │ ├── children │ │ │ ├── ChildrenController.java │ │ │ └── ChildrenControllerHelper.java │ └── resources │ ├── com │ │ └── springhack │ │ └── okozukaisystem │ │ └── integration │ │ └── mapper │ │ └── ChildrenMapper.xml │ └── templates │ ├── children │ │ ├── index.html │ │ ├── register.html │ │ ├── register_confirm.html
テーブル構造
このサンプルでは下記のテーブルを使用します。
child_id | BIGINT |
name | VARCHAR(50) |
birthday | DATE |
編集のコントローラー層
@Controller
public class ChildrenController {
private final ChildrenService childrenService;
public ChildrenController(ChildrenService childrenService) {
this.childrenService = childrenService;
}
@RequestMapping("/children")
public String index(Model model) {
// 子どもリストを取得
List<Child> children = childrenService.getAll();
// 画面表示
model.addAttribute("children", children);
return "children/index";
}
@GetMapping("/children/edit")
public String edit(Long id, Model model) {
// 編集可否チェック
Child child = childrenService.get(id);
if (Objects.isNull(child)) {
model.addAttribute("message", "選択した子どもが存在しません。");
return "children/index";
}
// 画面遷移
ChildEditForm childEditForm = ChildrenControllerHelper.toEditForm(child);
model.addAttribute("childEditForm", childEditForm);
return "children/edit";
}
@PostMapping("/children/editConfirm")
public String editConfirm(@Validated ChildEditForm childEditForm, BindingResult bindingResult, Model model) {
// 入力チェック
if (bindingResult.hasErrors()) {
return "children/edit";
}
// 編集可否チェック
var child = ChildrenControllerHelper.toChild(childEditForm);
boolean canRegister = childrenService.canEdit(child);
if (!canRegister) {
model.addAttribute("message", "既に同じ名前が登録されています。");
return "children/edit";
}
// 画面遷移
return "children/edit_confirm";
}
@PostMapping("/children/editFinish")
public String editFinish(@Validated ChildEditForm childEditForm, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
// 入力チェック
if (bindingResult.hasErrors()) {
return "children/edit";
}
// 編集実行
var child = ChildrenControllerHelper.toChild(childEditForm);
childrenService.edit(child);
// 画面遷移
redirectAttributes.addFlashAttribute("message", child.getName() + "を編集しました。");
return "redirect:/children";
}
}
編集のサービス層
@Service
public class ChildrenService {
private final ChildrenMapper childrenMapper;
public ChildrenService(ChildrenMapper childrenMapper) {
this.childrenMapper = childrenMapper;
}
/**
* 全件取得する
*/
public List<Child> getAll() {
// DBから取得
List<ChildrenEntity> allChildren = childrenMapper.selectAll();
var children = allChildren
.stream()
.map(child -> new Child(child.getChildId(), child.getName(), child.getBirthday()))
.collect(Collectors.toList());
return children;
}
/**
* 1件取得する
*/
public Child get(Long childId) {
ChildrenEntity childrenEntity = childrenMapper.findById(childId);
if (Objects.isNull(childrenEntity)) {
return null;
}
return new Child(
childrenEntity.getChildId(),
childrenEntity.getName(),
childrenEntity.getBirthday());
}
/**
* 編集する
*/
public void edit(Child newChild) {
var childrenEntity = new ChildrenEntity(newChild.getChildId(), newChild.getName(), newChild.getBirthday());
childrenMapper.update(childrenEntity);
}
/**
* 編集可能かチェックする
*/
public boolean canEdit(Child newChild) {
List<ChildrenEntity> sameNameChildren = childrenMapper.findByName(newChild.getName());
if (!sameNameChildren.isEmpty() && !sameNameChildren.get(0).getChildId().equals(newChild.getChildId())) {
return false;
}
return true;
}
}
編集のインテグレーション層
@Mapper
public interface ChildrenMapper {
List<ChildrenEntity> selectAll();
ChildrenEntity findById(Long childId);
List<ChildrenEntity> findByName(String name);
void update(ChildrenEntity newChild);
}
DB接続にはMybatisを使用しました。
そのため、@Repositoryではなく@Mapperを付与したインターフェースを作成し、命名は「テーブル名+Mapper」とします。
<mapper namespace="com.springhack.okozukaisystem.integration.mapper.ChildrenMapper">
<select id="selectAll" resultType="com.springhack.okozukaisystem.integration.entity.ChildrenEntity">
SELECT c.child_id, c.name, c.birthday FROM children c
</select>
<select id="findById" resultType="com.springhack.okozukaisystem.integration.entity.ChildrenEntity">
SELECT c.child_id, c.name, c.birthday FROM children c WHERE c.child_id = #{childId}
</select>
<select id="findByName" resultType="com.springhack.okozukaisystem.integration.entity.ChildrenEntity">
SELECT c.child_id, c.name, c.birthday FROM children c WHERE c.name = #{name}
</select>
<update id="update">
UPDATE children SET name = #{name}, birthday = #{birthday} WHERE child_id = #{childId}
</update>
</mapper>
Mybatisの場合は、ネイティブSQLを使用しますので、
MapperのselectAllとinsertに対するSQLを定義しておきます。
@AllArgsConstructor
@Data
public class ChildrenEntity {
private Long childId;
private String name;
private LocalDate birthday;
}
Childrenテーブルに対するエンティティを作成します。
MybatisでSELECT結果を自動で格納させるために、@AllArgsConstructorを付与します。
編集で使用するモデル
@AllArgsConstructor
@Getter
public class Child {
/** ID */
private Long childId;
/** 名前 */
private String name;
/** 誕生日 */
private LocalDate birthday;
}
編集画面のビュー
一覧表示画面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>子どもマスタ|子どものお小遣い帳アプリ</title>
</head>
<body>
<header></header>
<main>
<h1>子どもマスタ</h1>
<div th:text="${message}"></div>
<table border="1">
<tr>
<th>名前</th>
<th>生年月日</th>
<th>年齢</th>
<th>操作</th>
</tr>
<tr th:each="child: ${children}">
<td th:text="${child.name}"></td>
<td th:text="${child.birthday}"></td>
<td>TODO</td>
<th>
<button th:attr="onclick='location.href=\'/children/edit?id=' + ${child.childId} + '\''">編集</button>
<button th:attr="onclick='location.href=\'/children/remove?id=' + ${child.childId} + '\''">削除</button>
</th>
</tr>
</table>
<a href="/children/register">子どもを登録</a>
</main>
<footer>
<a href="/">トップページ</a>
</footer>
</body>
</html>
編集入力画面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>子どもマスタ|子どものお小遣い帳アプリ</title>
</head>
<body>
<header></header>
<main>
<h1>子どもを編集</h1>
<div th:text="${message}"></div>
<form method="POST" action="/children/editConfirm" th:object="${childEditForm}">
<table border="1">
<tr>
<th>名前</th>
<td>
<input type="text" th:field="*{name}"/>
<span th:errors="*{name}"></span>
</td>
</tr>
<tr>
<th>生年月日</th>
<td>
<input type="date" th:field="*{birthday}">
<span th:errors="*{birthday}"></span>
</td>
</tr>
</table>
<input type="hidden" th:field="*{childId}">
<button onclick="location.href='/children';return false;">キャンセル</button>
<input type="submit" value="編集内容を確認" />
</form>
</main>
<footer>
<a href="/">トップページ</a>
</footer>
</body>
</html>
編集確認画面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>子どもマスタ|子どものお小遣い帳アプリ</title>
</head>
<body>
<header></header>
<main>
<h1>編集する子どもを確認</h1>
<div th:text="${message}"></div>
<form method="POST" action="/children/editFinish" th:object="${childEditForm}">
<table border="1">
<tr>
<th>名前</th>
<td th:text="*{name}"></td>
</tr>
<tr>
<th>生年月日</th>
<td th:text="*{birthday}"></td>
</tr>
</table>
<input type="hidden" th:field="*{childId}">
<input type="hidden" th:field="*{name}">
<input type="hidden" th:field="*{birthday}">
<button onclick="location.href='/children/edit?id=123';return false;">キャンセル</button>
<input type="submit" value="編集を確定する" />
</form>
</main>
<footer>
<a href="/">トップページ</a>
</footer>
</body>
</html>