#21 Spring Mybatisを利用したデータベース操作[2. INSERT]

# #21 Spring Mybatisを利用したデータベース操作[2. INSERT]
今回はMybatisを用いてデータの挿入を行っていきます。

# 前提条件
この記事はSpringの最低限の知識が必要になります。
また、なるべく分かりやすく書くつもりですが、この記事の目的は自分の勉強のアウトプットであるため所々説明は省略します。

# 前回まで
前回はMybatisを利用するための環境構築とOracleDBを用いたテーブル作成を行いました。

https://qiita.com/Jackoguro/items/00343f0d5734c45dbdfb

# 構築環境
1. 各バージョン
Spring Boot ver 2.7.5
mybatis-spring-boot-starter ver 2.2.2
Model Mapper ver 3.1.0
jquery ver 3.6.1
bootstrap ver 5.2.2
webjars-locator ver 0.46
thymeleaf-layout-dialect ver 3.0.0

2. 依存関係
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884987/58b53ccd-7cf2-2d82-4214-9f0c08fced2e.png)

# 今回行うこと
今回は以下の流れに沿って進めていきます。
1. エンティティとは
    1. クラス名
    2. データベースへ値の引き渡し
    3. データベースから値の取得
1. テーブル(M_USER)用のエンティティクラス(MUser.java)の作成
1. テーブル(M_USER)用のリポジトリー(UserMapper.java)の作成
　1. Mapperとxmlのマッピング
　2. SQLとメソッドのマッピング
　3. メソッドの引数とSQLパラメータのマッピング
1. UserMapper.xmlにSQLを記述
1. サービスの処理の記述
    1. UserService.java(インターフェース)
    2. UserServiceImpl.java(実装クラス)
1. ModelMapperをBeanに登録
1. コントローラー(SignupController.java)の修正

## 1. エンティティとは
エンティティは``テーブルの1行``に対応するクラスです。エンティティに定義されたフィールドは``テーブルのカラム値``に対応します。

### 1. クラス名
エンティティのクラス名は対応するデータベースのテーブルと同じ名前にすることが多い

### 2. データベースへ値の引き渡し
データベースへ値の挿入、更新をする際に、エンティティに値を入れ引き渡す

### 3. データベースから値の取得
データベースから値を取得した場合に、値をエンティティに保持しておく

## 2. テーブル(M_USER)用のエンティティクラス(MUser.java)の作成
テーブル(M_USER)に対応させるためのエンティティクラス(MUser.Java)を作成します。

```java:MUser.java
package com.example.model;

import java.util.Date;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;

@Data
public class MUser {

	private String userId; 
	private Integer phoneNumber; 
	private Integer postalNumber1;
	private Integer postalNumber2;
	private String address;
	private String userName;
	private String password;
	private Date birthday1;
	private Date birthday2;
	private Integer age;
	private byte[] accountIcon;
	private Integer gender;
}
```
:::note warn
byte[]：画像を取り込む場合、エンティティクラスのデータ型はbyte[]を使用する
:::

## 3. テーブル(M_USER)用のリポジトリー(UserMapper.java)の作成
リポジトリーとは``データベースのデータ操作を行うクラス``です。リポジトリーを作成する場合、特定の実装への依存を回避するために必ずインテーフェースを定義した上で実装します。

Mybatisでリポジトリーを作成するには、インターフェースに``@Mapper``を付与します。
```java:UserMapper.java
package com.example.repository;

import org.apache.ibatis.annotations.Mapper;
import com.example.model.MUser;

@Mapper
public interface UserMapper {
	
	/* ユーザー登録 */
	public int insertOne(MUser user);
}
```

## 4. UserMapper.xmlにSQLを記述
次にUserMapper.xmlに内容を記述します。

