今回は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
を使った実装例です。
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での利用例
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での利用例 (ちょっと煩雑・・・)
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の使い方については、こちらの記事をご覧ください。本記事のサンプルコードも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/661RowBounds
を使用した範囲検索がバグで動作しないため利用できません。 https://github.com/mybatis/mybatis-3/issues/660
Cursor機能を使う場合は、Mapperメソッドの返り値をorg.apache.ibatis.cursor.Cursor
にするだけです。
@Mapper
public interface TodoMapper {
Cursor<Todo> selectCursor();
}
<?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を指定することができるようになりました
@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
の利用例
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機能も利用できます。
まとめ
個人的にはいい感じのインターフェースだな〜と思っています が、
MyBatis 3.4.0だとバグに伴う制約があるので、実際に使うのはMyBatis 3.4.1のリリースを待ってからの方が無難な気がします。
↓
2016/6/26に3.4.1がリリースされました!!