前置き
MyBatis にて値オブジェクト(Value Object)をマッピングさせる際に少しハマったので整理しました。
実現させたいこと
- SELECT の結果をオブジェクト内の Value Object にマッピングさせたい
- SELECT のパラメーターを Value Object にしたい
環境
- Spirng Boot
- MyBatis
- h2 DataBase
実装
以下のようなUserName
という Value Object クラスがあったとします。
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
というクラスも保持しています。
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定義を書きます。
CREATE TABLE users (
id int NOT NULL
, user_name VARCHAR(50)
, register_date DATE
);
テスト用のデータ挿入用のdata.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
を定義します。
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 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
で、引数名を指定する
以下の部分です。
<association property="userId" javaType="com.example.demo.domain.model.UserId">
<constructor>
<arg name="value" column="id"/>
</constructor>
</association>
結果
テストコードを書いてブレークさせた結果は以下のように。
Value Object のインスタンスも生成されて、値もマップされています。
