0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Spring入門】第11章 社員新規登録② 確認画面と完了画面

0
Last updated at Posted at 2026-10-01

第10章で作った確認画面に「戻る」「登録」のボタンを付け、完了画面を作ります。「登録」ボタンで社員がデータベースに登録されるところまで完成させましょう。

この章で学ぶこと

  • 確認画面から入力画面に「戻る」しくみ
  • フォームの内容を Entity に詰め替えて、データベースに登録する方法
  • 登録が終わったらセッションの入力内容を破棄する方法
  • PRG パターンで二重登録を防ぐ方法

11.1 この章で作るもの

この章が終わると、新規登録は次のように動くようになります。

ch11-1.png
図11-1 新規登録の画面の流れ(URL の先頭の /employees/register は省略)

この章で追加・作成するのは、次のものです。

種類 ファイル 内容
Controller EmployeeRegisterController.java 戻る・登録・完了の3つの処理を追加する
テンプレート employee/register/confirm.html 第10章で作った確認画面に、「戻る」「登録」のボタンを追加する
テンプレート employee/register/complete.html 完了画面(新規作成)

Controller に追加する処理と URL の対応は次のとおりです。

URL メソッド Java のメソッド 処理
/employees/register/back GET back 確認画面から入力画面に戻る
/employees/register/complete POST complete 社員を登録し、完了画面へリダイレクトする
/employees/register/done GET done 完了画面を表示する

11.2 Controller に処理を追加する

第10章で作った EmployeeRegisterController を開き、3つのメソッド(back、complete、done)を追加します。

  1. パッケージ・エクスプローラーで src/main/java → com.example.employeeapp.controller → EmployeeRegisterController.java をダブルクリックして開きます。
  2. 次のコードのとおりに、import 文と3つのメソッド(back、complete、done)を追加します。@PostMapping や SessionStatus などを補完(Ctrl + Space)で入力すれば、import 文は自動で追加されます(第4章「入力は補完で行う」)。
  3. Ctrl + S で保存します。
src/main/java/com/example/employeeapp/controller/EmployeeRegisterController.java
package com.example.employeeapp.controller;

import java.util.List;

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.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

import com.example.employeeapp.entity.Department;
import com.example.employeeapp.entity.Employee;
import com.example.employeeapp.form.RegisterForm;
import com.example.employeeapp.repository.DepartmentRepository;
import com.example.employeeapp.repository.EmployeeRepository;

@Controller
@SessionAttributes("registerForm")
public class EmployeeRegisterController {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private DepartmentRepository departmentRepository;

    /** セッションに RegisterForm オブジェクトがないとき呼ばれる
     * @return 空の RegisterForm オブジェクト */
    @ModelAttribute
    public RegisterForm createForm() {
        return new RegisterForm();
    }

    /** 社員登録の入力画面を表示
     * @param model 表示する入力フォームと部署一覧
     * @return 社員登録の入力画面 */
    @GetMapping("/employees/register")
    public String input(Model model) {
        model.addAttribute("registerForm", new RegisterForm());
        List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
        model.addAttribute("departments", departments);
        return "employee/register/input";
    }

    /** 社員登録の確認画面を表示
     * @param registerForm 入力された社員情報
     * @param model 表示する部署情報
     * @return 社員登録の確認画面 */
    @PostMapping("/employees/register/confirm")
    public String confirm(@ModelAttribute RegisterForm registerForm, Model model) {
        Department department = departmentRepository.findByDeptId(registerForm.getDeptId());
        model.addAttribute("department", department);
        return "employee/register/confirm";
    }

    /** 社員登録の確認画面から入力画面に戻る
     * @param model 表示する部署一覧
     * @return 社員登録の入力画面 */
    @GetMapping("/employees/register/back")
    public String back(Model model) {
        List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
        model.addAttribute("departments", departments);
        return "employee/register/input";
    }

    /** 社員登録の完了画面を表示
     * @param registerForm 入力された社員情報
     * @param sessionStatus セッション破棄時に利用
     * @return 社員登録の完了画面 */
    @PostMapping("/employees/register/complete")
    public String complete(@ModelAttribute RegisterForm registerForm,
            SessionStatus sessionStatus) {
        Employee employee = new Employee();
        employee.setName(registerForm.getName());
        employee.setGender(registerForm.getGender());
        employee.setAge(registerForm.getAge());
        employee.setBirthDate(registerForm.getBirthDate());
        employee.setDepartment(departmentRepository.findByDeptId(registerForm.getDeptId()));
        employee.setAddress(registerForm.getAddress());
        employeeRepository.save(employee);

        // セッションに保存していた入力内容を破棄する
        sessionStatus.setComplete();
        return "redirect:/employees/register/done";
    }

