(第11回) 日記の編集画面と更新機能を追加、CRUDのU(Update、更新)【Spring Boot2で日記ウェブアプリ】

Spring Boot2.3,2.4で日記投稿ウェブアプリ入門の第11回です。日記の編集画面とコントローラクラスに更新処理をするメソッドを追加します。CRUDでいうと、U(Update、更新)の部分になります。Spring Bootの初心者・入門者の方は、参考にしてみてください。

動作環境と今回の目的

最終更新日:2021/5/19

前回は、日記アプリの新規登録処理にバリデーション機能(検証チェック)を追加しました。
(第10回) Spring Boot2で登録フォームのバリデーション機能を追加【Spring Boot2で日記ウェブアプリ】

今回は、日記の編集画面と更新機能を開発していきます。CRUDでいうと、U(Update、データ更新)の部分になります。
Thymeleafでフロントエンドの編集画面を作成し、コントローラクラスに日記の更新処理をするメソッドを追加していきます。

◾️動作環境やバージョンは以下の通りです。
OS:macOS Catalina(バージョン10.15.5)
開発環境:Eclipse(Pleiades All in One、4.16(2020-06)、Java Full Edition版)
Spring Bootバージョン:2.3.3、2.4.0
Java:11
データベース:H2
Bootstrap4.5.3

今回作成するファイル

今回作成するファイルを先に書いておきます。

新規に作成するファイルは「edit.html」「EditDiaryForm.java」の2つで、編集するファイルは「DiaryController.java」の1つです。

編集画面edit.htmlの作成、フロントエンド

まずはフロントエンド側の日記の編集画面の作成についてです。
編集画面は、日記の一覧画面の編集ボタンから遷移できるようにします。

第7回のページのフロント側のsummary.htmlから、編集ボタンのHTML部分を抜粋して示します。

<td><a th:href="@{/diary/edit(id=${diary.id})}" class="btn btn-primary">編集</a></td>

編集画面への遷移URLです(クエリストリングのidの値2は編集対象の日記のidです)。

http://localhost:8080/diary/edit?id=2

それでは編集画面を作っていきます。src/main/resources/templateディレクトリ下にedit.htmlというファイル名で新規作成して編集します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>【Spring Boot】日記の編集</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
</head>
<body class="container mt-3">
<h1 class="display-5">日記の編集</h1>
<form th:action="@{/diary/update}" method="post">
<input type="text" name="updateddiary" th:value="${editdiary.bodytext}"  class="form-control mb-2">
<input type="hidden" name="id" th:value="${editdiary.id}"/>
<button type="submit" class="btn btn-primary">更新する</button><br/><br/>
<div th:if="${#fields.hasErrors('editDiaryForm.updateddiary')}" th:errors="*{editDiaryForm.updateddiary}" class="alert alert-danger" >投稿エラー</div>
<a href="/diary/summary">日記一覧に戻る</a>
</form>
</body>
</html>

まだコントローラクラスの方を作っていないので編集画面の表示はできませんが、イメージしやすいように先に編集画面を示しておきます。
【Spring Boot】日記アプリの編集画面

画面自体は編集するためのテキストボックスと更新ボタンだけなので、とてもシンプルです。

編集画面のソース(edit.html)を見ると、画面を開いた時にテキストボックスは編集前の日記本文を表示するために「th:value="*{editdiary.bodytext}"」という属性を付けています。

また、hiddenタグに編集する日記のidを保持するために「th:value="${editdiary.id}"」という属性を付けています。
この「th:value="${…}"」は、サービス側のコントローラクラスから受け取る情報ですので、属性の値はこれから作るコントローラクラスと合わせる必要があります。

また、編集する日記本文の内容がバリデーションチェックでエラーになった場合、エラーメッセージを表示するようにしています。

<div th:if="${#fields.hasErrors('editDiaryForm.updateddiary')}" th:errors="*{editDiaryForm.updateddiary}" class="alert alert-danger" >投稿エラー</div>
エラーメッセージの表示については、前回の新規投稿フォームのバリデーション時と同じです。

編集画面用のフォームクラスの作成、バリデーション

次に、編集画面のフォームに合わせてフォームクラスEditDiaryForm.javaを新規に作成します。そして、このフォームクラスに実際に検証したいバリデーションルールを書いていきます。

com.example.demoパッケージ内にEditDiaryForm.javaを作成して編集します。

package com.example.demo;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class EditDiaryForm {
  //編集する日記id
  @NotNull
  private int id;

  //編集投稿された日記本文
  @NotNull
  @Size(min=3, max=150)
  private String updateddiary;
}

EditDiaryFormクラスにあるフィールドはidとupdateddiaryです。

