0
1

More than 3 years have passed since last update.

Spring Boot + Thymeleaf+JPA+PostgreSQLでCRUDサンプルの改良・開発(Spring Boot ver 2.2以降)

Posted at

記事を作成するにあたり@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を有効化する必要があります(下記を参照)。

application.properties
#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 

プロジェクトのディレクトリ構成

ディレクトリは以下の構成です(画像が縦に長いので分割して表示させています)。
画像1.png
画像2.png

pom.xmlファイルの構成

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のテーブル構成

player.sql
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クラス

controller/PlayerController.java
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クラス

domain/Player.java
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クラス

repository/PlayerRepository.java
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メソッドを書くようにしましょう。

service/PlayerService.java
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

players/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

players/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

players/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

players/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]を選択します。
コンソール画面にビルド処理が開始されます。
画像5.png

ブラウザを立ち上げてURLに以下のアドレスを入力してアプリを表示します。

URL:http://localhost:8080/players

一覧画面

画像6.png

新規作成画面

画像7.png

データを入力して登録する。

画像8.png

再度、一覧表示画面に戻ると登録データが表示されます。

画像9.png

登録データの中身を閲覧する。

一覧画面にある参照ボタンをクリックします。デザインは凝っていないので悪しからず...
画像10.png

データの更新

一覧画面の「編集」ボタンをクリックして編集画面に遷移します。
画像11.png

更新すると一覧画面に更新内容が反映されます。

画像12.png

登録データの削除

一覧画面の削除ボタンをクリックすると該当レコードが削除されます。
画像13.png

以上、Spring Boot 2.2以降のバージョンアップによってHttp通信の処理の変更について解説してみました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1