Spring Securityを使ったWebアプリケーションの基本④　〜ユーザー情報を一覧と作成〜


<h2>はじめに</h2>
<a href="https://qiita.com/homes31285/items/9e7b4916954e55f15487">前回の記事</a>では、ユーザー情報をデータベースに登録し、ユーザー情報テーブルから、ログインできるように実装をしていきました。今回はユーザー情報を一覧で表示し、ユーザーと思います。

<details>
<summary>ファイル構成</summary>
<pre>
<code>
.
├── .gradle
├── .idea
├── build
├── gradle
└── src
     ├── main
     │    ├── java
     │    │    └── com
     │    │         └── example
     │    │              └── practice 
     │    │                  ├── config
     │    │                  │    └── SecurityConfig.java
     │    │                  ├── web 
     │    │                  │    ├── user
     │    │                  │    │    ├── UserController.java
     │    │                  │    │    └── userForm.java
     │    │                  │    ├── order
     │    │                  │    │    ├── OrderController.java
     │    │                  │    │    └── OrderForm.java
     │    │                  │    └── IndexController.java
     │    │                  └── domain
     │    │                      ├── authentication
     │    │                      │    ├── CustomUserDetails.java
     │    │                      │    ├── CustomUserDetailsService.java
     │    │                      │    ├── User.java
     │    │                      │    ├── UserService.java
     │    │                      │    └── UserRepository
     │    │                      └── order 
     │    │                           ├── OrderEntity.java
     │    │                           ├── OrderService.java
     │    │                           └── OrderRepository.java
     │    │                        
     │    └── resources
     │         ├── static
     │         ├── templates
     │         │    ├── users
     │         │    │    ├── creationForm.html
     │         │    │    └── list.html
     │         │    ├── order
     │         │    │    ├── delete_confirmation.html
     │         │    │    ├── detail.html 
     │         │    │    ├── form.html 
     │         │    │    └── list.html
     │         │    ├── login.html
     │         │    └── index.html
     │         ├── schema.sql
     │         ├── data.sql 
     │         └── application.properties
     └── test
.gitignore
build.gradle
gradlew.bat
HELP.md
settings.gradle
</code>
</pre>
</details>

<h2>UserRepository</h2>
まずは、ユーザー情報を一覧で表示するために、ユーザー情報をSELECT文で取得します。

```java:UserRepository
package com.example.practice.domain.authentication;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Optional;

@Mapper
public interface UserRepository {
        @Select("select * from users where username = #{username}")
        Optional<User> findByUsername(String username);

        @Select("select * from users")
        List<User> findAll();
}
```

<h2>UserService</h2>
次に、ビジネスロジックの部分であるUserServiceを記載していきます。


```java:UserService
package com.example.practice.domain.authentication;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class UserService {

    // UserRepositoryのインスタンスを注入
    private final UserRepository userRepository;

    // ユーザー一覧を取得するメソッド
    public List<User> findAll() {
        // userRepositoryを使ってデータベースからユーザー一覧を取得し、返す
        return userRepository.findAll();
    }
}
```

<h2>VIEWの作成</h2>
次に、ユーザー一覧を表示するためのVIEWを作成していきます。
以下がその実装になります。

```html:list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>トップページ | 業務用アプリケーション</title>
    <!-- Bootstrap CSS link -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
    <!-- ヘッダー部分 -->
    <header class="mb-4 bg-light p-3 rounded">
        <div class="d-flex justify-content-between align-items-center">
            <h1 class="mb-0"><a href="../index.html" th:href="@{/}" class="text-dark text-decoration-none">業務管理アプリケーション</a></h1>
            <a href="#" th:href="@{/logout}" th:attr="data-method='post'" class="btn btn-dark" style="font-size: 18px;">ログアウト</a>
        </div>
    </header>
    <div class="d-flex justify-content-center align-items-center flex-column">
        <h1 class="mt-3">ユーザー一覧</h1>
        <table class="table table-striped">
            <thead>
            <tr>
                <th>ユーザー名</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="user : ${userList}">
                <td th:text="${user.username}"></td>
            </tr>
            </tbody>
        </table>
    </div>
</div>
</body>
</html>
```

<h2>UserController</h2>
最後に、Controllerの実装をしていきます。


```java:UserController
package com.example.practice.web.user;

import com.example.practice.domain.authentication.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Collections;

@Controller
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping
    public String showList(Model model) {
        model.addAttribute("userList", userService.findAll());
        return "users/list";
    }
}
```

<h2>ユーザー作成</h2>
次に、ユーザー情報をデータベースに追加する処理を追加していきたいと思います。
こちらもMVCモデルに従って作成していきましょう。


<h2>UserForm</h2>
ユーザー情報をフォームとして受け取るために、データベースの定義をしていきます。

```java:UserForm
package com.example.practice.web.user;

import lombok.AllArgsConstructor;
import lombok.Data;
import javax.validation.constraints.NotBlank;

@Data
@AllArgsConstructor
public class UserForm {
    @NotBlank
    private String username;
    @NotBlank
    private String password;

}
```

<h3>UserRepository</h3>

ユーザー情報を追加するINSERT文の処理を追加していきます。

