目次
はじめに
Javaのフレームワーク「Spring Boot」とテンプレートエンジン「Thymeleaf」を使用して、List<Object>をPOSTする方法を紹介します。
本記事で作成する画面は次のとおりです。
[ユーザー情報一覧]

ユーザー情報一覧画面では一覧に表示されている「名前」「住所」「電話番号」を一括で更新できるようにしています。
また、入力チェックも実装しているため「更新」ボタン押下時に入力チェックエラーが発生した場合は、画面上部にエラーメッセージが表示されます。

スポンサーリンク
テーブル定義
本画面で使用するテーブルのテーブル定義は次のとおりです。
[ユーザー情報テーブル]
| 物理名 | 論理名 | データ型 | 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 | 論理削除した日時 | 
※本記事ではユーザー情報テーブルにデータが登録されていることを前提としています。
[CREATE文]
CREATE TABLE `sampledb`.`user` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(100) NOT NULL,
  `address` VARCHAR(255) NULL,
  `phone` VARCHAR(50) NULL,
  `update_date` DATETIME NOT NULL,
  `create_date` DATETIME NOT NULL,
  `delete_date` DATETIME NULL,
  PRIMARY KEY (`id`));ディレクトリ構成
ディレクトリ構成は次のとおりです。
springSample
  |
  |___src.main.java
       |
       |___com.example.demo
       |    |
       |    |___controller
       |    |    |
       |    |    |__UserController.java
       |    |
       |    |___dto
       |    |    |
       |    |    |__UserData.java
       |    |    |
       |    |    |__UserListParam.java
       |    |
       |    |___entity
       |    |    |
       |    |    |___User.java
       |    |
       |    |___repository
       |    |    |
       |    |    |___UserRepository.java
       |    |
       |    |___service
       |         |
       |         |___UserService.java
       |
       |___src.main.resources
            |
            |___templates
            |    |
            |    |___user
            |         |
            |         |___list.html
            |
            |___application.propertiesバックエンド(サーバー)側のソースコード
コントローラークラス(UserController)
コントローラクラスは次のとおりです。
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.example.demo.dto.UserListParam;
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) {
    UserListParam userListParam = userService.searchAll();
    model.addAttribute("userListParam", userListParam);
    return "user/list";
  }
  /**
   * ユーザー情報一覧更新
   * @param userRequest リクエストデータ
   * @param model Model
   * @return ユーザー情報詳細画面
   */
  @RequestMapping(value = "/user/listUpdate", method = RequestMethod.POST)
  public String listUpdate(@Validated @ModelAttribute UserListParam userListParam, BindingResult result, Model model) {
    if (result.hasErrors()) {
      List<String> errorList = new ArrayList<String>();
      for (ObjectError error : result.getAllErrors()) {
        if (!errorList.contains(error.getDefaultMessage())) {
          errorList.add(error.getDefaultMessage());
        }
      }
      model.addAttribute("validationError", errorList);
      return "user/list";
    }
    // ユーザー情報の更新
    userService.updateAll(userListParam);
    return "redirect:/user/list";
  }
}コントローラークラスには、ユーザー情報一覧画面の初期表示時に動作する「displayList」メソッドと、「更新」ボタン押下時に動作する「listUpdate」メソッドを用意しています。
「更新」ボタン押下時には、入力チェックを実装しており、引数のresultに入力チェックの結果が格納されています。
- 入力チェックNG:エラー内容を設定して一覧画面に戻る
- 入力チェックOK:画面からの更新内容をテーブルに反映し一覧画面を最新化する(リダイレクトすることで、一覧画面の初期表示処理を呼び出す)
スポンサーリンク
サービスクラス(UserService.java)
ビジネスロジックを記述するサービスクラスは次のとおりです。
package com.example.demo.service;
import java.util.ArrayList;
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.UserData;
import com.example.demo.dto.UserListParam;
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 UserListParam searchAll() {
    // ユーザー情報の取得
    List<User> userList = userRepository.findAll();
    UserListParam userListParam = new UserListParam();
    List<UserData> list = new ArrayList<UserData>();
    // エンティティを画面データに詰め替える
    for(User user : userList) {
      UserData data = new UserData();
      data.setId(user.getId());
      data.setName(user.getName());
      data.setAddress(user.getAddress());
      data.setPhone(user.getPhone());
      data.setUpdateDate(user.getUpdateDate());
      list.add(data);
    }
    userListParam.setUserDataList(list);
    return userListParam;
  }
  /**
   * ユーザー情報更新
   * @param param 画面パラメータ
   */
  public void updateAll(UserListParam param) {
    List<User> userList = new ArrayList<User>();
    // 画面パラメータをエンティティに詰め替える
    for (UserData data : param.getUserDataList()) {
      User user = userRepository.findById(data.getId()).get();
      user.setAddress(data.getAddress());
      user.setName(data.getName());
      user.setPhone(data.getPhone());
      user.setUpdateDate(new Date());
      userList.add(user);
    }
    userRepository.saveAll(userList);
  }
}サービスクラスでは、初期表示時に呼び出す「searchAll」メソッドと、更新処理時に呼び出す「updateAll」メソッドを用意しています。
エンティティクラス(User.java)
ユーザー情報テーブルのエンティティクラスは次のとおりです。
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;
}テーブルと1対1の関係性になるエンティティクラスでは「@Data」アノテーションを使用して、getter、setterの定義を省略しています。
Data Transfer Objectクラス
データ転送用のDTOクラスは次のとおりです。
[UserListParam]
package com.example.demo.dto;
import java.io.Serializable;
import java.util.List;
import javax.validation.Valid;
import lombok.Data;
/**
 * ユーザー情報一覧画面用 データクラス
 */