    /** 社員登録の完了画面を表示
     * @return 社員登録の完了画面 */
    @GetMapping("/employees/register/done")
    public String done() {
        return "employee/register/complete";
    }
}

【知識メモ】3つのメソッドの役割

  • back … 入力画面をもう一度表示する。入力内容はセッションに残っているので、フォームを作り直す必要はない
  • complete … フォームの内容を Entity に詰め替えてデータベースに登録し、完了画面へリダイレクトする
  • done … 完了画面を表示するだけ

11.3 確認画面にボタンを付け、完了画面を作る

確認画面(confirm.html)にボタンを付ける

第10章で作った templates/employee/register/confirm.html を開き、</table> の後ろに「戻る」と「登録」のボタンを追加して保存します。追加するのは、<a class="button" …>戻る</a> から </form> までの4行です。

src/main/resources/templates/employee/register/confirm.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員新規登録(確認)</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
  <h1>社員新規登録(確認)</h1>
  <p>次の内容で登録します。よろしければ「登録」ボタンを押してください。</p>

  <table th:object="${registerForm}">
    <tr><th>氏名</th><td th:text="*{name}"></td></tr>
    <tr><th>性別</th><td th:switch="*{gender}"><span th:case="1">男</span><span th:case="2">女</span></td></tr>
    <tr><th>年齢</th><td th:text="*{age}"></td></tr>
    <tr><th>生年月日</th><td th:text="*{#dates.format(birthDate, 'yyyy/MM/dd')}"></td></tr>
    <tr><th>部署</th><td th:text="${department?.deptName}"></td></tr>
    <tr><th>住所(都道府県)</th><td th:text="*{address}"></td></tr>
  </table>

  <a class="button" th:href="@{/employees/register/back}">戻る</a>
  <form class="inline" th:action="@{/employees/register/complete}" method="post">
    <button type="submit">登録</button>
  </form>
</body>
</html>

完了画面(complete.html)

同じ register フォルダーに complete.html を作ります。

src/main/resources/templates/employee/register/complete.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>社員新規登録(完了)</title>
<link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>
  <h1>社員新規登録(完了)</h1>
  <p>社員の登録が完了しました。</p>
  <p><a th:href="@{/employees}">一覧へ戻る</a></p>
</body>
</html>

これで、新規登録に必要なファイルがすべてそろいました。register フォルダーの中は次のようになっています。

templates
└─ employee
    ├─ list.html
    └─ register
        ├─ input.html      … 入力画面(第10章)
        ├─ confirm.html    … 確認画面(第10章。この章でボタンを追加)
        └─ complete.html   … 完了画面(この章)

11.4 動作確認

アプリを起動している場合は、コンソールの赤い四角(終了)ボタンで一度停止してから、プロジェクトを右クリック → 実行 → Spring Boot アプリケーション で起動し直します(Boot ダッシュボードの再起動ボタンでもかまいません)。停止している場合は、そのまま起動します。保存しただけでは、動いているアプリには反映されません。

確認画面を表示する

  1. Chrome で http://localhost:8080/employees を開き、「新規登録」ボタンを押します。

  2. 入力画面に次の内容を入力します。

    項目 入力する値
    氏名 田中 美咲
    性別 女
    年齢 24
    生年月日 2001/06/15
    部署 開発部
    住所(都道府県) 愛知県
  3. 「確認」ボタンを押します。第10章の確認画面に「戻る」「登録」のボタンが付いていれば成功です。

ch11-register-confirm.png
図11-2 「戻る」「登録」のボタンが付いた確認画面

「戻る」で入力画面に戻る

  1. 確認画面の「戻る」ボタンを押します。
  2. 入力画面に戻り、さきほど入力した内容がそのまま残っている ことを確認します。

ch11-register-back.png
図11-3 確認画面から戻った入力画面(入力内容が残っている)

登録する

  1. もう一度「確認」ボタンを押して確認画面に進み、「登録」ボタンを押します。
  2. 完了画面が表示されれば成功です。アドレス欄が /employees/register/done になっていることも確認してください。

ch11-register-complete.png
図11-4 完了画面

このとき Eclipse のコンソールには、Hibernate が実行した SQL が表示されています。insert into employee の SQL があれば、データベースに登録された証拠です。

