mybatis-spring-boot-starterの使い方

今回は2015年11月にバージョン1.0.0がリリースされ、2016年4月19日に1.1がリリースされた[mybatis-spring-boot-starter](https://github.com/mybatis/spring-boot-starter)の使い方を紹介します。

MyBatisをSpring Boot上で使う際は、[mybatis-spring](https://github.com/mybatis/spring)から提供されている`SqlSessionFactoryBean`や`SqlSessionTemplate`のBean定義を開発者が行う必要がありましたが、mybatis-spring-boot-starterの登場によりこれらのBean定義は自動コンフィギュレーションによって解決されます :smile:
まずは、実際にmybatis-spring-boot-starterを利用して簡単なCLI(Command Line Interface)アプリケーションを作ってみます 。

> **Note:**
>
> **2019/7/16: 追記**
> JDK 13で導入予定(現時点ではPreview版)のText Blocksを使ったデモアプリケーションへのリンクを追加しました。
>
> **2019/7/15: 追記**
> 2019年7月15日にバージョン2.1.0がリリースされたので、内容を2.1ベースにしました。
>
> **2019/5/7: 追記**
> mybatis-spring-boot-starter 2.0の変更点へのリンクを追加しました。加えて、2.0.1上で`mybatis.type-aliases-package`を利用する時に発生する可能性がある不具合に関する説明（説明へのリンク）を追加しました。
>
> **2019/5/3: 追記**
> 2019年1月22日にバージョン2.0.0(2019年4月にバージョン2.0.1)がリリースされたので、内容を2.0ベースにしました。
>
> **2017/4/10: 追記**
> 2017年4月10日にバージョン1.3.0がリリースされたので、内容を1.3ベースにしました。
>
> **2017/1/4: 追記**
> 2017年1月2日にバージョン1.2.0がリリースされたので、内容を1.2ベースにしました。 なお、バージョン1.2を使う場合は、Spring Boot 1.4以降(正確にはSpring Framework 4.3以降)上で動かす必要があります。(2017/1/4)

なお、メジャー/マイナーバージョンアップ時の変更点については、以下の投稿にまとめてあります。

* [mybatis-spring-boot-starter 2.1の変更点](https://qiita.com/kazuki43zoo/items/3d39543fd1234118f3bc)
* [mybatis-spring-boot-starter 2.0の変更点](https://qiita.com/kazuki43zoo/items/38a24e051359e39c012c)
* [mybatis-spring-boot-starter 1.3の変更点](http://qiita.com/kazuki43zoo/items/3f94d2049d331f81e6f5)
* [mybatis-spring-boot-starter 1.2の変更点](http://qiita.com/kazuki43zoo/items/84b4a89846a7d0d65922)
* [mybatis-spring-boot-starter 1.1の変更点](http://qiita.com/kazuki43zoo/items/5c46ac2f539d365aa53b)


# 動作検証バージョン

* MyBatis Spring Boot Starter 2.1.0
* MyBatis 3.5.2
* MyBatis Spring 2.0.2
* Spring Boot 2.1.6.RELEASE
* Spring Framework 5.1.8.RELEASE

# 検証コード

## Java版

* https://github.com/kazuki43zoo/qiita-materials/tree/master/mybatis-spring-boot/mybatis-demo

## Text Blocks版(JDK 13)

[JDK 13 Early-Access Builds](https://jdk.java.net/13/)を使用して、MapperメソッドにText Blocksを使ったデモアプリケーションをGitHubに公開しました。

* https://github.com/kazuki43zoo/mybatis-demo-with-jdk13

## Groovy版

2016/7/13に、MapperインタフェースをGroovyで作る方法（「[GroovyでMyBatisのMapperをつくる（とSQLの可読性がGood!!）](http://qiita.com/kazuki43zoo/items/0c6ac5acaee1201a433e)」）を投稿しました。

* https://github.com/kazuki43zoo/qiita-materials/tree/groovy-with-mybatis-spring-boot-2.0/mybatis-spring-boot/mybatis-demo

## Kotlin版

2016/8/3に、Kotlin上でMyBatis(mybatis-spring-boot-starter)を使う方法（「[Kotlinでmybatis-spring-boot-starterを使う](http://qiita.com/kazuki43zoo/items/9dfe35c47b08ce5f2ebb)」）を投稿しました。

* https://github.com/kazuki43zoo/qiita-materials/tree/master/mybatis-spring-boot/mybatis-kotlin-demo

# 純正の簡易サンプル

MyBatis Spring Bootから簡易的なサンプルがいくつか提供されているので、そちらも合わせて参照すると良いかと思います。

* https://github.com/mybatis/spring-boot-starter/tree/master/mybatis-spring-boot-samples

# 開発プロジェクトの作成

Spring Bootは、Spring-Boot向けの開発プロジェクトを生成するためのWeb Service「[SPRING INITIALIZR](http://start.spring.io/)」を提供しています。今回は、このWeb Serviceを利用して開発プロジェクトを作成します。
SPRING INITIALIZRはMavenとGradleプロジェクトをサポートしていますが、今回の記事ではMavneプロジェクトを作成します。ウィザードに以下の値を入力して「Generate Project」ボタンを押下すると、Mavenプロジェクトがダウンロードされます。

| 入力項目 | 値 | 備考 |
| --- | --- | --- | 
| Project | Maven Project | ※デフォルト |
| Language | Java | ※デフォルト |
| Spring Boot | 2.1.6 | ※デフォルト |
| Group | com.example | ※デフォルト |
| Artifact | mybatis-demo | ※デフォルト(demo)から変更 |
| Dependencies | MyBatis, H2 |  |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117313/8437cb50-6200-e535-ffce-cf1aae6d97e7.png)



ダウンロードしたZipファイルを解凍すると、以下のようなディレクトリ構成のプロジェクトが生成できます。

```text:ディレクトリ構成
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── mybatisdemo
    │   │               └── MybatisDemoApplication.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── com
                └── example
                    └── mybatisdemo
                        └── MybatisDemoApplicationTests.java

```


> **Note:**
>
> curlとtarコマンドが使える環境であれば、以下のコマンドを実行するだけで開発プロジェクトを作ることもできます。
>
> ```
> $ curl -s https://start.spring.io/starter.tgz\
    -d name=mybatis-demo\
    -d artifactId=mybatis-demo\
    -d dependencies=mybatis,h2\
    -d baseDir=mybatis-demo\
    | tar -xzvf -
> ```

作成した開発プロジェクトをSpring Bootアプリケーションとして起動してみます。

```text:コンソール
$ ./mvnw spring-boot:run
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.example:mybatis-demo >----------------------
[INFO] Building mybatis-demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> spring-boot-maven-plugin:2.1.6.RELEASE:run (default-cli) > test-compile @ mybatis-demo >>>
[INFO] 
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mybatis-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ mybatis-demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mybatis-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /private/tmp/mybatis-demo/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ mybatis-demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] <<< spring-boot-maven-plugin:2.1.6.RELEASE:run (default-cli) < test-compile @ mybatis-demo <<<
[INFO] 
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.1.6.RELEASE:run (default-cli) @ mybatis-demo ---

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

2019-07-15 17:25:33.712  INFO 55692 --- [           main] c.e.mybatisdemo.MybatisDemoApplication   : Starting MybatisDemoApplication on shimizukazutakanoMacBook-puro.local with PID 55692 (/private/tmp/mybatis-demo/target/classes started by shimizukazuki in /private/tmp/mybatis-demo)
2019-07-15 17:25:33.714  INFO 55692 --- [           main] c.e.mybatisdemo.MybatisDemoApplication   : No active profile set, falling back to default profiles: default
2019-07-15 17:25:34.049  WARN 55692 --- [           main] o.m.s.mapper.ClassPathMapperScanner      : No MyBatis mapper was found in '[com.example.mybatisdemo]' package. Please check your configuration.
2019-07-15 17:25:34.579  INFO 55692 --- [           main] c.e.mybatisdemo.MybatisDemoApplication   : Started MybatisDemoApplication in 1.121 seconds (JVM running for 4.19)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.064 s
[INFO] Finished at: 2019-07-15T17:25:34+09:00
[INFO] ------------------------------------------------------------------------
```

とりあえずエラーはでないことは確認できたので、先に進みます。

# mybatis-spring-boot-starterの確認

ウィザードで「MyBatis」を選択すると、Spring Bootのバージョンに対応するmybatis-spring-boot-starterが依存アーティファクトに追加されます。今回はSpring Boot 2.1.6を選択しているため、mybatis-spring-boot-starter 2.1.0が追加されます。（ちなみに・・・Spring Boot 1.5系を選ぶとmybatis-spring-boot-starter 1.3.4が追加されます）

```xml:pom.xml
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
```

mybatis-spring-boot-starter 2.1.0は、推移的に以下のアーティファクトも追加してくれます :v: 

```text:コンソール
$ ./mvnw dependency:tree
...
[INFO] +- org.mybatis.spring.boot:mybatis-spring-boot-starter:jar:2.1.0:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.1.6.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:2.1.6.RELEASE:compile
[INFO] |  |  |  \- org.springframework:spring-context:jar:5.1.8.RELEASE:compile
[INFO] |  |  |     +- org.springframework:spring-aop:jar:5.1.8.RELEASE:compile
[INFO] |  |  |     \- org.springframework:spring-expression:jar:5.1.8.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.6.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.6.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.2:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.11.2:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.26:compile
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.3.2:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.23:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.6.RELEASE:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:3.2.0:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:5.1.8.RELEASE:compile
[INFO] |  |     +- org.springframework:spring-beans:jar:5.1.8.RELEASE:compile
[INFO] |  |     \- org.springframework:spring-tx:jar:5.1.8.RELEASE:compile
[INFO] |  +- org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:jar:2.1.0:compile
[INFO] |  +- org.mybatis:mybatis:jar:3.5.2:compile
[INFO] |  \- org.mybatis:mybatis-spring:jar:2.0.2:compile
...
```

# ドメインオブジェクトの作成

今回は、ドメインオブジェクトとして`Todo`クラスを作ります。

```java:src/main/java/com/example/mybatisdemo/domain/Todo.java
package com.example.mybatisdemo.domain;

public class Todo {
    private int id;
    private String title;
    private String details;
    private boolean finished;
    // ... setter and getter
}
```

> **Note:**
> setterとgetterの生成はIDEの機能を使ってもよいですが、[Lombok](https://projectlombok.org/)を使う方が開発効率があがります！！

# Mapperインターフェースの作成

`Todo`へのCRUD操作を提供するMapperインターフェースを作ります。

```java:src/main/java/com/example/mybatisdemo/mapper/TodoMapper.java
package com.example.mybatisdemo.mapper;

import com.example.mybatisdemo.domain.Todo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface TodoMapper {

    @Insert("INSERT INTO todo (title, details, finished) VALUES (#{title}, #{details}, #{finished})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(Todo todo);

    @Select("SELECT id, title, details, finished FROM todo WHERE id = #{id}")
    Todo select(int id);

}
```

> **Note:**
> SQLをXMLファイルに書きたい場合は、以下のようなMapperインターフェースとXMLファイルを作成します。
>
> ```java:src/main/java/com/example/mybatisdemo/mapper/TodoMapper.java
> @Mapper
> public interface TodoMapper {
>     void insert(Todo todo);
>     Todo select(int id);
> }
> ```
> 
> ```xml:src/main/resources/com/example/mybatisdemo/mapper/TodoMapper.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.mybatisdemo.mapper.TodoMapper">
>     <insert id="insert" useGeneratedKeys="true" keyProperty="id">
>         INSERT INTO todo (title, details, finished) VALUES (#{title}, #{details}, #{finished})
>     </insert>
>     <select id="select" resultType="com.example.mybatisdemo.domain.Todo">
>         SELECT id, title, details, finished FROM todo WHERE id = #{id}
>     </select>
> </mapper>
> ```

# todoテーブルの作成

H2の組み込みデータベースにtodoテーブルを作成します。
Spring Bootの自動コンフィギュレーションで作成される`DataSource`を使う場合は、クラスパス直下に`schema.sql`と`data.sql`というファイルを配置しておくと、Spring Boot起動時にこれらのファイルを読み込んでSQLを実行してくれます。

```sql:src/main/resources/schema.sql
CREATE TABLE todo (
    id IDENTITY
    ,title TEXT NOT NULL
    ,details TEXT
    ,finished BOOLEAN NOT NULL
);
```


# MybatisDemoApplicationの修正とSpring Bootアプリケーションの起動

`MybatisDemoApplication`を修正し、Mapperインタフェースを経由してデータベースにアクセスします。

* 修正前

```java:src/main/java/com/example/mybatisdemo/MybatisDemoApplication.java
package com.example.mybatisdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MybatisDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisDemoApplication.class, args);
    }
}
```

* 修正後

```java:src/main/java/com/example/mybatisdemo/MybatisDemoApplication.java
package com.example.mybatisdemo;

import com.example.mybatisdemo.domain.Todo;
import com.example.mybatisdemo.mapper.TodoMapper;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.Transactional;

@SpringBootApplication
public class MybatisDemoApplication implements CommandLineRunner { // CommandLineRunnerを実装する

    public static void main(String[] args) {
        SpringApplication.run(MybatisDemoApplication.class, args);
    }

    private final TodoMapper todoMapper;

    public MybatisDemoApplication(TodoMapper todoMapper) {
        this.todoMapper = todoMapper; // Mapperをインジェクションする
    }

    // Spring Boot起動時にCommandLineRunner#runメソッドが呼び出される
    @Transactional
    @Override
    public void run(String... args) throws Exception { 
        Todo newTodo = new Todo();
        newTodo.setTitle("飲み会");
        newTodo.setDetails("銀座 19:00");

        todoMapper.insert(newTodo); // 新しいTodoをインサートする

        Todo loadedTodo = todoMapper.select(newTodo.getId()); // インサートしたTodoを取得して標準出力する
        System.out.println("ID       : " + loadedTodo.getId());
        System.out.println("TITLE    : " + loadedTodo.getTitle());
        System.out.println("DETAILS  : " + loadedTodo.getDetails());
        System.out.println("FINISHED : " + loadedTodo.isFinished());
    }

}
```

`MybatisDemoApplication`を修正したら、Spring Bootアプリケーションとして起動します。

```text:コンソール
$ ./mvnw spring-boot:run
...
[INFO] --- spring-boot-maven-plugin:2.1.6.RELEASE:run (default-cli) @ mybatis-demo ---

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

2019-07-15 17:35:04.734  INFO 55816 --- [           main] c.e.mybatisdemo.MybatisDemoApplication   : Starting MybatisDemoApplication on shimizukazutakanoMacBook-puro.local with PID 55816 (/private/tmp/mybatis-demo/target/classes started by shimizukazuki in /private/tmp/mybatis-demo)
2019-07-15 17:35:04.736  INFO 55816 --- [           main] c.e.mybatisdemo.MybatisDemoApplication   : No active profile set, falling back to default profiles: default
2019-07-15 17:35:05.561  INFO 55816 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2019-07-15 17:35:05.820  INFO 55816 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2019-07-15 17:35:06.176  INFO 55816 --- [           main] c.e.mybatisdemo.MybatisDemoApplication   : Started MybatisDemoApplication in 1.746 seconds (JVM running for 5.696)
ID       : 1
TITLE    : 飲み会
DETAILS  : 銀座 19:00
FINISHED : false
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.341 s
[INFO] Finished at: 2019-07-15T17:35:06+09:00
[INFO] ------------------------------------------------------------------------
2019-07-15 17:35:06.266  INFO 55816 --- [       Thread-3] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2019-07-15 17:35:06.270  INFO 55816 --- [       Thread-3] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
...
```

標準出力にインサートしたTodoの状態が出力されました :clap: :clap: :clap:

ちなみに、開発プロジェクトは以下のような状態になります。

```text:ディレクトリ構成

.
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── mybatisdemo
    │   │               ├── MybatisDemoApplication.java // 修正したファイル
    │   │               ├── domain
    │   │               │   └── Todo.java // 追加したファイル
    │   │               └── mapper
    │   │                   └── TodoMapper.java // 追加したファイル
    │   └── resources
    │       ├── application.properties
    │       ├── com
    │       │   └── example
    │       │       └── mybatisdemo
    │       │           └── mapper
    │       │               └── TodoMapper.xml // 追加したファイル(SQLをXMLに記述する場合のみ)
    │       └── schema.sql // 追加したファイル
    └── test
        └── java
            └── com
                └── example
                    └── mybatisdemo
                        └── MybatisDemoApplicationTests.java
```

# JUnit上で`MybatisDemoApplication`を起動

ダウンロードした開発プロジェクトには、JUnit用のテストケースクラス(`MybatisDemoApplicationTests`)が格納されています。

```text:コンソール
$ ./mvnw test
...
2019-07-15 17:36:55.348  INFO 55839 --- [           main] c.e.m.MybatisDemoApplicationTests        : Started MybatisDemoApplicationTests in 1.4 seconds (JVM running for 2.08)
ID       : 1
TITLE    : 飲み会
DETAILS  : 銀座 19:00
FINISHED : false
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.125 s - in com.example.mybatisdemo.MybatisDemoApplicationTests
2019-07-15 17:36:55.688  INFO 55839 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2019-07-15 17:36:55.690  INFO 55839 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.304 s
[INFO] Finished at: 2019-07-15T17:36:56+09:00
[INFO] ------------------------------------------------------------------------
```

テストは成功したみたいですが、ダウンロードした状態だとテスト結果をassertしていません。
せっかくなので、`MybatisDemoApplicationTests`を以下のように修正してテスト結果をassertしてみます。

```java:src/test/java/com/example/mybatisdemo/MybatisDemoApplicationTests.java
package com.example.mybatisdemo;

import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.test.context.junit4.SpringRunner;

import static org.hamcrest.Matchers.containsString;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisDemoApplicationTests {

    @ClassRule
    public static OutputCapture out = new OutputCapture(); // System.outの内容をキャプチャする

    @Test
    public void contextLoads() {
        // System.outした内容を検証する
        out.expect(containsString("ID       : 1"));
        out.expect(containsString("TITLE    : 飲み会"));
        out.expect(containsString("DETAILS  : 銀座 19:00"));
        out.expect(containsString("FINISHED : false"));
    }

}
```

再度テストを実行すると、標準出力した内容が正しいことが検証できました :clap: :clap: :clap:

# Mapperインタフェースのスキャンの仕組み

なにやらMyBatisを使ってデータベースにアクセスすることはできましたが、どうやってMapperインタフェースがスキャンされたのでしょうか？？
mybatis-springの機能だけだと、`@MapperScan`を使ってスキャン対象のベースパッケージを指定する必要がありました。しかし、mybatis-spring-boot-starterを使うとSpring Bootアプリケーション(`MybatisDemoApplication`)が格納されているパッケージ(`com.example`)配下に格納されているインターフェースのうち、`@org.apache.ibatis.annotations.Mapper`(mybatis 3.4で追加されたアノテーション)が付与されたインタフェースがMapperインターフェースとしてスキャンされます。

> **Note:**
> mybatis-spring-boot-starter 1.0系では、Spring Bootアプリケーション(`MybatisDemoApplication`)が格納されているパッケージ(`com.example`)配下に格納されているインタフェースが全てMapperインタフェースとしてスキャンされていました。
この仕組みは便利な反面で、意図していないインターフェースがMapperインタフェースとしてスキャンされてDIコンテナに登録されてしまうという問題がありました。 `@Mapper`はこの問題を解決する手段として追加されたマーカーアノテーションです。

Mapperインタフェースに`@Mapper`を付与しない場合は、`@MapperScan`を使ってスキャン対象のベースパッケージを明示的に指定しましょう。

```java:src/main/java/com/example/mybatisdemo/MybatisDemoApplication.java
@MapperScan("com.example.mybatisdemo.mapper") // スキャンするベースパッケージを明示的に指定する
@SpringBootApplication
public class MybatisDemoApplication {
    // ...
}
```


# タイプエイリアスの利用

SQLをXMLに記述する際に、`parameterType`や`resultType`属性にクラスのFQCN(Fully Qualified Class Name)ではなく、シンプルなクラス名(エイリアス名)を指定したい場合があると思います。
そのような場合は、`applicaion.properties`に以下の定義を追加するだけです。

```properties:src/main/resources/applicaion.properties
mybatis.type-aliases-package=com.example.mybatisdemo.domain
```
カンマ区切りで複数のベースパッケージを指定することができます。2.0.1よりワイルドカード(例：`com.example.**.domain`)が指定できるようになっています(mybatis-spring 2.0.1+で利用可能だが**2.0.2+の利用を推奨**)。

> ~~**Important:**~~
> ~~2.0.1で`mybatis.type-aliases-package`を使用すると、特定の条件をみたす時にエラーが発生する不具合が報告されています。不具合の詳細については、[mybatis-spring-boot-starter 2.0の変更点](https://qiita.com/kazuki43zoo/items/38a24e051359e39c012c#%E3%83%99%E3%83%BC%E3%82%B9%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E6%8C%87%E5%AE%9A%E6%99%82%E3%81%AE%E3%83%AF%E3%82%A4%E3%83%AB%E3%83%89%E3%82%AB%E3%83%BC%E3%83%89%E3%82%92%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88)を参照してください。~~
>
> 2019/7/15 追記
> 2.1.0で解決済みです。

2.0.0よりタイプエイリアスを登録するクラスであることを示す親クラスを指定するためのプロパティが追加されています。これはmybatis-springでもともとサポートされていた仕組みを、Spring Bootのコンフィギュレーションプロパティで指定できるように改善したものです。

```properties:src/main/resources/applicaion.properties
mybatis.type-aliases-super-type=com.example.mybatisdemo.TypeAliasTarget
```

# タイプハンドラーの適用

MyBatisがデフォルトで用意していない型のタイプハンドラーを追加したい場合や、デフォルトで適用されるタイプハンドラーを上書きしたい場合があると思います。
そのような場合は、`applicaion.properties`に以下の定義を追加するだけです。

```properties:src/main/resources/applicaion.properties
mybatis.type-handlers-package=com.example.mybatisdemo.typehandler
```
カンマ区切りで複数のベースパッケージを指定することができます。2.0.1よりワイルドカード(例：`com.example.**.typehandler`)が指定できるようになっています(mybatis-spring 2.0.1+で利用可能だが**2.0.2+の利用を推奨**)。

バージョン2.1.0より、DIコンテナに登録した`TypeHandler`を検出してMyBatisに適用することができます。

```java:src/main/java/com/example/mybatisdemo/MybatisDemoApplication.java
@SpringBootApplication
public class MybatisDemoApplication {
    @Bean
    MyTypeHandler myTypeHandler(){
        return new MyTypeHandler();
    }
}
```

複数の`TypeHandler`をMyBatisに適用したい場合は、それぞれBean定義するだけです。


# SqlSessionの実行モードの変更

デフォルトの実行モードはMyBatis設定の設定値（デフォルトはSIMPLEモード）ですが、アプリケーションの種類や要件によっては実行モードを変更したい場合があると思います。
そのような場合は、`applicaion.properties`に以下の定義を追加するだけです。もちろんMyBatis設定の設定値を変更してもOKです。

```properties:src/main/resources/applicaion.properties
mybatis.executor-type=BATCH
```

# Mapper XMLファイルを明示的に読み込む

Mapperインタフェースを経由しないで直接`SqlSession`(`SqlSessionTemplate`)を使ってSQLを実行する場合は、SQLが定義されているMapper XMLファイルを明示的に読み込む必要があります。
そのような場合は、`applicaion.properties`に以下の定義を追加するだけです。

```properties:src/main/resources/applicaion.properties
mybatis.mapper-locations=classpath*:/mybatis/sqls/**/*.xml
```

カンマ区切りで複数のロケーションを指定することもできますし、

```properties:src/main/resources/applicaion.properties
mybatis.mapper-locations[0]=classpath*:/mybatis/sqls/aaa/**/*.xml
mybatis.mapper-locations[1]=classpath*:/mybatis/sqls/bbb/**/*.xml
```

という感じで指定することもできます。

また、propertiesファイルではなくyamlファイルを使用すると、

```yaml:src/main/resources/applicaion.yml
mybatis:
    mapper-locations:
        - classpath*:/mybatis/sqls/aaa/**/*.xml
        - classpath*:/mybatis/sqls/bbb/**/*.xml
```

という記述にすることもできます。

# MyBatisの設定

バージョン1.0系では、MyBatis自体の動作をカスタマイズしたい場合は、MyBatis設定ファイルの`<settings>`タグ内に設定を追加する必要がありましたが、バージョン1.1系から`applicaion.properties`に直接設定できるようになりました。また、バージョン1.3(1.2.1にもバックポート済み)から`ConfigurationCustomizer`インタフェースが追加され、MyBatisの設定を完全にカスタマイズすることが可能になっています。

## MyBatis設定ファイル使用時の設定例

```properties:src/main/resources/applicaion.properties
mybatis.config-location=classpath:/mybatis/mybatis-config.xml
```

```xml:src/main/resources/mybatis/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>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true" />
        <setting name="defaultFetchSize" value="100" />
        <setting name="defaultStatementTimeout" value="30" />
    </settings>
</configuration>
```

もちろん`<settings>`以外のタグも利用できます。
ちなみに・・・MyBatis設定ファイルが存在するかチェックを行う仕組みも提供されており、デフォルトでは無効になっています。この仕組みを有効にしたい場合は、以下の定義を追加してください。

```properties:src/main/resources/applicaion.properties
mybatis.check-config-location=true
```

## `applicaion.properties`使用時の設定例

MyBatis設定ファイルを使わずに`applicaion.properties`だけで設定することもできます。

```properties:src/main/resources/applicaion.properties
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-fetch-size=100
mybatis.configuration.default-statement-timeout=30
```
なお、`mybatis.configuration.xxxx`と`mybatis.config-location`を同時に指定することはできません。
指定するとエラーになります。


> **Note:**
> ~~バージョン1.1.1までは、IDE上で`mybatis.configuration`配下のプロパティが補完されませんでしたが、バージョン1.2で解決されています。（ただ・・・同じプロパティキーが重複してるな・・・・）~~
>
> ~~2017/1/8 追記~~
> ~~入力補完時にプロパティキーが重複しているのは・・・maven-bundle-plugin 2.5.4のバグ？が原因っぽく、maven-bundle-plugin 3.0.0+を使ってビルドしたら解決できました。これは、[mybatis-spring-boot-starter#gh-127](https://github.com/mybatis/spring-boot-starter/issues/127)で対応予定でバージョン1.2.1で直ると思います。~~
>
> 2017/4/10 追記
> 入力補完時にプロパティキーが重複する問題は、バージョン1.3(1.2.1にもバックポート済み)で解決済みです。

## `ConfigurationCustomizer`を使用したカスタマイズ例

バージョン1.3.0(1.2.1にもバックポート済み）より、MyBatisの設定情報を保持するBean(`org.apache.ibatis.session.Configuration`のBean)をJavaコードでカスタマイズするためのコールバックインタフェース(`org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer`)が追加されました。以下のように、このインタフェースを実装したクラスをBean定義しておくと、`ConfigurationCustomizer`の`configure`メソッドがコールバックされる仕組みになっており、Javaコードを使用してMyBayisの設定を完全にカスタマイズすることができます。

```java:ラムダ式を使用したConfigurationCustomizerのBean定義例
@Configuration
public class MyBatisConfiguration {
	@Bean
	ConfigurationCustomizer mybatisConfigurationCustomizer() {
		return (configuration) -> {
			// aplication.propertiesで指定・表現できないカスタマイズコードを実装
			configuration.getTypeHandlerRegistry().register(RoundingMode.class, EnumOrdinalTypeHandler.class);
		};
	}
}
```

# 外部定義したプロパティ値の適用

MyBatisでは、外部定義したプロパティ値をMyBatis設定ファイルやMapperファイルの中に埋め込むことができ、外部定義はMyBatis設定ファイルの`<properties>`要素を使って指定します。（プログラムで直接指定することもできますが、本投稿では扱いません）

* http://www.mybatis.org/mybatis-3/ja/configuration.html#properties

さらに、MyBatis-Springを使う場合はMyBatis設定ファイルの`<properties>`要素に加えて、`SqlSessionFactoryBean`の`configurationProperties`プロパティ(`java.util.Properties`型)に外部定義を指定することができ、バージョン1.2より、`application.properties`(or `application.yml`)を使って指定できるようになりました。

例えば、MyBatis設定ファイルで以下のように定義していた部分は・・・

```xml:MyBatis設定ファイル（抜粋）
<properties>
  <property name="key" value="value"/>
</properties>
```

以下のように、`application.properties`(or `application.yml`)の中で指定するように書き換えることができます。

```properties:src/main/resources/application.properties
mybatis.configuration-properties.key=value
```

> **Note:**
> 実は・・・バージョン1.1.1でも、以下のように定義することで同じことが実現できました。この方法はバージョン1.2以降でも使うことができ、同じキー名がある場合はバージョン1.2で追加された`mybatis.configuration-properties`で指定した値が優先されます。
>
> ```properties:src/main/resources/application.properties
> mybatis.configuration.variables.key=value
> ```
>
> たとえば・・・`mybatis.configuration.variables`には環境依存しない値（or デフォルト値）を定義しておき、プロファイル毎（環境毎）のプロパティファイルで`mybatis.configuration-properties`を使用して環境依存値に上書きするといった使い方もできます。

# Plugin(Interceptor)の適用

MyBatisは、MyBatisのコアコンポーネントが行う処理に割り込む仕組み(`Interceptor`)を提供しています。
この仕組みを利用することはあまりないと思いますが、MyBatisに`Interceptor`を適用したい場合は、以下のようなBean定義を追加するだけです。

```java:src/main/java/com/example/mybatisdemo/MybatisDemoApplication.java
@SpringBootApplication
public class MybatisDemoApplication {
    @Bean
    Interceptor myInterceptor(){
        return new MyInterceptor();
    }
    // ...
}
```

複数の`Intereptor`をMyBatisに適用したい場合は、それぞれBean定義するだけです。

# `DatabaseIdProvider`の適用

MyBatisは、接続中のデータベースを識別するための仕組み(`DatabaseIdProvider`)を提供しています。
この仕組みを利用することはあまりないと思いますが、MyBatisに`DatabaseIdProvider`を適用したい場合は、以下のようなBean定義を追加するだけです。

```java:src/main/java/com/example/mybatisdemo/MybatisDemoApplication.java
@SpringBootApplication
public class MybatisDemoApplication　{
    @Bean
    VendorDatabaseIdProvider vendorDatabaseIdProvider() {
        VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
        Properties vendorProperties = new Properties();
        vendorProperties.put("PostgreSQL", "postgresql");
        vendorProperties.put("H2", "h2");
        databaseIdProvider.setProperties(vendorProperties);
        return databaseIdProvider;
    }
    // ...
}
```

# `LanguageDriver`の適用

MyBatisは、動的SQLを組み立てるため言語を変更（カスタマイズ）するための仕組み(`LanguageDriver`)を提供しています。
デフォルトではMyBatisに組み込まれているXMLベースの言語(`XMLLanguageDriver`)が適用されますが、デフォルトの動作を変更したい場合は、開発者が独自に作成した `LanguageDriver` クラスは、以下のようなBean定義を追加するだけです（バージョン2.1より利用可能）。

```java:独自のLanguageDriverのBean定義例
@Bean
MyLanguageDriver myLanguageDriver() {
  return MyLanguageDriver();
}
```

さらに、DIコンテナから検出した `LanguageDriver` のBeanが１つの場合は、検出した `LanguageDriver` がデフォルトの `LanguageDriver` クラスとして扱われます（複数のBeanを検出した場合は、MyBatis本体のデフォルト設定である `XMLLanguageDriver` がそのまま適用されます）。自動検出機能によってデフォルトの `LanguageDriver` クラスを変更したくない場合や、複数の `LanguageDriver` を共存して利用する際にデフォルトの `LanguageDriver` クラスを変更したい場合は、 次に紹介する `mybatis.default-scripting-language-driver` プロパティ(バージョン2.1で追加したプロパティ)を使用して、デフォルトの `LanguageDriver` クラスを明示的に指定するようにしてください。

# デフォルトで使用する`LanguageDriver`の指定

デフォルトで使用する`LanguageDriver`を明示的に指定する場合は、以下のように設定するだけです。

```properties
mybatis.default-scripting-language-driver={デフォルトのLanguageDriverクラスのFQCN}
```
このプロパティは、バージョン2.1より利用可能です。なお、バージョン2.0までは、デフォルトで使う `LanguageDriver` クラスを指定する方法として、 `mybatis.configuration.default-scripting-language` というプロパティ(MyBatis本体が提供するコンフィギュレーションクラスへ直接値を設定するためのプロパティ)をサポートしていましたが、後述の「`LanguageDriver`の自動コンフィグレーション」と組み合わせた時に期待通りの動作にならない事があるため、このプロパティはバージョン2.1で廃止（使用禁止）になっています。

# `LanguageDriver`の自動コンフィグレーション

バージョン2.1より、MyBatis純正の以下の３つのサブモジュール（`LanguageDriver`）については、自動コンフィギュレーションの仕組みがサポートされています。

* [mybatis-velocity](https://github.com/mybatis/velocity-scripting)
* [mybatis-freemarker](https://github.com/mybatis/freemarker-scripting)
* [mybatis-thymeleaf](https://github.com/mybatis/thymeleaf-scripting)

上記３つのサブモジュールのjarファイルをクラスパス上に追加すると、自動でサブモジュール提供の `LanguageDriver` クラスのBeanがDIコンテナに登録されてMyBatisへ適用されます。なお、開発者が明示的に上記サブモジュールから提供されている`LanguageDriver`クラスをBean定義した場合は、自動コンフィギュレーションは行われません。

> **WARNING:**
>
> 以下の条件に全て一致する場合は、下位互換性が失われるため、追加で対策が必要になります。
> 
> * 上記３つのサブモジュール(mybatis-velocity, mybatis-freemarker, mybatis-thymeleaf)のjarが１つ**のみ**クラスパス上にある
> * デフォルトの `LanguageDriver` がサブモジュール提供の`LanguageDriver`では**ない**
>
> 条件に一致する場合は、サブモジュール提供の`LanguageDriver`クラスがデフォルトの `LanguageDriver` に設定される仕組みになっているため、以下のように明示的にデフォルトの `LanguageDriver` を指定する必要があります。
>
> ```properties
> mybatis.default-scripting-language-driver={デフォルトのLanguageDriverクラスのFQCN}
> ```

### MyBatis Velocityの利用

以下のアーティファクトを追加すると、`org.mybatis.scripting.velocity.VelocityLanguageDriver`(mybatis-velocity 2.0以前を指定すると`org.mybatis.scripting.velocity.Driver`)をMyBatisへ適用します。

```xml:pom.xml
<!-- Mybatis Velocityを利用する場合 -->
<dependency>
  <groupId>org.mybatis.scripting</groupId>
  <artifactId>mybatis-velocity</artifactId>
  <version>2.1.0</version>
</dependency>
```

mybatis-velocity 2.1.0以降を利用する場合は、コンフィギュレーションプロパティの仕組みを利用してMyBatis VelocityおよびMyBatis Velocityの中で利用するVelocityのテンプレートエンジンの動作をカスタマイズすることができます。

```properties:src/main/resources/application.properties
#
# mybatis.scripting-language-driver.velocity.{key} = {value} 形式
#

# Velocity本体の動作をカスタマイズするプロパティ
# mybatis.scripting-language-driver.velocity.velocity-settings.{name} = {value}形式
# {name}に設定可能なプロパティはリファレンスドキュメントを参照してください。
mybatis.scripting-language-driver.velocity.velocity-settings.runtime.custom_directives = com.example.directives.MyDirective

# テンプレートエンジンに渡す追加属性(オブジェクト)を指定するプロパティ
# mybatis.scripting-language-driver.velocity.additional-context-attributes.{name} = {value}
mybatis.scripting-language-driver.velocity.additional-context-attributes.likeEscape = com.example.helpers.LikeEscape
```

### MyBatis FreeMarkerの利用

以下のアーティファクトを追加すると、`org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver`をMyBatisへ適用します。

```xml:pom.xml
<dependency>
  <groupId>org.mybatis.scripting</groupId>
  <artifactId>mybatis-freemarker</artifactId>
  <version>1.2.0</version>
</dependency>
```

mybatis-velocity 1.2.0以降を利用する場合は、コンフィギュレーションプロパティの仕組みを利用してMyBatis FreeMarkerおよびMyBatis FreeMarkerの中で利用するFreeMarkerのテンプレートエンジンの動作をカスタマイズすることができます。

```properties:src/main/resources/application.properties
#
# mybatis.scripting-language-driver.freemarker.{key} = {value} 形式
#

# FreeMarker本体の動作をカスタマイズするプロパティ
# mybatis.scripting-language-driver.freemarker.freemarker-settings.{name} = {value}形式
# {name}に設定可能なプロパティはリファレンスドキュメントを参照してください。
mybatis.scripting-language-driver.freemarker.freemarker-settings.interpolation_syntax = dollar

# MyBatis FreeMarkerの動作をカスタマイズするプロパティ
# mybatis.scripting-language-driver.freemarker.{name} = {value}
# {name}に設定可能なプロパティはリファレンスドキュメントを参照してください。
mybatis.scripting-language-driver.freemarker.template-file.base-dir = sql
```


### MyBatis Thymeleafの利用

以下のアーティファクトを追加すると、`org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver`をMyBatisへ適用します。

```xml:pom.xml
<dependency>
  <groupId>org.mybatis.scripting</groupId>
  <artifactId>mybatis-thymeleaf</artifactId>
  <version>1.0.1</version>
</dependency>
```

コンフィギュレーションプロパティの仕組みを利用してMyBatis ThymeleafおよびMyBatis Thymeleafの中で利用するThymeleafのテンプレートエンジンの動作をカスタマイズすることができます。

```properties:src/main/resources/application.properties
#
# mybatis.scripting-language-driver.thymeleaf.{key} = {value} 形式
#

# MyBatis Thymeleafの動作をカスタマイズするプロパティ
# mybatis.scripting-language-driver.thymeleaf.{name} = {value}
# {name}に設定可能なプロパティはリファレンスドキュメントを参照してください。
mybatis.scripting-language-driver.thymeleaf.use2way = false
mybatis.scripting-language-driver.thymeleaf.template-file.cache-enabled = false
mybatis.scripting-language-driver.thymeleaf.dialect.like-additional-escape-target-chars = ％, ＿
```

# Mapperの遅延初期化制御

バージョン2.1より、DIコンテナに登録したMapperの初期化タイミング（DIコンテナ初期化時、インジェクションまたは利用時）を制御することができます。デフォルトの動作は、DIコンテナ初期化時に全てのMapperが初期化されます。

これは、Spring Boot 2.2(投稿時点では正式版は未リリース)でサポートされるBeanの遅延初期化の仕組みを有効化した際に、特定の条件下においてMapprのメソッド呼び出し時にエラーが発生してしまうため、Mapperに対する遅延初期化の適用有無を利用者側で制御できるようにするために追加した仕組みです。エラーを回避するだけなら、Mapperに対する遅延初期化を無条件で無効化すればよいのですが、開発時（テスト時）には遅延初期化の仕組みは有効だと思うので、MyBatis(mybatis-spring & mybatis-spring-boot-starter)独自にMapperを遅延初期化を制御できるようにしました。詳しくは、「[mybatis-spring-boot 2.1.0で追加されるmybatis.lazy-initializationについて](https://qiita.com/kazuki43zoo/items/3d131abffc1f37dc9352)」をご覧ください。

> **NOTE:**
>
> バージョン2.1より提供する遅延初期化の仕組みはMyBatis(mybatis-spring & mybatis-spring-boot-starter)独自の仕組みなので、Spring Boot 2.1系でも利用することができます。


# `@MybatisTest`(mybatis-spring-boot-starter-test)の利用

バージョン1.3より、MyBatisの機能をテストする時に必要となるBean定義をサポートする`@MybatisTest`アノテーションが追加されました。これは、Spring Bootが提供する[`@DataJpaTest`](http://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test)や[`@JdbcTest`](http://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jdbc-test)のMyBatis版になります（当然ながら・・使い方もSpring Boot提供のアノテーションと同じです！！）。
この対応に伴い、MyBatisのテストをする際に必要となるライブラリ群を解決するための`mybatis-spring-boot-starter-test`が新設されているので、MyBatis提供の機能に対するテストを行う場合は、`pom.xml`に以下の定義を追加してください。

```xml:pom.xml
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter-test</artifactId>
	<version>2.1.0</version>
	<scope>test</scope>
</dependency>
```

たとえば、以下のようなMapperインタフェースに対するテストを行う場合は、

```java:src/main/java/com/example/mybatisdemo/mapper/TodoMapper.java
package com.example.mybatisdemo.mapper;

import com.example.mybatisdemo.domain.Todo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface TodoMapper {

    @Insert("INSERT INTO todo (title, details, finished) VALUES (#{title}, #{details}, #{finished})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(Todo todo);

    @Select("SELECT id, title, details, finished FROM todo WHERE id = #{id}")
    Todo select(int id);

}
```

`@MybatisTest`を使用して、以下のようなテストケースクラスを作ればOKです。

```java:src/main/java/com/example/mybatisdemo/mapper/TodoMapperTests.java
package com.example.mybatisdemo.mapper;

import com.example.mybatisdemo.domain.Todo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@MybatisTest // (1)
public class TodoMapperTests {

    @Autowired
    private TodoMapper todoMapper; // (2)

    @Autowired
    private NamedParameterJdbcOperations jdbcOperations; // (3)

    @Test
    public void insert() {

        // setup
        // none

        // perform test and assertions
        {
            // (4)
            Todo newTodo = new Todo();
            newTodo.setTitle("飲み会");
            newTodo.setDetails("銀座 19:00");
            todoMapper.insert(newTodo);

            // (5)
            Todo actualTodo =
                jdbcOperations.queryForObject("SELECT * FROM todo WHERE id = :id",
                    new MapSqlParameterSource("id", newTodo.getId()),
                    new BeanPropertyRowMapper<>(Todo.class));
            assertThat(actualTodo.getId()).isEqualTo(newTodo.getId());
            assertThat(actualTodo.getTitle()).isEqualTo("飲み会");
            assertThat(actualTodo.getDetails()).isEqualTo("銀座 19:00");
            assertThat(actualTodo.isFinished()).isEqualTo(false);
        }
    }


    @Test
    public void select() {

        // (6)
        // setup
        Todo newTodo = new Todo();
        newTodo.setTitle("飲み会");
        newTodo.setDetails("銀座 19:00");

        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcOperations.update(
            "INSERT INTO todo (title, details, finished) VALUES(:title, :details, :finished)",
            new BeanPropertySqlParameterSource(newTodo), keyHolder);

        // perform test and assertions
        {
            // (7)
            Todo actualTodo = todoMapper.select(keyHolder.getKey().intValue());

            assertThat(actualTodo.getId()).isEqualTo(keyHolder.getKey().intValue());
            assertThat(actualTodo.getTitle()).isEqualTo("飲み会");
            assertThat(actualTodo.getDetails()).isEqualTo("銀座 19:00");
            assertThat(actualTodo.isFinished()).isEqualTo(false);
        }
    }

}
```

| 項番 | 説明 |
| --- | ---- |
| (1) | クラスに`@MybatisTest`を付与する。このアノテーションを付与することで、MyBatisを動かすために必要になるAutoConfigureクラスだけが有効になります。デフォルトの動作ではコンポーネントスキャンが無効化されているので、テスト時に必要ないBeanが無題にDIコンテナに登録されることを防ぐことができます。 | 
| (2) | テスト対象のMapperをインジェクションする。`@MybatisTest`を付与すると、MyBatis提供のAutoConfigureによってMapperのBeanが生成されます。 | 
| (3) | 登録データの検証およびテストデータを登録するために、`NamedParameterJdbcTemplate` のBeanをインジェクションする。`@MybatisTest`を付与すると、`JdbcTemplate`と`NamedParameterJdbcTemplate`のBeanがDIコンテナに登録されます。 | 
| (4) | テスト対象の`insert`メソッドを呼び出す。 | 
| (5) | `insert`メソッドの呼び出し結果を検証する。ここでは、`NamedParameterJdbcOperations`のメソッドを介して登録したデータを取得し、登録したデータの妥当性を検証しています。 | 
| (6) | `select`メソッドのテストを行うためにテストデータを登録する。ここでは、`NamedParameterJdbcOperations`のメソッドを介してテストデータを登録しています。なお、Spring Framework提供の`@Sql`アノテーションなどを使用してテストデータを登録する方法もあります。 | 
| (7) | テスト対象の`select`メソッドを呼び出す。 |
| (8) | `select`メソッドの呼び出し結果を検証する。 | 

この状態でテストを実行すると、上位パッケージに存在するSpring Bootアプリケーションクラス(`@SpringBootApplication`を付与したクラス)内で定義しているBean定義も読み込まれる仕組みになっているため、MyBatisのテストに必要ないBeanがDIコンテナに登録される可能性があります。
もし、MyBatisのテストに必要ないBeanをDIコンテナに登録したくない場合は、テストケースクラスと同じパッケージに以下のような空のSpring Bootアプリケーションクラスを作成してください。こうすることで、上位パッケージにあるSpring Bootアプリケーションクラスを無効化することができます。

```java:src/test/java/com/example/mybatisdemo/mapper/MapperTestApplication.java
package com.example.mybatisdemo.mapper;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MapperTestApplication {

}
```

さらに詳しい情報は、[公式リファレンス](http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-test-autoconfigure/)およびSpring Bootが提供する[`@DataJpaTest`](http://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test)や[`@JdbcTest`](http://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jdbc-test)のリファレンスをご覧ください。


# まとめ

今回は、仕事でよく使うMyBatisをSpring Bootで動かす時に使うと便利なmybatis-spring-boot-starterを紹介しました。MyBatisのデフォルトの動作で問題なければ、特別な設定はいっさい不要です。また、バージョン1.3よりテストをサポートする新機能(`@MybatisTest`)が追加されており、MapperやDAOのテストを行う際に必要となるBean定義を（いろんな意味で）効率的に行うことができます。

# 参考サイト
* https://github.com/mybatis/spring-boot-starter
* http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
* http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-test-autoconfigure/
* http://www.mybatis.org/mybatis-3/ja/
* http://terasolunaorg.github.io/guideline/5.5.1.RELEASE/ja/ArchitectureInDetail/DataAccessDetail/DataAccessMyBatis3.html
* http://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test
* http://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jdbc-test