レコードの保存機能の実装について
本日のお題
本日のお題は「レコードの保存機能の実装」です。
以前にフォームのバインド機能を実装してからというもの、バリデーション(こちらとこちら)やテンプレートなど脇道に逸れた話が多くなっていました。
今回は、ようやく元に戻ってレコード保存機能を実装していきます。
というと、「丸々ひとつの記事を使ってまとめるほどのことか?」と疑問を持たれる方も多くいらっしゃると思います。
確かに、RailsやLaravelなどの言語であればわずか数行で書けてしまうこの工程、さほど手間のかかるものとは思えません。
しかし、Spring Bootではやることが思いの外多かったので、こうして記事として取り上げることにした次第です。
以下、SignupControllerのpostSignupメソッドでサインアップを行う際のレコード保存機能の実装を例にとり、話を進めていきます。
目次
- 登場人物紹介
- ライブラリのインストール
- MUser.javaの編集
- UserMapper.javaの編集
- UserMapper.xmlの編集
- Userservice.javaの編集
- UserServiceImpl.javaの編集
- JavaConfig.javaの編集
登場人物紹介
今回の登場人物及びその役割は以下の通りです。
- MyBatis, ModelMapper
ともに今回使用するライブラリです。
MyBatisはDB操作のために使用します。
ModelMapperはインスタンスの内容をコピーしてくれるものになります。 - MUser.java
postSignupメソッドにより作成されるレコードの雛形を定義しているファイルです。
インスタンスフィールドなどを記述していきます。 - UserMapper.java
生成されたインスタンスをDBに追加する処理が記述されているインターフェースです。 - UserMapper.xml
UserMapper.javaで定義された処理に対応するSQL文を記述するためのファイルです。 - UserService.java
サインアップ処理の雛形を作るためのインターフェースです。 - UserserviceImpl.java
サインアップ処理の具体的な中身が記述されているクラスです。
上記のUserService.javaを継承して作られています。 - JavaConfig.java
ModelMapperをDIコンテナに登録して@Autowiredアノテーションを使用できるようにするためのjavaクラスです。 - SignupController.java
サインアップ処理を担当しているクラスです。
他の全ての登場人物を処理した後で、最後にこのファイルを編集します。
以上が今回の登場人物です。
めちゃくちゃ多いですね。
とりあえず、一つずつ見ていきます。
ライブラリのインストール
まず最初に、ライブラリをインストールします。
例によってpom.xmlを開き、以下の内容を追加します。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.modelmapper.extensions</groupId>
<artifactId>modelmapper-spring</artifactId>
<version>2.3.9</version>
</dependency>
これで、今回必要なMyBatisとModelMapperが導入されます。
次に、今回DBに保存するレコードの雛形を作成します。
MUser.javaの編集
MUser.javaというファイルを作り、その中でインスタンスフィールドを設定していきます。
package com.example.demo.domain.user.model;
import java.util.Date;
import lombok.Data;
@Data
public class MUser {
privateString userId;
private String password;
private String userName;
private Date birthday;
privateInteger age;
privateInteger gender;
private Integer departmentId;
privateString role;
}
特に説明することもないですね。
次に、このMUserクラスのインスタンスをDBに保存する処理を作って名前をつけます。
UserMapper.javaの編集
UserMapper.javaというインターフェースを作成し、そこにDB保存のためのメソッドを定義します。
実際の保存のロジックについては、この後のUserMapper.xmlに記述するので、ここでは名前だけで十分です。
UserMapper.javaに、以下のように記述してください。
package com.example.demo.repository;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.domain.user.model.MUser;
@Mapper
public interface UserMapper {
public int insertOne(MUser user);
}
UserMapperというインターフェースの中に、insertOneメソッドを定義しています。
引数は、先ほど作成したMUserクラスのインスタンスです。
次に、UserMapper.xmlで、このinsertOneメソッドの中身を定義していきます。
UserMapper.xmlの編集
ファイルは最初は空のはずなので、以下のように編集します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org.//DTD Mapper 3.0//EN" "http://mybatis.rog/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.repository.UserMapper">
<insert id="insertOne">
insert into m_user(user_id, password, user_name, birthday, age, gender, department_id, role)values(#{userId}, #{password}, #{userName}, #{birthday}, #{age}, #{gender}, #{departmentId}, #{role})
</insert>
</mapper>
以下、重要な部分の解説です。
<mapper namespace="com.example.demo.repository.UserMapper">
前項で定義したインターフェースへのパスをnamespace属性の値として記述します。
<insert id="insertOne">
UserMapperインターフェースのどのメソッドの中身を定義するのかを指定しています。
insert into m_user(user_id, password, user_name, birthday, age, gender, department_id, role)values(#{userId}, #{password}, #{userName}, #{birthday}, #{age}, #{gender}, #{departmentId}, #{role})
insertOneメソッドの中身を定義しています。
基本文型はSQL文よろしく、「insert into テーブル名(カラム名1, カラム名2, .....) values(値1, 値2, .....)」です。
今回は、m_userテーブルのそれぞれのカラムにデータを保存する処理を書いています。
ポイントは、values以下の#{userId} などという部分ですね。
これは、#{インスタンスフィールド名}とすることで、insertOneメソッドの引数(MUserモデルのインスタンス)のインスタンスフィールドの値を参照できるようにするというものです。
これで、MUserの内容をm_userテーブルに保存することはできるようになりました。
次に、サインアップ処理を書いていきます。
UserService.javaの編集
package com.example.demo.application.service;
import com.example.demo.domain.user.model.MUser;
public interface UserService {
public void signup(MUser user);
}
ここでは、MUserを使ってサインアップするという処理の雛形のみを書いています。
次に、UserSerViceImpl.javaを編集してこのメソッドの詳細を記述していきます。
UserServiceImpl.javaの編集
package com.example.demo.domain.user.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.application.service.UserService;
import com.example.demo.domain.user.model.MUser;
import com.example.demo.repository.UserMapper;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper mapper;
@Override
public void signup(MUser user) {
user.setDepartmentId(1);
user.setRole("ROLE_GENERAL");
mapper.insertOne(user);
}
}
やっていることは単純で、UserMapperクラスをインスタンス化した上で、インスタンスメソッドであるinsertOneメソッドを実行しています。
その直前のセッターに関しては、フォームで入力されないカラムの情報を補っているだけなので、今回の本題ではないですね。
さて、これでMUserモデルのインスタンスの情報をDBに保存するという処理が書けました。
残るはフォームの入力内容からMUserのインスタンスを作成する部分のみなのですが、もう一つやることがあります。
ModelMapperをDIコンテナに登録することです。
ModelMapperは自分で勝手に作ったクラスなので、DIコンテナへの登録も自分で行う必要があります。
というわけで、JavaConfig.javaを作成してDIコンテナへ登録していきます。
JavaConfig.javaの編集
以下のように編集します。
package com.example.demo.config;
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JavaConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
最後に、コントローラを編集します。
前項で言及していた、「フォームの内容からMUserインスタンスを作る」という部分ですね。
SignupControllerの編集
SignupControllerメソッドを以下のように編集します。
赤字部分が今回追加される箇所です。
@Autowired
private UserService userService;
@Autowired
private ModelMapper modelMapper;
public String postSignup(Model model, Locale locale,@Validated(GroupOrder.class) @ModelAttribute SignupForm form, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return getSignup(model, locale, form);
}
log.info(form.toString());
MUser user = modelMapper.map(form, MUser.class);
userService.signup(user);
return "redirect:/login";
}
MUser user = modelMapper.map(form, MUser.class);
まず、mapメソッドを用いてフォームの内容からMUserクラスのインスタンスを作成しています。
mapメソッドはModelMapperクラスのインスタンスメソッドでインスタンスフィールドの値をコピーしてくれる機能を持っています。
具体的に書くと、
- 第二引数(今回はMUserクラス)のインスタンス(user)を新しく生成。
- userの各インスタンスフィールドについて、同名のものが第一引数(今回であればform)に存在するかどうかをチェック。
- もし存在していれば、formからuserに値をコピーする。
ちなみに、この処理は内部的にはゲッターとセッターを用いて行っているそうなので、あらかじめ両者(formとuser)にゲッターとセッターが定義されている必要があります。
何はともあれ、これでフォームの値を元にインスタンスを作成することができました。
後の、足りない部分の情報を補う処理とDBへの保存は
userService.signup(user);
で行われるので、これでめでたく保存完了です。
終わりに
以上で、フォームに入力された値をDBに保存する仕組みが実装できました。
どうしても他の言語よりも手間がかかりますが、少しづつ覚えていきたいですね。