コンソール(抜粋)
Hibernate:
    select
        seq_emp.nextval
    from
        dual
Hibernate:
    insert
    into
        employee
        (address, age, birth_date, dept_id, gender, name, emp_id)
    values
        (?, ?, ?, ?, ?, ?, ?)

insert の前に、select seq_emp.nextval from dual が実行されていることに注目してください。JPA がシーケンス SEQ_EMP から次の番号を取り出し、その番号を社員ID(emp_id)にして insert しています(第3章・第6章)。? の部分には、取り出した番号とフォームに入力した値が入ります。

一覧画面で確認する

  1. 完了画面の「一覧へ戻る」をクリックします。
  2. 一覧の最後に、田中 美咲さんが追加されていれば成功です。

ch11-list-after-register.png
図11-5 登録後の一覧画面

【知識メモ】社員IDが 4 になる理由

社員ID 3 の林檎 星飛さんは第9章で削除したため、一覧にはありません。シーケンスは一度払い出した番号を再利用しないので、新しい社員には次の番号の 4 が振られます。番号が飛んでいても問題ありません。

11.5 解説

「戻る」で入力内容が残るしくみ

第10章で見たように、@ModelAttribute を付けた引数には、セッションに保存されたフォームに送信された値を詰めたものが渡されます。この章では、そのフォームを「戻る」と「登録」でも使い回します。

ch11-2.png
図11-6 セッションのフォームを、確認・戻る・登録で使い回す

back メソッドは、フォームを何も用意せずに入力画面を返しています。

@GetMapping("/employees/register/back")
public String back(Model model) {
    List<Department> departments = departmentRepository.findAllByOrderByDeptIdAsc();
    model.addAttribute("departments", departments);
    return "employee/register/input";
}

@SessionAttributes を付けた Controller では、セッションに保存されているフォームが自動的に Model に入ります。そのため入力画面の th:object="${registerForm}" には、セッションのフォーム、つまり入力済みの内容が表示されます。

一方、最初に入力画面を開くときの input メソッドでは、new RegisterForm() で空のフォームを Model に入れて、セッションの内容を上書きしています。そのため、新規登録ボタンから開いたときは、いつも空の入力画面になります。

メソッド Model の registerForm 入力画面の表示
input(新規登録ボタン) new RegisterForm() で空のフォームに入れ替える 空欄
back(戻るボタン) セッションのフォームがそのまま入る 入力済みの内容

確認画面のボタン

「戻る」は入力画面を表示するだけなので、第10章の「新規登録」ボタンと同じように、リンク(class="button")で作っています。「登録」はデータを変えるので、POST のフォームです。<form> は通常、次の行に分かれて表示されます。そこで class="inline" を付けて、「戻る」の横に並ぶようにしています(app.css の form.inline。第9章で「削除」ボタンのフォームに付けたものと同じです)。

フォームから Entity への詰め替え

データベースに登録するのは Entity(Employee)です。フォーム(RegisterForm)は画面の入力を受け取るためのクラスなので、そのままでは保存できません。そこで complete メソッドでは、フォームの値を1つずつ Entity に詰め替えています。

Employee employee = new Employee();
employee.setName(registerForm.getName());
employee.setGender(registerForm.getGender());
employee.setAge(registerForm.getAge());
employee.setBirthDate(registerForm.getBirthDate());
employee.setDepartment(departmentRepository.findByDeptId(registerForm.getDeptId()));
employee.setAddress(registerForm.getAddress());
employeeRepository.save(employee);
  • 社員IDはセットしません。社員IDは、保存するときに JPA がシーケンスから番号を取り出して自動で採番します(第3章・第6章)。
  • 部署は、部署IDではなく Department オブジェクトをセットします。Employee は部署を Department 型で持っている(第6章の @ManyToOne)ので、部署IDから部署を検索してセットします。
  • save は、主キーが null なら登録(INSERT)します。社員IDをセットしていないので、新しい社員として登録されます。

【知識メモ】フォームと Entity を分ける理由

フォームは「画面の都合」に合わせたクラス、Entity は「テーブルの都合」に合わせたクラスです。たとえば画面では部署を部署IDで選びますが、Entity は Department オブジェクトで持っています。役割の違うクラスを分けておくと、画面かテーブルの一方を変えたときに、もう一方への影響を小さくできます。

sessionStatus.setComplete() でセッションを片付ける