@Data
public class UserListParam implements Serializable {
  /**
   * ユーザー情報リスト
   */
  @Valid
  private List<UserData> userDataList;
}[UserData]
package com.example.demo.dto;
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;
/**
 * ユーザー情報
 */
@Data
public class UserData implements Serializable {
  /**
   * ID
   */
  private Long id;
  /**
   * 名前
   */
  @NotEmpty(message = "名前を入力してください")
  @Size(max = 100, message = "名前は100桁以内で入力してください")
  private String name;
  /**
   * 住所
   */
  @NotEmpty(message = "住所を入力してください")
  @Size(max = 255, message = "住所は255桁以内で入力してください")
  private String address;
  /**
   * 電話番号
   */
  @Pattern(regexp = "0\\d{1,4}-\\d{1,4}-\\d{4}", message = "電話番号の形式で入力してください")
  private String phone;
  /**
   * 更新日時
   */
  @DateTimeFormat(pattern="yyyy/MM/dd")
  private Date updateDate;
}本記事では「Bean Validation」を使って入力チェックしています。
上記のとおり「名前」と「住所」に必須チェック(@NotEmpty)と桁数チェック(@Size)、「電話番号」に形式チェック(@Pattern)を実装しています。
スポンサーリンク
「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>リポジトリクラス(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> {}フロントエンド(クライアント)側のソースコード
ユーザー情報一覧画面(list.html)
ユーザー情報一覧画面のHTMLファイルは次のとおりです。画面デザインには「Bootstrap」を使っています。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <title th:text="ユーザー情報一覧"></title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  <meta charset="utf-8" />
</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/listUpdate}" th:object="${userListParam}" th:method="post">
      <div  class="text-end">
        <input type="submit" class="btn btn-primary" value=" 更新 ">
      </div><br />
      <table class="table table-striped">
        <thead>
          <tr>
            <th>ID</th>
            <th>名前</th>
            <th>住所</th>
            <th>電話番号</th>
            <th>更新日時</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <tr th:each="user, st : *{userDataList}" class="align-middle">
            <td>
              <label th:text="${user.id}"></label>
              <input type="hidden" th:field="*{userDataList[__${st.index}__].id}">
            </td>
            <td>
              <input type="text" class="form-control" th:field="*{userDataList[__${st.index}__].name}">
            </td>
            <td>
              <input type="text" class="form-control" th:field="*{userDataList[__${st.index}__].address}">
            </td>
            <td>
              <input type="text" class="form-control" th:field="*{userDataList[__${st.index}__].phone}">
            </td>
            <td>
              <label th:text="${#dates.format(user.updateDate, 'yyyy/MM/dd')}"></label>
              <input type="hidden" th:field="*{userDataList[__${st.index}__].updateDate}">
            </td>
          </tr>
        </tbody>
      </table>
    </form>
  </div>
</body>
</html>formタグに「th:object="${userListParam}"」を定義し、「th:each="user, st : *{userDataList}"」でUserListParamクラスが持っているuserDataListを繰り返し表示することでユーザー情報の一覧を生成しています。
入力項目ではない「id」と「更新日時」を"hidden"でデータを隠し持つことで、入力チェックエラーが発生したときに表示内容が消えないようにしています。
動作確認
Spring Bootプロジェクトを実行して http://localhost:8080/user/list へアクセスします。

ユーザー情報一覧画面が表示されるので、一覧のデータを編集し「更新」ボタンを押下。データベースに画面で編集した内容が反映され、ユーザー情報一覧が最新化すれば完了です。
また「名前」や「住所」を未入力で「更新」ボタンを押下すると、画面上部に入力チェックエラーの内容が表示されます。

 
 