查询游标

查询游标可让应用以便捷的批量方式检索查询结果,因此我们建议使用查询游标,而不是使用整数偏移来实现分页功能。如需详细了解如何为应用设计查询结构,请参阅查询

查询游标

查询游标让应用以便捷的批量方式检索查询结果,而且不会因查询偏移而产生开销。执行检索操作后,应用会获得一个游标,该游标是一个不透明的 base64 编码字符串,用于标记上次检索的结果的索引位置。应用可以保存此字符串(例如,保存到 Datastore、Memcache、任务队列的任务载荷中,或者以 HTTP GETPOST 参数形式嵌入网页中),然后可使用游标作为起点来执行后续检索操作,以从上一次检索结束的位置获取下一批结果。检索还可以指定结束游标,以限制所返回的结果集的范围。

偏移与游标

尽管 Datastore 支持整数偏移,但应该尽量避免使用此类偏移。而应该使用游标。使用偏移只能避免将跳过的实体返回给应用,但这些实体仍会在内部被检索。跳过的实体会影响查询的延迟时间,同时由于检索这些实体需要执行读取操作,您的应用会因此而产生费用。使用游标取代偏移可让您省掉所有这些费用。

查询游标示例

在低层级 API 中,应用可以通过 QueryResultListQueryResultIterableQueryResultIterator 接口使用游标,这些接口分别由 PreparedQueryasQueryResultList()asQueryResultIterable()asQueryResultIterator() 方法返回。其中的每个结果对象均提供 getCursor() 方法,该方法又会返回一个 Cursor 对象。应用可通过调用 Cursor 对象的 toWebSafeString() 方法来获取一个表示游标的网页安全字符串,之后可使用静态方法 Cursor.fromWebSafeString() 从该字符串重构游标。

以下示例展示了分页游标的用法:


import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.QueryResultList;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ListPeopleServlet extends HttpServlet {

  static final int PAGE_SIZE = 15;
  private final DatastoreService datastore;

  public ListPeopleServlet() {
    datastore = DatastoreServiceFactory.getDatastoreService();
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    FetchOptions fetchOptions = FetchOptions.Builder.withLimit(PAGE_SIZE);

    // If this servlet is passed a cursor parameter, let's use it.
    String startCursor = req.getParameter("cursor");
    if (startCursor != null) {
      fetchOptions.startCursor(Cursor.fromWebSafeString(startCursor));
    }

    Query q = new Query("Person").addSort("name", SortDirection.ASCENDING);
    PreparedQuery pq = datastore.prepare(q);

    QueryResultList<Entity> results;
    try {
      results = pq.asQueryResultList(fetchOptions);
    } catch (IllegalArgumentException e) {
      // IllegalArgumentException happens when an invalid cursor is used.
      // A user could have manually entered a bad cursor in the URL or there
      // may have been an internal implementation detail change in App Engine.
      // Redirect to the page without the cursor parameter to show something
      // rather than an error.
      resp.sendRedirect("/people");
      return;
    }

    resp.setContentType("text/html");
    resp.setCharacterEncoding("UTF-8");
    PrintWriter w = resp.getWriter();
    w.println("<!DOCTYPE html>");
    w.println("<meta charset=\"utf-8\">");
    w.println("<title>Cloud Datastore Cursor Sample</title>");
    w.println("<ul>");
    for (Entity entity : results) {
      w.println("<li>" + entity.getProperty("name") + "</li>");
    }
    w.println("</ul>");

    String cursorString = results.getCursor().toWebSafeString();

    // This servlet lives at '/people'.
    w.println("<a href='/people?cursor=" + cursorString + "'>Next page</a>");
  }
}

游标的限制

游标存在如下限制:

  • 游标只能由执行原始查询的同一应用使用,并且只能用于继续执行相同查询。为了在后续检索操作中使用游标,您必须准确地重构原始查询,包括相同的实体种类、祖先实体过滤条件、属性过滤条件以及排序顺序。如果没有设置最初生成游标的同一查询,则无法使用游标检索结果。
  • 由于 NOT_EQUALIN 运算符是使用多个查询实现的,因此使用它们的查询不支持游标,使用 CompositeFilterOperator.or 方法构造的复合查询也不支持游标。
  • 对于使用不等性过滤条件或排序顺序来处理多值属性的查询,游标并非总能发挥预期的效果。这种多值属性的去重逻辑不会在多次检索之间持续存在,可能导致多次返回相同结果。
  • 新的 App Engine 版本可能会更改内部实现细节,导致依赖于这些细节的游标失效。如果应用尝试使用不再有效的游标,则 Datastore 会引发 IllegalArgumentException(低层级 API)、JDOFatalUserException (JDO) 或 PersistenceException (JPA) 异常。

游标和数据更新

游标的位置是指结果列表中上次返回的结果之后的位置。游标不是列表中的相对位置(它不是偏移量),而是 Datastore 在开始对结果进行索引扫描时可以跳转到的标记。如果查询的结果在两次使用游标之间发生了变化,则查询只会注意位于游标之后的结果中发生的变化。如果一个新结果出现在查询的游标位置之前,则在获取游标后的结果时不会返回该结果。同样,如果某个不再是查询结果的实体出现在游标之前,则出现在游标后的结果也不会变化。如果从结果集中移除了上次返回的结果,则游标仍会知道如何定位下一个结果。

检索查询结果时,您可以同时使用开始游标和结束游标,以从 Datastore 返回一组连续的结果。使用开始游标和结束游标检索结果时,结果大小不一定会与生成游标时的大小相同。在生成游标与在查询中使用游标这两个时间点之间,可以在 Datastore 中添加实体或者从中删除实体。

后续步骤