MENU

[Java 初心者学習] Spring Boot 開発 (機能追加) – 0015

Spring Boot での開発作業、ここまで実施することで形にはなってきています。しかし、まだ機能が全然足りていない状態です。この記事では、Webでよくある機能 (入力チェック、例外処理、更新処理) を付け加えていきます。

ここから先は、以下の記事まで対応済みであることを前提としています。ご了承ください。

目次

Webアプリケーションの機能拡張

ここまでの流れで作成したToDoアプリでは、実際にはまだまだ昨日の肉付けが必要です。実務のWebアプリケーションとして成立させるために、プログラム側の機能を肉付けしていきます。

データのバリデーション
(入力チェック)
空文字のタスクや、100文字を超えるような不正な入力をエラーとして弾く仕組み (jakarta.validation) を導入する
例外処理の共通化
(エラーハンドリング)
万が一システム内部でエラーが起きた際、ブラウザに「Whitelabel Error Page」ではなく、ユーザー向けにデザインされたエラー画面を返す構造(@ControllerAdvice) を作る
更新処理
(タスクの完了ステータス追加)
DBのテーブルに is_completed (真偽値) というカラムを追加し、画面上で「完了チェックボックス」を押すと、取り消し線が引かれるような更新処理を実装する。

データのバリデーション (入力チェック)

これまで作成してきた Todoリストに、以下の入力チェックを行うよう機能追加します。

  • タスク名は必須
  • 20文字以内

実装の流れは以下の通りです。

  1. pom.xml にバリデーション用ライブラリ追加
  2. データを受け取る Form クラス作成
  3. TodoController.java の修正
  4. todo.html にエラーメッセージを追加
  5. 動作確認

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リストに、以下の例外処理を行うよう機能追加します。

  • 例外が発生したときにエラー画面に遷移する
  • アプリ内のどこでエラーが起きても、それを一括でキャッチする

実装の流れは以下の通りです。

  1. エラー表示用画面作成
  2. 全体のエラーを監視する共通ハンドラークラス作成
  3. TodoController.java の修正
  4. todo.html にエラーテスト用ボタンを配置
  5. 動作確認

エラー表示用画面作成

<!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内の該当データのステータスが更新され、画面上でタスク名に打ち消し線(横線)が引かれる仕組みを実装します。

実装の流れは以下の通りです。

  1. Task エンティティにカラムを追加
  2. TodoController.java の修正
  3. todo.html にボタンを配置
  4. 動作確認

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 カラムが自動で追加されます

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

CAPTCHA


目次