Abfrage-Cursor

Mit Abfrage-Cursors kann eine Anwendung die Ergebnisse einer Abfrage in praktischen Batches abrufen. Sie werden statt ganzzahliger Offsets für die Paginierung empfohlen. Weitere Informationen zur Strukturierung von Abfragen für Ihre Anwendung finden Sie unter Abfragen.

Abfrage-Cursors

Mit Abfrage-Cursors kann eine Anwendung die Ergebnisse einer Abfrage in praktischen Batches ohne den Overhead eines Abfrage-Offsets abrufen. Nach Ausführung eines Abrufvorgangs kann die Anwendung einen Cursor abrufen, bei dem es sich um einen intransparenten base64-codierten String handelt, der die Indexposition des letzten abgerufenen Ergebnisses markiert. Die Anwendung kann diesen String speichern (beispielsweise in Datastore, in Memcache, in der Aufgabennutzlast einer Aufgabenwarteschlange oder eingebettet in einer Webseite als HTTP-GET- oder -POST-Parameter). Dann kann sie den Cursor als Ausgangspunkt für einen nachfolgenden Abrufvorgang verwenden, um den nächsten Batch Ergebnisse ab dem Punkt abzurufen, an dem der vorherige Abrufvorgang beendet wurde. Für den Abruf kann auch ein Endcursor angegeben werden, um den Umfang der zurückgegebenen Ergebnisse zu begrenzen.

Offsets und Cursors im Vergleich

Datastore unterstützt zwar ganzzahlige Offsets, aber Sie sollten diese nach Möglichkeit nicht verwenden. Verwenden Sie stattdessen Cursors. Die Verwendung eines Offsets verhindert nur die Rückgabe der übersprungenen Entitäten an Ihre Anwendung, die Entitäten werden jedoch dennoch intern abgerufen. Die übersprungenen Entitäten wirken sich auf die Latenz der Abfrage aus. Außerdem werden Ihrer Anwendung die Lesevorgänge in Rechnung gestellt, die für deren Abruf erforderlich sind. Wenn Cursors anstelle von Offsets verwendet werden, können Sie diese Kosten vermeiden.

Beispiel für einen Abfrage-Cursor

In der Low-Level API kann die Anwendung Cursors über die Schnittstellen QueryResultList, QueryResultIterable und QueryResultIterator verwenden, die jeweils von den PreparedQuery-Methoden asQueryResultList(), asQueryResultIterable() und asQueryResultIterator() zurückgegeben werden. Jedes dieser Ergebnisobjekte bietet eine getCursor()-Methode, die wiederum ein Cursor-Objekt zurückgibt. Die Anwendung kann einen websicheren String abrufen, der den Cursor darstellt. Dabei wird die Methode toWebSafeString() des Cursor-Objekts aufgerufen und später die statische Methode Cursor.fromWebSafeString() verwendet, um den Cursor aus dem String zu rekonstruieren.

Im folgenden Beispiel wird die Verwendung von Cursors für die Paginierung demonstriert.


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

Einschränkungen von Cursors

Cursors unterliegen folgenden Einschränkungen:

  • Ein Cursor kann nur von der Anwendung, die die ursprüngliche Abfrage durchgeführt hat, und nur zur Fortsetzung derselben Abfrage verwendet werden. Um den Cursor bei einem nachfolgenden Abrufvorgang zu verwenden, müssen Sie die ursprüngliche Abfrage genau rekonstruieren, einschließlich der Entitätsart, des Ancestor-Filters, der Attributfilter und der Sortierfolgen. Ergebnisse können mit einem Cursor nur abgerufen werden, wenn genau die Abfrage eingerichtet wird, aus der er ursprünglich generiert wurde.
  • Da die Operatoren NOT_EQUAL und IN mit mehreren Abfragen implementiert werden, unterstützen Abfragen, von denen sie verwendet werden, keine Cursors – ebenso wenig wie zusammengesetzte Abfragen, die mit der Methode CompositeFilterOperator.or konstruiert werden.
  • Cursors funktionieren häufig nicht wie erwartet bei Abfragen, die einen Ungleichheitsfilter oder eine Sortierfolge bei einem Attribut mit mehreren Werten verwenden. Die Deduplizierungslogik für derartige mehrwertige Attribute bleibt zwischen Abrufvorgängen nicht bestehen, weshalb dasselbe Ergebnis mehr als einmal zurückgegeben werden kann.
  • Bei neuen App Engine-Versionen können sich die Details der internen Implementierung ändern, sodass Cursors, die davon abhängen, ungültig werden. Wenn eine Anwendung versucht, einen Cursor zu verwenden, der nicht mehr gültig ist, löst Datastore eine IllegalArgumentException (Low-Level-API), JDOFatalUserException (JDO) oder PersistenceException (JPA) aus.

Cursors und Datenaktualisierungen

Die Position des Cursors ist als die Position in der Ergebnisliste nach dem letzten zurückgegebenen Ergebnis definiert. Ein Cursor ist keine relative Position in der Liste (er ist kein Offset). Er ist eine Markierung, zu der Datastore springen kann, wenn ein Indexscan nach Ergebnissen gestartet wird. Wenn sich die Ergebnisse einer Abfrage zwischen Cursorverwendungen ändern, berücksichtigt die Abfrage nur Änderungen, die in Ergebnissen nach dem Cursor auftreten. Wenn für die Abfrage ein neues Ergebnis vor der Position des Cursors angezeigt wird, wird dieses beim Abrufen der Ergebnisse nach der Position des Cursors nicht zurückgegeben. Dasselbe gilt, wenn eine Entität kein Ergebnis einer Abfrage mehr ist, aber vor der Position des Cursors angezeigt wurde; in diesem Fall ändern sich die Ergebnisse nach der Position des Cursors nicht. Auch wenn das letzte zurückgegebene Ergebnis aus den Ergebnissen entfernt wird, kann der Cursor die Position des nächsten Ergebnisses bestimmen.

Beim Abrufen von Abfrageergebnissen können Sie einen Start- und einen Endcursor einsetzen, um fortlaufende Ergebnisse aus Datastore zurückzugeben. Wenn Sie beim Abrufen der Ergebnisse einen Start- und einen Endcursor verwenden, ist nicht garantiert, dass der Umfang der Ergebnisse dem Umfang beim Generieren der Cursors entspricht. Das liegt daran, dass es passieren kann, dass Entitäten in Datastore zwischen dem Zeitpunkt der Generierung der Cursors und deren Verwendung in einer Abfrage hinzugefügt oder gelöscht werden.

Weitere Informationen