VSCodeでSpring Bootアプリケーション作成 とDB連携

# 概要
VSCodeでSpring Bootアプリケーション作成と
PostgreSQLに登録されているユーザでログインを行う<br>
### 全体像
![スクリーンショット 2024-03-18 14.48.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459413/f3a15788-9ff3-1e0e-c233-9e82c1f32897.png)


### 前提知識
SpringBoot, Thymeleaf を利用したアプリケーションの基本的な理解

# 環境情報

- Java SE 17
- PostgreSQL 16.2

## 拡張機能

|拡張機能名|備考|
|---|---|
|Java Extension Pack|Java開発のための機能が含まれた一連のパッケージ|
|Spring Boot Extension Pack|Spring Bootアプリケーション開発のための機能が含まれた一連のパッケージ|
|Maven for Java|Mavenプロジェクトの管理用|

# Spring Boot アプリケーションの作成

1. **VScodeの左下歯車マークを選択し、コマンドパレットを選択**
    ![スクリーンショット 2024-03-17 19.07.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459413/07348a33-e6ce-b3d2-eb65-e9035c60805f.png)
    <br>
2. **Create a Maven Project を選択**
    ![スクリーンショット 2024-03-17 23.55.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459413/91ce7b1c-4294-20b1-4337-028174903560.png)
    <br>
3. **プロジェクトの選択内容**

    |選択項目|選択内容|
    |---|---|
    |Spring Boot Version|3.2.3|
    |Project Language|Java|
    |パッケージ名|com.example　（任意のパッケージ名）|
    |プロジェクト名|demo　 （任意のプロジェクト名）|
    |Packaging Type|jar|
    |Java Version|17|
    <br>
4. **依存関係の追加内容**
下記依存関係を選択したらEnterを押し、任意のフォルダに保存する

    |dependencies|備考|
    |---|---|
    |Spring Web|自己完結型の HTTP サーバーを作成できる|
    |Spring Boot Devtools|再起動なしにソースコードの修正を反映させることができる  （無くても問題ないがあると便利）|
    |Thymeleaf|Javaのウェブ開発用テンプレートエンジン|
    |JDBC API|JavaとDBを接続するためのAPI|
    |PostgreSQL Driver|PostgreSQLと通信するためのドライバー|
    <br>
    
    ![スクリーンショット 2024-03-18 0.42.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459413/578f0b3f-0b6d-1fa0-977a-ae6c6b14c9e4.png)
    <br>
5. **保存したプロジェクトの確認**
プロジェクトを開き以下のように作成されていればOK
    ![スクリーンショット 2024-03-18 9.47.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3459413/913fbf05-1a22-4b3e-c789-7138773ae2a7.png)

ここで一旦起動したいところですが、DB接続エラーが発生してしまうため
PostgreSQLとの接続情報を追加します
```実行エラー内容
Consider the following:
    If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
```
# PostgreSQLとの連携
1. **アプリケーションプロファイルの設定**
src/main/resources配下の「application.properties」を「application.yml」にリネームし、以下の内容を記載
    ```yml:appliation.yml
    spring:
      datasource:
        url: jdbc:postgresql://localhost:5432/test   # 接続先URL
        username: postgres    # ユーザー名
        password: postgres    # パスワード
    ```
    <br>
1. **簡易的なhtmlの作成**
ログインページ「src/main/resources/tmplates/login.html」
ログイン成功ページ「src/main/resources/tmplates/home.html」
    ```html:login.html
    <!DOCTYPE html>
    <html lang="ja" xmlns:th="https://www.thymeleaf.org">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>ログインページ</title>
    </head>
    
    <body>
      <h1>ログイン</h1>
      <div style="color: red;" th:text="${message}"></div>
      <form action="/login" method="post">
        <div>
          <label for="username">ユーザ名</label>
          <input type="text" id="name" name="name" th:value="${name}">
        </div>
        <div>
          <label for="password">パスワード</label>
          <input type="password" id="password" name="password">
        </div>
        <button type="submit">ログイン</button>
      </form>
    </body>
    
    </html>
    ```
    ```html:home.html
    <!DOCTYPE html>
    <html lang="ja" xmlns:th="https://www.thymeleaf.org">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>ログインページ</title>
    </head>
    
    <body>
      <h1>ログイン成功</h1>
    </body>
    
    </html>
    ```
    <br>
