絶対分かるMyBatis！MyBatisで覚えるべきチェックルール25（前半）

# はじめに

今回はO/R MappingツールであるMyBatisの概要とその使い方について、初心者がよく間違えるポイントを踏まえ、チェックルールとして纏めてみました。

(2018/10/24 追記)
つづきは「[絶対分かるMyBatis！MyBatisで覚えるべきチェックルール25（中編）](https://qiita.com/5zm/items/0864d6641c65f976d415)」を参照ください。

## MyBatisとは？

* JavaのDBアクセス用のOSSライブラリ（いわゆるO/R Mappingツール）です。
* SQLをXMLファイルに記述し、Javaのインターフェースのメソッドを実行すると、メソッド名に対応するSQLが実行されます。
* メソッドの引数や戻り値を、JavaのオブジェクトとSQL（PreparedStatement、ResultSet等）とマッピングしてくれます。
* 動的SQLという引数の状態に応じて異なるSQLを発行する便利な機能があります。

## 読み方、利用方法

チェックルールは重要度の高い順および実装を考慮した順に説明していきます。
前から順に読むことで、MyBatis3を利用したDBアクセス処理の基本的な実装が理解できるかと思います。
なお、後半は特定の利用シーンでしか使われない可能性もありますのでご了承ください。

## 検証環境

チェックルールなのでほとんど検証環境に依存しないと思いますが、一応記載します。
MyBatis単体ではなくSpring Frameworkと合わせて利用しています。そのためmybatis-springに関する箇所のみ必要／不必要で分かれるかと思います。

* Java 1.8.0_131
* PostgreSQL 9.3
* MyBatis3 3.4.2
* mybatis-spring 1.3.1
* Spring Framework 4.3.5

## 前提条件、初期設定
[TERASOLUNA Server Framework for Java 5.3.0](http://terasolunaorg.github.io/guideline/5.3.0.RELEASE/ja/index.html) のデフォルト設定です。
以下にmavenのgenerateコマンドで自動生成された設定ファイル（mybatis-config.xml、SpringのBean定義ファイル）を記載します。手動で設定する場合は参考にしてください。

なお、TERASOLUNA5.xでPostgreSQLを利用する場合は「[TERASOLUNA5.xのブランクプロジェクトをPostgreSQL対応に変更する方法](https://qiita.com/5zm/items/03915a7232fcbbd5768a)」を参照ください。

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

    <!-- See http://mybatis.github.io/mybatis-3/configuration.html#settings -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true" />
        <setting name="lazyLoadingEnabled" value="true" />
        <setting name="defaultFetchSize" value="100" />
<!--
        <setting name="defaultExecutorType" value="REUSE" />
        <setting name="jdbcTypeForNull" value="NULL" />
        <setting name="localCacheScope" value="STATEMENT" />
-->
    </settings>

    <typeAliases>
        <package name="com.example.demo.mybatis3.domain.model" />
        <package name="com.example.demo.mybatis3.domain.repository" />
<!--
        <package name="com.example.demo.mybatis3.infra.mybatis.typehandler" />
-->
    </typeAliases>

    <typeHandlers>
<!--
        <package name="com.example.demo.mybatis3.infra.mybatis.typehandler" />
-->
    </typeHandlers>

</configuration>
```

```xml:SpringのBean定義ファイル（***-infra.xml）
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd
    ">

    <import resource="classpath:/META-INF/spring/demo-mybatis-env.xml" />

    <!-- define the SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/META-INF/mybatis/mybatis-config.xml" />
    </bean>

    <!-- scan for Mappers -->
    <mybatis:scan base-package="com.example.demo.mybatis3.domain.repository" />

</beans>
```

# チェックルール
## 1. Mapperファイルはインターフェースと同一パッケージに配置すること

Mapperファイルの読み込み方法は他にもありますが、一番単純で明確な方法であるインターフェースと同一パッケージになるように配置します。

インターフェースのFQCN

* **com.example.demo.mybatis3.domain.repostiory.**CustomerRepostiory

Mapperファイルの格納先（Mavenプロジェクトの場合の例）

* src/main/resources/**com/example/demo/mybatis3/domain/repostiory**

**（注意）**TERASOLUNA5.xのマルチプロジェクト構成の場合、Domainプロジェクトに`*********.domain.repository`パッケージが初期状態で作成されています。インターフェースはこのパッケージ（サブパッケージ含む）配下に実装します。

## 2. Mapperファイルはインターフェースと同一のファイル名で作成すること
ファイル名から関係が分かるようにするため、Mapperファイルはインターフェースと同一のファイル名で作成します。
なお、実際の紐付けはファイル名ではなく、後述するnamespace属性で行います。

* CustomerRepostiory.**java** `--->` CustomerRepository.**xml**
* ReservationRepostiory.**java** `--->` ReservationRepostiory.**xml**

## 3. Mapperファイルとインターフェースを紐付けるため、namespace属性でインターフェースのFQCNを指定すること
Mapperファイルとインターフェースの紐付けは、Mapperファイルの<mapper>要素のnamespace属性にインターフェースのFQCNを指定することで行います。
なお、以下に示すのが必要最低限の内容を記述したMapperファイルになります。

```xml:必要最低限の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.mybatis3.domain.repostiory.CustomerRepostiory">

</mapper>
```

## 4. インターフェースに対応するMapperファイルが存在すること

インターフェースに対応するMapperファイルが存在しない場合、初期化の処理でエラーになります。
Spring Frameworkと合わせて利用する場合、DIコンテナの初期化でエラーになるため、アプリケーションの起動に失敗します。
インターフェースを実装した際は必ずMapperファイルを作成してください。
Mapperファイルの内容は全て記述する必要はなく「必要最低限のMapperファイル」であればOKです。

* Mapperファイルが存在しないとエラーになる
 * CustomerRepostiory.**java** `--->` CustomerRepository.**xml**
 * ReservationRepostiory.**java** `--->` **未作成**
 * EmployeeRepostior.**java** `--->` **未作成**

* 反対にインターフェースが存在しない場合はエラーにならない
 * CustomerRepostiory.**java** `--->` CustomerRepository.**xml**
 * **未作成** `--->` ReservationRepostiory.**xml**
 * **未作成** `--->` EmployeeRepostiory.**xml**

**（注意）mybatis-springでインターフェースをパッケージスキャンを利用して登録する場合に注意すべき観点です。**

* （例１）mybatis-springにおいて、`<mybatis:scan/>`を利用する場合

TERASOLUNA5.xはこの方法でインターフェースをMyBatisに登録します。
デフォルトでは`*****.domain.repository`パッケージ配下をスキャンするように設定されています。
`<mybatis:scan/>`の詳細についてはhttp://www.mybatis.org/spring/ja/mappers.html#scan を参照ください。

```xml:Bean定義ファイル
<!-- scan for Mappers -->
<mybatis:scan base-package="com.example.demo.mybatis3.domain.repostiory" />
```

* （例２）mybatis-springにおいて、`@MapperScan`を利用する場合

SpringBootでアプリを実装する場合、XMLではなくJavaConfigを利用するのは一般的かと思います。この際、`@MapperScan`アノテーションでパッケージスキャンを利用した場合に注意する必要があります。

```java:JavaConfing
@Configuration
@MapperScan("com.example.demo.mybatis3.domain.repostiory")
public class AppConfig {
// omitted
}
```


## 5. CRUDに対応するXMLタグを利用すること
MapperファイルにSQLを記述しますが、SQLはCRUDに対応するXMLタグで囲む必要があります。

* 登録（CREATE）

```xml
<insert>
<!--
ここにinsertのSQLを書く
-->
</insert>
```

* 検索（READ）

```xml
<select>
<!--
ここにselectのSQLを書く
-->
</select>
```

* 更新（UPDATE）

```xml
<update>
<!--
ここにupdateのSQLを書く
-->
</update>
```

* 削除（DELETE）

```xml
<delete>
<!--
ここにdeleteのSQLを書く
-->
</delete>
```

## 6. メソッドとSQLを紐付けるため、id属性にインターフェースのメソッド名を指定すること
インターフェースのメソッドとそのメソッドを呼び出した際に実行するSQLを紐付けるため、CRUDに対応したXMLタグのid属性にインターフェースのメソッド名を指定します。

```java:CustomerRepostiory.java
public interface CustomerRepository {
    void insert(Customer customer);
    Customer fineOne(String customerCode);
}
```

```xml:CustomerRepostiory.xml
<mapper namespace="com.example.demo.mybatis3.domain.repostiory.CustomerRepostiory">

    <insert id="insert">
    <!--
    ここにinsertのSQLを書く
    このSQLはインターフェースのinsertメソッドを呼び出すと実行される
    -->
    </insert>

    <select id="findOne">
    <!--
    ここにselectのSQLを書く
    このSQLはインターフェースのfineOneメソッドを呼び出すと実行される
    -->
    </select>
</mapper>
```

## 7. メソッドの引数が１つの場合、引数のデータ型を特定するため、parameterType属性に引数のデータ型を指定すること

メソッドの引数が１つの場合のみ、引数のデータ型を特定するため、parameterType属性に引数のデータ型を指定します。

データ型はFQCNかタイプエイリアスを設定している場合はエイリアス（短縮名）で指定します。
なお、プリミティブ型やそのラッパークラス等の一般的なデータ型についてはエイリアスが用意されています。詳細についてMyBatis3の公式ドキュメントを参照ください。
http://www.mybatis.org/mybatis-3/ja/configuration.html#typeAliases

**（注意）**
TERASOLUNA5.xでは`********.domain.model`パッケージにタイプエイリアスが設定されているため、このパッケージ配下（サブパッケージも含む）はエイリアス（短縮名）で指定します。

```java:CustomerRepostiory.java
public interface CustomerRepository {
    void insert(Customer customer);
    Customer fineOne(String customerCode);
}
```

```xml:CustomerRepostiory.xml
<mapper namespace="com.example.demo.mybatis3.domain.repostiory.CustomerRepostiory">

    <!-- 引数のデータ型はCustomer -->
    <insert id="insert" parameterType="Customer">
    <!--
    ここにinsertのSQLを書く
    このSQLはインターフェースのinsertメソッドを呼び出すと実行される
    -->
    </insert>

    <!-- 引数のデータ型はString-->
    <select id="findOne" parameterType="string">
    <!--
    ここにselectのSQLを書く
    このSQLはインターフェースのfineOneメソッドを呼び出すと実行される
    -->
    </select>
</mapper>
```

## 8. メソッドの引数が複数（２つ以上）の場合、parameterType属性は指定しないこと

引数のデータ型が同じとは限りません。また、データ型が複数ある場合はどの引数がどのparameterType属性に対応するのか分かりません。そのため、引数が複数の場合はparameterType属性は不要です。指定してはいけません。

## 9. 検索（READ）のSQLの場合、戻り値のデータ型を特定するため、resultType属性に戻り値のデータ型を指定すること

検索（READ）のSQLの場合のみ、戻り値のデータ型を特定するため、resultType属性に戻り値のデータ型を指定します。

**登録（CREATE）、更新（UPDATE）、削除（DELETE）の場合、resultType属性は指定しないこと。**

```java:CustomerRepository.java
public interface CustomerRepository {
    void insert(Customer customer);
    Customer fineOne(String customerCode);
}
```

```xml:CustomerRepository.xml
<mapper namespace="com.example.demo.mybatis3.domain.repostiory.CustomerRepostiory">

    <!-- 引数のデータ型はCustomer -->
    <insert id="insert" parameterType="Customer">
    <!--
    ここにinsertのSQLを書く
    このSQLはインターフェースのinsertメソッドを呼び出すと実行される
    -->
    </insert>

    <!-- 引数のデータ型はString-->
    <!-- 戻り値のデータ型はCustomer -->
    <select id="findOne" parameterType="string" resultType="Customer">
    <!--
    ここにselectのSQLを書く
    このSQLはインターフェースのfineOneメソッドを呼び出すと実行される
    -->
    </select>
</mapper>
```

## 10. 登録（CREATE）、更新（UPDATE）、削除（DELETE）のSQLの場合、インターフェースのメソッドの戻り値は「なし」、「真偽」、「更新件数」のいずれかになっていること

登録（CREATE）、更新（UPDATE）、削除（DELETE）のSQLの場合、インターフェースのメソッドの戻り値は「なし」、「真偽」、「更新件数」のいずれかを指定することができます。
各自がそれぞれ判断すると実装者によって異なってしまうため、プロジェクトでルールを設けるようにしてください。

| 戻り値 | 説明 |
|:-----:|:-----|
| なし |戻り値はvoid<br>テーブルの反映結果が不要な場合に設定します。 |
| 真偽 |戻り値はboolean、Boolean<br>テーブルに１レコード以上の反映があった場合は真、１件も無かった場合は偽を返します。 |
| 更新件数 |戻り値はint,long等の整数型（ラッパークラスもOK）<br>反映のあったレコード件数を返します。<br>登録なら登録されたレコード件数、更新なら更新されたレコード件数、削除なら削除されてたレコード件数です。 |

```java:戻り値が「なし」の場合
void insert(Customer customer);
```

```java:戻り値が「真偽」の場合
boolean delete(String customerCode);
// ラッパークラスの場合
// Boolean delete(String customerCode);
```

```java:戻り値が「更新件数」の場合
int update(Customer customer);
// ラッパークラスの場合
// Integer update(Customer customer);
```


## 11. 複雑なマッピングが必要な場合、戻り値のデータ型を特定するため、resultMap属性に戻り値のデータ型を指定すること

MyBatis3のデフォルトで一般的なマッピングは自動で行うことができますが、それでは対応できないような複雑なマッピングが必要な場合、resultMap属性に別途定義した<resultMap>要素のid属性を指定します。複雑なマッピングとは以下のようなマッピングです。

* 自動で認識されるデータ型以外のデータマッピングを行いたい場合
* ネストしたクラスに値をマッピングしたい場合
* 一対多のコレクションを同時にマッピングしたい場合

```java:CustomerRepository.java
public interface CustomerRepository {
    void insert(Customer customer);
    Customer fineOne(String customerCode);
}
```

```xml:CustomerRepository.xml
<mapper namespace="com.example.demo.mybatis3.domain.repostiory.CustomerRepostiory">

    <!-- 戻り値のデータ型はCustomer -->
    <resultMap id="customerResultMap" type="Customer">
    <!--
    resultMapの定義については複雑なため、別途説明。ここでは省略。
    -->
    </resultMap>

    <!-- 引数のデータ型はCustomer -->
    <insert id="insert" parameterType="Customer">
    <!--
    ここにinsertのSQLを書く
    このSQLはインターフェースのinsertメソッドを呼び出すと実行される
    -->
    </insert>

    <!-- 引数のデータ型はString-->
    <!-- 戻り値のデータ型はCustomer （idが"customerResultMap"である <resultMap>要素のtype属性を参照） -->
    <select id="findOne" parameterType="string" resultMap="customerResultMap">
    <!--
    ここにselectのSQLを書く
    このSQLはインターフェースのfineOneメソッドを呼び出すと実行される
    -->
    </select>
</mapper>
```

## 12. 戻り値の指定方法で、resultType属性とresultMap属性は同時に利用しないこと

resultType属性とresultMap属性のどちらも戻り値のデータ型を指定するものです。
二通りの方法で指定する必要性はまったくありません。同時に利用しないでください。
**なお、２つを指定した場合、より詳細な設定が可能であるresultMap属性が優先されます。**

基本的にはresultType属性を利用し、resultType属性では実現できない場合にresultMap属性を利用するようしましょう。

# つづく

今回はMyBatisの概要と、MyBatisを利用する際に覚えておくべきチェックルールについて説明しました。
分量が多くなってしまったため、チェックルールの13～25は次の記事で説明したいと思います。