レコードの保存機能の実装について

本日のお題

本日のお題は「レコードの保存機能の実装」です。

以前にフォームのバインド機能を実装してからというもの、バリデーション(こちらこちら)やテンプレートなど脇道に逸れた話が多くなっていました。

今回は、ようやく元に戻ってレコード保存機能を実装していきます。

 

というと、「丸々ひとつの記事を使ってまとめるほどのことか?」と疑問を持たれる方も多くいらっしゃると思います。

確かに、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クラスのインスタンスメソッドでインスタンスフィールドの値をコピーしてくれる機能を持っています。

具体的に書くと、

  1. 第二引数(今回はMUserクラス)のインスタンス(user)を新しく生成。
  2. userの各インスタンスフィールドについて、同名のものが第一引数(今回であればform)に存在するかどうかをチェック。
  3. もし存在していれば、formからuserに値をコピーする。

ちなみに、この処理は内部的にはゲッターとセッターを用いて行っているそうなので、あらかじめ両者(formとuser)にゲッターとセッターが定義されている必要があります。

 

何はともあれ、これでフォームの値を元にインスタンスを作成することができました。

後の、足りない部分の情報を補う処理とDBへの保存は

userService.signup(user);

で行われるので、これでめでたく保存完了です。

終わりに

以上で、フォームに入力された値をDBに保存する仕組みが実装できました。

どうしても他の言語よりも手間がかかりますが、少しづつ覚えていきたいですね。