
Spring Boot での開発作業、ここまで実施することで形にはなってきています。しかし、まだ機能が全然足りていない状態です。この記事では、Webでよくある機能 (入力チェック、例外処理、更新処理) を付け加えていきます。
ここから先は、以下の記事まで対応済みであることを前提としています。ご了承ください。
Webアプリケーションの機能拡張
ここまでの流れで作成したToDoアプリでは、実際にはまだまだ昨日の肉付けが必要です。実務のWebアプリケーションとして成立させるために、プログラム側の機能を肉付けしていきます。
| データのバリデーション (入力チェック) |
空文字のタスクや、100文字を超えるような不正な入力をエラーとして弾く仕組み (jakarta.validation) を導入する |
| 例外処理の共通化 (エラーハンドリング) |
万が一システム内部でエラーが起きた際、ブラウザに「Whitelabel Error Page」ではなく、ユーザー向けにデザインされたエラー画面を返す構造(@ControllerAdvice) を作る |
| 更新処理 (タスクの完了ステータス追加) |
DBのテーブルに is_completed (真偽値) というカラムを追加し、画面上で「完了チェックボックス」を押すと、取り消し線が引かれるような更新処理を実装する。 |
データのバリデーション (入力チェック)
これまで作成してきた Todoリストに、以下の入力チェックを行うよう機能追加します。
- タスク名は必須
- 20文字以内
実装の流れは以下の通りです。
- pom.xml にバリデーション用ライブラリ追加
- データを受け取る Form クラス作成
- TodoController.java の修正
- todo.html にエラーメッセージを追加
- 動作確認
pom.xml にバリデーション用ライブラリ追加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>pom.xml の <dependencies> の中に上記を追記し、Mavenの更新をします。
データを受け取る Form クラス作成
package com.example.demo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class TaskForm {
// 空文字やスペースのみの入力を弾く
@NotBlank(message = "タスク名は必須入力です。")
// 文字数の制限をかける
@Size(max = 20, message = "タスク名は20文字以内で入力してください。")
private String title;
// ゲッターとセッター
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
}com.example.demo パッケージの中に、新しく TaskForm.java を作成し、上記のコードを記述してください。
実務では、DBのテーブル構造を表す「Entityクラス(Task.java)」と、画面からの入力データを受け取る「Formクラス」を明確に分離します。画面からの入力ルールは、このFormクラスにアノテーションで記述します。
TodoController.java の修正
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.BindingResult; // 追記
import org.springframework.validation.annotation.Validated; // 追記
import jakarta.validation.Valid; // 追記
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.RequestParam;
@Controller
public class TodoController {
@Autowired
private TaskRepository taskRepository;
@GetMapping("/todo")
public String index(Model model, @ModelAttribute TaskForm taskForm) {
// 画面初期表示時に空のFormオブジェクトを渡す必要があります
var tasks = taskRepository.findAll();
model.addAttribute("taskList", tasks);
return "todo";
}
@PostMapping("/todo/add")
// @Valid: 入力チェックを実行 / BindingResult: エラー結果が格納される
public String addTask(@Valid @ModelAttribute TaskForm taskForm, BindingResult bindingResult, Model model) {
// もし入力チェックに引っかかった場合
if (bindingResult.hasErrors()) {
// 一覧データを再取得して画面に戻す
var tasks = taskRepository.findAll();
model.addAttribute("taskList", tasks);
return "todo"; // リダイレクトせず、そのまま画面を表示してエラーを出す
}
// エラーがない場合のみDBに保存
var id = "T" + (System.currentTimeMillis() % 1000);
var task = new Task(id, taskForm.getTitle());
taskRepository.save(task);
return "redirect:/todo";
}
@GetMapping("/todo/delete")
public String deleteTask(@RequestParam(name = "id") String id) {
taskRepository.deleteById(id);
return "redirect:/todo";
}
}TodoController.java を開き、addTask メソッドとその周辺 を上記のように書き換えてください。
コントローラー側で、送られてきたデータに対して「チェックを実行しろ」という指示(@Valid)を出します。また、エラーがあった場合はDBに保存せず、エラーメッセージを画面に返して入力をやり直させます。
todo.html にエラーメッセージを追加
<form action="/todo/add" method="post" th:object="${taskForm}">
<input type="text" th:field="*{title}" placeholder="新しいタスクを入力">
<button type="submit">追加</button>
<div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" style="color: red; margin-top: 5px;">
ここにエラーメッセージが表示されます
</div>
</form>todo.html を開き、フォーム(<form> タグ)の内部を上記のように書き換えてください。
エラーが発生した際、TaskForm に書いたメッセージ(「20文字以内で〜」など)を赤文字で画面に表示させる仕組みをThymeleafで追記します。
動作確認

