Cursores de consultas

Los cursores de consulta permiten a una aplicación obtener los resultados de una consulta en lotes cómodos y se recomiendan en lugar de usar desplazamientos de enteros para la paginación. Consulta Consultas para obtener más información sobre cómo estructurar las consultas de tu aplicación.

Cursores de consultas

Los cursores de consulta permiten que una aplicación recupere los resultados de una consulta en lotes cómodos sin incurrir en la sobrecarga de un desplazamiento de consulta. Después de realizar una operación de recuperación, la aplicación puede obtener un cursor, que es una cadena opaca codificada en base64 que marca la posición del índice del último resultado recuperado. La aplicación puede guardar esta cadena, por ejemplo, en Datastore, en Memcache, en una carga útil de una tarea de cola de tareas o insertada en una página web como parámetro HTTP GET o POST. Después, puede usar el cursor como punto de partida de una operación de recuperación posterior para obtener el siguiente lote de resultados desde el punto en el que finalizó la recuperación anterior. Una recuperación también puede especificar un cursor final para limitar el alcance del conjunto de resultados devuelto.

Desplazamientos frente a cursores

Aunque Datastore admite desplazamientos de números enteros, no se recomienda usarlos. En su lugar, usa cursores. Si usas un desplazamiento, solo evitarás que se devuelvan las entidades omitidas a tu aplicación, pero estas entidades se seguirán obteniendo internamente. Las entidades omitidas afectan a la latencia de la consulta y se te factura por las operaciones de lectura necesarias para recuperarlas. Si utilizas cursores en lugar de desplazamientos, puedes evitar todos estos costes.

Ejemplo de cursor de consulta

En la API de nivel bajo, la aplicación puede usar cursores a través de las interfaces QueryResultList, QueryResultIterable y QueryResultIterator que devuelven los métodos PreparedQuery asQueryResultList(), asQueryResultIterable() y asQueryResultIterator(), respectivamente. Cada uno de estos objetos de resultado proporciona un método getCursor(), que a su vez devuelve un objeto Cursor. La aplicación puede obtener una cadena segura para la Web que represente el cursor llamando al método toWebSafeString() del objeto Cursor y, más adelante, puede usar el método estático Cursor.fromWebSafeString() para reconstituir el cursor a partir de la cadena.

En el siguiente ejemplo se muestra el uso de cursores para la paginación:


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

Limitaciones de los cursores

Los cursores están sujetos a las siguientes limitaciones:

  • Un cursor solo puede utilizarlo la misma aplicación que realizó la consulta original y solo para continuar la misma consulta. Para usar el cursor en una operación de recuperación posterior, debe reconstituir la consulta original exactamente, incluidos el mismo tipo de entidad, el filtro de ancestro, los filtros de propiedad y los órdenes de clasificación. No es posible obtener resultados mediante un cursor sin configurar la misma consulta a partir de la que se generó originalmente.
  • Como los operadores NOT_EQUAL y IN se implementan con varias consultas, las consultas que los usan no admiten cursores, ni tampoco las consultas compuestas creadas con el método CompositeFilterOperator.or.
  • Los cursores no siempre funcionan como se espera con una consulta que usa un filtro de desigualdad o un orden de clasificación en una propiedad con varios valores. La lógica de desduplicación de estas propiedades de varios valores no se mantiene entre las recuperaciones, lo que puede provocar que se devuelva el mismo resultado más de una vez.
  • Las nuevas versiones de App Engine pueden cambiar los detalles de implementación internos, lo que invalida los cursores que dependen de ellos. Si una aplicación intenta usar un cursor que ya no es válido, Datastore genera un error IllegalArgumentException (API de nivel inferior), JDOFatalUserException (JDO) o PersistenceException (JPA).

Cursores y actualizaciones de datos

La posición del cursor se define como la ubicación en la lista de resultados después del último resultado devuelto. Un cursor no es una posición relativa en la lista (no es un desplazamiento), sino un marcador al que Datastore puede saltar al iniciar un análisis de índice para obtener resultados. Si los resultados de una consulta cambian entre usos de un cursor, la consulta solo detecta los cambios que se producen en los resultados después del cursor. Si aparece un nuevo resultado antes de la posición del cursor de la consulta, no se devolverá cuando se obtengan los resultados posteriores al cursor. Del mismo modo, si una entidad deja de ser un resultado de una consulta, pero había aparecido antes del cursor, los resultados que aparecen después del cursor no cambian. Si se elimina del conjunto de resultados el último resultado devuelto, el cursor sigue sabiendo cómo localizar el siguiente resultado.

Al recuperar los resultados de una consulta, puede usar tanto un cursor de inicio como un cursor de finalización para devolver un grupo continuo de resultados de Datastore. Cuando se usan cursores de inicio y de finalización para obtener los resultados, no se garantiza que el tamaño de los resultados sea el mismo que cuando se generaron los cursores. Es posible que se añadan o eliminen entidades de Datastore entre el momento en que se generan los cursores y el momento en que se usan en una consulta.

Siguientes pasos