MyBatisで値オブジェクトとかを使う際の構文とかメモとか

値オブジェクトとMyBatisの組み合わせでハマったポイントをメインに構文とかメモとかを書いておく。~~ハマったと言いつつどうハマったのかは書いてません。~~

## 雛型
上2行はおまじない。
`namespace`に、xmlに対応するMapper.javaの絶対パスを記入する。
~~~xml:雛型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="">

</mapper>
~~~

~~~Java:例:Mapper.java
package com.example.demo.infrastructure

@Mapper
public interface Mapper{

}
~~~

~~~xml:例: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.infrastructure.Mapper"> <-Mapper.javaの絶対パス

</mapper>
~~~

## 便利設定
通常は、マッパーインターフェースが配置されているフォルダ構造と同じようにMapper.xmlを配置する必要がある。

application.propertiesにこのように記述するとプロジェクト配下の全てのフォルダを対象にMapper.xmlを探す。

メリット：`HogeMapper.java`と`HogeMapper.xml`を同じフォルダに配置できる。
~~~properties:application.properties
mybatis.mapper-locations:classpath:project_name/**/*.xml 
~~~

## CRUD
<mapper>タグ内でCRUDするためのSQLを書く時は下記のようにする。
タグ|説明
:-|:-
id|SQLに対する識別子。Mapper.javaのメソッド名と対応させる
resultType|戻り値のデータ型。プリミティブ型、オブジェクト型どちらも可

~~~Java:Mapper.java
@Mapper
public interface Mapper{
    public String select();
    public void register();
    public void update();
    public void delete();
}
~~~

~~~xml
<select id="select" resultType="String">SQL</select>
~~~

~~~xml
<insert id="register">SQL</insert>
~~~

~~~xml
<update id="update">SQL</update>
~~~

~~~xml
<delete id="delete">SQL</delete>
~~~

## 文字列代入
MyBatisでは2つの方法で文字列の代入が行える。
`#{}`はエスケープ処理がされる。安全で高速。公式の推奨記法。
`${}`はエスケープ処理がされない。文字列をそのまま代入したい場合などに利用。

~~~
#{hoge}
~~~

~~~
${piyo}
~~~

## 値オブジェクトへのマッピング
値オブジェクトへDBの値をマッピングする際は、マッピング先を明示的に記載する必要がある。

~~~
SELECT hoge as "piyo.value";
~~~

<details><summary>モデル例</summary>

モデルはある程度簡略化してます。
下記のモデルはドメイン貧血症になっていますが、MyBatisを扱う上で必要な情報は揃っているため割愛。
~~~Java:Person.java
package com.example.demo.domain.model;

public class Person {

    Name name;
    Age age;

    public Person(Name name, Age age) {
        this.name = name;
        this.age = age;
    }

    @Deprecated
    public Person(){}
    
    public Name name(){
        return this.name;
    }
    
    public Age age(){
        return this.age;
    }
}
~~~

~~~Java:Name.java
package com.example.demo.domain.model;

public class Name {

    String value;

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

    @Deprecated
    public Name(){}
    
    public String value(){
        return this.value;
    }
}
~~~

~~~Java:Age.java
package com.example.demo.domain.model;

public class Age {

    String value;

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

    @Deprecated
    public Age(){}
    
    public String value(){
        return this.value;
    }
}
~~~
</details>

~~~xml:例
<select id="hoge" resultType="com.example.demo.domain.model.Person">
    SELECT
        name as "name.value",
        age as "age.value"
    FROM hogehoge
</select>
~~~

## 値オブジェクトから値を取り出す

文字列代入などでクラス内のデータを取り出したい時は、下記の3パターンの方法がある。
2つ以上引数を持つ場合は、`@Param("")`で引数を一意に識別できるようにする必要がある。

<details><summary>モデル例</summary>

モデルはある程度簡略化してます。
下記のモデルはドメイン貧血症になっていますが、MyBatisを扱う上で必要な情報は揃っているため割愛。
~~~Java:Person.java
package com.example.demo.domain.model;

public class Person {

    Name name;
    Age age;

    public Person(Name name, Age age) {
        this.name = name;
        this.age = age;
    }

    @Deprecated
    public Person(){}
    
    public Name name(){
        return this.name;
    }
    
    public Age age(){
        return this.age;
    }
}
~~~

~~~Java:Name.java
package com.example.demo.domain.model;

public class Name {

    String value;

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

    @Deprecated
    public Name(){}
    
    public String value(){
        return this.value;
    }
}
~~~

