Cursorをつかったページング

1.3.1までは,データストアからの読み出し個数が1000個以下に限られていた.なので,1000個以上のデータを取り出すときに,rangeを使ってやろうとするとうまく動かなかった.というのは,range(990, 1010) とやると,0から1100までのキーをとりだして,その後,990から1010までの中身を取りに行く,という実装なので,0から1100までのキーを取りだすという時点で,落ちてしまうからだ.また,そもそも20個だけ読みたいのに,1100個キーを取りだしてしまうので非効率.

これまでの方法

そこでよく知られているテクが,これ.条件式を組み合わせることで,rangeのオフセットを常に0にしたままスキャンする方法.このサンプルはPythonだけど,JDOで書いてもそのまま動く.

1.3.1

ところが,1.3.1になって事情が変わった.1000個の制約が無くなったので,とりあえず性能に目をつぶれば,rangeだけで大規模データに対してもページングできる(2/11日現在,JDOでは,オフセットが1000以下という縛りがまだ取れていないようだけど).

さらに,Cursorという機能が導入されたことで条件式を組み合わせなくても,ページングできるようになった.Cursorは,検索した結果が指している場所をおぼえておき,次の検索の際にそこから続きを検索する仕掛け.Cursorは文字列に変換できるので,生成したページに埋め込んでおけば,次にクリックした時に続きを実行するようにできる.

サンプル

ソースコードはこんな感じ.JDOだと,カーソルを設定するのに,マップをわざわざ作って,extensionMapとしてセットしなければならないのが面倒くさいところ.

public void doGet(HttpServletRequest req, 
                  HttpServletResponse resp)
throws IOException {
  resp.setContentType("text/html");
  PrintWriter writer = resp.getWriter();

  String cursorString = req.getParameter("cursor");
        
  PersistenceManager pm = null;
  try {
    pm = PMF.get().getPersistenceManager();
           
    Query query = pm.newQuery(TestEntity.class);
    query.setOrdering("num asc");
    query.setRange(0, 100);
    if (cursorString != null) {
      Cursor cursor = 
           Cursor.fromWebSafeString(cursorString);
      Map<String, Object> extensionMap = 
           new HashMap<String, Object>();
      extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, 
                       cursor);
      query.setExtensions(extensionMap);
    }
    List<TestEntity> entities = (List<TestEntity>) query.execute();
    for (TestEntity e: entities)
      writer.println(e.getNum() + "<br/>");
            
    Cursor newCursor = JDOCursorHelper.getCursor(entities); 
            
    writer.println("<a href=\"cursortest?cursor=" + 
                   newCursor.toWebSafeString() + 
                   "\">next</a>");
            
  } finally {            
    if (pm != null && !pm.isClosed())
      pm.close();                                
  }
}

所感

Cursor便利,だけど,Cursorだと,nextリンクを表示するべきかどうか,つまりもっとデータがあるのかどうかを知るのがちょっと面倒.Cursorを使って検索をかけてやればいいのだけど,2度検索することになるのでなんかイマイチ.条件式を組み合わせるテクの場合は,あらかじめ1個余分にリクエストしておいて,いくつ返ってきたかで,判断することができたので,検索は一回ですんだのだけど.

そもそも,rangeの実装が変更されてキーを全部取ってくる,なんて構造じゃなくなっていれば,この辺のことは全部rangeでやるのが一番素直なんだけど,どうなんだろう?