レコード詳細機能の実装
今回のお題
今回のお題は、「レコード詳細機能の実装」です。
基本的なロジックは、レコードの主キーの情報を含むURLにリクエストを送ることでビューが返され、そこに対象のレコードの情報が含まれている、というものになります。
前提知識として、前回取り上げた「動的URL」の知識が必要になります。
よろしければ先にご一読ください。
目次
全体の流れ
先に全体の流れを説明しておきます。
今回は、"/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>
こんなものをいきなり見せられても何が何だかわからないですね(少なくとも私はわかりませんでした)。
なので、重要な部分を順に順に解説していきます。
- <resultMap type="xxx" id="xxx">
今回はm_userテーブルからMUserクラスのインスタンスを生成するのですが、<resultMap>タグは、m_userテーブルのカラムとMUserクラスのフィールド名を対応させるために使用します。
設定するべき属性は2つで、type属性には、対象のモデル(MUser)をパッケージ名を含めて記述します。
id属性には、任意の名前をつけます。用途については後述します。 - <id column="xxx" property="yyy">, <result column="xxx" property="yyy">
ともに<resultMap></resultMap>の内部に配置します。
column属性で指定した"xxx"カラムとproperty属性で指定した"yyy"フィールドを対応させます。
つまり、m_userテーブルの"xxx"カラムに"a"と入っているレコードから、MUserクラスの"yyy"というインスタンスフィールドに"a"という値を持つインスタンスが生成されます。 - <id>タグと<result>タグの違い
基本的な使い分けとしては、値の重複を許したくないカラムや主キーに対応するカラムには<id>タグを使い、それ以外のカラムには<result>タグを使います。
この理由については、<id>タグ内で指定したカラムについては、値が一意になるようにレコードが取得されるからです。
つまり、仮に複数のレコードを取得する操作であっても、<id>タグ内で指定したカラムの値が重複するレコードは検索結果から除外されてしまうというわけですね。
(例えば、ageカラムの値が30以上のレコードを全て取得する処理を定義した際に、nameカラムが"太郎"である30歳以上のレコードが3つあったとする。
nameカラムを<result>タグで指定していれば3人の太郎は全て戻り値に含まれるが、<id>タグで指定していた場合には"太郎"は1人しか戻り値に含まれない。)
何はともあれ、これでカラムとフィールドのマッピングが完了しました。
次に、SQL文を書いていきます。 - <select id="findOne" resultMap="user">
SQL文を囲むタグはselectタグを用います(SQL文がselect文のため)。
id属性はメソッド名のfindOneですね。
また、戻り値の型に関してはresutlTypeではなくresultMap属性を用いて定義しています。
先程、<resultMap>タグを用いて戻り値の型を定義しました。
その<resultMapタグ>のidの値を<select>タグのresultMap属性の値一致させることで、間接的にresutlTypeの指定がなされています。 - 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}>などとフィールド名を指定していくだけです。
ここについても不明点があれば(参考)をご一読ください。