変更機能を作成していきましょう。
2.設計で検討した一覧機能をおさらい
機能一覧
3.タスクの変更:
ユーザーが既存タスクを変更できる機能。タイトル、説明、期日、ステータスの変更が可能。
画面遷移
一覧画面 ⇔ 変更画面 ⇔ 確認画面 → 完了画面 (→ 一覧画面)
画面一覧
- 変更画面
- 確認画面
- 完了画面
画面項目
- 変更画面
タイトル 入力/出力 100文字まで/必須
説明 入力/出力 200文字まで
期限 入力/出力 必須 yyyy/mm/dd
ステータス 入力/出力 必須 0~3までの範囲
確認 ボタン 確認画面に遷移
もどる ボタン 一覧画面に遷移
- 確認画面
タイトル 出力
説明 出力
期限 出力 yyyy/mm/dd
ステータス 出力 変更のときのみ
完了 ボタン 登録/変更処理を実行し、完了画面に遷移
もどる ボタン 確認画面に遷移
- 完了画面
完了メッセージ 出力
"The data was successfully updated."変更したときのみ表示
go to list ボタン 一覧画面に遷移
バリデーションメッセージ
必須チェックエラー:
文字数チェックエラー:
整合性チェックエラー:
Titleを入力してください。
100文字以内で入力してください。
descriptionは200文字以内で入力してください。
deadlineを入力してください。
変更機能は登録機能と画面の流れは似ていているようです。
変更画面でステータスの項目があるのが違いのようです。以下のような画面になりそうです。
*
ユーザーからの見られ方を想像してみます。
- ブラウザから一覧画面にアクセス
- 一覧画面が表示される
- タスクの変更ボタンを押下
- タイトル、説明、期限、ステータスを入力し、確認ボタンを押下
- 確認画面で内容を確認し、完了ボタンを押下
- 完了画面で登録できたメッセージを確認し、go to listボタンを押下
- 一覧画面で新規登録したタスクが一覧に表示される
*
URL設計で検討したもので、変更機能で関係があるのは以下のものでしょうか
画面名: タスク変更画面
URL: /task/edit
HTTPメソッド: GET
画面名: タスク確認画面
URL: /task/confirm
HTTPメソッド: GET
画面名: (リダイレクト先が存在しないため、リダイレクトされないため画面名はなし)
URL: /task/save
HTTPメソッド: POST
画面名: タスク完了画面
URL: /task/complete
HTTPメソッド: GET
実装方針(箇条書き)
-
Controllerクラス
- HTTPリクエストを受け取り、適切なServiceクラスのメソッドを呼び出す。
- 必要なデータを取得し、ビューに渡して適切なHTMLファイルを返す。
-
Serviceクラス
- ビジネスロジックを実装。
- データの操作や処理を行い、Repositoryクラスを介してデータベースとのやり取りを行う。
- ビジネスロジックをControllerクラスから切り離し、コードの再利用性や保守性を向上。
-
Repositoryクラス
- データベースとのやり取りを担当。
- Mapperクラスを介してSQLクエリを定義し、Repositoryクラスから呼び出す。
-
Mapperクラス
- MyBatisを使用してSQLクエリを実行し、データの永続化や取得、更新、削除などの操作を行う。
実装の流れ
- まずControllerクラスから始めて、リクエストに対する処理を実装。
- 次にServiceクラスでビジネスロジックを実装し、必要なデータの操作を行う。
- Repositoryクラスを実装し、Mapperクラスを呼び出す。
- Mapperクラスを実装し、MyBatisを使用してSQLクエリを実行するメソッドを定義。
- 最後にHTMLファイルを作成し、ThymeleafでHTMLにマージする。
URL設計をベースに変更画面~確認画面~完了画面と画面遷移をしながらタスクを登録する処理を書いていきます。今回は、登録画面と変更画面でhtmlを分けず、1つのhtmlファイルで実装していきます。
それでは、Javaの実装を行いましょう。
controllerクラスを書いてみよう
URL設計で検討したURLをもとにcontrollerクラスのメソッドを書いていきましょう。
登録画面と変更画面は1つのhtmlで表現する仕様で考えているため(確認画面も1つのhtmlで表現する)、今回controllerクラスで記載するメソッドは1つです。「/task/edit」というURLを受け取り、(登録画面と同じ)変更画面のhtmlを返り値として返してあげます。
このメソッドのポイントは、taskIdをパラメータクエリで受け取って、DBからタスクを取得し、取得したデータをModelにセットすることです。変更する対象のタスクをDBから取得します。
@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";
}
※formクラスについて、変更機能で使用するタスクIDやステータスなどの値も、新規登録機能の実装の際に記載しているため、ここではスキップします。
serviceクラスを書いてみよう
controllerクラスからserviceクラスを呼び出したのは、タスクIDをもとにタスク1件のデータを取得する処理でした。この処理が動くようにserviceクラスを書いていきましょう。getTaskメソッドにはentityオブジェクトから、formオブジェクトに変換する処理が必要になるので、convertToTaskFormというインターフェースも記載します。
まず、taskService.javaにserviceのインターフェースを書きます。
TaskForm getTask(int taskId);
TaskForm convertToTaskForm(Task task);
*
次に実装クラスの記述を行っていきます。オーバーライドのアノテーションをつけてtaskIdを受け取り、タスクを取得するメソッドを記載します。repositoryクラスから取得するタスクは、entityクラスの型となっていますので、formオブジェクトに変換します。変換する理由は、オブジェクトの用途が異なるからです。
@Override
public TaskForm getTask(int taskId) {
//タスクを取得
Task task =taskRepository.getTask(taskId);
//変換処理
TaskForm taskForm =convertToTaskForm(task);
return taskForm;
}
@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;
}
*
さらに、serviceクラスのsaveメソッドに、変更処理もできるように処理を追加します。
登録処理だけの記述だったところを変更処理にも対応できるようにします。ポイントは2つあります。まず1つ目は、taskIdが0かどうかで登録処理なのか、変更処理なのか分岐させることです。taskIdがsaveメソッドに渡ってくるのは、変更処理の時だけです(登録処理は初回登録なので、まだtaskIdは振り出されていません)。そのため、taskIdが0以外のときは、変更処理のときと判断できます。
2つ目のポイントは、楽観ロックです。楽観ロックとは、複数のトランザクションが同じデータを同時に更新しようとする際にデータの整合性を保つための手法です。楽観ロックは、データの競合が発生しないと楽観的に仮定し、データの更新時にのみ競合をチェックします。
レコードを更新する際に、読み取った時点(変更画面を表示するために、DBからデータを取得した時点)のバージョン情報(ここでは更新日時updated_at)が現在のバージョン情報と一致しているかを確認します(SQLのupdate文の「WHERE taskId = #{taskId} AND updated_at = #{updatedAt}」のことをいっています)。一致していれば更新を実行します。一方、一致しない場合(他のトランザクションによってupdated_atが変更されている場合)、更新は実行されず、更新件数(updateCount)が0になります。更新件数が0の時は、serviceクラスで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;
}
}
【FYI】
排他制御(楽観ロック・悲観ロック)の基礎
https://qiita.com/NagaokaKenichi/items/73040df85b7bd4e9ecfc
repositoryクラスを書いてみよう
repositoryクラスでタスク1件を取得するメソッドと、変更のメソッドを書いていきましょう。
以下のように記載します。
public Task getTask(int taskId) {
return taskMapper.getTask(taskId);
}
public int update(Task task) {
return taskMapper.update(task);
}
mybatisを書いてみよう
ます、mapperのインターフェースを書いていきます。
タスク1件を取得するインターフェースと、変更のインターフェースを書いていきましょう。
Task getTask(int taskId);
int update(Task task);
次に、mapper.xmlの中にSQLを書いていきます。まず、タスクIDを指定してタスクを取得するSQLを書きます。変更画面を表示するときに使うSQLです。その次に、タスク更新のSQLを書きます。楽観ロックのことも考えて、「WHERE taskId = #{taskId} and updated_at = #{updatedAt}」とします。データを更新すると、更新日時も更新するので、「updated_at = CURRENT_TIMESTAMP」のような記載にしています。
<!-- タスクIDを指定してタスクを取得 -->
<select id="getTask" resultType="com.example.demo.entity.Task">
SELECT * FROM task where taskId = #{taskId} and deleteFlg = 0;
</select>
<!-- タスクの更新 -->
<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>
htmlマージをしてみよう
次に、htmlを書いていきましょう。変更機能では、設計より以下のように画面遷移します。
画面遷移
一覧画面 ⇔ 変更画面 ⇔ 確認画面 → 完了画面 (→ 一覧画面)
まずは、一覧画面を修正します。ボタンに修正を入れます。
editボタンの記述は以下のようになっているかと思います
<td><a class="btn btn-primary" href="/task/edit/1">edit</a></td>
taskIdをクエリとして持たせるようにhrefを書き換えます。
<td><a class="btn btn-primary" th:href="@{/task/edit(taskId=${taskList.taskId})}">edit</a></td>
*
次に、edit.htmlを変更画面としても使用できるように、処理を追加していきましょう。
このedit.htmlに対して、行うことは以下の通りです。
- ステータスの入力欄を追加する
- タスクID(taskId)と更新日時(updatedAt)をhiddenで保持する
では、書いていきます。
- ステータスの入力欄を追加する
以下のように追加しました。変更のときだけ表示する項目なので、
th:if="${taskForm.taskId} !=0"
と書いて、表示/非表示をコントロールしています。そして、ステータスはセレクトボックスで記載しています。
<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>
- タスクID(taskId)と更新日時(updatedAt)をhiddenで保持する
以下のように追加しました。taskIdもupdatedAtもupdate文で使用するので、hiddenで保持します。
<input type="hidden" name="taskId" th:value="*{taskId}">
<input type="hidden" name="updatedAt" th:value="*{updatedAt}">
*
次は、確認画面を書いていきましょう。
このconfirm.htmlに対して、行うことは以下の通りです。
- ステータスの入力欄を追加する
- ステータス(status)とタスクID(taskId)と更新日時(updatedAt)をhiddenで保持する
では、書いていきましょう。
- ステータスの入力欄を追加する
ステータスを表示する部分も含めて、以下のように記載しました。変更のときだけ表示するように、th:if="${taskForm.taskId} !=0"
で制御していますね。また、未着手、作業中、完了のステータスを三項演算子を重ねる形で表示を制御しました。
<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>
- ステータス(status)とタスクID(taskId)と更新日時(updatedAt)をhiddenで保持する
formタグの中身に、ステータスとタスクIDと更新日時の項目を足しました。
<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>
動作確認
ここまでで、変更処理を行うコードを追加しました。
変更機能で確認すべきポイントは、大きく2つです。
1.画面に沿って遷移して、変更画面で値を変更すると、値がupdateされるか
2.変更処理の時にステータスの入力欄が表示され、登録処理の時にステータスの入力欄が表示されない
1.について、DB上変更されるかは、ブラウザ上で変更操作をした後に、MySQLworkbenchで以下のSQLを打ってDBに値が入っているかを確認して、チェックします。
select * from tutorialtodoapplication.task;
2,について、新規登録で画面に沿って操作し、ステータスの入力欄がないことを目視で確認します。そして、変更で画面に沿って操作し、ステータスの入力欄が表示されることを確認します。
次の投稿では、削除処理を実装していきます。
【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";
}
}
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 タスクエンティティ
*/
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 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);
}
}
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);
}
<?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>
<!-- タスクの更新 -->
<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>
<!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>
<!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" 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>
<!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>
<button type="submit" class="btn btn-outline-secondary">back</button>
</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>
<!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>