登録が終わったら、セッションに保存していた入力内容はもう必要ありません。sessionStatus.setComplete() を呼ぶと、この Controller が @SessionAttributes で保存していた registerForm がセッションから削除されます。

SessionStatus は、メソッドの引数に書くだけで Spring が渡してくれます。

PRG パターンで二重登録を防ぐ

complete メソッドは完了画面を直接返さずに、redirect:/employees/register/done でリダイレクトしています。これは第9章の削除でも使った PRG パターン(Post → Redirect → Get)です。

作り方 完了画面で再読み込み(F5)すると
POST のあと完了画面を直接返す ブラウザが「登録」の POST をもう一度送るので、同じ社員がもう一人登録される
POST のあとリダイレクトする(PRG) ブラウザは最後の GET(/done)を送り直すだけなので、完了画面が表示されるだけ

実際に完了画面で F5 を押して、一覧に同じ社員が増えないことを確かめてみましょう。

登録が終わったあと、ブラウザの「戻る」で確認画面に戻って、もう一度「登録」を押すと、Whitelabel エラー画面になります。この教材では、この操作への対処はしていません。

11.6 入力チェックはまだありません

今のアプリには、まだ入力チェックがありません。試しに、入力画面で 何も入力せずに「確認」→「登録」と進んでみましょう。

【ご注意】何も入力せずに登録するとエラーになる

確認画面は空欄のまま表示されますが、「登録」を押すと Whitelabel エラー画面(ステータス 500)になります。コンソールには次のようなエラーが表示されます。

ORA-01400: ("EMPAPP"."EMPLOYEE"."NAME")にはNULLは挿入できません。

第3章で EMPLOYEE テーブルの列に NOT NULL を付けたため、データベースが空の値の登録を拒否したのです。本来は、データベースに送る前にアプリ側で入力内容をチェックし、利用者にわかりやすいメッセージで知らせるべきです。これを第13章で作ります。

11.7 よくあるエラー

症状 原因 対処
「戻る」を押すと 405 になる back メソッドを @PostMapping にしてしまった @GetMapping("/employees/register/back") にする(リンクは GET でリクエストを送る)
「登録」を押すと 405 になる 確認画面の「登録」のフォームが method="get" になっている 「登録」のフォームは method="post" にする
完了画面で F5 を押すと同じ社員が増える complete メソッドで redirect: を付けずに完了画面を直接返している 戻り値の先頭に redirect: を付けて、/done へリダイレクトする
新規登録ボタンで開いた入力画面に、前回の内容が残っている setComplete() を呼び忘れた、または input メソッドで空のフォームを Model に入れていない この章のコードと見比べる

11.8 まとめと確認問題

まとめ

  • 確認画面から「戻る」と、セッションのフォームがそのまま表示されるので、入力内容が残る
  • データベースに登録するときは、フォームの値を Entity に詰め替えて save する。社員IDはデータベースが採番する
  • 登録が終わったら sessionStatus.setComplete() でセッションの入力内容を破棄する
  • POST のあとはリダイレクトして完了画面を表示する(PRG パターン)ことで、二重登録を防ぐ
  • 今はまだ入力チェックがないので、空欄のまま登録するとデータベースのエラーになる(第13章で対応)

確認問題

問1 確認画面から「戻る」ボタンを押すと、入力画面に入力内容が残っているのはなぜですか。

解答を見る

入力内容を入れたフォーム(registerForm)が @SessionAttributes によってセッションに保存されていて、back メソッドで入力画面を表示するときに、そのフォームが自動的に Model に入るからです。

問2 complete メソッドで、Entity に社員IDをセットしないのはなぜですか。

解答を見る

社員IDは、保存するときに JPA がシーケンスから番号を取り出して自動で採番するからです。主キーが null のまま save すると、新しいデータとして登録(INSERT)されます。

問3 sessionStatus.setComplete() は何をするメソッドですか。

解答を見る

その Controller が @SessionAttributes でセッションに保存していた値(ここでは registerForm)を、セッションから削除します。登録が終わって入力内容が不要になったときに呼びます。

問4 complete メソッドで完了画面のテンプレートを直接返さず、redirect:/employees/register/done にしているのはなぜですか。

解答を見る

完了画面で再読み込みされたときに、「登録」の POST がもう一度送られて二重登録になるのを防ぐためです(PRG パターン)。リダイレクトすると、ブラウザが最後に送ったのは GET(/done)になるので、再読み込みしても完了画面が表示されるだけです。

前へ

第10章 社員新規登録① 入力画面とセッション

次へ

第12章 社員変更

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?