Cursores de consulta

Os cursores de consulta permitem a recuperação, por parte de um aplicativo, dos resultados de uma consulta em lotes convenientes, sendo o uso recomendado em deslocamentos inteiros para paginação. Para mais informações sobre a estruturação de consultas para o aplicativo, acesse a página Consultas.

Cursores de consulta

Os cursores de consulta permitem a recuperação, por parte de um aplicativo, dos resultados de uma consulta em lotes convenientes, evitando a sobrecarga do deslocamento dessa consulta. Após executar uma operação de recuperação, o aplicativo recebe um cursor, uma string opaca codificada em base64 que marca a posição do índice do último resultado recuperado. Por exemplo, o aplicativo salva essa string no Cloud Datastore, no Memcache, em um payload de tarefa da Fila de tarefas ou a incorpora em uma página da Web como parâmetro HTTP GET ou POST e usa o cursor como ponto de partida para uma operação de recuperação posterior, para receber o próximo lote de resultados do ponto em que a recuperação anterior terminou. É possível que uma recuperação também especifique um cursor de término para limitar a extensão do conjunto de resultados retornados.

Deslocamentos versus cursores

O Cloud Datastore é compatível com deslocamentos de números inteiros, mas convém evitá-los. Em vez disso, use cursores. O uso de um deslocamento evita apenas o retorno de entidades ignoradas ao aplicativo, mas é possível recuperá-las internamente. Na verdade, as entidades ignoradas afetam a latência da consulta. Além disso, o aplicativo será cobrado pelas operações de leitura necessárias para a recuperação dessas entidades. Use cursores em vez de deslocamentos para evitar todos esses custos.

Exemplo de cursor de consulta

Na API de nível inferior, o aplicativo pode usar cursores por meio das interfaces QueryResultList, QueryResultIterable e QueryResultIterator, que são retornadas pelos métodos PreparedQuery asQueryResultList(), asQueryResultIterable() e asQueryResultIterator(), respectivamente. Cada um desses objetos de resultado fornece um método getCursor(), que, por sua vez, retorna um objeto Cursor. Para receber uma string segura para Web que representa o cursor, o aplicativo pode chamar o método toWebSafeString() do objeto Cursor e, posteriormente, usar o método estático Cursor.fromWebSafeString() para reconstituir o cursor a partir da string (conteúdo dos links em inglês).

O exemplo a seguir demonstra o uso de cursores para paginação:

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

Limitações dos cursores

Os cursores estão sujeitos às seguintes limitações:

  • Um cursor é usado somente pelo mesmo projeto que executou a consulta original e para continuar a mesma consulta. Para usar o cursor em uma operação de recuperação posterior, reconstitua na íntegra a consulta original, incluindo o mesmo tipo de entidade, filtro de ancestrais e de propriedades e ordens de classificação. Não é possível recuperar resultados usando um cursor sem configurar a mesma consulta que o gerou.
  • Como os operadores NOT_EQUAL e IN são implementados com várias consultas, as que os utilizam não são compatíveis com cursores ou com as consultas compostas criadas com o método CompositeFilterOperator.or.
  • Cursores nem sempre funcionam como esperado em uma consulta que usa um filtro de desigualdade ou uma ordem de classificação em uma propriedade com valores múltiplos. A lógica de eliminar a duplicação para essas propriedades não se mantém entre as recuperações. Assim, é possível que o mesmo resultado seja retornado mais de uma vez.
  • Novas versões do App Engine podem alterar os detalhes de implementação internos, invalidando cursores que dependem deles. Se um aplicativo tentar usar um cursor que não é mais válido, o Cloud Datastore gerará uma IllegalArgumentException (API de nível inferior), JDOFatalUserException (JDO) ou PersistenceException (JPA) (conteúdo dos links em inglês).

Cursores e atualizações de dados

A posição do cursor é definida como o local na lista de resultados após o último resultado retornado. Um cursor não é uma posição relativa na lista, ou seja, não é um deslocamento. Ele é um marcador para onde o Cloud Datastore é direcionado ao iniciar uma verificação de resultados de índice. Caso os resultados de uma consulta sofram alteração entre os usos de um cursor, ela refletirá apenas as alterações ocorridas nos resultados após o cursor. Se na consulta aparecer um novo resultado antes da posição do cursor, ele não será retornado quando forem recuperados os resultados após o cursor. Da mesma forma, caso uma entidade não seja mais o resultado de uma consulta, mesmo tendo aparecido antes do cursor, os resultados após o cursor não serão alterados. Caso o último resultado retornado seja removido do conjunto de resultados, o cursor ainda saberá como localizar o próximo.

Ao recuperar resultados da consulta, use um cursor de início e um de término para retornar um grupo contínuo de resultados a partir do Cloud Datastore. Usar um cursor de início e de término na recuperação de resultados não garante que o tamanho dos resultados seja o mesmo de quando os cursores foram gerados. As entidades são adicionadas ou excluídas do Cloud Datastore entre o momento em que os cursores são gerados e em que são usados em uma consulta.

A seguir