查詢游標

「查詢游標」讓應用程式能夠方便地分批擷取查詢結果,建議在分頁時使用這個游標取代整數偏移。要進一步瞭解如何建構應用程式的查詢,請參閱查詢的說明。

查詢游標

「查詢游標」讓應用程式能夠方便地分批擷取查詢結果,而不會產生查詢偏移的負擔。執行擷取作業後,應用程式會取得一個游標,這個游標為不透明的 base64 編碼字串,用來標示擷取的最後結果的索引位置。應用程式可將這個字串儲存於如 Cloud Datastore、Memcache 或工作佇列的工作酬載之中,或是做為 HTTP GETPOST 參數嵌入網頁,然後再以游標做為後續擷取作業的起點,即可從上次擷取結束的點開始擷取下一批查詢結果。擷取也可以指定結束游標,用於限制傳回結果集的範圍。

偏移與游標的比較

Cloud Datastore 支援整數偏移,但建議盡量避免使用,最好改用游標。使用偏移只能避免將略過的實體傳回應用程式中,但是系統仍會在內部擷取這些實體。略過的實體其實仍會影響到查詢延遲時間,而且會為了擷取實體而進行讀取作業,因此需要向應用程式收費。改用游標而不使用偏移就能避免這些額外費用。

查詢游標範例

在低階 API 中,應用程式可以透過 QueryResultListQueryResultIterableQueryResultIterator 介面,使用由 PreparedQueryasQueryResultList()asQueryResultIterable()asQueryResultIterator() 方法個別傳回的游標。上述的每個結果物件都會提供 getCursor() 方法,繼而傳回 Cursor 物件。應用程式可藉由呼叫 Cursor 物件的 toWebSafeString() 方法來取得代表游標的網路安全字串,稍後再使用 Cursor.fromWebSafeString() 靜態方法從字串重新建構游標。

以下範例說明如何使用游標進行分頁作業:

Java 8

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>");
  }
}

Java 7

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 新版本可能會變更內部實作的詳細資料,而使依靠這些實作細節的游標失效。如果應用程式嘗試使用已經失效的游標,Cloud Datastore 就會發出 IllegalArgumentException (低階 API)、JDOFatalUserException (JDO) 或 PersistenceException (JPA)。

游標與資料更新

游標位置的定義是傳回最後一個結果後,游標在結果清單中的位置。游標不是清單中的相對位置 (並非偏移);而是開始在結果中進行索引掃描時,用於標出 Cloud Datastore 可以跳至的標記。如果查詢結果在兩次使用游標之中出現變化,查詢只會注意到游標之後的結果變化。如果新的查詢結果出現在游標位置之前,則在擷取游標之後的結果時,並不會傳回新的結果。同樣地,如果某項實體已不再是查詢結果,但卻出現在游標之前,則出現在游標之後的結果不會改變。如果將傳回的最後結果從結果集移除,游標仍然知道如何找到下一個結果。

擷取查詢結果時,您可以使用開始游標及結束游標傳回來自 Cloud Datastore 的多組連續結果。使用開始游標和結束游標擷取結果時,系統無法保證結果的大小會與您產生游標時的大小相同。在產生游標到將游標用於查詢這段期間,系統可能會新增或刪除 Cloud Datastore 的實體。

後續步驟

本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Java 適用的 App Engine 標準環境