Cursores de consultas

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. Saiba mais sobre como estruturar consultas para o aplicativo em Consultas.

Cursores de consultas

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, que é uma string opaca codificada em base64 que marca a posição do índice do último resultado recuperado. O aplicativo pode salvar essa string, por exemplo, no Datastore, no Memcache, em um payload de tarefa do Task Queue ou incorporada em uma página da Web como parâmetro HTTP GET ou POST. Depois, ele pode usar o cursor como ponto de partida para uma operação de recuperação subsequente. Nesse caso, o lote seguinte de resultados será conseguido a partir 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 Datastore é compatível com deslocamentos de números inteiros, mas é melhor 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 usar o método estático Cursor.fromWebSafeString() para reconstituir o cursor a partir da string.

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


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 Datastore apontará IllegalArgumentException (API de baixo nível), JDOFatalUserException (JDO) ou PersistenceException (JPA).

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, não é um deslocamento, e sim um marcador para o qual o Datastore pode pular ao iniciar uma varredura de índice em busca de resultados. 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 os resultados da consulta, é possível usar um cursor inicial e um cursor final para retornar um grupo contínuo de resultados do 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 podem ser adicionadas ou excluídas do Datastore entre o momento em que os cursores são gerados e o momento em que são usados em uma consulta.

A seguir