カーソル検索

カーソル検索とは?

そもそもカーソル検索とは?について説明するページがあります。

安全性を確保したカーソル検索

カーソル検索のよくある悩みは、どうしても安全性を失ってしまうやり方になってしまうことですが、 DBFluteでは、安全性を確保した状態でカーソル検索ができるような工夫がされております。

ConditionBean

ConditionBeanでは、EntityRowHandler を利用したコールバックを指定することで実装できます。

e.g. ConditionBeanでカーソル検索 {MEMBER} @Java
MemberCB cb = new MemberCB();
cb.setupSelect_MemberStatus();
cb.query().setMemberName_PrefixSearch("S");

memberBhv.selectCursor(cb, new EntityRowHandler<Member>() {
    public void handle(Member entity) { // called per one record
        ... = entity.getMemberId();
        ... = entity.getMemberName();
        ... = entity.getMemberStatus();
    }
});

外だしSQL

外だしSQLでは、Sql2Entityで TypeSafeCursor を自動生成することで実装できます。

e.g. 外だしSQLでカーソル検索 @Java
memberBhv.outsideSql().cursorHandling()
.selectCursor(pmb, new PurchaseSummaryMemberCursorHandler() {
    protected Object fetchCursor(PurchaseSummaryMemberCursor cursor) thr... {
        while (cursor.next()) { // on memory per one record
            // type safe access
            Integer memberId = cursor.getMemberId();
            String memberName = cursor.getMemberName();
            Date birthdate = cursor.getBirthdate();
            Timestamp formalizedDatetime = cursor.getFormalizedDatetime();
            Long purchaseSummary = cursor.getPurchaseSummary();

            ...
        }
        return null; // basically null
    }
});

トランザクションには注意

大量データを検索して別のテーブルに登録処理(insert)をするような処理の場合で、 カーソル検索のループの中でのコミット処理をカーソル検索と同じトランザクションでコミットしてしまうと、 カーソル検索自体が(ループが終わる前に)終わってしまいます。当然のことながら、そのカーソルの接続が切断されるからです。 そのような場合は、必ず カーソル検索と登録処理のトランザクションは別々 にします。

e.g. カーソル内で登録処理のトランザクション {Spring, Lasta Di} @Java
public void migrateMember() {
    MemberCB cb = new MemberCB();
    ...
    memberBhv.selectCursor(cb, new EntityRowHandler<Member>() {
        public void handle(Member entity) {
            registerAnotherMember(entity); // 一件ごとにコミット
        }
    });
}

@Transactional(TxType.REQUIRES_NEW)
public void registerAnotherMember(Member entity) { // 新しいトランザクション
    ... // 別のテーブルに登録
}

データの受け取り方の違い

大きくは、この二つDBアクセス方法に対応するカーソル検索、ということで理解しておけば問題ありませんが、厳密な理解をするための説明をすると、 データの受け取り方の違いによるそれぞれの特徴があり、実は四つの種類に分けることができます。

サポートしている二つのやり方

ConditionBeanのカーソル検索は、通常の検索と同じように Entity のオブジェクトグラフ の形式でデータを受け取ります。一方で、外だしSQLのカーソル検索は、(厳密には)Entity ではなく、フラットな ResultSet のタイプセーフなラッパークラス (Sql2Entityで自動生成され、取得するカラムの指定がタイプセーフになる)でデータを受け取ります。

ConditionBean
Entityのオブジェクトグラフの形式
外だしSQL
フラットな ResultSet のタイプセーフラッパー

この二つを比較すると、実装上の安全性は ConditionBean、但し、Entity へのマッピングコスト の分、外だしSQLの方が若干速い、という特徴になります。(ConditionBeanの場合は、ループの中で一レコードずつ Entity へのマッピング処理を行う)

どちらを利用するかは、当然そもそも発行するSQLがどちらで実現できるのか、に寄りますが、どちらでも実現できるという場合は、 単純にメモリのオーバーフローさえ回避できれば良いのであれば ConditionBean、一ミリ秒でも速く処理したい(Entity へのマッピングコストの分を稼ぐ)のであれば外だしSQL、という位置付けになります。

サポートしていないもう二つのやり方

実は、データの受け取り方を逆転させた、もう二つのやり方が概念的には存在していて、DBFluteではサポートしていません。

  • ConditionBeanで、ResultSet のラッパークラス
  • 外だしSQLで、(Customize)Entity

これらは本来(DBFluteで)実現しようとと思えばできるのですが、無意味なのでサポートしていません。 ConditionBeanは、(安全に)Entityのオブジェクトグラフの形式で受け取るために最適化されたものであり、 フラットで受け取ることに向いていません。また、外だしSQLで Entity で受け取るのは、 同じくタイプセーフなラッパークラスがあれば十分であり、かつ、大量データのために検索なのでスピード重視で判断しています。

外だしSQLのカーソル検索は最速

外だしSQLのカーソル検索は、マッピングをしない(それでも、タイプセーフ)という点から、論理的には通常の外だしSQLよりも速い検索となります。 同じ論理で当然ConditionBeanによる検索よりも速いため、実はDBFluteの中で最速の検索は、外だしSQLのカーソル検索であると言えるかもしれません。 もちろん、カーソル検索してもアプリで独自のDTOなどにマッピングするのであれば結局同じことですが、そうではなく、 直接コールバックの中で処理が完結してしまうような場合は、メモリの考慮に関わらず(パフォーマンス的に)カーソル検索は最適であると考えられます。 但し、マッピングコストは大量件数でなければ目立ったコストにはならないので、普段はあまりこのことを意識しても意味がないでしょう。

厳密には 1 ループで破棄しないことも

DBFluteのデフォルトのResultSetタイプは TYPE_FORWARD_ONLY なので、基本的には 1 ループごとにメモリが解放される(解放対象になる)ことが想定されますが、 JDBCの設定とJDBCドライバの実装次第でそういう挙動にならないことも頭に入れておくことをお奨めします。

Exampleのススメ

dbflute-basic-example では、テストケースの中で実際にカーソル検索を利用しています。 (他の多くのExampleでも同様に利用されています)

DBFlute Example - DIコンテナ