【Java・SpringBoot】Spring JDBC（SpringBootアプリケーション実践編9）

ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して
Spring JDBCの使い方について学びます⭐️
今回はまず**JDBC Templateを実装するための画面作成**を行います

構成はこれまでの記事を参考にしてください
[【Java・SpringBoot・Thymeleaf】ブラウザ言語設定でエラーメッセージの言語を変更（SpringBootアプリケーション実践編8）](https://qiita.com/suema0331/items/e3faf24876f788a5a6c5)
[【Java・SpringBoot】Spring AOP実践](https://qiita.com/suema0331/items/c7f2287fd5bf84d83691)

#Spring JDBCとは
* **JDBC**は**JavaでDBにアクセスするためのライブラリ**
* SpringJDBCは、**JDBCを使ってデータベースにアクセス**
* 通常のJDBCでは、DBへの接続やクローズ処理などを毎回書かないといけない。。。
* →🌟SpringJDBCを使えば、**DBの接続やクローズなどの処理を書かなくてOK**^^
* **🌟DB製品固有のエラーコードを解釈して、適切な例外を投げてくれる**
    * 一意制約違反(他の行の値と重複禁止)が発生した場合、SQLServerでは`2627`、Oracleでは`ORA0001`
    * 一意制約違反が発生した場合は、`DuplicateKeyException`という例外クラスでキャッチ
    * →**製品ごとに実装を分ける必要がない！**
* **`DataAccessException`クラス**：すべての例外クラスのスーパークラス
    * **データベース関連のエラーをすべてキャッチ**できる

#SpringJDBC実践！
* 以下の画面を作成しながら、SpringJDBCを使います
* それぞれの画面は、**テンプレート部分とコンテンツ部分**に分かれている
    * テンプレート部分：共通部分のhtml（ヘッダー、サイドバーなど）
* レイアウトを変更する場合、**テンプレート用のファイルを修正するだけ**でOK！

###ホーム画面
* ログインボタンを押した後に表示される
        * ログイン画面については過去の記事のコードを参考にしてください
    * ログアウトやユーザー一覧画面に飛ぶことができる<>
[【Java・SpringBoot・Thymeleaf】ログイン・新規登録画面作成（SpringBootアプリケーション実践編1）](https://qiita.com/suema0331/items/5e69a90a2f18ea3b5b67)

###**ユーザー一覧画面**
* DBから全ユーザーの情報を取得して、画面に表示
    * `詳細ボタン`を押すと、各ユーザーの詳細画面に移る
    * ユーザー一覧をCSV出力できる


###**ユーザー詳細画面**
* ユーザーの詳細を表示
    * ユーザーの更新、削除をする
    * 更新・削除した後はユーザー一覧画面に移る

##画面作成
* 先に画面を作成して、タイムリーフでテンプレート画面を作る方法について学びます
* 構成は以下のようになってます

```
Project Root
└─src
    └─ main
        └─ java  
            └─ com.example.demo
                └─ login
                    └─ aspect                    ...AOP用パッケージ
                    └─ controller                ...コントローラクラス用パッケージ
                         └─ HomeController.java
                └─ domain                        ...ビジネスロジック用パッケージ
                    └─ model                     ...Model(DTO)用パッケージ
                         └─ User.java
                    └─ repository                ...リポジトリクラス用パッケージ
                         └─ UserDao.java
                         └─ jdbc
                             └─ UserDaoJdbcImpl.java
                    └─ service                   ...サービスクラス用パッケージ
                             └─ UserService.java
        └─ resouces
            └─ static                            ...css,js用フォルダ
                └─ cs
                    └─ home.css
            └─ templates
                └─ login
                    └─ home.html
                    └─ homeLayout.html

```

##ホーム画面のテンプレート用htmlを作成
###**th:include属性**
* タグ内に別ファイルのコンテンツ部分のhtmlが追加される
* **`th:includeの値th:include="<ファイルパス>::<th:fragment属性の値>"`**
    * **ファイルパス**：コンテンツ部分のhtmlファイルのファイルパスです。
    * **th:fragment属性**：コンテンツ部分のhtmlで使う属性
        * ex: loginフォルダー内にあるhome.htmlというコンテンツ用のhtml内に、**`th:fragment="home_contents"`**と書き、<br> th:includeには、**`th:include="login/home::home_contents"`**と記述
* コンテンツ部分を動的に変更する場合は、`th:include`の値は**Modelに登録された値を参照するようにする**
    * Modelに`login/home::home_contents`という文字列を登録
    * ※動的にコンテンツ部分のhtmlを変える場合、プリプロセッシングを使う必要がある

###**プリプロセッシング**
* **通常の式よりも先に評価させる仕組み**
* プリプロセッシングでは**変数（${変数名}）に__（アンダースコア２つ）を前後に付ける**
    * `th:include="__${contents}__"`の部分が`th:include="login/home::home_contents"`と**先に評価され、その後でhtmlが作成**される
* →**コンテンツ部分のhtmlを表示させることができる！**

```homeLayout.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">

<head>
    <meta charset="UTF-8"></meta>

    <!-- Bootstrap -->
    <link th:href="@{/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css}" rel="stylesheet"></link>
    <script th:src="@{/webjars/jquery/1.11.1/jquery.min.js}"></script>
    <script th:src="@{/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js}"></script>

    <!-- CSS読込 -->
    <link th:href="@{/css/home.css}" rel="stylesheet"></link>

    <title>Home</title>
</head>
<body>
    <!--  ヘッダー   -->
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand" href="#">SpringBoot</a>
            </div>
            <form method="post" th:action="@{/logout}">
                <button class="btn btn-link pull-right navbar-brand" type="submit">
                    ログアウト
                </button>
            </form>
        </div>
    </nav>
    <!--  サイドバー  -->
    <div class="container-fluid">
        <div class="row">
            <div class="col-sm-2 sidebar">
                <ul class="nav nav-pills nav-stacked">
                    <li role="presentation">
                        <a th:href="@{'/userList'}">ユーザ管理</a>
                    </li>
                </ul>
            </div>
        </div>
    </div>
    <!--  コンテンツ  -->
    <div class="container-fluid">
        <div class="row">
            <div class="col-sm-10 col-sm-offset-2 main">
                <div th:include="__${contents}__"></div>
            </div>
        </div>
    </div>
</body>
</html>
```

##ホーム画面のhtmlを作成
###th:fragment
* th:fragment属性が付いているタグ内のhtmlが、**テンプレート用のhtml内に追加**される


```home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <meta charset="UTF-8"></meta>
</head>
<body>
    <!-- ポイント：th:fragment -->
    <div th:fragment="home_contents">
        <div class="page-header">
            <h1>ホーム</h1>
        </div>
    </div>
</body>
</html>
```

##ホーム画面用のコントローラークラスを作成
###ホーム画面にGETリクエスト
* /homeにGETリクエストが来たときに、**Modelクラスの"contents"というキー**に`"login/home::home_contents"`という値をセット
* この値が**th:include属性**に入る
    * `th:include="login/home::home_contents"`
* ログアウトボタンが押されたら、**ログイン画面にリダイレクト**する

```HomeController.java
package com.example.demo.login.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.demo.login.domain.service.UserService;

@Controller
public class HomeController {

    @Autowired
    UserService userService;

    @GetMapping("/home")
    public String getHome(Model model) {

        //コンテンツ部分にユーザー詳細を表示するための文字列を登録
        model.addAttribute("contents", "login/home :: home_contents");
        return "login/homeLayout";
    }

    @PostMapping("/logout")
    public String postLogout() {
        return "redirect:/login";
    }
}


```

##ホーム画面のcssを作成

```home.css
body {
  padding-top: 50px;
}

.sidebar {
  position: fixed;
  display: block;
  top: 50px;
  bottom: 0;
  background-color: #F4F5F7;
}

.main {
  padding-top: 50px;
  padding-left: 20px;
  position: fixed;
  display: block;
  top: 0px;
  bottom: 0;
}

.page-header {
  margin-top: 0px;
}
```

##ユーザーテーブルのカラムをフィールドに保持
* **データベースから取得した値を、コントローラークラスやサービスクラスなどの間でやり取りするためのクラス**を用意
* @Dataアノテーション：Lombokでgetterやsetterを自動で作る

```User.java
package com.example.demo.login.domain.model;
import java.util.Date;
import lombok.Data;

@Data
public class User {
    private String userId; //ユーザーID
    private String password; //パスワード
    private String userName; //ユーザー名
    private Date birthday; //誕生日
    private int age; //年齢
    private boolean marriage; //結婚ステータス
    private String role; //ロール
}

```

##リポジトリークラスのインターフェース
* インターフェースを作る理由は、後で中身の実装クラスを簡単に切替えられるようにするため
* **DataAccessException**
    * Springでは、**データベース操作で例外が発生した場合、Springが提供しているDataAccessExceptionを投げる**
    * SpringJDBCだけでなく、Spring+MyBatisを使った時にも投げられる

```UserDao.java
package com.example.demo.login.domain.repository;
import java.util.List;
import org.springframework.dao.DataAccessException;
import com.example.demo.login.domain.model.User;

public interface UserDao {

    // Userテーブルの件数を取得.
    public int count() throws DataAccessException;
    // Userテーブルにデータを1件insert.
    public int insertOne(User user) throws DataAccessException;
    // Userテーブルのデータを１件取得
    public User selectOne(String userId) throws DataAccessException;
    // Userテーブルの全データを取得.
    public List<User> selectMany() throws DataAccessException;
    // Userテーブルを１件更新.
    public int updateOne(User user) throws DataAccessException;
    // Userテーブルを１件削除.
    public int deleteOne(String userId) throws DataAccessException;
    //SQL取得結果をサーバーにCSVで保存する
    public void userCsvOut() throws DataAccessException;
}
```

##インターフェース実装クラスを作成
* 各メソッドの中身は、まだ空の状態
* JdbcTemplateはSpringが用意しているため、既にBean定義がされている
    * **→@AutowiredするだけでOK**
* このクラスのメソッドでSQLを実行

```UserDaoJdbcImpl.java
package com.example.demo.login.domain.repository.jdbc;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.example.demo.login.domain.model.User;
import com.example.demo.login.domain.repository.UserDao;

@Repository("UserDaoJdbcImpl")
public class UserDaoJdbcImpl implements UserDao {

    @Autowired
    JdbcTemplate jdbc;

    // Userテーブルの件数を取得.
    @Override
    public int count() throws DataAccessException {
        return 0;
    }
    // Userテーブルにデータを1件insert.
    @Override
    public int insertOne(User user) throws DataAccessException {
        return 0;
    }
    // Userテーブルのデータを１件取得
    @Override
    public User selectOne(String userId) throws DataAccessException {
        return null;
    }
    // Userテーブルの全データを取得.
    @Override
    public List<User> selectMany() throws DataAccessException {
        return null;
    }
    // Userテーブルを１件更新.
    @Override
    public int updateOne(User user) throws DataAccessException {
        return 0;
    }
    // Userテーブルを１件削除.
    @Override
    public int deleteOne(String userId) throws DataAccessException {
        return 0;
    }
    //SQL取得結果をサーバーにCSVで保存する
    @Override
    public void userCsvOut() throws DataAccessException {
    }
}
```



##サービス用のクラスを作成
* クラスを用意するだけ

```UserService.java
package com.example.demo.login.domain.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.login.domain.repository.UserDao;

@Service
public class UserService {
    @Autowired
    UserDao dao;
}

```

##ログ出力用のアスペクトクラス
* ユーザーDaoクラスのメソッドが呼び出されたときに、**どのクラスのどのメソッドが呼ばれたのかをログ出力できるようにする**

```LogAspct.java
package com.example.demo.login.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAspct {

    @Around("execution(* *..*.*Controller.*(..))")
    public Object startLog(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("メソッド開始： " + jp.getSignature());

        try {
            //メソッド実行
            Object result = jp.proceed();
            System.out.println("メソッド終了： " + jp.getSignature());
            return result;

        } catch (Exception e) {
            System.out.println("メソッド異常終了： " + jp.getSignature());
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * Daoクラスのログ出力用アスペクトを追加
     */
    @Around("execution(* *..*.*UserDao*.*(..))")
    public Object daoLog(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("メソッド開始： " + jp.getSignature());
        try {
            Object result = jp.proceed();
            System.out.println("メソッド終了： " + jp.getSignature());
            return result;
        } catch (Exception e) {
            System.out.println("メソッド異常終了： " + jp.getSignature());
            e.printStackTrace();
            throw e;
        }
    }
}

```
##テーブルを作成します
###ユーザーテーブル作成

```sql:schema.sql
/* 従業員テーブル */
CREATE TABLE IF NOT EXISTS employee (
    employee_id INT PRIMARY KEY,
    employee_name VARCHAR(50),
    age INT
);

/* ユーザーマスタ */
CREATE TABLE IF NOT EXISTS m_user (
    user_id VARCHAR(50) PRIMARY KEY,
    password VARCHAR(100),
    user_name VARCHAR(50),
    birthday DATE,
    age INT,
    marriage BOOLEAN,
    role VARCHAR(50)
);
```
###ユーザーテーブルの初期化データを作成

```sql:data.sql
/* 従業員テーブルのデータ */
INSERT INTO employee (employee_id, employee_name, age)
VALUES(1, 'Teshita Neko', 3);

/* ユーザーマスタのデータ（ADMIN） */
INSERT INTO m_user (user_id, password, user_name, birthday, age, marriage, role)
VALUES('nekomofu@xxx.co.jp', 'password', 'Oyakata Neko', '2020-01-01', 3, false, 'ROLE_ADMIN');
```

##ログイン画面からホーム画面に遷移
* ログインボタンを押すとホーム画面に遷移
* `return "redirect:/home";`


```LoginController.java
package com.example.demo.login.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String getLogin(Model model) {

        //login.htmlに画面遷移
        return "login/login";
    }

    @PostMapping("/login")
    public String postLogin(Model model) {

        //ホーム画面に遷移
        return "redirect:/home";

    }
}
```

#SpringBootを起動して画面を確認！
* http://localhost:8080/home
* ホーム画面を作成できました〜〜^^
* 次回はJDBC Templateを実装します🌟
* ![home.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282755/d682bc00-0955-bedc-f5c4-f194d3ab10c1.png)