```java:UserRepository
package com.example.practice.domain.authentication;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Optional;

@Mapper
public interface UserRepository {
    // ユーザー名を指定してユーザー情報を取得するクエリ
    @Select("select * from users where username = #{username}")
    Optional<User> findByUsername(String username);

    // ユーザー一覧を取得するクエリ
    @Select("select * from users")
    List<User> findAll();

    // ユーザー情報を挿入するクエリ。#{param1} と #{param2} は引数の順番にバインドされるプレースホルダー
    @Insert("insert into users (username, password) values (#{param1}, #{param2})")
    void insert(String username, String password);
}
```

UserRepositoryでは、上記のコメントを含むように、データベースからユーザー情報を取得したり、新しいユーザー情報を挿入するクエリ処理を定義しています。

<h3>UserService</h3>

次にUserServiceに先ほど追加したINSERT文の処理を説明していきます。

```java:UserService
package com.example.practice.domain.authentication;

import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class UserService {

    // UserRepositoryのインスタンスを注入
    private final UserRepository userRepository;

    // ユーザー一覧を取得するメソッド
    public List<User> findAll() {
        // userRepositoryを使ってデータベースからユーザー一覧を取得し、返す
        return userRepository.findAll();
    }
    
    // ユーザーを作成するメソッド
    public void create(String username, String password) {
        // ユーザー名とパスワードを引数としてuserRepositoryのinsertメソッドを呼び出し、新しいユーザー情報を挿入する
        userRepository.insert(username, password);
    }
}
```

<h2>VIEW</h2>

次に、VIEWの作成をしていきます。

以下がユーザー作成画面になります。

```html:creationForm.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ユーザー作成 |  業務用アプリケーション</title>
    <!-- Bootstrap CSS link -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
    <!-- ヘッダー部分 -->
    <header class="mb-4 bg-light p-3 rounded">
        <div class="d-flex justify-content-between align-items-center">
            <h1 class="mb-0"><a href="../index.html" th:href="@{/}" class="text-dark text-decoration-none">業務管理アプリケーション</a></h1>
            <a href="#" th:href="@{/logout}" th:attr="data-method='post'" class="btn btn-dark" style="font-size: 18px;">ログアウト</a>
        </div>
    </header>
    <form action="#" th:action="@{/users}" method="post" th:object="${userForm}">
    <div class="mt-3">
            <label for="usernameInput" class="form-label">ユーザー名</label>
            <input type="text" id="usernameInput" class="form-control" th:field="*{username}">
        </div>
        <div class="mt-3">
            <label for="passwordInput" class="form-label">パスワード</label>
            <input type="password" id="passwordInput" class="form-control" th:field="*{password}">
        </div>
        <div class="mt-3">
            <button type="submit" class="btn btn-primary">作成</button>
            <a href="./list.html" th:href="@{/users}" class="btn btn-secondary">取消</a>
        </div>
    </form>
</div>
</body>
</html>
```

ユーザー一覧画面に遷移リンクを追加します。

```html:list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>トップページ | 業務用アプリケーション</title>
    <!-- Bootstrap CSS link -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
    <!-- ヘッダー部分 -->
    <header class="mb-4 bg-light p-3 rounded">
        <div class="d-flex justify-content-between align-items-center">
            <h1 class="mb-0"><a href="../index.html" th:href="@{/}" class="text-dark text-decoration-none">業務管理アプリケーション</a></h1>
            <a href="#" th:href="@{/logout}" th:attr="data-method='post'" class="btn btn-dark" style="font-size: 18px;">ログアウト</a>
        </div>
    </header>
    <!-- ヘッダー部分の最後 -->
    <h2 class="text-center">ユーザー一覧</h2>
        <a href="../index.html" th:href="@{/}" class="btn btn-primary">トップページ</a>
        <a href="../creationForm.html" th:href="@{/users/creationForm}"class="btn btn-success">作成</a>
        <table class="table table-striped">
            <thead>
            <tr>
                <th>ユーザー名</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="user : ${userList}">
                <td th:text="${user.username}"></td>
            </tr>
            </tbody>
        </table>
    </div>
</div>
</body>
</html>
```

<h2>UserController</h2>

最後にControllerの設定をしていきます。

```java:UserController
package com.example.practice.web.user;

import com.example.practice.domain.authentication.UserService;
import com.example.practice.web.order.OrderForm;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Collections;

@Controller
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {

    // UserServiceのインスタンスを注入
    private final UserService userService;

    // ユーザーリストを表示するGETリクエスト
    @GetMapping
    public String showList(Model model) {
        // UserServiceを使って全てのユーザー情報を取得し、"userList"という名前でモデルに追加
        model.addAttribute("userList", userService.findAll());
        return "users/list"; // "users/list" テンプレートを返す
    }

    // ユーザー作成フォームを表示するGETリクエスト
    @GetMapping("/creationForm")
    public String showCreationForm(@ModelAttribute UserForm form) {
        return "users/creationForm"; // "users/creationForm" テンプレートを返す
    }

    // ユーザーを作成するPOSTリクエスト
    @PostMapping
    public String create(@Validated UserForm form, BindingResult bindingResult) {
        // 入力の検証エラーがある場合
        if (bindingResult.hasErrors()) {
            // ユーザー作成フォームを再表示
            return showCreationForm(form);
        }

        // UserServiceを使って新しいユーザー情報を作成
        userService.create(form.getUsername(), form.getPassword());

        // ユーザーリストを表示するページにリダイレクト
        return "redirect:/users";
    }
}
```