Spring Boot

Spring Boot + Thymeleafで編集画面を作成する

はじめに

Javaの人気フレームワーク「Spring Boot」と「Spring Boot」と相性の良いテンプレートエンジン「Thymeleaf」を使用して編集画面を作成し、編集画面から更新処理を行う方法を紹介します。

本記事で作成する「ユーザー情報編集」画面のイメージは以下のとおり。

ユーザー情報編集

「保存」または「キャンセル」ボタン押下で「ユーザー情報詳細」画面に戻るようにします。

詳細画面

入力項目

項目 チェック内容
名前 必須、100桁以内
住所 255桁以内
電話番号 必須、電話番号形式

スポンサーリンク

各イベント処理

「キャンセル」リンク押下:詳細画面へ戻る

「保存」ボタン押下時:下記の挙動とする

  • 入力チェックOK:データを更新し詳細画面へ遷移
  • 入力チェックNG:ユーザー編集画面の画面上部にエラー内容を表示する

詳細画面については「Spring Boot + Thymeleafで詳細画面を作成する」の記事をご覧ください。

テーブル定義

物理名 論理名 データ型 NOT NULL 説明
id ID BIGINT 主キー(AUTO_INCREMENT)
name 名前 VARCHAR(100) ユーザーの名前
address 住所 VARCHAR(255) ユーザーの住所
phone 電話番号 VARCHAR(50) ユーザーの電話番号
update_date 更新日時 DATETIME 最終更新日時
create_date 作成日時 DATETIME 登録日時
delete_date 削除日時 DATETIME 論理削除した日時

ディレクトリ構成

ディレクトリ構成は次のとおり。

springSample
  |
  |___src.main.java
       |
       |___com.example.demo
       |    |
       |    |___controller
       |    |    |
       |    |    |__UserController.java
       |    |
       |    |___entity
       |    |    |
       |    |    |___User.java
       |    |
       |    |___dto
       |    |    |
       |    |    |___UserRequest.java
       |    |    |
       |    |    |___UserUpdateRequest.java
       |    |
       |    |___repository
       |    |    |
       |    |    |___UserRepository.java
       |    |
       |    |___service
       |         |
       |         |___UserService.java
       |
       |___src.main.resources
            |
            |___templates
            |    |
            |    |___common
            |    |    |
            |    |    |___head.html 
            |    |
            |    |___user
            |         |
            |         |___add.html
            |         |
            |         |___edit.html
            |         |
            |         |___list.html
            |         |
            |         |___view.html
            |
            |___application.properties

バックエンド(サーバー)側のソースコード

コントローラークラス(UserController.java)

コントローラクラスの内容は以下のとおり。

package com.example.demo.controller;

import java.util.ArrayList;
import java.util.List;

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.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.example.demo.dto.UserUpdateRequest;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;

/**
 * ユーザー情報 Controller
 */
@Controller
public class UserController {

  /**
   * ユーザー情報 Service
   */
  @Autowired
  private UserService userService;

  /**
   * ユーザー情報一覧画面を表示
   * @param model Model
   * @return ユーザー情報一覧画面
   */
  @GetMapping(value = "/user/list")
  public String displayList(Model model) {
    List<User> userlist = userService.searchAll();
    model.addAttribute("userlist", userlist);
    return "user/list";
  }

  /**
   * ユーザー新規登録画面を表示
   * @param model Model
   * @return ユーザー情報一覧画面
   */
  @GetMapping(value = "/user/add")
  public String displayAdd(Model model) {
    return "user/add";
  }

  /**
   * ユーザー情報詳細画面を表示
   * @param id 表示するユーザーID
   * @param model Model
   * @return ユーザー情報詳細画面
   */
  @GetMapping("/user/{id}")
  public String displayView(@PathVariable Long id, Model model) {
    User user = userService.findById(id);
    model.addAttribute("userData", user);
    return "user/view";
  }

  /**
   * ユーザー編集画面を表示
   * @param id 表示するユーザーID
   * @param model Model
   * @return ユーザー編集画面
   */
  @GetMapping("/user/{id}/edit")
  public String displayEdit(@PathVariable Long id, Model model) {
    User user = userService.findById(id);
    UserUpdateRequest userUpdateRequest = new UserUpdateRequest();
    userUpdateRequest.setId(user.getId());
    userUpdateRequest.setName(user.getName());
    userUpdateRequest.setPhone(user.getPhone());
    userUpdateRequest.setAddress(user.getAddress());
    model.addAttribute("userUpdateRequest", userUpdateRequest);
    return "user/edit";
  }