~~~Java:Age.java
package com.example.demo.domain.model;

public class Age {

    String value;

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

    @Deprecated
    public Age(){}
    
    public String value(){
        return this.value;
    }
}
~~~
</details>

~~~Java:例:Mapper.java
@Mapper
public interface Mapper{
    public void methodA(Name name);

    public void methodB(Person person);

    public void methodC(@Param("aaa") Name name, @Param("bbb") Age age);
}
~~~

~~~:methodA
#{value}　// Name.javaのインスタンス変数 value から値を取得
~~~

~~~:methodB
#{name.value} //Personクラスのインスタンス変数 name 内の value から値を取得
#{age.value}　//Personクラスのインスタンス変数 age 内の value から値を取得
~~~

~~~:methodC
#{aaa.value}
#{bbb.value}
~~~

## resultMap

複雑なオブジェクトへデータをマッピングする際に利用。

オプション|説明
:-|:-
id|識別子。一意に特定できる名前を付ける
type|戻り値のオブジェクトの型
property|値のマッピング先
column|マッピングしたいデータが入っているDBのカラム名

resultMapを使う時は`SELECT hoge as piyo ~`の`piyo`の部分を、マッピング先のインスタンス変数ではなくマッピング元のDBのカラム名を書く。

<details><summary>モデル例</summary>

モデルはある程度簡略化してます。
下記のモデルはドメイン貧血症になっていますが、MyBatisを扱う上で必要な情報は揃っているため割愛。
~~~Java:Manager.java
package com.example.demo.domain.model;

public class Manager {
    
    ManagerNumber number;
    ManagerName name;
    Telephones telephones;

    public Manager(ManagerNumber number, ManagerName name, Telephones telephones) {
        this.number = number;
        this.name = name;
        this.telephones = telephones;
    }

    @Deprecated
    public Manager() {
    }

    public ManagerNumber number(){
        return this.number;
    }

    public ManagerName name(){
        return this.name;
    }

    public Telephones telephones(){
        return this.telephones;
    }

}
~~~

~~~Java:ManagerNumber.java
package com.example.demo.domain.model;

public class ManagerNumber {
    
    int value;

    public ManagerNumber(int value) {
        this.value = value;
    }

    @Deprecated
    public ManagerNumber() {
    }

    public int value(){
        return this.value;
    }
    
}
~~~

~~~Java:ManagerName.java
package com.example.demo.domain.model;

public class ManagerName {

    String value;

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

    @Deprecated
    public ManagerName() {
    }

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

}
~~~

~~~Java:Telephones.java
package com.example.demo.domain.model.telephone;

public class Telephones {
    
    List<Telephone> list;

    public Telephones(List<Telephone> list) {
        this.list = list;
    }

    @Deprecated
    public Telephones() {
    }
    
    public List<Telephone> asList(){
        return Collections.unmodifiableList(this.list);
    }
}
~~~

~~~Java:Telephone.java
package com.example.demo.domain.model.telephone;

public class Telephone {
    
    TelephoneId id;
    TelephoneNumber number;

    public Telephone(TelephoneId id, TelephoneNumber number) {
        this.id = id;
        this.number = number;
    }

    @Deprecated
    public Telephone() {
    }

    public TelephoneId id(){
        return this.id;
    }

    public TelephoneNumber number(){
        return this.number;
    }
    
}
~~~
</details>

~~~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.infrastructure.Mapper">

    <resultMap id="readAllMap" type="com.example.demo.domain.model.Manager">
        <result property="number.value" column="manager_no"/>
        <result property="name.value" column="manager_name"/>

        <collection property="telephones.list" ofType="com.example.demo.domain.model.telephone.Telephone">
            <id property="id.value" column="id"/>
            <result property="number.value" column="telephone_number"/>
        </collection>

    </resultMap>

    <select id="readAll" resultMap="readAllMap">
        SELECT
            ml.manager_no as "manager_no",
            ml.manager_name as "manager_name",
            tl.id as "id",
            tl.telephone_number as "telephone_number"
        FROM
            manager_list as ml
        LEFT OUTER JOIN
            telephone_list as tl
        ON ml.manager_no = tl.manager_no
    </select>
</mapper>
~~~

## java.lang.IllegalArgumentException: argument type mismatch
Mapperまでは問題なく動くがデータのマッピングが上手くいかない場合、modelに空のコンストラクタが無いせいでエラーになっている可能性あり。

空のコンストラクタを作成する際は`@Depricated`を付けて利用非推奨を明示する。