アプリを起動し、ブラウザから (http://localhost:8080/todo) にアクセスします。以下の2パターンを試してください。
- タスク名を未入力にし追加ボタンを押すと、「タスク名は必須入力です。」と表示されることを確認
- タスク名を20文字以上にし追加ボタンを押すと、「タスク名は20文字以内で入力してください」と表示されることを確認
例外処理の共通化 (エラーハンドリング)
これまで作成してきた Todoリストに、以下の例外処理を行うよう機能追加します。
- 例外が発生したときにエラー画面に遷移する
- アプリ内のどこでエラーが起きても、それを一括でキャッチする
実装の流れは以下の通りです。
- エラー表示用画面作成
- 全体のエラーを監視する共通ハンドラークラス作成
- TodoController.java の修正
- todo.html にエラーテスト用ボタンを配置
- 動作確認
エラー表示用画面作成
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>システムエラー</title>
<style>
body { font-family: sans-serif; margin: 50px; text-align: center; color: #333; }
.error-container { border: 1px solid #ddd; padding: 30px; display: inline-block; background-color: #fafafa; border-radius: 5px; }
h1 { color: #d9534f; }
a { color: #337ab7; text-decoration: none; }
</style>
</head>
<body>
<div class="error-container">
<h1>システムエラーが発生しました</h1>
<p th:text="${errorMessage}">予期せぬエラーが発生しました。</p>
<p>大変お手数ですが、時間をおいて再度お試しください。</p>
<br>
<a href="/todo">ToDo管理アプリのトップへ戻る</a>
</div>
</body>
</html>src/main/resources/templates フォルダの中に、新しく error-page.html というファイルを作成してください。error-page.html に上記のHTMLコードを貼り付けて保存してください。
全体のエラーを監視する共通ハンドラークラス作成
package com.example.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice // アプリ全体のコントローラーを監視するアノテーション
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// アプリ内で「RuntimeException」またはその派生エラーが起きたら、このメソッドが強制的に実行されます
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException ex, Model model) {
// 1. 開発者向けに、エラーの具体的な内容(ログ)をコンソールに出力する
log.error("【システム共通エラーキャッチ】想定外の例外が発生しました: ", ex);
// 2. ユーザー画面に表示するメッセージをセット
model.addAttribute("errorMessage", "エラー詳細: " + ex.getMessage());
// 3. ステップ1で作った「error-page.html」を画面に表示する
return "error-page";
}
}com.example.demo パッケージの中に、新しく GlobalExceptionHandler.java というクラスを作成してください。作成後、GlobalExceptionHandler.java に上記コードを記述します。
Spring Bootには、アプリケーション全体のコントローラーを横断的に監視し、エラーが発生した瞬間に割り込んで処理を引き取る @ControllerAdvice という非常に強力な仕組みがあります。
TodoController.java の修正
// 呼び出されると、わざと例外(NullPointerException)を発生させるメソッド
@GetMapping("/todo/error-test")
public String errorTest() {
// 実務でもよくある「中身が空の変数(null)にアクセスしようとした」というエラーを意図的に発生させます
String emptyString = null;
emptyString.length(); // ここで必ず NullPointerException(RuntimeExceptionの仲間)が発生します
return "redirect:/todo";
}TodoController.java に上記メソッドを追記してください。このメソッドは次の todo.html のエラーを処理するものです。
todo.html にエラーテスト用ボタンを配置
<form action="/todo/add" method="post" th:object="${taskForm}">
</form>
<div style="margin-top: 15px;">
<a href="/todo/error-test" style="color: gray; font-size: 0.9em;">【テスト用】内部エラーを発生させる</a>
</div>todo.html を開き、フォーム(<form> タグ)のすぐ下に以下のコードを追記してください。
動作確認


アプリを起動し、ブラウザから (http://localhost:8080/todo) にアクセスしてください。追加した「【テスト用】内部エラーを発生させる」のリンクをクリックしてください。上記画像のようにリンクをクリックすると、システムエラー画面に遷移すれば完了です。
更新処理 (タスクの完了ステータス追加)
ここでは、各タスクの左側に「完了」ボタンを配置し、それをクリックすると、DB内の該当データのステータスが更新され、画面上でタスク名に打ち消し線(横線)が引かれる仕組みを実装します。
実装の流れは以下の通りです。
- Task エンティティにカラムを追加
- TodoController.java の修正
- todo.html にボタンを配置
- 動作確認
Task エンティティにカラムを追加
package com.example.demo;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class Task {
@Id
private String id;
private String title;
private boolean isCompleted; // ⬇️ 追記:完了ステータスを管理するフラグ
public Task() {}
// 既存のコンストラクタ(初期状態は完了フラグを false にする)
public Task(String id, String title) {
this.id = id;
this.title = title;
this.isCompleted = false;
}
// ゲッターメソッド
public String getId() { return id; }
public String getTitle() { return title; }
public boolean isCompleted() { return isCompleted; } // 追記
// ⬇️ 追記:完了ステータスを切り替えるためのセッターメソッド
public void setCompleted(boolean isCompleted) {
this.isCompleted = isCompleted;
}
}タスクが完了したかどうかを管理する真偽値(boolean)のフィールドを、Entityクラスに追加します。Task.java を開き、コードを上記のように書き換えてください。(既存のコードに isCompleted の変数と、それに関連するコンストラクタ・ゲッターを追記します)
TodoController.java の修正
// タスクの完了状態を切り替える処理
@GetMapping("/todo/toggle")
public String toggleTask(@RequestParam(name = "id") String id) {
// 1. 指定されたIDのタスクをDBから検索・取得する(見つからない場合は例外を投げる)
Task task = taskRepository.findById(id)
.orElseThrow(() -> new RuntimeException("指定されたタスクが見つかりません。ID: " + id));
// 2. 現在の完了状態を反転させる(trueならfalse、falseならtrueへ)
task.setCompleted(!task.isCompleted());
// 3. 状態を変更したオブジェクトをDBに再度保存する(Idが既に存在するため、内部で自動的に UPDATE 文が実行されます)
taskRepository.save(task);
return "redirect:/todo";
}TodoController.java の一番下に、上記のメソッドを追記してください。こちらは、指定されたIDのタスクをDBから1件取り出し、完了フラグを反転させて更新するものです。
todo.html にボタンを配置
<style>
body { font-family: sans-serif; margin: 40px; }
table { border-collapse: collapse; width: 500px; margin-top: 20px; } /* 幅を少し広げます */
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.delete-btn { color: red; text-decoration: none; }
/* ⬇️ 追記:完了したタスクに適用するスタイル */
.completed-task { text-decoration: line-through; color: #aaa; }
</style>todo.html を開き、<style> タグの中に、打ち消し線用のCSSクラスを1行追記します。こちらは、完了状態 (true/false) に応じて、タスク名にスタイル(打ち消し線)を適用し、ボタンの文字を「完了」または「戻す」に切り替える処理をThymeleafの条件分岐 (th:classappend や th:text) で実装するものです。
動作確認


アプリを起動し、ブラウザから (http://localhost:8080/todo) にアクセスしてください。そして、タスクを新規登録すると、タスク横に✅完了というリンクが表示されます。以下の2つを確認してください。
- 「✅完了」をクリックすると、画面が更新され、タスク名にグレーの打ち消し線が引かれ、リンクの文字が「🔄戻す」に変化する
- 「🔄戻す」をクリックすると、再び元の未完了状態に戻る
JPAの自動更新機能により、H2データベース内の task テーブルに is_completed カラムが自動で追加されます
コメント