  /**
   * ユーザー更新
   * @param userRequest リクエストデータ
   * @param model Model
   * @return ユーザー情報詳細画面
   */
  @RequestMapping(value = "/user/update", method = RequestMethod.POST)
  public String update(@Validated @ModelAttribute UserUpdateRequest userUpdateRequest, BindingResult result, Model model) {

    if (result.hasErrors()) {
      List<String> errorList = new ArrayList<String>();

      for (ObjectError error : result.getAllErrors()) {
        errorList.add(error.getDefaultMessage());
      }
      model.addAttribute("validationError", errorList);
      return "user/edit";
    }

    // ユーザー情報の更新
    userService.update(userUpdateRequest);
    return String.format("redirect:/user/%d", userUpdateRequest.getId());
  }

}

ユーザー編集画面の初期表示は「displayEdit」メソッドでおこなっています。処理の流れは下記のとおり

処理の流れ

  • リクエストデータ(HTTP GET)から表示対象のIDを取得
  • データベースからIDをキーにデータを抽出
  • 抽出したデータを「User」クラスに設定する
  • ユーザー編集画面を表示する

 

ユーザーデータの更新は「update」メソッドでおこなっています。処理の流れは下記のとおり

処理の流れ

  • リクエストデータの入力チェックをおこなう
  • 入力チェックNGの場合は、エラーメッセージを設定しユーザー編集画面へ
  • 入力チェックOKの場合は、入力データの内容をデータベースに反映する
  • ユーザー詳細画面へ遷移する

スポンサーリンク

サービスクラス(UserService.java)

データベース接続はJPA、トランザクション管理は「@Transactional」のアノテーションを使用、「@Transactional」を使う事で「create」メソッドの登録に成功すればコミット、失敗してExceptionが発生すればロールバックを自動でおこなってくれます。

package com.example.demo.service;

import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.dto.UserUpdateRequest;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;

/**
 * ユーザー情報 Service
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class UserService {
  /**
   * ユーザー情報 Repository
   */
  @Autowired
  private UserRepository userRepository;

  /**
   * ユーザー情報 全検索
   * @return 検索結果
   */
  public List<User> searchAll() {
    return userRepository.findAll();
  }

  /**
   * ユーザー情報 主キー検索
   * @return 検索結果
   */
  public User findById(Long id) {
    return userRepository.findById(id).get();
  }

  /**
   * ユーザー情報 更新
   * @param user ユーザー情報
   */
  public void update(UserUpdateRequest userUpdateRequest) {
    User user = findById(userUpdateRequest.getId());
    user.setAddress(userUpdateRequest.getAddress());
    user.setName(userUpdateRequest.getName());
    user.setPhone(userUpdateRequest.getPhone());
    user.setUpdateDate(new Date());
    userRepository.save(user);
  }
}

エンティティクラス(User.java)

データベースから取得したデータを格納するエンティティクラスの内容は以下のとおり。「@Data」アノテーションを使用して「getter」「setter」の定義を省略しています。

package com.example.demo.entity;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;

/**
 * ユーザー情報 Entity
 */
@Entity
@Data
@Table(name = "user")
public class User implements Serializable {
  /**
   * ID
   */
  @Id
  @Column(name = "id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  /**
   * 名前
   */
  @Column(name = "name")
  private String name;
  /**
   * 住所
   */
  @Column(name = "address")
  private String address;
  /**
   * 電話番号
   */
  @Column(name = "phone")
  private String phone;
  /**
   * 更新日時
   */
  @Column(name = "update_date")
  private Date updateDate;
  /**
   * 登録日時
   */
  @Column(name = "create_date")
  private Date createDate;
  /**
   * 削除日時
   */
  @Column(name = "delete_date")
  private Date deleteDate;
}

リポジトリクラス(UserRepository.java)

データベースにアクセスする為のリポジトリクラスの内容は以下の通り。

package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.entity.User;

/**
 * ユーザー情報 Repository
 */
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

Data Transfer Object(UserRequest.java)

ユーザー編集画面からのリクエストデータを格納するオブジェクトクラスは以下のとおり。

ユーザー編集画面はユーザー新規登録と項目がほぼ同じため(ユーザーIDが追加されるだけ)、「UserUpdateRequest」クラスは「UserRequest」クラスを継承して作成しています。

ユーザー新規登録の作成手順はこちら

入力チェックは「Bean Validation」を使用しています。「@NotEmpty」や「@Size」などを定義することで入力チェックを実装することができます。

package com.example.demo.dto;

import java.io.Serializable;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import lombok.Data;

/**
 * ユーザー情報 リクエストデータ
 */
@Data
public class UserRequest implements Serializable {

