【Mybatis】オブジェクトを持つクラスのmapping

# はじめに
MyBatisを用いてオブジェクトを持つクラスのマッピング方法を学んだので備忘のために投稿します。

# 開発環境・実行環境
 - Spring Tool Suite 4 Version: 4.14.1.RELEASE
 - Spring boot 2.7.2
 - Java11
 - Marvin 3.8.4
 - PostgreSQL 11.15
 - mybatis 2.2.2

# やりたいこと
 - 子を複数もつ親レコードの取得を行いたい。
 - 親レコード取得の際は、id指定で1件取得したい。
 - 後程記載するdomain `Parentクラス`をMyBatisでマッピングしたい。

## ER図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711483/38e74afa-48aa-1783-f8ca-a0dbd45cecb1.png)

## DDL文
~~~sql
create table parents(
	id serial NOT NULL,
	name text NOT NULL,
	gender VARCHAR(1) NOT NULL
);

create table children(
	id serial NOT NULL,
	name text NOT NULL,
	gender VARCHAR(1) NOT NULL,
	parent_id int NOT NULL
);
~~~

# 実装
## domain
~~~java:Parent.java
package com.example.domain;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * 親テーブル
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Parent {
	
	private int id;
	private String name;
	private int gender;
    /** 子オブジェクトを複数もつ **/
	private List<Children> children;
	
}
~~~

~~~java:Children.java
package com.example.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * 子テーブル
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Children {
	
	private int id;
	private String name;
	private int gender;
	private int parentId;

}
~~~


## mapper
まずMapperを作成
~~~java:ParentMapper.java
package com.example.dao;

import org.apache.ibatis.annotations.Mapper;

import com.example.domain.Parent;

@Mapper
public interface ParentMapper {
	
	/** idから親レコードを取得 **/
	public Parent findById(int id);

}
~~~

## 【本題】xmlファイルの作成
~~~xml:ParentMapper.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 namespace="com.example.dao.ParentMapper">
<select id="findById" parameterType="int" resultType="com.example.domain.Parent" resultMap="ParentMapper">
  SELECT p.id, p.name, p.gender, c.id as children_id, c.name as children_name, c.gender as children_gender 
  FROM parents p
  LEFT JOIN children c
  ON p.id = c.parent_id
  WHERE p.id = #{id}
</select>
<resultMap id="ParentMapper" type="com.example.domain.Parent">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <result property="gender" column="gender"/>
  <!-- collectionでchildrenをマッピングする -->
  <collection property="children" ofType="com.example.domain.Children">
    <id property="id" column="children_id"/>
    <result property="name" column="children_name"/>
    <result property="gender" column="children_gender"/>
    <result property="parentId" column="id"/>
  </collection>
</resultMap>
</mapper>
~~~
resultMap内のcollectionで、childrenをマッピングする。
propertyにはchildrenクラスのフィールド名を、columnにはSQL実行したときのカラム名を指定する。
もしchildrenが居なかった場合、LEFT JOINをしているので、nullのChildrenリストが生成される。

## テスト
Childrenのリストを持ったParentオブジェクトを取得できているか確認する。

### SQL
テスト用に準備したSQL
~~~sql
insert into parents(name, gender) values ('佐藤太郎', 1);
insert into parents(name, gender) values ('高橋花子', 2);
insert into parents(name, gender) values ('山田裕子', 2);

insert into children(name, gender, parent_id) values ('佐藤次郎', 1, 1);
insert into children(name, gender, parent_id) values ('高橋智子', 2, 2);
insert into children(name, gender, parent_id) values ('高橋達郎', 1, 2);
~~~

### テストコード
~~~java:ParentMapperTest.java
package com.example.dao;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;

import com.example.domain.Children;
import com.example.domain.Parent;

@MybatisTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 単体テストでPostgreSQLを使用するための設定
class ParentMapperTest {
	
	@Autowired
	private ParentMapper parentMapper;

	@BeforeAll
	static void setUpBeforeClass() throws Exception {
	}

	@AfterAll
	static void tearDownAfterClass() throws Exception {
	}

	@BeforeEach
	void setUp() throws Exception {
	}

	@AfterEach
	void tearDown() throws Exception {
	}

	@Test
	@DisplayName("親：高橋花子、子：高橋智子、高橋達郎 のParentオブジェクトを取得できているか")
	public void findByIdTest() {
        // parentのid
		int id = 2;
		
		// 期待値
		Children child1 = new Children(2, "高橋智子", 2, 2);
		Children child2 = new Children(3, "高橋達郎", 1, 2);
		List<Children> children = new ArrayList<>() {
			{
				add(child1);
				add(child2);
			}
		};

		Parent expected = new Parent(2, "高橋花子", 2, children);
		
        // 実際
		Parent actual = parentMapper.findById(id);
		
		// 結果
		assertEquals(expected.toString(), actual.toString());
		System.out.println(actual);
	}

}
~~~

### コンソール出力結果
~~~
Parent(id=2, name=高橋花子, gender=2, 
children=[Children(id=2, name=高橋智子, gender=2, parentId=2), 
Children(id=3, name=高橋達郎, gender=1, parentId=2)])
~~~

# 終わりに
MyBatisのresultMapを用いてオブジェクトを持つクラスのマッピングを行いました。
間違っている部分もあるかもしれませんので、その際はご指摘頂ければと思います。

# 参考文献
[mybatis 公式](https://mybatis.org/mybatis-3/ja/sqlmap-xml.html)
[MybatisのMapperを使った高度なマッピングー親子関係にあるテーブルの検索結果を格納するResultMap](https://qiita.com/alpha_pz/items/9c88acd814ffad2f1a36)
[mybatisで値オブジェクト（Value Object）を扱う場合のポイント](https://rikupapa-shima.hatenablog.com/entry/mybatis_value_object_mapping)