MyBatis 3.4で追加されたCursorの使い方

今回はMyBaitsのCursor機能の使い方について紹介します。
Cursor機能はMyBatis 3.4から追加された新機能で、大量データを効率的＋直感的にJavaBeanにマッピングするために追加されました。

# 動作検証バージョン

* MyBatis 3.4.0
* MyBatis Spring 1.3.0
* Spring Framework 4.2.5.RELEASE
* Spring Boot 1.3.3.RELEASE

# 3.3.xまでの実装方法のおさらい

MyBatis 3.3までは、大量データを扱う時は`org.apache.ibatis.session.ResultHandler`を使っていました。

以下は、`ResultHandler`を使った実装例です。

```java:src/main/java/com/example/mapper/TodoMapper.java
public interface TodoMapper {
    @Select("SELECT id, title, details, finished FROM todo ORDER BY id")
    @ResultType(Todo.class)
    void collect(ResultHandler<Todo> handler);
}
```

Java SE 8でサポートされた関数型インタフェース + ラムダ式を活用することでシンプルに利用できますが、Java SE 7ユーザーにとっては多少煩雑な書き方が求められます。

* Java SE 8での利用例

```java
todoMapper.collect(context -> {
    Todo todo = context.getResultObject();
    System.out.println("ID       : " + todo.getId());
    System.out.println("TITLE    : " + todo.getTitle());
    System.out.println("DETAILS  : " + todo.getDetails());
    System.out.println("FINISHED : " + todo.isFinished());
});
```

* Java SE 7での利用例 (ちょっと煩雑・・・)

```java
todoMapper.collect(new ResultHandler<Todo>() {
    @Override
    public void handleResult(ResultContext<? extends Todo> context) {
        Todo todo = context.getResultObject();
        System.out.println("ID       : " + todo.getId());
        System.out.println("TITLE    : " + todo.getTitle());
        System.out.println("DETAILS  : " + todo.getDetails());
        System.out.println("FINISHED : " + todo.isFinished());
    }
});
```

なお、MyBatisをてっとり早く試したい方は、MyBatis-Spring-Bootを使うことをお勧めします！！
MyBatis-Spring-Bootの使い方については、[こちらの記事](http://qiita.com/kazuki43zoo/items/ea79e206d7c2e990e478)をご覧ください。本記事のサンプルコードもMyBatis-Spring-Bootを使って動作検証を行っています。


# MyBatisのCursor機能の利用

MyBatisのCursor機能を使ってみましょう。

> **Warning: Cursor機能の制約事項(バグ)について**
> 3.4.0には以下のバグがありましたが、これらのバグはMyBatis 3.4.1で解消されました！！
>
> * `@Select`などのアノテーションを使ってSQLを指定する仕組みと一緒に利用できません。 https://github.com/mybatis/mybatis-3/issues/661
> * `RowBounds`を使用した範囲検索がバグで動作しないため利用できません。 https://github.com/mybatis/mybatis-3/issues/660

Cursor機能を使う場合は、Mapperメソッドの返り値を`org.apache.ibatis.cursor.Cursor`にするだけです。

```java:src/main/java/com/example/mapper/TodoMapper.java
@Mapper
public interface TodoMapper {
    Cursor<Todo> selectCursor();
}
```

```xml:src/main/resource/com/example/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.mapper.TodoMapper">
    <select id="selectCursor" resultType="com.example.domain.Todo">
        SELECT id, title, details, finished FROM todo ORDER BY id
    </select>
</mapper>
```

MyBatis 3.4.1がリリースされたため、XMLファイルだけではなく、以下のようにアノテーションにもSQLを指定することができるようになりました :smile: 

```java:src/main/java/com/example/mapper/TodoMapper.java
@Mapper
public interface TodoMapper {
    @Select("SELECT id, title, details, finished FROM todo ORDER BY id")
    Cursor<Todo> selectCursor();
}
```

`Cursor`インタフェースは`java.io.Closable`(`java.lang.AutoCloseable`)と`java.util.Iterable`を継承しているため、以下のように利用することができます。

* `Cursor`の利用例

```java
try (Cursor<Todo> todos = todoMapper.selectCursor()) { // try-with-resources文でCursorを確実にクローズする
    for (Todo todo : todos) { // 拡張for文でカーソルをフェッチする
        System.out.println("ID       : " + todo.getId());
        System.out.println("TITLE    : " + todo.getTitle());
        System.out.println("DETAILS  : " + todo.getDetails());
        System.out.println("FINISHED : " + todo.isFinished());
    }
}
```

> **Note: Cursorのクローズについて**
> カーソル上にあるすべてのデータを読み込んだタイミングでMyBatis側が`Cursor`をクローズしてくれますが、途中でエラーが発生した場合はクローズの保証はありません。そのため、`Cursor`を利用する側が`Cursor`のクローズを保証する必要があります。

# RowBoundsを利用したCursorの範囲検索を利用する際の注意点

MyBatis 3.4.0ではバグで範囲検索を利用することができませんが、この問題はMyBatis 3.4.1で解消されました。
バグが修正されても、実は`Cursor`の範囲検索には潜在的な問題があります。
潜在的な問題とは、取得開始位置に移動（スキップ）する仕組みにあります。
現在の実装では、開始位置までの不要データの読み込み＋読み込んだデータをJavaBeanへのマッピングする処理も実行されるため、効率的なスキップ処理とは言えません。これは、1:1や1:Nの関係をもつネストしたプロパティへのマッピングをサポートした上でのスキップ処理を実現しているためです。

結論としては・・・・

* `Cursor`の母体がたいした件数にならない(スキップ処理にかかる処理コストが問題にならない)なら、`RowBounds`を使ってもOK
* 0件目からN件目までの`Cursor`を取得したい場合は `RowBounds`を使ってもOK
* N件目からY件目までの`Cursor`を取得したい場合は、SQLでN件目までのデータを取得しないようにした方がよい (Y件目の制御はSQLでもいいし、`RowBounds`どちらでもいい)

かな〜。

# MyBatis-Springでのサポート

MyBatis 1.3からCursor機能がサポートされており、Spring Batch用に`org.mybatis.spring.batch.MyBatisCursorItemReader`というクラスも追加されています。

# MyBatis-Spring-Bootでのサポート

MyBatis-Spring-Bootの1.1系(投稿時点での最新バージョン)では、MyBatis 3.4 + MyBatis-Spring 1.3がサポートされているため、Cursor機能も利用できます。

# まとめ
個人的にはいい感じのインターフェースだな〜と思っています :smile: が、
MyBatis 3.4.0だとバグに伴う制約があるので、実際に使うのはMyBatis 3.4.1のリリースを待ってからの方が無難な気がします。
↓
2016/6/26に3.4.1がリリースされました！！