updateddiaryは、編集投稿された日記本文がセットされてきます。 そして、updateddiaryと言うフィールド名は、画面側の日記本文のinputタグのname属性に合わせています。

編集投稿された日記本文のバリデーションルールは新規投稿時と同じですので、@NotNull、@Sizeなどのアノテーションも同じです。 ですのでフォームクラスのバリデーションルールの説明は、前回のページのフォームクラスの作成、バリデーションルールの設定を参考にしてください。

もう1つのフィールドのidは、編集対象の日記idがセットされてきます。idには@NotNullを付けてnullチェックだけしています。

今回は新規投稿用と編集投稿用のフォームクラスをそれぞれ作りましたが、もしも新規投稿時と編集時のフィールドやバリデーションルールが同じなら、フォームクラスを1つにまとめても良いと思います。ただフォームクラスを1つにするなら、フォームクラスのフィールド名と画面側のname属性名はすべて合わせる必要があります。

コントローラクラスに編集画面の呼び出しメソッドと日記の更新処理メソッドを追加

次に、コントローラクラスDiaryController.javaです。 上で作成した編集画面を呼び出すeditメソッドと、日記の更新処理を行うupdateメソッドを追加します。editメソッドとupdateメソッド追加後のDiaryController.javaのコードです。

package com.example.demo;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

import javax.validation.Valid;

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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("diary")
public class DiaryController {
  @Autowired
  DiaryRepository diaryRepository;
  
  //日記一覧情報の取得
  @GetMapping("summary")
  public String summary(Model model, NewDiaryForm newDiaryForm) {
    Iterable<Diary> diarys = diaryRepository.findAll();
    model.addAttribute("diarys", diarys);
    return "summary";
  }

  //指定されたidの日記を削除する
  @PostMapping("delete")
  public String delete(@RequestParam Integer id) {
    diaryRepository.deleteById(id);
    return "redirect:/diary/summary";
  }

  //@Validを@Validatedに変更しても問題なく動作します
  //日記の新規登録
  @PostMapping("add")
  public String add(Model model, @Valid NewDiaryForm newDiaryForm, BindingResult bindingResult) {
    //バリデーションエラーがあるかどうかチェックして、エラーがあるなら新規登録処理をせずに、一覧表示メソッドを呼び出す
    if (bindingResult.hasErrors()) {
    	return summary(model, newDiaryForm);
    }
    //ChronoUnit.SECONDSで秒以下を切り捨て
    Diary diary = new Diary(newDiaryForm.getNewdiary(), LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));
    diaryRepository.save(diary);
    return "redirect:/diary/summary";
  }

  //編集画面を表示する
  @GetMapping("edit")
  String edit(Model model, EditDiaryForm editDiaryForm) {
    Diary diary = diaryRepository.findById(editDiaryForm.getId()).get();
    model.addAttribute("editdiary", diary);
    return "edit";
  }

  //日記を更新する
  @PostMapping("update")
  String update(Model model, @Valid EditDiaryForm editDiaryForm, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
      return edit(model, editDiaryForm);
    }
    Diary diary = new Diary(editDiaryForm.getId(), editDiaryForm.getUpdateddiary(), LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));
    diaryRepository.save(diary);
    return "redirect:/diary/summary";
  }
}

先に、編集画面を呼び出すeditメソッドについてです。
editメソッドには@GetMapping("edit")が付いていますので、このURLにGETリクエストされた時に実行されます。

http://localhost:8080/diary/edit?id=2
クエリストリングのidの値2は編集対象の日記のidです。そして、この値がeditメソッドの引数"EditDiaryForm editDiaryForm"にあるフィールドidにセットされます。

次に、editメソッド内の1行目ですが、まずは編集対象の日記をDBのdiaryテーブルから取得します。

Diary diary = diaryRepository.findById(editDiaryForm.getId()).get();
diaryテーブルからの取得は、第4回のページで作成したリポジトリインタフェースDiaryRepositoryを使用しています。findByIdメソッドは、Spring Data JPAのCrudRepositoryが宣言しているメソッドで、戻り値はOptional<T>です。

次に、editメソッド内の2行目です。ModelクラスのaddAttributeメソッドを使用して、編集対象の日記オブジェクトdiaryをフロントエンドへ返す準備をしています。

model.addAttribute("editdiary", diary);
第5回のページのsummaryメソッドの時にも説明しましたが、ModelクラスのaddAttributeメソッドを使用する事で、サーバサイドからフロントエンド(クライアント側)へデータの受け渡しができるようになります。
これでフロントエンドでは、editdiaryという変数で編集対象の日記データを扱えるようになります。

そして最後の行で、編集画面のedit.htmlをフロントエンドへ返しています。

return "edit";

次に、日記の更新処理を行うupdateメソッドについてです。
updateメソッドには@PostMapping("update")が付いていますので、このURLにPOSTリクエストされた時に実行されます。

