【Java・SpringBoot】Spring JDBC でユーザー詳細画面（SpringBootアプリケーション実践編12）


ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して、Spring JDBCの使い方について学びます⭐️
前回はユーザー一覧画面を作ったので、今回は**ユーザー詳細画面**を作ります^^
構成は前回の記事を参考にしてください

⭐️前回の記事
[【Java・SpringBoot】Spring JDBC でユーザー一覧画面（SpringBootアプリケーション実践編11）](https://qiita.com/suema0331/items/11d35d5808214260457c)

#リポジトリークラスに１件検索するメソッドを実装
###queryForMapメソッドで１件取得
* 戻り値はMap<String,Object>型
* 第１引数：**SQL文**、第２引数以降に**PreparedStatement**を指定
    * `Map<String, Object> map = jdbc.queryForMap("SELECT * FROM m_user"
                + " WHERE user_id = ?", userId);`
    * 戻り値のMapのgetメソッドにカラム名を指定することで、値を取得する


```UserDaoJdbcImpl.java
package com.example.demo.login.domain.repository.jdbc;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
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 {

        //全件取得してカウント
        int count = jdbc.queryForObject("SELECT COUNT(*) FROM m_user", Integer.class);

        return count;
    }

    // Userテーブルにデータを1件insert.
    @Override
    public int insertOne(User user) throws DataAccessException {

        //１件登録
        int rowNumber = jdbc.update("INSERT INTO m_user(user_id,"
                + " password,"
                + " user_name,"
                + " birthday,"
                + " age,"
                + " marriage,"
                + " role)"
                + " VALUES(?, ?, ?, ?, ?, ?, ?)",
                user.getUserId(),
                user.getPassword(),
                user.getUserName(),
                user.getBirthday(),
                user.getAge(),
                user.isMarriage(),
                user.getRole());

        return rowNumber;
    }

    // Userテーブルのデータを１件取得
    @Override
    public User selectOne(String userId) throws DataAccessException {

        // １件取得
        Map<String, Object> map = jdbc.queryForMap("SELECT * FROM m_user"
                + " WHERE user_id = ?", userId);

        // 結果返却用の変数
        User user = new User();

        // 取得したデータを結果返却用の変数にセットしていく
        user.setUserId((String) map.get("user_id")); //ユーザーID
        user.setPassword((String) map.get("password")); //パスワード
        user.setUserName((String) map.get("user_name")); //ユーザー名
        user.setBirthday((Date) map.get("birthday")); //誕生日
        user.setAge((Integer) map.get("age")); //年齢
        user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス
        user.setRole((String) map.get("role")); //ロール

        return user;
    }

    // Userテーブルの全データを取得.
    @Override
    public List<User> selectMany() throws DataAccessException {

        // M_USERテーブルのデータを全件取得
        List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM m_user");

        // 結果返却用の変数
        List<User> userList = new ArrayList<>();

        // 取得したデータを結果返却用のListに格納していく
        for (Map<String, Object> map : getList) {

            //Userインスタンスの生成
            User user = new User();

            // Userインスタンスに取得したデータをセットする
            user.setUserId((String) map.get("user_id")); //ユーザーID
            user.setPassword((String) map.get("password")); //パスワード
            user.setUserName((String) map.get("user_name")); //ユーザー名
            user.setBirthday((Date) map.get("birthday")); //誕生日
            user.setAge((Integer) map.get("age")); //年齢
            user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス
            user.setRole((String) map.get("role")); //ロール

            //結果返却用のListに追加
            userList.add(user);
        }

        return userList;
    }

    // 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 java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.login.domain.model.User;
import com.example.demo.login.domain.repository.UserDao;

@Service
public class UserService {
    @Autowired
    UserDao dao;

    public boolean insert(User user) {
        // insert実行
        int rowNumber = dao.insertOne(user);
        // 判定用変数
        boolean result = false;

        if (rowNumber > 0) {
            // insert成功
            result = true;
        }
        return result;
    }

    public int count() {
        return dao.count();
    }

    public List<User> selectMany() {
        // 全件取得
        return dao.selectMany();
    }

    /**
     * １件取得用メソッド.
     */
    public User selectOne(String userId) {
        // selectOne実行
        return dao.selectOne(userId);
    }
}
```

#ホーム画面用のコントローラークラスを修正
###動的なURLに対応したメソッドを作る
* @GetMappingや@PostMappingの値に**`/{<変数名>}`を付ける**
    * ユーザーIDを受け取る場合は、**`@GetMapping(/userDetail/{id})`**
    * 通常は`/userDetail/{id}`とすればいいが。。。
    * **ユーザーIDがメールアドレス形式**の場合、`sample@xxx.co.jp`というユーザーIDが渡されてくると、**`sample@xxx.co`しか受け取れない**
        * →**正規表現**を使用して**`@GetMapping("/userDetail/{id:.+}")`**とする

###@PathVariable
* @PathVariableアノテーションを付けると、**渡されてきたパス（URL）の値を引数の変数に入れる**ことができる
    *  例：`@PathVariable("id") String userId) {...}`
    * `http://localhost:8080/userDetail/sample@xxx.co.jp`というURLでリクエストが来た場合、<br>`sample@xxx.co.jp`という値が引数の**userId**という変数に入れられる


```HomeController.java
package com.example.demo.login.controller;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.demo.login.domain.model.SignupForm;
import com.example.demo.login.domain.model.User;
import com.example.demo.login.domain.service.UserService;

@Controller
public class HomeController {

    @Autowired
    UserService userService;
    //結婚ステータスのラジオボタン用変数
    private Map<String, String> radioMarriage;

    /**
     * ラジオボタンの初期化メソッド
     */
    private Map<String, String> initRadioMarrige() {
        Map<String, String> radio = new LinkedHashMap<>();
        // 既婚、未婚をMapに格納
        radio.put("既婚", "true");
        radio.put("未婚", "false");
        return radio;
    }

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

    @GetMapping("/userList")
    public String getUserList(Model model) {

        //コンテンツ部分にユーザー一覧を表示するための文字列を登録
        model.addAttribute("contents", "login/userList :: userList_contents");
        //ユーザー一覧の生成
        List<User> userList = userService.selectMany();
        //Modelにユーザーリストを登録
        model.addAttribute("userList", userList);
        //データ件数を取得
        int count = userService.count();
        model.addAttribute("userListCount", count);
        return "login/homeLayout";
    }

    /**
     * ユーザー詳細画面のGETメソッド用処理.
     */
    @GetMapping("/userDetail/{id:.+}")
    public String getUserDetail(@ModelAttribute SignupForm form,
            Model model,
            @PathVariable("id") String userId) {
        // ユーザーID確認
        System.out.println("userId = " + userId);

        // コンテンツ部分にユーザー詳細を表示するための文字列を登録
        model.addAttribute("contents", "login/userDetail :: userDetail_contents");
        // 結婚ステータス用ラジオボタンの初期化
        radioMarriage = initRadioMarrige();
        // ラジオボタン用のMapをModelに登録
        model.addAttribute("radioMarriage", radioMarriage);

        // ユーザーIDのチェック
        if (userId != null && userId.length() > 0) {
            // ユーザー情報を取得
            User user = userService.selectOne(userId);
            // Userクラスをフォームクラスに変換
            form.setUserId(user.getUserId()); //ユーザーID
            form.setUserName(user.getUserName()); //ユーザー名
            form.setBirthday(user.getBirthday()); //誕生日
            form.setAge(user.getAge()); //年齢
            form.setMarriage(user.isMarriage()); //結婚ステータス
            // Modelに登録
            model.addAttribute("signupForm", form);
        }
        return "login/homeLayout";
    }

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

    @GetMapping("/userList/csv")
    public String getUserListCsv(Model model) {
        return getUserList(model);
    }
}
```

#ユーザー詳細画面作成

```userDetail.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>
    <div th:fragment="userDetail_contents">
        <div class="row">
            <div class="col-sm-5">
                <div class="page-header">
                    <h1>ユーザー詳細</h1>
                </div>
                <form method="post" th:action="@{/userDetail}" th:object="${signupForm}">
                    <table class="table table-bordered table-hover">
                        <tr>
                            <!-- ユーザーID（入力不可） -->
                            <th class="active col-sm-2">ユーザID</th>
                            <td class="col-sm-3">
                                <div class="form-group">
                                    <input type="text" class="form-control"
                                        th:field="*{userId}"
                                        readonly="readonly" />
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <!-- パスワード -->
                            <th class="active">パスワード</th>
                            <td>
                                <div class="form-group"
                                    th:classappend="${#fields.hasErrors('password')} ? 'has-error'">
                                    <input type="text" class="form-control"
                                        th:field="*{password}" />
                                    <span class="text-danger"
                                        th:if="${#fields.hasErrors('password')}"
                                        th:errors="*{password}">
                                        password error
                                    </span>
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <!-- ユーザー名 -->
                            <th class="active">ユーザ名</th>
                            <td>
                                <div class="form-group"
                                    th:classappend="${#fields.hasErrors('userName')} ? 'has-error'">
                                    <input type="text" class="form-control"
                                        th:field="*{userName}" />
                                    <span class="text-danger"
                                        th:if="${#fields.hasErrors('userName')}"
                                        th:errors="*{userName}">
                                        userName error
                                    </span>
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <!-- 誕生日 -->
                            <th class="active">誕生日</th>
                            <td>
                                <div class="form-group"
                                    th:classappend="${#fields.hasErrors('birthday')} ? 'has-error'">
                                    <input type="text" class="form-control"
                                        placeholder="yyyy/MM/dd"
                                        th:field="*{birthday}" />
                                    <span class="text-danger"
                                        th:if="${#fields.hasErrors('birthday')}"
                                        th:errors="*{birthday}">
                                        birthday error
                                    </span>
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <!-- 年齢 -->
                            <th class="active">年齢</th>
                            <td>
                                <div class="form-group"
                                    th:classappend="${#fields.hasErrors('age')} ? 'has-error'">
                                    <input type="text" class="form-control"
                                        th:field="*{age}" />
                                    <span class="text-danger"
                                        th:if="${#fields.hasErrors('age')}"
                                        th:errors="*{age}">
                                        age error
                                    </span>
                                </div>
                            </td>
                        </tr>
                        <tr>
                            <!-- 結婚 -->
                            <th class="active">結婚</th>
                            <td>
                                <div class="form-group">
                                    <div th:each="item : ${radioMarriage}">
                                        <input type="radio" name="radioMarrige"
                                            th:value="${item.value}"
                                            th:text="${item.key}"
                                            th:field="*{marriage}">
                                        </input>
                                    </div>
                                    <span class="text-danger"
                                        th:if="${#fields.hasErrors('marriage')}"
                                        th:errors="*{marriage}">
                                        marriage error
                                    </span>
                                </div>
                            </td>
                        </tr>
                    </table>
                    <!-- 更新ボタン -->
                    <button class="btn btn-primary btn-lg pull-right"
                        type="submit"
                        name="update">
                        更新
                    </button>
                    <!-- 削除ボタン -->
                    <button class="btn btn-danger btn-lg" type="submit" name="delete">
                        削除
                    </button>
                </form>
            </div>
        </div>
    </div>
</body>
</html>
```

#SpringBootを起動してユーザ一覧画面確認！
* http://localhost:8080/userList
* ユーザ一覧から、ユーザ詳細画面に遷移します
* パスワード以外の情報が反映されました〜^^

![詳細.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282755/3e5e229f-02e2-130f-d350-e9e968c5b305.png)