記事を作成するにあたり@ozaki25さんの以下のサンプルを参考にさせていただきました。
【参考記事のタイトル】
SpringBoot + JPA + Thymeleafで簡単なCRUDを作る②~画面と機能作成まで~
URL:https://qiita.com/ozaki25/items/3b348874b6db5ab4f04f
開発環境
Windows10
Eclipse
PostgreSQL
各種フレームワーク
Spring Boot バージョン:2.5.0
Bootstrap バージョン:5.1.0
Spring Boot ver2.2以降での開発の際に変更になった点
HttpHiddenMethodFilterがデフォルトで無効化(disabled)されました。
それによってHttpメソッドの中のPutメソッド(リソースの更新、作成を担う処理)とDeleteメソッド(リソースの削除を担う処理)の設定がデフォルトの状態のままでは実行できません。
なので、設定ファイル(application.propertiesまたはapplication.yml)の中でHttpHiddenMethodFilterを有効化する必要があります(下記を参照)。
#Hibernateの設定
spring.jpa.hibernate.ddl-auto=update
#PostgreSQLの設定
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=[ユーザ名]
spring.datasource.password=[パスワード]
#【Spring Boot 2.2以降での変更点】
# Http PutまたはDeleteメソッドを使う場合は
#「spring.mvc.hiddenmethod.filter.enabled=true」を追加・設定すること。
spring.mvc.hiddenmethod.filter.enabled=true
プロジェクトのディレクトリ構成
ディレクトリは以下の構成です(画像が縦に長いので分割して表示させています)。
pom.xmlファイルの構成
pom.xmlファイルの中身は以下の通りです。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>baseball</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>baseball</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.webjars/bootstrap 20210829追加-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>5.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.webjars/jquery 20210829追加-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.webjars/popper.js 20210829追加-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>popper.js</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
PostgreSQLのテーブル構成
CREATE TABLE public.player
(
id bigint NOT NULL,
age integer,
name character varying(255) COLLATE pg_catalog."default",
"position" character varying(255) COLLATE pg_catalog."default",
team character varying(255) COLLATE pg_catalog."default",
CONSTRAINT player_pkey PRIMARY KEY (id)
)
サーバ側(src/main/java/com.example/baseball)のファイルの中身
次にサーバ側のファイルの中身を見ていきます。
Controllerクラス
package com.example.baseball.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.baseball.domain.Player;
import com.example.baseball.service.PlayerService;
@Controller
@RequestMapping("/players")
public class PlayerController {
@Autowired
private PlayerService playerService;
@GetMapping
public String index(Model model) {
List<Player> players = playerService.findAll();
model.addAttribute("players", players);
return "players/index";
}
@GetMapping("new")
public String newPlayer(Model model) {
return "players/new";
}
@GetMapping("{id}/edit")
public String edit (@PathVariable Long id, Model model) {
Player player = playerService.findById(id);
model.addAttribute("player",player);
return "players/edit";
}
@GetMapping("{id}")
public String show(@PathVariable Long id, Model model) {
Player player = playerService.findById(id);
model.addAttribute("player", player);
return "players/show";
}
@PutMapping("{id}")
public String update(@PathVariable Long id, @ModelAttribute Player player) {
player.setId(id);
playerService.save(player);
return "redirect:/players";
}
@DeleteMapping("{id}")
public String destroy(@PathVariable Long id) {
playerService.delete(id);
return "redirect:/players";
}
//new.htmlの「作成」ボタンをクリックしたときに実行される処理
// http://localhost:8080/players/new/
@PostMapping(path="/new")
public String getInfo(@ModelAttribute Player player) {
playerService.insertSave(player);
return "redirect:/players";
}
}
Entityクラス
package com.example.baseball.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Player {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
private String team;
private String position;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getTeam() {
return team;
}
public void setTeam(String team) {
this.team = team;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
@Override
public String toString() {
return "Player [ id=" + id + ", name=" + name + ", age=" + age + ", team=" + team + ", position=" + position + "]";
}
}
Repositoryクラス
package com.example.baseball.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.baseball.domain.Player;
@Repository
public interface PlayerRepository extends JpaRepository <Player, Long> {
}
Serviceクラス
【Spring Boot 2.2以降の変更点】
データベースから1件のレコードを取得するfindOneメソッドを書くと、findByIdメソッドに変更するよう、推奨されます。
なのでfindByIdメソッドを書くようにしましょう。
package com.example.baseball.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate; //追加 20210902
import org.springframework.stereotype.Service;
import com.example.baseball.domain.Player;
import com.example.baseball.repository.PlayerRepository;
@Service
public class PlayerService {
@Autowired
private PlayerRepository playerRepository;
//JDBC Templateを定義する。
@Autowired
private final JdbcTemplate jdbcTemplate;
//コンストラクタを定義する。
public PlayerService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<Player> findAll(){
return playerRepository.findAll();
}
//IDを引数にして1件のレコードを取得する処理
public Player findById(Long id) {
return playerRepository.findById(id).get();
}
//追加 20210902
public Player insertSave(Player player) {
//関数findMaxIdメソッドからIDの最大値を取得して変数maxIdに入れる。
long maxId = findMaxId();
//maxIdに1を加算して新規IDを取得する。
maxId = maxId + 1;
//クエリ文を実行する。
jdbcTemplate.update("INSERT INTO PLAYER (id,age,name,position,team) VALUES(?,?,?,?,?) ",
maxId,player.getAge(),player.getName(),player.getPosition(),player.getTeam());
return player;
}
/*
* IDの最大値を取得する処理
* @return IDの最大値
*/
public long findMaxId() {
//クエリ文を用意する。
String queryString = "SELECT MAX(id) FROM PLAYER";
//jdbcTemplate.queryForObjectメソッドから返却された値を入れる変数selectedMaxIdを用意する。
Object selectedMaxId ="";
//IDの最大値を入れる変数maxIdを用意して0を入れて初期化する。
long maxId =0;
/*
* jdbcTemplate.queryForObjectにクエリ文を入れて実行する。
* @selectedMaxId データベースにレコードが存在する場合、IDの最大値が変数selectedMaxIdに格納される。
* @selectedMaxId データベースにレコードが存在しない場合、NULLが変数selectedMaxIdに格納される。
* */
selectedMaxId = jdbcTemplate.queryForObject(queryString, long.class);
//変数selectedMaxIdに格納されている値がNULLの場合、変数maxIdを返却(return)する。
if (selectedMaxId == null) {
return maxId;
}
//queryForObjectメソッドでIDの最大値を取得・返却する。
return jdbcTemplate.queryForObject(queryString, long.class);
}
/*
* 該当レコードを削除する処理
* @param id
* */
public void delete(Long id){
playerRepository.deleteById(id);
}
}
フロント側(src/main/resources/templates)のファイルの中身
次に画面に表示される(フロント側)側のファイルの中身を見てみましょう。
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<title>Listing Players - baseball</title>
<!-- 共通のフレームワーク -->
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.1.0/css/bootstrap.min.css}" />
<script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.0/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.0/js/bootstrap.min.js}"></script>
<script th:src="@{/webjars/popper.js/2.9.3/umd/popper.min.js}"></script>
</head>
<body>
<div class="container">
<h1>Listing Players</h1>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>名前</th>
<th>年齢</th>
<th>チーム名</th>
<th>守備位置</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<!-- ② -->
<tr th:each="player:${players}" th:object="${player}">
<!-- ③ -->
<td th:text="*{id}"></td>
<td th:text="*{name}"></td>
<td th:text="*{age}"></td>
<td th:text="*{team}"></td>
<td th:text="*{position}"></td>
<!-- ④ -->
<td><a class="btn btn-default btn-xs" th:href="@{/players/{id}(id=*{id})}">参照</a></td>
<td><a class="btn btn-default btn-xs" th:href="@{/players/{id}/edit(id=*{id})}">編集</a></td>
<td>
<!-- ⑤ -->
<form th:action="@{/players/{id}(id=*{id})}" th:method="delete">
<input class="btn btn-default btn-xs" type="submit" value="削除" />
</form>
</td>
</tr>
</tbody>
</table>
<a class="btn btn-default" href="/players/new">新規作成</a>
</div>
</body>
</html>
new.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<title>New Player - baseball</title>
<!-- 共通のフレームワーク -->
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.1.0/css/bootstrap.min.css}" />
<script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.0/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.0/js/bootstrap.min.js}"></script>
<script th:src="@{/webjars/popper.js/2.9.3/umd/popper.min.js}"></script>
</head>
<body>
<div class="container">
<h1>New Player</h1>
<!-- 成功例: @{/players} Controllerは@PostMappingと書く-->
<!-- 成功例:@{/players/new} Controllerは@RequestMapping(value="/new",method = RequestMethod.POST)と書く -->
<form th:action="@{/players/new}" th:method="post">
<div class="form-group">
<label class="control-label">名前</label>
<input class="form-control" type="text" name="name" />
</div>
<div class="form-group">
<label class="control-label">年齢</label>
<input class="form-control" type="number" name="age" />
</div>
<div class="form-group">
<label class="control-label">チーム名</label>
<input class="form-control" type="text" name="team" />
</div>
<div class="form-group">
<label class="control-label">守備位置</label>
<input class="form-control" type="text" name="position" />
</div>
<button class="btn btn-default" type="submit">作成</button>
</form>
<div class="pull-right">
<a class="btn btn-link" href="/players">一覧画面へ</a>
</div>
</div>
</body>
</html>
edit.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<title>Editing Player - baseball</title>
<!-- 共通のフレームワーク -->
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.1.0/css/bootstrap.min.css}" />
<script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.0/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.0/js/bootstrap.min.js}"></script>
<script th:src="@{/webjars/popper.js/2.9.3/umd/popper.min.js}"></script>
</head>
<body>
<div class="container">
<h1>Editing Player</h1>
<!-- フォームの送信をPostからPutに変更 2021/08/29 -->
<form th:action="@{/players/{id}(id=*{id})}" th:method="put" th:object="${player}">
<div class="form-group">
<label class="control-label">名前</label>
<!-- ① -->
<input class="form-control" type="text" th:field="*{name}" />
</div>
<div class="form-group">
<label class="control-label">年齢</label>
<input class="form-control" type="number" th:field="*{age}" />
</div>
<div class="form-group">
<label class="control-label">チーム名</label>
<input class="form-control" type="text" th:field="*{team}" />
</div>
<div class="form-group">
<label class="control-label">守備位置</label>
<input class="form-control" type="text" th:field="*{position}" />
</div>
<button class="btn btn-danger" type="submit">更新</button>
</form>
<div class="pull-right">
<a class="btn btn-link" href="/players">一覧画面へ</a>
</div>
</div>
</body>
</html>
show.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<title>Show Player - baseball</title>
<!-- 共通のフレームワーク -->
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.1.0/css/bootstrap.min.css}" />
<script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.0/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/bootstrap/5.1.0/js/bootstrap.min.js}"></script>
<script th:src="@{/webjars/popper.js/2.9.3/umd/popper.min.js}"></script>
</head>
<body>
<div class="container">
<h1>Show Player</h1>
<div th:object="${player}" class="border border-dark">
<div>
<label>ID</label>
<p th:text="*{id}"></p>
</div>
<div>
<label>名前</label>
<p th:text="*{name}"></p>
</div>
<div>
<label>年齢</label>
<p th:text="*{age}"></p>
</div>
<div>
<label>チーム名</label>
<p th:text="*{team}"></p>
</div>
<div>
<label>守備位置</label>
<p th:text="*{position}"></p>
</div>
</div>
<div>
<a class="btn btn-secondary" th:href="@{/players/{id}/edit(id=*{id})}">編集</a>
</div>
<div class="pull-right">
<a class="btn btn-link" href="/players">一覧画面へ</a>
</div>
</div>
</body>
</html>
ローカルサーバーでアプリケーションを起動する。
ここまで出来たらアプリケーションを起動させます。
プロジェクトの上で右クリックして[実行]⇒[Spring Boot]を選択します。
コンソール画面にビルド処理が開始されます。
ブラウザを立ち上げてURLに以下のアドレスを入力してアプリを表示します。
URL:http://localhost:8080/players
一覧画面
新規作成画面
データを入力して登録する。
再度、一覧表示画面に戻ると登録データが表示されます。
登録データの中身を閲覧する。
一覧画面にある参照ボタンをクリックします。デザインは凝っていないので悪しからず...
データの更新
一覧画面の「編集」ボタンをクリックして編集画面に遷移します。
更新すると一覧画面に更新内容が反映されます。
登録データの削除
一覧画面の削除ボタンをクリックすると該当レコードが削除されます。
以上、Spring Boot 2.2以降のバージョンアップによってHttp通信の処理の変更について解説してみました。