例外処理について書いています。
共通的な例外処理を書いてみよう
まず、例外処理とは、プログラムの実行中に発生する予期しないエラーや問題(例外)を適切に処理するための仕組みです。例外が発生したときにプログラムがクラッシュするのを防ぎ、ユーザーに適切なエラーメッセージを表示したり、エラーの原因をログに記録したりすることができます。これにより、システムの安定性とユーザー体験を向上させることができます。
*
controllerパッケージの中にGlobalAdviceController.javaという名前のJavaファイルを作成します。
クラスに@ControllerAdviceというアノテーションを付与します。@ControllerAdviceは、アプリケーション全体で共通の例外処理を一箇所にまとめるための仕組みを提供します。
@ControllerAdvice
public class GlobalAdviceController {
}
今回は、@ExceptionHandlerというアノテーションを使って、「データベースアクセスエラーが発生した場合のハンドラーメソッド」「不正な引数が検出された場合のハンドラーメソッド」を書いていきます。
@ExceptionHandlerアノテーションは、特定の例外をキャッチして適切に処理するために使用されます。このアノテーションを付けたメソッドは、指定された種類の例外がスローされたときに呼び出されます。
今回書いたメソッドはこちらです。
-
handleDatabaseErrorメソッド:
DataAccessExceptionをキャッチし、エラーメッセージをモデルに追加してエラーページを表示。SQLエラーやデータベース接続エラーなどを処理。 -
handleIllegalArgumentExceptionメソッド:
不正な引数が渡された場合にIllegalArgumentExceptionをキャッチし、エラーメッセージを表示。不適切な引数が渡された場合などを処理。
@ExceptionHandler(DataAccessException.class)
public String handleDatabaseError(DataAccessException ex, Model model) {
model.addAttribute("errorMessage", "データベースアクセスエラーが発生しました。");
return "task/systemError";
}
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgumentException(IllegalArgumentException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "task/systemError";
}
htmlを書いてみよう
続いて、HTMLファイルを書いていきます。template>task配下に、systemError.htmlというファイルを作ります。
controllerで書いたhandleDatabaseErrorメソッドとhandleIllegalArgumentExceptionメソッドで、systemError.htmlを表示してユーザーにエラーが発生したことを伝えます。
thymeleafの記載のない素のhtmlを張り付けます
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>error</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">Practice</span>
</div>
</nav>
<div class="container">
<div class="text-center px-4 pt-3 my-3">
<p>errorMessage</p>
<a type="button" class="btn btn-primary" href="/task/list">Go to List</a>
</div>
</div>
<footer class="py-3 my-4">
<p class="text-center text-body-secondary">© 2023 Company, Inc</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
このsystemError.htmlに対して、行うことは以下の通りです。
- タグにThymeleafの名前空間を追加する。
-
タグの th:text 属性を追加
- hrefの記載を修正する
では、書いていきます。
- タグにThymeleafの名前空間を追加する。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2.< p> タグの th:text 属性を追加
<p th:text="${errorMessage}">errorMessage</p>
3.hrefの記載を修正する
<a type="button" class="btn btn-primary" th:href="@{/task/list}">Go to List</a>
*
これでtodoアプリケーションの実装は終了です。チュートリアルとして文字にしてみると、意外と理解が浅かったり知らなかったこともありました。一度todoアプリケーションを作成して、次はブログの文章を書きながら再度コードを書くのは時間がかかって大変でしたが、よい経験になりました。大変で終わらないと思っていたので、とりあえず、最後まで書ききれてほっとしています。
以下にここまで書いてきたコードの全量を掲載しました。
最後まで読んでいただいた方、ありがとうございました。
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-①イントロダクション-
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-②設計-
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-③実装方針と環境構築-
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-④一覧機能の作成-
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-⑤新規登録機能の作成-
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-⑥変更機能の作成-
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-⑦削除機能の実装-
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-⑧戻る機能の実装-
【Java】Spring Bootを使ったToDoアプリケーションを作成しよう-⑨例外処理の実装-
ここまでで書いたコードの全量
controllerクラス
package com.example.demo.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.demo.entity.Task;
import com.example.demo.form.TaskForm;
import com.example.demo.service.TaskService;
/**
* Webアプリケーションのタスク関連機能を担当するControllerクラスです。
* タスクの一覧表示、登録、変更などの機能が含まれています。
*
*/
@Controller
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
/**
* タスクの一覧を表示するメソッドです。
*
* @param model タスク一覧をViewに渡すためのSpringのModelオブジェクト
* @return "task/index" - タスク一覧表示用のHTMLテンプレートのパス
*/
@RequestMapping(value = "/task/list", method = RequestMethod.GET)
public String showTask(Model model) {
//タスクの一覧を取得
List<Task> taskList = taskService.findAll();
model.addAttribute("taskList", taskList);
return "task/index";
}
/**
* タスクの新規登録画面を表示するメソッドです。
*
* @param model タスク一覧をViewに渡すためのSpringのModelオブジェクト
* @return "task/edit" - タスク新規登録画面のHTMLテンプレートのパス
*/
@GetMapping(value = "/task/add")
public String showForm(Model model) {
// タスクフォームを作成
TaskForm taskForm = new TaskForm();
model.addAttribute("taskForm", taskForm);
return "task/edit";
}
/**
* タスクの変更画面を表示するメソッドです。
*
* @param taskId タスクのID
* @param model タスク一覧をViewに渡すためのSpringのModelオブジェクト
* @return "task/edit" - タスク変更画面のHTMLテンプレートのパス
*/
@GetMapping(value = "/task/edit")
public String showEditForm(@RequestParam("taskId") int taskId,Model model) {
// タスクIDに基づいてタスクを取得
TaskForm taskForm = taskService.getTask(taskId);
model.addAttribute("taskForm", taskForm);
return "task/edit";
}
/**
* タスクの確認画面を表示するメソッドです。
*
* @param taskForm タスクのフォームデータ
* @param bindingResult バリデーション結果を保持するオブジェクト
* @param model タスク一覧をViewに渡すためのSpringのModelオブジェクト
* @return "task/confirm" - タスク確認画面のHTMLテンプレートのパス
*/
@GetMapping(value = "/task/confirm")
public String showConfirmForm(@Validated TaskForm taskForm, BindingResult bindingResult, Model model) {
// バリデーションチェックでエラーがある場合は変更画面に戻る
if (bindingResult.hasErrors()) {
return "task/edit";
}
model.addAttribute("taskForm", taskForm);
return "task/confirm";
}
/**
* タスクを保存するメソッドです。
*
* @param taskForm タスクのフォームデータ
* @param bindingResult バリデーション結果を保持するオブジェクト
* @param redirectAttributes リダイレクト時に属性を渡すためのSpringのRedirectAttributesオブジェクト
* @param model タスク一覧をViewに渡すためのSpringのModelオブジェクト
* @return "redirect:/task/complete" - タスク確認画面へのリダイレクト
*/
@PostMapping(value = "/task/save")
public String saveTask(@Validated TaskForm taskForm, BindingResult bindingResult, RedirectAttributes redirectAttributes,Model model) {
//バリデーションチェック
if (bindingResult.hasErrors()) {
// バリデーションエラーがある場合は変更画面に遷移
return "task/edit";
}
//保存処理
String completeMessage =taskService.save(taskForm);
//redirect先に値を渡す
redirectAttributes.addFlashAttribute("completeMessage", completeMessage);
return "redirect:/task/complete";
}
/**
* タスク完了画面を表示するメソッドです。
*
* @return "task/complete" - タスク完了画面のHTMLテンプレートのパス
*/
@GetMapping("/task/complete")
public String showCompletePage() {
return "task/complete";
}
/**
* タスクの削除確認画面を表示するメソッドです。
*
* @param taskForm タスクのフォームデータ
* @param model タスク一覧をViewに渡すためのSpringのModelオブジェクト
* @return "task/confirm" - タスク確認画面のHTMLテンプレートのパス
*/
@GetMapping(value = "/task/delete")
public String showDeleteForm(@RequestParam("taskId") int taskId, Model model) {
// タスクIDに基づいてタスクを取得
TaskForm taskForm = taskService.getTask(taskId);
model.addAttribute("taskForm", taskForm);
return "task/deleteConfirm";
}
/**
* タスクを削除するメソッドです。
*
* @param taskForm タスクのフォームデータ
* @param bindingResult バリデーション結果を保持するオブジェクト
* @param redirectAttributes リダイレクト時に属性を渡すためのSpringのRedirectAttributesオブジェクト
* @param model タスク一覧をViewに渡すためのSpringのModelオブジェクト
* @return "redirect:/task/complete" - タスク確認画面へのリダイレクト
*/
@PostMapping(value = "/task/delete")
public String deleteTask(@RequestParam("taskId") int taskId, RedirectAttributes redirectAttributes,Model model) {
//保存処理
String completeMessage =taskService.delete(taskId);
//redirect先に値を渡す
redirectAttributes.addFlashAttribute("completeMessage", completeMessage);
return "redirect:/task/complete";
}
/**
* タスクの確認画面から変更画面に戻るメソッドです。
*
* @param taskForm タスクのフォームデータ
* @param model タスク一覧をViewに渡すためのSpringのModelオブジェクト
* @return "task/edit" - タスク変更画面のHTMLテンプレートのパス
*/
@GetMapping("/task/back")
public String backToEditPage(TaskForm taskForm,Model model) {
model.addAttribute("taskForm", taskForm);
return "task/edit";
}
}
GlobalAdviceController.java
package com.example.demo.controller;
import org.springframework.dao.DataAccessException;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalAdviceController {
/**
* データベースアクセスエラーが発生した場合のハンドラーメソッドです。
* @param ex データアクセス例外
* @param model SpringのModelオブジェクト
* @return エラーページのパス
*/
@ExceptionHandler(DataAccessException.class)
public String handleDatabaseError(DataAccessException ex, Model model) {
model.addAttribute("errorMessage", "データベースアクセスエラーが発生しました。");
return "task/systemError";
}
/**
* 不正な引数が検出された場合のハンドラーメソッドです。
* @param ex 不正な引数例外
* @param model SpringのModelオブジェクト
* @return エラーページのパス
*/
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgumentException(IllegalArgumentException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "task/systemError";
}
}
serviceクラス
package com.example.demo.service;
import java.util.List;
import com.example.demo.entity.Task;
import com.example.demo.form.TaskForm;
/**
* タスク関連のサービスを提供するインターフェースです。
*/
public interface TaskService {
/**
* すべてのタスクを取得します。
*
* @return タスクのリスト
*/
List<Task> findAll();
/**
* タスクを保存します。
*
* @param taskForm タスクのフォームデータ
* @return 保存完了メッセージ
*/
String save(TaskForm taskForm);
/**
* 指定されたタスクIDに対応するタスクを取得します。
*
* @param taskId タスクID
* @return タスクのフォームデータ
*/
TaskForm getTask(int taskId);
/**
* タスクを削除します。
*
* @param taskForm タスクのフォームデータ
* @return 削除完了メッセージ
*/
String delete(int taskId);
/**
* タスクのフォームデータをタスクエンティティに変換します。
*
* @param taskForm タスクのフォームデータ
* @return タスクエンティティ
*/
Task convertToTask(TaskForm taskForm);
/**
* タスクエンティティをタスクのフォームデータに変換します。
*
* @param task タスクエンティティ
* @return タスクのフォームデータ
*/
TaskForm convertToTaskForm(Task task);
}
package com.example.demo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.common.Constants;
import com.example.demo.entity.Task;
import com.example.demo.form.TaskForm;
import com.example.demo.repository.TaskRepository;
/**
* タスク関連のビジネスロジックを担当するサービスクラスです。
* タスクの検索、保存、更新などの機能を提供します。
*/
@Service
public class TaskServiceImpl implements TaskService{
@Autowired
TaskRepository taskRepository;
/**
* タスク一覧を取得するメソッドです。
*
* @return List<Task> タスク一覧。
*/
@Override
public List<Task> findAll() {
return taskRepository.findAll();
}
/**
* タスクを保存するメソッドです。
*
* @param task タスクエンティティ
* @return String 完了メッセージ
* @throws OptimisticLockingFailureException 楽観ロックエラーが発生した場合
*/
@Override
@Transactional
public String save(TaskForm taskForm) {
//変換処理
Task task = convertToTask(taskForm);
//完了メッセージを宣言
String completeMessage = null;
if(task.getTaskId() != 0) {
//変更処理の場合
//楽観ロック
int updateCount =taskRepository.update(task);
if (updateCount == 0) {
throw new OptimisticLockingFailureException("楽観ロックエラー");
}
//完了メッセージをセット
completeMessage = Constants.EDIT_COMPLETE;
return completeMessage;
}else {
//登録処理の場合
taskRepository.save(task);
//完了メッセージをセット
completeMessage = Constants.REGISTER_COMPLETE;
return completeMessage;
}
}
/**
* タスクIDに基づいて1件のタスクを取得し、対応するタスクフォームに変換するメソッドです。
*
* @param taskId タスクID
* @return 対応するタスクフォーム
*/
@Override
public TaskForm getTask(int taskId) {
//タスクを取得
Task task =taskRepository.getTask(taskId);
//変換処理
TaskForm taskForm =convertToTaskForm(task);
return taskForm;
}
/**
* タスクを削除するメソッドです。
*
* @param task タスクエンティティ
* @return String 完了メッセージ
* @throws OptimisticLockingFailureException 楽観ロックエラーが発生した場合
*/
@Override
@Transactional
public String delete(int taskId) {
//削除処理
taskRepository.delete(taskId);
//完了メッセージをセット
String completeMessage = Constants.DELETE_COMPLETE;
return completeMessage;
}
/**
* タスクフォームをタスクエンティティに変換するメソッドです。
*
* @param taskForm タスクフォーム
* @return 変換されたタスクエンティティ
*/
@Override
public Task convertToTask(TaskForm taskForm) {
Task task = new Task();
task.setTaskId(taskForm.getTaskId());
task.setTitle(taskForm.getTitle());
task.setDescription(taskForm.getDescription());
task.setDeadline(taskForm.getDeadline());
task.setStatus(taskForm.getStatus());
task.setUpdatedAt(taskForm.getUpdatedAt());
return task;
}
/**
* タスクエンティティをタスクフォームに変換するメソッドです。
*
* @param task タスクエンティティ
* @return 変換されたタスクフォーム
*/
@Override
public TaskForm convertToTaskForm(Task task) {
TaskForm taskForm = new TaskForm();
taskForm.setTaskId(task.getTaskId());
taskForm.setTitle(task.getTitle());
taskForm.setDescription(task.getDescription());
taskForm.setDeadline(task.getDeadline());
taskForm.setStatus(task.getStatus());
taskForm.setUpdatedAt(task.getUpdatedAt());
return taskForm;
}
}
repositoryクラス
package com.example.demo.repository;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.example.demo.entity.Task;
import com.example.demo.mapper.TaskMapper;
/**
* タスク情報にアクセスするためのリポジトリクラスです。
*/
@Repository
public class TaskRepository {
private final TaskMapper taskMapper;
/**
* コンストラクタ
*
* @param taskMapper タスクデータへのマッパー
*/
public TaskRepository(TaskMapper taskMapper) {
this.taskMapper = taskMapper;
}
/**
* 全てのタスクを取得します。
*
* @return タスクのリスト
*/
public List<Task> findAll() {
return taskMapper.findAll();
}
/**
* タスクを保存します。
*
* @param task 保存するタスク
*/
public void save(Task task) {
taskMapper.save(task);
}
/**
* 指定されたタスクIDに対応するタスクを取得します。
*
* @param taskId タスクID
* @return タスク
*/
public Task getTask(int taskId) {
return taskMapper.getTask(taskId);
}
/**
* タスクを更新します。
*
* @param task 更新するタスク
* @return 更新された行数
*/
public int update(Task task) {
return taskMapper.update(task);
}
/**
* タスクを削除します。
*
* @param task 削除するタスク
* @return 削除された行数
*/
public int delete(int taskId) {
return taskMapper.delete(taskId);
}
}
mapperクラス
package com.example.demo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.entity.Task;
/**
* タスクエンティティにアクセスするための MyBatis マッパーインターフェースです。
*/
@Mapper
public interface TaskMapper {
/**
* 全てのタスクを取得します。
*
* @return タスクのリスト
*/
List<Task> findAll();
/**
* タスクを保存します。
*
* @param task 保存するタスク
*/
void save(Task task);
/**
* 指定されたタスクIDに対応するタスクを取得します。
*
* @param taskId タスクID
* @return タスク
*/
Task getTask(int taskId);
/**
* タスクを更新します。
*
* @param task 更新するタスク
* @return 更新された行数
*/
int update(Task task);
/**
* タスクを削除します。
*
* @param task 削除するタスク
* @return 削除された行数
*/
int delete(int taskId);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.TaskMapper">
<!-- タスクの全件取得 -->
<select id="findAll" resultType="com.example.demo.entity.Task">
SELECT * FROM task where deleteFlg = 0;
</select>
<!-- 新規タスクの登録 -->
<insert id="save">
INSERT INTO task
(title, description, deadline, status,created_at,updated_at, deleteFlg)
VALUES
(#{title}, #{description}, #{deadline}, 1,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,0);
</insert>
<!-- タスクIDを指定してタスクを取得 -->
<select id="getTask" resultType="com.example.demo.entity.Task">
SELECT * FROM task where taskId = #{taskId} and deleteFlg = 0;
</select>
<!-- タスクの削除 -->
<delete id ="delete">
DELETE FROM task where taskId = #{taskId} and deleteFlg = 0;
</delete>
<!-- タスクの更新 -->
<update id="update" parameterType="com.example.demo.entity.Task">
UPDATE task SET title = #{title}, description = #{description},deadline = #{deadline},status = #{status}, updated_at = CURRENT_TIMESTAMP WHERE taskId = #{taskId} and updated_at = #{updatedAt};
</update>
</mapper>
formクラス
package com.example.demo.form;
import java.time.LocalDateTime;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class TaskForm {
// タスクID
private int taskId;
// タイトルは1文字以上100文字以下
@NotBlank
@Size(min = 1, max = 100)
private String title;
// 説明は最大200文字
@Size(max = 200)
private String description;
// デッドラインは必須項目
@NotNull
private LocalDateTime deadline;
// ステータスは1から3の範囲
@Min(value = 0)
@Max(value = 3)
private int status;
// 更新日時
private LocalDateTime updatedAt;
public int getTaskId() {
return taskId;
}
public void setTaskId(int taskId) {
this.taskId = taskId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public LocalDateTime getDeadline() {
return deadline;
}
public void setDeadline(LocalDateTime deadline) {
this.deadline = deadline;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}
entityクラス
package com.example.demo.entity;
import java.time.LocalDateTime;
/**
* タスクエンティティクラス
*/
public class Task {
/**
* タスクID(自動インクリメントされる一意の識別子)。
*/
private int taskId;
/**
* タスクのタイトル。
*/
private String title;
/**
* タスクの説明。
*/
private String description;
/**
* タスクの締め切り日時。
*/
private LocalDateTime deadline;
/**
* タスクのステータス(例: 1 - 未着手, 2 - 作業中, 3 - 完了)。
*/
private int status;
/**
* ユーザーID(タスクを所有するユーザーの識別子)。
*/
private Integer userId;
/**
* タスクの作成日時(デフォルトは現在の日時)。
*/
private LocalDateTime createdAt;
/**
* タスクの更新日時(更新時に現在の日時に自動設定)。
*/
private LocalDateTime updatedAt;
/**
* 削除フラグ(タスクが削除されたかどうかを示す)。
*/
private Boolean deleteFlg;
public int getTaskId() {
return taskId;
}
public void setTaskId(int taskId) {
this.taskId = taskId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getDeadline() {
return deadline;
}
public void setDeadline(LocalDateTime deadline) {
this.deadline = deadline;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public Boolean getDeleteFlg() {
return deleteFlg;
}
public void setDeleteFlg(Boolean deleteFlg) {
this.deleteFlg = deleteFlg;
}
}
定数クラス
package com.example.demo.common;
/**
* 定数クラス
*
*/
public class Constants {
// インスタンスの生成禁止
private Constants (){}
public static final String REGISTER_COMPLETE = "The data was successfully saved.";
public static final String EDIT_COMPLETE = "The data was successfully updated.";
public static final String DELETE_COMPLETE = "The data was successfully deleted.";
public static final String ERROR_MESSAGE = "エラーが発生しました.";
public static final String ILLEGALARGUMENTEXCEPTION_ERROR = "タスクIDは正の整数である必要があります。";
}
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>List page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">Practice</span>
</div>
</nav>
<div class="container">
<div class="px-4 pt-3 my-3 text-center border-bottom">
<h1 class="display-4 fw-bold text-body-emphasis mb-3">ToDo Application</h1>
<div class="col-lg-6 mx-auto">
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center mb-5">
<a type="button" class="btn btn-primary btn-lg px-4 me-sm-3" th:href="@{/task/add}">create</a>
</div>
</div>
</div>
<div class="px-4 pt-2 my-2 text-center">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Title</th>
<th scope="col">description</th>
<th scope="col">deadline</th>
<th scope="col">status</th>
<th scope="col">button</th>
<th scope="col">button</th>
</tr>
</thead>
<tbody>
<!-- Sample Data -->
<tr th:each="taskList : ${taskList}" th:object="${taskList}">
<th scope="row" th:text="*{taskId}">1</th>
<td th:text="*{title}">Title</td>
<td th:text="*{description}">description</td>
<td th:text="${#temporals.format(taskList.deadline, 'yyyy/MM/dd HH:mm')}">Formatted Deadline</td>
<td th:text="*{status == 1 ? '未着手' : status == 2 ? '作業中' : '完了'}">Status</td>
<td><a class="btn btn-primary" th:href="@{/task/edit(taskId=${taskList.taskId})}">edit</a></td>
<td><a class="btn btn-primary" th:href="@{/task/delete(taskId=${taskList.taskId})}">delete</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<footer class="py-3 my-4">
<p class="text-center text-body-secondary">© 2023 Company, Inc</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
edit.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>form page</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">Practice</span>
</div>
</nav>
<div class="container">
<h1>Form</h1>
<form class="px-4 pt-3 my-3" method="get" th:action="@{/task/confirm}" th:object="${taskForm}">
<div class="form-group mb-3">
<label>Title</label>
<input type="text" class="form-control" placeholder="Title" name="title" th:value="*{title}">
<div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="text-danger"></div>
</div>
<div class="form-group mb-3">
<label>Description</label>
<input type="text" class="form-control" placeholder="Description" name="description" th:value="*{description}">
<div th:if="${#fields.hasErrors('description')}" th:errors="*{description}" class="text-danger"></div>
</div>
<div class="form-group mb-3">
<label>Deadline</label>
<input type="datetime-local" class="form-control" name="deadline" th:value="*{deadline}">
<div th:if="${#fields.hasErrors('deadline')}" th:errors="*{deadline}" class="text-danger"></div>
</div>
<div class="form-group mb-3" th:if="${taskForm.taskId} !=0">
<label>status</label>
<select name="status">
<option value="1" th:selected="*{status == 1}">未着手</option>
<option value="2" th:selected="*{status == 2}">作業中</option>
<option value="3" th:selected="*{status == 3}">完了</option>
</select>
<div th:if="${#fields.hasErrors('status')}" th:errors="*{status}" class="text-danger"></div>
</div>
<input type="hidden" name="taskId" th:value="*{taskId}">
<input type="hidden" name="updatedAt" th:value="*{updatedAt}">
<!-- 送信ボタン -->
<button type="submit" class="btn btn-primary" value="Confirm">Confirm</button>
<a type="button" class="btn btn-outline-secondary" th:href="@{/task/list}">back</a>
</form>
</div>
<footer class="py-3 my-4">
<p class="text-center text-body-secondary">© 2023 Company, Inc</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
confirm.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Confirm</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">Practice</span>
</div>
</nav>
<div class="container">
<h1>Confirm</h1>
<div class="px-4 pt-3 my-3">
<div class="mb-3">
<label>Title</label>
<p th:text="${taskForm.title}">title</p>
</div>
<div class="mb-3">
<label>Description</label>
<p th:text="${taskForm.description}">description</p>
</div>
<div class="mb-3">
<label>Deadline</label>
<p th:text="${#temporals.format(taskForm.deadline, 'yyyy/MM/dd HH:mm')}">Formatted Deadline</p>
</div>
<div class="mb-3" th:if="${taskForm.taskId} !=0">
<label>Status</label>
<p th:text="${taskForm.status == 1 ? '未着手' : taskForm.status == 2 ? '作業中' : '完了'}">Status</p>
</div>
</div>
<div class="d-flex justify-content-start">
<form th:action="@{/task/save}" method="post" th:object="${taskForm}"class="mb-3">
<input type="hidden" name="title" th:field="*{title}">
<input type="hidden" name="description" th:field="*{description}">
<input type="hidden" name="deadline" th:field="*{deadline}">
<input type="hidden" name="taskId" th:field="*{taskId}">
<input type="hidden" name="status" th:field="*{status}">
<input type="hidden" name="updatedAt" th:value="*{updatedAt}">
<!-- 送信ボタン -->
<button type="submit" class="btn btn-primary me-2" value="submit">submit</button>
</form>
<form th:action="@{/task/back}" method="get" th:object="${taskForm}">
<input type="hidden" name="title" th:field="*{title}">
<input type="hidden" name="description" th:field="*{description}">
<input type="hidden" name="deadline" th:field="*{deadline}">
<input type="hidden" name="taskId" th:field="*{taskId}">
<input type="hidden" name="status" th:field="*{status}">
<input type="hidden" name="updatedAt" th:value="*{updatedAt}">
<button type="submit" class="btn btn-outline-secondary">back</button>
</form>
</div>
</div>
<footer class="py-3 my-4">
<p class="text-center text-body-secondary">© 2023 Company, Inc</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
complete.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>complete</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">Practice</span>
</div>
</nav>
<div class="container">
<div class="text-center px-4 pt-3 my-3">
<p th:text="${completeMessage}">complete</p>
<a type="button" class="btn btn-primary" th:href="@{/task/list}">Go to List</a>
</div>
</div>
<footer class="py-3 my-4">
<p class="text-center text-body-secondary">© 2023 Company, Inc</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
deleteConfirm.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DeleteConfirm</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">Practice</span>
</div>
</nav>
<div class="container">
<h1>Delete Confirm</h1>
<form th:action="@{/task/delete}" , method="post" th:object="${taskForm}"class="mb-3">
<div class="px-4 pt-3 my-3">
<div class="mb-3">
<label>Title</label>
<p th:text="*{title}">title</p>
</div>
<div class="mb-3">
<label>Description</label>
<p th:text="*{description}">description</p>
</div>
<div class="mb-3">
<label>Deadline</label>
<p th:text="${#temporals.format(taskForm.deadline, 'yyyy/MM/dd HH:mm')}">Formatted Deadline</p>
</div>
<div class="mb-3">
<label>Status</label>
<p th:text="*{status == 1 ? '未着手' : status == 2 ? '作業中' : '完了'}">Status</p>
</div>
</div>
<input type="hidden" name="title" th:field="*{title}">
<input type="hidden" name="description" th:field="*{description}">
<input type="hidden" name="deadline" th:field="*{deadline}">
<input type="hidden" name="taskId" th:field="*{taskId}">
<input type="hidden" name="status" th:field="*{status}">
<input type="hidden" name="updatedAt" th:value="*{updatedAt}">
<button type="submit" class="btn btn-primary" value="submit">submit</button>
<a type="button" class="btn btn-outline-secondary" th:href="@{/task/list}">back</a>
</form>
</div>
<footer class="py-3 my-4">
<p class="text-center text-body-secondary">© 2023 Company, Inc</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
systemError.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>error</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<nav class="navbar bg-body-tertiary">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">Practice</span>
</div>
</nav>
<div class="container">
<div class="text-center px-4 pt-3 my-3">
<p th:text="${errorMessage}">errorMessage</p>
<a type="button" class="btn btn-primary" th:href="@{/task/list}">Go to List</a>
</div>
</div>
<footer class="py-3 my-4">
<p class="text-center text-body-secondary">© 2023 Company, Inc</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>