レコード詳細機能の実装

今回のお題

今回のお題は、「レコード詳細機能の実装」です。

基本的なロジックは、レコードの主キーの情報を含むURLにリクエストを送ることでビューが返され、そこに対象のレコードの情報が含まれている、というものになります。

前提知識として、前回取り上げた「動的URL」の知識が必要になります。

よろしければ先にご一読ください。

目次

  • 全体の流れ
  • DB操作関数の定義
  • SQL文の記述
  • フォームの作成
  • インスタンス生成関数の定義
  • インスタンス生成関数の中身の記述
  • コントローラの編集
  • ビューの編集

全体の流れ

先に全体の流れを説明しておきます。

今回は、"/user/detail/xx"(xxにはユーザーIDが入る)にリクエストを送るとUserDetailControllerのgetUserメソッドが呼び出されてuser/detail.htmlが返されるという流れになります。

DB操作関数の定義

まずは、DB操作関数を定義します。

ファイルは、引き続きUserMapper.javaを用います(参考)。

 

以下のコードを追加してください。

public MUser findOne(String userId);

 

userIdを与えるとMUserクラスのインスタンスを返すfindOneメソッドを定義しています。

次に、このメソッドで実行されるSQL文をUserMapper.xmlに定義していきます。

SQL文の記述

以下をUserMapper.xmlに追加してください。

// <mapper>タグのすぐ下に追加

<resultMap type="com.example.demo.domain.user.model.MUser" id="user">

<id column="user_id" property="userId"/>

<result column="password" property="password"/>

<result column="user_name" property="userName"/>

<result column="birthday" property="birthday"/>

<result column="age" property="age"/>

<result column="gender" property="gender"/>

<result column="department_id" property="departmentId"/>

<result column="role" property="role"/>

</resultMap>

 

// </mapper>タグの直前に追加

<select id="findOne" resultMap="user">

select * from m_user where user_id = #{userId}

</select>

 

こんなものをいきなり見せられても何が何だかわからないですね(少なくとも私はわかりませんでした)。

なので、重要な部分を順に順に解説していきます。

 

  1. <resultMap type="xxx" id="xxx">
    今回はm_userテーブルからMUserクラスのインスタンスを生成するのですが、<resultMap>タグは、m_userテーブルのカラムとMUserクラスのフィールド名を対応させるために使用します。

    設定するべき属性は2つで、type属性には、対象のモデル(MUser)をパッケージ名を含めて記述します。
    id属性には、任意の名前をつけます。用途については後述します。
  2. <id column="xxx" property="yyy">, <result column="xxx" property="yyy">
    ともに<resultMap></resultMap>の内部に配置します。
    column属性で指定した"xxx"カラムとproperty属性で指定した"yyy"フィールドを対応させます。

    つまり、m_userテーブルの"xxx"カラムに"a"と入っているレコードから、MUserクラスの"yyy"というインスタンスフィールドに"a"という値を持つインスタンスが生成されます。

  3. <id>タグと<result>タグの違い
    基本的な使い分けとしては、値の重複を許したくないカラムや主キーに対応するカラムには<id>タグを使い、それ以外のカラムには<result>タグを使います。

    この理由については、<id>タグ内で指定したカラムについては、値が一意になるようにレコードが取得されるからです。
    つまり、仮に複数のレコードを取得する操作であっても、<id>タグ内で指定したカラムの値が重複するレコードは検索結果から除外されてしまうというわけですね。
    (例えば、ageカラムの値が30以上のレコードを全て取得する処理を定義した際に、nameカラムが"太郎"である30歳以上のレコードが3つあったとする。
    nameカラムを<result>タグで指定していれば3人の太郎は全て戻り値に含まれるが、<id>タグで指定していた場合には"太郎"は1人しか戻り値に含まれない。)

    何はともあれ、これでカラムとフィールドのマッピングが完了しました。
    次に、SQL文を書いていきます。
  4. <select id="findOne" resultMap="user">
    SQL文を囲むタグはselectタグを用います(SQL文がselect文のため)。
    id属性はメソッド名のfindOneですね。

    また、戻り値の型に関してはresutlTypeではなくresultMap属性を用いて定義しています。
    先程、<resultMap>タグを用いて戻り値の型を定義しました。
    その<resultMapタグ>のidの値を<select>タグのresultMap属性の値一致させることで、間接的にresutlTypeの指定がなされています。
  5. where user_id = #{userId}
    #{}で囲むことによって、insetOneメソッドの引数の値を参照できるようになります。
    今回は引数として与えたuserIdがm_userテーブルのuser_idカラムと一致する箇所を調べたいので、このように記述しています。

