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/register")
public String register(Model model) {
model.addAttribute("childRegisterForm", new Child(null, null, null));
return "children/register";
}
@PostMapping("/children/registerConfirm")
public String registerConfirm(@Validated ChildRegisterForm childRegisterForm, BindingResult bindingResult, Model model) {
// 入力チェック
if (bindingResult.hasErrors()) {
return "children/register";
}
// 登録可否チェック
boolean canRegister = childrenService.canRegister(childRegisterForm.getName());
if (!canRegister) {
model.addAttribute("message", "既に同じ名前が登録されています。");
return "children/register";
}
// 画面表示
return "children/register_confirm";
}
@PostMapping("/children/registerFinish")
public String registerFinish(@Validated ChildRegisterForm childRegisterForm, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
// 入力チェック
if (bindingResult.hasErrors()) {
return "children/register";
}
// 登録可否チェック
boolean canRegister = childrenService.canRegister(childRegisterForm.getName());
if (!canRegister) {
model.addAttribute("message", "既に同じ名前が登録されています。");
return "children/register";
}
// 登録実行
var child = ChildrenControllerHelper.toChild(childRegisterForm);
childrenService.register(child);
// 画面遷移
redirectAttributes.addFlashAttribute("message", child.getName() + "を登録しました。");
return "redirect:/children?id=" + child.getChildId();
}
}
新規登録のサービス層
@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;
}
/**
* 登録可能かチェックする
*
* @param name
* @return
*/
public boolean canRegister(String name) {
List<ChildrenEntity> sameNameChildren = childrenMapper.findByName(name);
if (!sameNameChildren.isEmpty()) {
return false;
}
return true;
}
/**
* 登録する
*/
public void register(Child newChild) {
var childrenEntity = new ChildrenEntity(null, newChild.getName(), newChild.getBirthday());
childrenMapper.insert(childrenEntity);
}
}
新規登録のインテグレーション層
@Mapper
public interface ChildrenMapper {
List<ChildrenEntity> selectAll();
void insert(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>
<insert id="insert">
INSERT INTO children (name, birthday) VALUES (#{name}, #{birthday})
</insert>
</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" lang="ja">
<head>
<meta charset="UTF-8">
<title>子どもマスタ|子どものお小遣い帳アプリ</title>
</head>
<body>
<header></header>
<main>
<h1>子どもを登録</h1>
<div th:text="${message}"></div>
<form method="POST" action="/children/registerConfirm" th:object="${childRegisterForm}">
<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>
<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" lang="ja">
<head>
<meta charset="UTF-8">
<title>子どもマスタ|子どものお小遣い帳アプリ</title>
</head>
<body>
<header></header>
<main>
<h1>登録する子どもを確認</h1>
<div>登録します。よろしいですか?</div>
<form method="POST" action="/children/registerFinish" th:object="${childRegisterForm}">
<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="*{name}">
<input type="hidden" th:field="*{birthday}">
<button onclick="location.href='/children/register';return false;">キャンセル</button>
<input type="submit" value="確定" />
</form>
</main>
<footer>
<a href="/">トップページ</a>
</footer>
</body>
</html>