```xml:UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- Mapperとxmlのマッピング -->
<mapper namespace="com.exeample.repository.UserMapper">
	
	<!-- ユーザー1件登録 -->
	<insert id="insertOne">
		<!-- テーブル(M_USER)の各項目 -->
		insert into m_user(
			user_id,
			phone_number,
			postal_number1,
			postal_number2,
			address,
			user_name,
			password,
			birthday1,
			birthday2,
			age,
			account_icon,
			gender
		)
		<!-- エンティティクラスに定義した各項目 -->
		values(
		values(
			#{userId},
			#{phoneNumber, jdbcType=VARCHAR},
			#{postalNumber1, jdbcType=INTEGER},
			#{postalNumber2, jdbcType=INTEGER},
			#{address, jdbcType=VARCHAR},
			#{userName},
			#{password},
			#{birthday1, jdbcType=DATE},
			#{birthday2, jdbcType=DATE},
			#{age, jdbcType=INTEGER},
			#{accountIcon, jdbcType=BLOB},
			#{gender, jdbcType=INTEGER}
		)
		)
	</insert>
</mapper>
```
ポイントとしては3点あります。
### 1. Mapperとxmlのマッピング
```xml
<!-- Mapperとxmlのマッピング -->
<mapper namespace="com.exeample.repository.UserMapper">
```
mapperタグを用いて、Mapperとxmlファイルをマッピングします。
mapperタグの``namespace属性``にMapperのインターフェース名を**パッケージ名を含めて指定します。**


### 2. SQLとメソッドのマッピング
次にSQLとメソッドのマッピングを行います。
```java:UserMapper.java
	/* UserMapper.javaで記述した抽象メソッド */
	public int insertOne(MUser user);
```
```xml:UserMapper.xml
	<!-- ユーザー1件登録 -->
	<insert id="insertOne">
```
``UserMapper.xml``のid属性に``UserMapper.java``で記述した抽象メソッドを指定します。
また、今回はINSERT処理を行うのでタグを``<insert></insert>``としておきます。

### 3. メソッドの引数とSQLパラメータのマッピング
次にMapperメソッドの引数をSQLのパラメータに挿入します。
``#{メソッド引き数名}``形式で指定できます。今回はメソッドの引数を``Muser``にしているため、``2. テーブル(M_USER)用のエンティティクラス(MUser.java)の作成``で作成したエンティティクラスのフィールド名をSQL内で指定します。

JDBCの仕様で、``insert``, ``update``, ``delete``でnullが許可されている項目を指定する場合、**JDBCデータ型（jdbcType)** を指定する必要があります。サポートされているJDBCデータ型の一覧は、MyBatisのドキュメントに記載されています。
Nullだとカラムのデータ型がわからないので定義が必要らしいです。

https://mybatis.org/mybatis-3/ja/sqlmap-xml.html

```xml:UserMapper.xml
		<!-- エンティティクラスに定義した各項目 -->
		values(
		values(
			#{userId},
			#{phoneNumber, jdbcType=VARCHAR},
			#{postalNumber1, jdbcType=INTEGER},
			#{postalNumber2, jdbcType=INTEGER},
			#{address, jdbcType=VARCHAR},
			#{userName},
			#{password},
			#{birthday1, jdbcType=DATE},
			#{birthday2, jdbcType=DATE},
			#{age, jdbcType=INTEGER},
			#{accountIcon, jdbcType=BLOB},
			#{gender, jdbcType=INTEGER}
		)
		)
```

## 5. サービスの処理の記述
次に先ほど作成したコードを利用して実際に行う処理を記述していきます。

### 1. UserService.java(インターフェース)

```java:UserService.java
package com.example.service;

import java.util.Locale;
import java.util.Map;
import com.example.model.MUser;

public interface UserService {
	
	/* 性別のMapを生成する */
	public Map<String, Integer> getGenderMap(Locale locale);
	
	/* ユーザー登録 */
	public void signUp(MUser muser);
}
```

### 2. UserServiceImpl.java(実装クラス)
``@Autowired``を用いてDIの注入を行い、``UserService.java``で記述した抽象メソッドの実装を行います。

```java:UserServiceImpl.java
package com.example.service.impl;

/* 省略 */

@Service
public class UserServiceImpl implements UserService {

    /* 省略 */
	
	/* レポジトリー(UserMapper.java)のDIを注入 */
	@Autowired
	private UserMapper mapper;
	
	/* データの挿入 */
	@Override
	public void signUp(MUser user) {
		mapper.insertOne(user);
	}
}
```

## 6. ModelMapperをBeanに登録
前回の記事でダウンロードした``ModelMapper``をBeanに登録していきます。
今回のようにライブラリに用意されているクラスをDIコンテナーに登録する場合、Beanのインスタンス(一般的にJavaConfig言われる)を作成します。