1. **usersテーブルの作成**
本来であればパスワードは暗号化して格納するべきだが、今回は暗号化処理未実装
    
    |カラム名|値|
    |---|---|
    |name|admin|
    |password|admin|
    ```sql:sql.txt
    -- usersテーブルがあれば削除
    DROP TABLE IF EXISTS users CASCADE;
    
    -- usersテーブル作成
    CREATE TABLE users(
     name varchar(20) PRIMARY KEY
     , password varchar(20) NOT NULL
    );
    
    -- データ挿入
    INSERT INTO users(name, password) VALUES('admin', 'admin');
    ```
    <br>
1. **Repositoryの作成**
    DBからの情報を取得するためのRepositoryクラスを作成する
    「 src/main/java/com/example/demo/repository/UserRepository.java 」
    - 依存関係で追加したJdbcTemplate([NamedParameterJdbcTemplate](https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.html))を利用
    - ユーザーが見つかればユーザー情報を、見つからなければ空を返す
     
    ```java:UserRepository.java
    package com.example.demo.repository;

    //〜〜〜〜import文は省略〜〜〜〜〜

    @Repository
    public class UserRepository {

      @Autowired
      private NamedParameterJdbcTemplate jdbcTemplate;
    
      // RowMapperの実装
      private static final RowMapper<User> ROW_MAPPER = (rs, i) -> {
        String name = rs.getString("name");
        String password = rs.getString("password");
        return new User(name, password);
      };
    
      /**
       * ユーザー名とパスワードからユーザーを取得する.
       * 
       * @param name     ユーザー名
       * @param password パスワード
       * @return ユーザー情報、存在しない場合は空のOptional
       */
      public Optional<User> findByUser(String name, String password) {
        SqlParameterSource param = new MapSqlParameterSource().addValue("name", name).addValue("password", password);
        String sql = "SELECT * FROM users WHERE name = :name AND password = :password";
        List<User> userList = jdbcTemplate.query(sql, param, ROW_MAPPER);
        return userList.stream().findFirst();
      }
    }
    ```
    <br>
1. **Domainの作成**
    
    ```java:User.java
    package com.example.demo.domain;
    
    public record User(String name, String password) {}
    ```
    <br>
1. **Serviceの作成**
    ```java:UserService.java
    package com.example.demo.service;

    //〜〜〜〜import文は省略〜〜〜〜〜
    
    @Service
    public class UserService {
    
      @Autowired
      private UserRepository userRepository;
    
      public Optional<User> findByUser(String name, String password) {
        Optional<User> user = userRepository.findByUser(name, password);
        System.out.println(user);
        return user;
      }
    
    }

    ```
    <br>
1.  **Controllerの作成**
    ```java:LoginController.java
    package com.example.demo.controller;
    
    //〜〜〜〜import文は省略〜〜〜〜〜
    
    @Controller
    @RequestMapping("")
    public class LoginController {
    
      @Autowired
      private UserService userService;
    
      @Autowired
      private HttpSession session;
    
      @GetMapping("")
      public String login() {
        return "/login";
      }
    
      @GetMapping("/login")
      public String loginPage(Model model) {
        return "/login";
      }
    
      /**
       * ログイン処理を行う
       * 
       * @param name     ユーザー名
       * @param password パスワード
       * @param model    モデル
       * @return ログイン成功時は/home、失敗時は/loginに遷移
       */
      @PostMapping("/login")
      public String login(String name, String password, Model model) {
        return userService.findByUser(name, password)
            .map(user -> {
              session.setAttribute("user", user);
              return "/home";
            })
            .orElseGet(() -> {
              model.addAttribute("name", name);
              model.addAttribute("message", "ユーザー名またはパスワードが違います");
              return "/login";
            });
    
      }
    
      @GetMapping("/home")
      public String home() {
        if (session.getAttribute("user") == null) {
          return "redirect:/login";
        }
        return "/home";
      }
    }
        
    ```

    # 参考資料
    [NamedParameterJdbcTemplate](https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.html)
    [SpringBootのO/Rマッパー](https://qiita.com/YJ2222/items/e814d1b4e0aaafeb54c0)
    [streamインターフェース リファレンス](https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Stream.html)
    [Optional リファレンス](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Optional.html)
    [Optional](https://www.sejuku.net/blog/60892)
    [recordクラス](https://docs.oracle.com/javase/jp/15/language/records.html#GUID-6699E26F-4A9B-4393-A08B-1E47D4B2D263)