http://localhost:8080/diary/update

updateメソッドの引数には"@Valid EditDiaryForm editDiaryForm"がありますので、editDiaryFormのフィールドのidとupdateddiaryに、HTTPのPOSTリクエストされるidとupdateddiaryを受け取る事ができます。
この変数名のidとupdateddiaryは、HTMLフォームのname属性名と一致させる必要があります。

上で書きました編集画面のedit.htmlの一部を抜粋して確認してみます。

<form th:action="@{/diary/update}" method="post">
<input type="text" name="updateddiary" th:value="${editdiary.bodytext}"  class="form-control mb-2">
<input type="hidden" name="id" th:value="${editdiary.id}"/>
textとhiddenのinputタグがあり、その2つのデータがupdateddiaryとidという名前(name属性)でPOSTリクエストで送信されてくるのがわかります(idは編集対象の日記idで、updateddiaryは編集投稿の日記本文です)。

また、引数のeditDiaryFormには@Validが付いていますので、editDiaryFormの各フィールドのバリデーションチェック機能が有効になります。
そして、editDiaryFormのバリデーションチェックの結果が、もう一つの引数の"BindingResult bindingResult"に入ります。

次に、updateメソッド内の最初のコードです。

if (bindingResult.hasErrors()) {
  return edit(model, editDiaryForm);
}

bindingResultのhasErrorsメソッドでバリデーションエラーが存在したかどうかを判別し、エラーがあった場合は更新処理をせずにeditメソッドを呼び出し、エラーメッセージと一緒に編集画面へ返すようにしています。

ここまでが、更新処理時のバリデーションチェックです。やっている事は、新規投稿時のバリデーションとほぼ同じです。
そして、バリデーションチェックが問題なければ、日記の更新処理へ進みます。

updateメソッドの次の行ですが、更新処理をする日記のdiaryオブジェクトを生成しています。

Diary diary = new Diary(editDiaryForm.getId(), editDiaryForm.getUpdateddiary(), LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));

続いて次の行で、前行で生成したdiaryオブジェクトをDBのdiaryテーブルで更新処理をしています。

diaryRepository.save(diary);
diaryテーブルのデータ更新処理は、第4回のページで作成したリポジトリインタフェースDiaryRepositoryを使用しています。saveメソッドは、Spring Data JPAのCrudRepositoryが宣言しているメソッドで、実装はSimpleJpaRepositoryクラスです。

◾️リポジトリクラスの新規作成処理と更新処理のコードは同じ
リポジトリクラスdiaryRepositoryの更新処理のコード"diaryRepository.save(diary);"ですが、これは新規投稿時(addメソッド)と同じです。
saveメソッドの実装ソースコード(SimpleJpaRepositoryクラス)を見ると、DBのテーブルにレコードが存在するかどうかで実行するSQLをinsertかupdateで使い分けてくれています。これは、実行時のSQLログを見てもわかると思います。

そしてupdateメソッドの最後の行ですが、日記の一覧画面へリダイレクトしています。 日記の更新処理が無事完了したら、日記一覧データを再表示するためです。

return "redirect:/diary/summary";

リダイレクト先URLです。

http://localhost:8080/diary/summary

アプリの実行と動作確認

それではSpring Bootアプリを起動して、ブラウザで動作確認をしてみます。
まずは日記一覧画面にアクセスしてみます。アクセスするURLは「http://localhost:8080/diary/summary」です。

日記一覧データが表示されたら、編集したい日記の編集ボタンを押して、日記の編集画面へ遷移します。
そして、編集画面で適当に日記本文を編集して更新するボタンを押します。
これで更新処理が完了し、日記一覧画面へ戻り、日記本文の更新が反映されていることがわかるはずです。

また、編集画面の日記本文の編集で、空文字や2文字以下にして更新ボタンを押すと、バリデーションチェックにかかり、エラーメッセージと一緒に編集画面が再表示されます。

[広告] Kindle Unlimitedなら対象の本が定額で読み放題
対象本には、プログラミングなどのIT関連本、マンガ、雑誌、ビジネス書などがたくさんあります!

最後に

以上今回は、日記の編集画面の作成と、コントローラクラスに編集画面を呼び出すメソッドと日記の更新処理をするメソッドを追加しました。

これで第5回から今回までで、日記のCRUD(Create・データ生成、Read・データ取得、Update・データ更新、Delete・データ削除)処理機能すべてを開発したことになります。

日記投稿というシンプルなウェブアプリでしたが、これでDBを使用して、基本的なCRUD機能を備えたウェブアプリをSpring Boot2で開発する事ができました。

Spring Bootの日記投稿ウェブアプリ開発入門トップ