JavaConfigには``@Configurationアノテーション``を付けます。

```java:JavaConfig.java
package com.example.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();
	}
}
```

## 7. コントローラー(SignupController.java)の修正
最後にコントローラの作成を行います。
```java:SignupController.java
package com.example.controller;

/* 省略 */

@Controller
@RequestMapping("/user")
@Slf4j
public class SignupController {
	
	@Autowired
	private UserService userService;
	
	@Autowired
	private ModelMapper modelMapper;

	/* ユーザー登録画面を表示 */
	@GetMapping("/signup")
	public String getSignup(Model model, Locale locale, @ModelAttribute SignupForm form) {
		// 性別を取得
		Map<String, Integer> genderMap = userService.getGenderMap(locale);
		model.addAttribute("genderMap", genderMap);
		
		// ユーザー登録画面に遷移
		return "user/signup";
	}
	
	/* ユーザー登録処理 */
	@PostMapping("/signup")
	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());
		
		// formをMUserクラスに変換
		MUser user = modelMapper.map(form, MUser.class);
		
        log.info(user.toString());

		// ユーザ登録
		userService.signUp(user);

		// ログイン画面にリダイレクト
		return "redirect:/login";
	}
}
```

先ほど``JavaConfig``に追加した``ModelMapper``の``mapメソッド``を使用することでフィールドの内容をコピーすることができます。
``Formクラス(SignupForm.java)``に格納されたデータを``エンティティクラス(MUser.java)``にコピーする。
ただし、コピー元のデータとコピー先のデータのフィールド名は一致している必要がある。

```java:SignupController.java
MUser user = modelMapper.map(form, MUser.class);
```
<br>

バリエーションの設定は省略した``SignupForm.java``
```java:SignupForm.java
import lombok.Data;

@Data
public class SignupForm {
	private String userId;
	private Integer phoneNumber;
	private Integer postalNumber1;
	private Integer postalNumber2;
	private String address;
	private String userName;
	private String password;
	private Date birthday1;
	private Date birthday2;
	private Integer age;
	private MultipartFile accountIcon;
	private Integer gender;
}
```

# 最後に
``7. コントローラー(SignupController.java)の修正``の中で、ModelMapperの``mapメソッド``を用いて``Formクラス(SignupForm.java)``に格納されたデータを``エンティティクラス(MUser.java)``にコピーしましたが、これには2つの理由があります。

**1. 画面に変更があっても、サービスの修正が不要になる**
**2. 他の画面からもサービスを再利用できるようになる**

よって、**サービスを利用する場合はFormクラスを利用しないようにする！**

試しに以下のようなデータを挿入すると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884987/7c0b41ed-b524-5ab2-4395-6d3698352dd6.png)

以下のログが``log.info(form.toString())``により出力されたデータです(見やすいように縦書きに直してあります)。
```console:log.info(form.toString())
 SignupForm(userId=Tokyo@xxx.co.jp, 
            phoneNumber=0353211111, 
            postalNumber1=163, 
            postalNumber2=8001, 
            address=東京都新宿区西新宿二丁目８番１号, 
            userName=TokyoTocho, 
            password=Tokyo_0353211111, 
            birthday1=null, 
            birthday2=Mon Apr 01 00:00:00 JST 1991, 
            age=31, 
            accountIcon=org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@4028cb11, 
            gender=null)
```

以下のログが``log.info(user.toString())``により出力されたデータです(見やすいように縦書きに直してあります)。
```console:log.info(user.toString())
MUser(userId=Tokyo@xxx.co.jp, 
      phoneNumber=0353211111, 
      postalNumber1=163, 
      postalNumber2=8001, 
      address=東京都新宿区西新宿二丁目８番１号, 
      userName=TokyoTocho, 
      password=Tokyo_0353211111, 
      birthday1=null, 
      birthday2=Mon Apr 01 00:00:00 JST 1991, 
      age=31, 
      accountIcon=null, 
      gender=null)
```

両方のログを比較すると、画像データである``accountIcon``がMUser側ではnullになってしまっています。
次回以降はここを修正していきます。