さて、これでSQL文が完成し、m_userテーブルのレコードをMUserモデルのインスタンスに変換できるようになりました。

本来であればこの後MUserモデルのインスタンスを生成する処理を書くのですが、最終的にはそれをさらにフォームにバインドしたクラスに変換するので、先にフォームのクラスを用意しておきます。

フォームの作成

フォームにバインドしたクラスを作成します。

UserDetailForm.javaを作成し、以下のように記述します。

 

@Data

public class UserDetailForm {

private String userId;

private String password;

private String userName;

private Date birthday;

private Integer age;

private Integer gender;

}

基本的にはMUser.javaとほぼ同じです。

MUser.javaに存在していたカラムの中で"role"と"department_id"については表示する予定がなかったのでUserDetailForm.javaには定義していません。

この辺りについてはもちろん自由度が認められる部分です。

 

では少し寄り道しましたが、次にMUserのインスタンスを作っていきます。

インスタンス生成関数の定義

まずはインターフェースを編集して関数の名前を定義していきます。

 

UserService.javaに以下を追記してください。

public MUser getUserOne(String userId);

 

userIdという引数からMUSerクラスのインスタンスを返すgetUserOneメソッドを定義しました。

次に、このメソッドの中身を定義していきます。

インスタンス生成関数の中身の記述

UserServiceImpl.javaに以下を追記します(UserServiceImpl.javaはUserService.javaを実装したクラスです)。

@Override
    public MUser getUserOne(String userId) {
        return mapper.findOne(userId);
    }

中身自体は大したことがなくて、getUserOneメソッドを行うとModelMapperクラスのインスタンスであるmapperに対してfindOneメソッドが実行されてMUserのインスタンスが返ってくるというものになります。

 

これでインスタンスが用意できました。

これをコントローラからビューに渡せるようにしていきます。

コントローラの編集

UserDetailControllerを作成し、以下のように記述します。

// import部分

package com.example.demo.controller;

import org.modelmapper.ModelMapper;
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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.application.service.UserService;
import com.example.demo.domain.user.model.MUser;
import com.example.demo.form.UserDetailForm;


@Controller
@RequestMapping("/user")
public class UserDetailController {

 // Userserviceをインスタンス

    @Autowired
    private UserService userService;

  // ModelMapperをインスタンス
    @Autowired
    private ModelMapper modelMapper;
    
    @GetMapping("/detail/{userId:.+}")
    public String getUser(UserDetailForm form, Model model, @PathVariable("userId")String userId) {

  // userIdを元にMUserクラスのインスタンスを生成
        MUser user = userService.getUserOne(userId);

  // passwordを一旦リセット(フォームで表示することを考えての処理なので必須ではない)

        user.setPassword(null);

  // MUserインスタンスをUserDetailFormのインスタンスに変換

        form = modelMapper.map(user, UserDetailForm.class);
        // 生成したUserDetailFormインスタンスをモデルに追加
        model.addAttribute("userDetailForm", form);
        
        return "user/detail";
    }
}

ビューの表示

ビューの表示については割愛します。

基本的に特別なことはなく、<form>タグにth:object="${userDetailForm}"を指定したのち<input th:field="*{userId}>などとフィールド名を指定していくだけです。

ここについても不明点があれば(参考)をご一読ください。