MyBatis にて値オブジェクト（Value Object）をマッピングさせる際のポイント

# 前置き

MyBatis にて値オブジェクト（Value Object）をマッピングさせる際に少しハマったので整理しました。

# 実現させたいこと
*  SELECT の結果をオブジェクト内の Value Object にマッピングさせたい
*  SELECT のパラメーターを Value Object にしたい 

# 環境

* Spirng Boot
* MyBatis
* h2 DataBase
 
# 実装

以下のような`UserName`という Value Object クラスがあったとします。

```java
package com.example.demo.domain.model;

public class UserName {
    private final String value;

    public UserName(String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }
}
```

`User`クラスが`UserName`を保持します。  
他に`UserName`と`RegisterDate`というクラスも保持しています。

```java
package com.example.demo.domain.model;

import lombok.Data;

@Data
public class User {
    private UserId userId;
    private UserName userName;
    private RegisterDate registerDate;
}
```

`schema.sql`にDB定義を書きます。

```sql
CREATE TABLE users (
  id int NOT NULL
  , user_name VARCHAR(50)  
  , register_date DATE
);
```

テスト用のデータ挿入用の`data.sql`です。

```sql
INSERT INTO users VALUES (1, 'Nocchi', '2020-02-01');
INSERT INTO users VALUES (2, 'Kashiyuka', '2020-02-02');
INSERT INTO users VALUES (3, 'A-Chan', '2020-02-03');
```

`UserRepository`にIDから`User`を取得するためのメソッド`findById`を定義します。

```java
package com.example.demo.domain.repository;

import com.example.demo.domain.model.User;
import com.example.demo.domain.model.UserId;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UserRepository {
    User findById(@Param("userId") UserId userId);
}
```

Mapper は以下のような内容になります。

```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.demo.domain.repository.UserRepository">
  <select id="findById" resultMap="UserMap" parameterType="map">
    select id, user_name, register_date from users where id = #{userId.value}
  </select>

  <resultMap id="UserMap" type="com.example.demo.domain.model.User">
      <association property="userId" javaType="com.example.demo.domain.model.UserId">
        <constructor>
          <arg name="value" column="id"/>
        </constructor>
      </association>
      <association property="userName" javaType="com.example.demo.domain.model.UserName">
        <constructor>
          <arg name="value" column="user_name"/>
        </constructor>
      </association>
      <association property="registerDate" javaType="com.example.demo.domain.model.RegisterDate">
        <constructor>
          <arg name="value" column="register_date"/>
        </constructor>
      </association>
  </resultMap>

</mapper>
```

# ポイント

*  Value Object を `select`の Parameter に使用したい場合`@Param`アノテーションをつける
*  `@Param`を使用する場合、`select`のオプションに`parameterType="map"`を付与する

上記を行わないと、`where id = #{userId.value}` のように Value Object の値を`select`内で使用できませんでした。

* SELECT結果のオブジェクト内の Value Object インスタンスを生成するには、`association`を使用する
* Value Object の コンストラクタに値をマップさせるために`constructor`を使用する
* `arg`のオプション`name`で、引数名を指定する

以下の部分です。

```xml
<association property="userId" javaType="com.example.demo.domain.model.UserId">
        <constructor>
          <arg name="value" column="id"/>
        </constructor>
</association>
```

# 結果

テストコードを書いてブレークさせた結果は以下のように。  
Value Object のインスタンスも生成されて、値もマップされています。

<img width="809" alt="スクリーンショット 2020-02-06 5.41.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/247e946d-c249-62cb-7942-e2a1bf7888ac.png">

# コード

[GitHub に公開しました。](https://github.com/devshima/mybatis-value-object-mapping)