  /**
   * 名前
   */
  @NotEmpty(message = "名前を入力してください")
  @Size(max = 100, message = "名前は100桁以内で入力してください")
  private String name;

  /**
   * 住所
   */
  @Size(max = 255, message = "住所は255桁以内で入力してください")
  private String address;

  /**
   * 電話番号
   */
  @Pattern(regexp = "0\\d{1,4}-\\d{1,4}-\\d{4}", message = "電話番号の形式で入力してください")
  private String phone;
}
package com.example.demo.dto;

import java.io.Serializable;

import javax.validation.constraints.NotNull;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * ユーザー情報更新リクエストデータ
 *
 */
@Data
@EqualsAndHashCode(callSuper=false)
public class UserUpdateRequest extends UserRequest implements Serializable {

  /**
   * ユーザーID
   */
  @NotNull
  private Long id;
}

Bean ValidationはSpring Bootのバージョンが2.3.1以降の場合、依存関係に「spring-boot-starter-validation」の追加が必要です。

gradleの場合は「build.gradle」に以下を追加。

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-validation'
}

mavenの場合は「pom.xml」に以下を追加。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

スポンサーリンク

フロントエンド(クライアント)側のソースコード

HTML(edit.html)

共通ヘッダ(head.html)、ユーザー情報一覧画面(list.html)のHTMLは一覧画面作成の記事、ユーザー情報詳細画面(view.html)のHTMLはユーザー情報詳細画面作成の記事で紹介していますので、そちらの記事をご覧ください。

ユーザー編集画面のHTMLは以下のとおり。※デザインは「Bootstrap」を使用しています。

入力チェックエラーが発生した場合は、画面上部にエラー内容を出力しています。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
<head
  th:replace="common/head :: head_fragment(title = 'ユーザー情報編集', scripts = ~{::script}, links = ~{::link})"></head>
<body>
  <div class="container">
    <div th:if="${validationError}" th:each="error : ${validationError}">
      <label class="text-danger" th:text="${error}"></label>
    </div>
    <h1>ユーザー情報編集</h1>

    <form th:action="@{/user/update}" th:object="${userUpdateRequest}" th:method="post">
      <input type="hidden" th:field="*{id}" />
      <div>
        <div class="row mx-md-n5">
          <div class="col-2 pt-3 border bg-light">名前<span class="text-danger">※</span></div>
          <div class="col py-2 border">
            <input type="text" class="form-control" th:field="*{name}">
          </div>
        </div>
        <div class="row mx-md-n5">
          <div class="col-2 pt-3 border bg-light">住所</div>
          <div class="col py-2 border">
            <input type="text" class="form-control" th:field="*{address}">
          </div>
        </div>
        <div class="row mx-md-n5">
          <div class="col-2 pt-3 border bg-light">電話番号<span class="text-danger">※</span></div>
          <div class="col py-2 border">
            <input type="text" class="form-control" th:field="*{phone}">
          </div>
        </div>
      </div>
      <br />
      <div class="text-center">
        <input type="submit" class="btn btn-primary" value=" 保存 ">
        <a th:href="@{/user/{id}(id=*{id})}" class="btn btn-secondary">キャンセル</a>
      </div>
    </form>
  </div>
</body>
</html>

動作確認

Spring Bootプロジェクトを実行して http://localhost:8080/user/list へアクセスします。

一覧画面のイメージ

ユーザー情報一覧画面が表示されるので、一覧から対象ユーザーの「詳細」ボタンを押下。

詳細画面

ユーザー詳細画面が表示されるので「編集」ボタンを押下、ユーザー情報編集画面が表示され、ユーザー情報が編集できればOKです。

ユーザー情報編集


チャンネル登録はこちら

フォローはこちら