Cursores de consulta

Los cursores de consulta permiten que una aplicación recupere los resultados de una consulta en lotes prácticos, y se recomienda usarlos en lugar de desplazamientos de números enteros para la paginación. Si deseas obtener más información sobre la estructuración de consultas para tu aplicación, revisa Consultas.

Cursores de consulta

Los cursores de consulta permiten que una aplicación recupere los resultados de una consulta en lotes prácticos sin 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 string opaca con codificación base64 que marca la posición en el índice del último resultado recuperado. La aplicación puede guardar esta string, por ejemplo, en Cloud Datastore, en Memcache, en la carga útil de una tarea de la lista de tareas en cola, o incorporada en una página web como un parámetro GET o POST de HTTP, y luego puede usar el cursor como punto de partida para una operación de recuperación posterior a fin de obtener el siguiente lote de resultados del punto donde terminó la recuperación anterior. Una recuperación también puede especificar un cursor de fin para limitar la extensión del conjunto de resultados que se muestran.

Desplazamientos frente a cursores

Si bien Cloud Datastore admite desplazamientos de números enteros, no es recomendable usarlos. En su lugar, usa cursores. Usar un desplazamiento solo evita que se muestren las entidades omitidas en la aplicación, pero se siguen recuperando internamente. Las entidades omitidas afectan a la latencia de la consulta y tu aplicación se factura por las operaciones de lectura necesarias para recuperarlas. Usar cursores en lugar de desplazamientos te permite evitar todos estos costos.

Ejemplo de cursor de consulta

En la API de bajo nivel, la aplicación puede usar cursores mediante las interfaces QueryResultList, QueryResultIterable y QueryResultIterator, que se muestran con los respectivos métodos de PreparedQuery asQueryResultList(), asQueryResultIterable() y asQueryResultIterator(). Cada uno de estos objetos de resultado proporciona un método getCursor(), que muestra un objeto Cursor. La aplicación puede obtener una string segura para la Web que representa al cursor mediante una llamada al método toWebSafeString() del objeto Cursor y, luego, puede usar el método estático Cursor.fromWebSafeString() para reconstituir el cursor a partir de la string.

En el ejemplo siguiente, se explica el uso de los cursores para paginación:

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

Limitaciones de los cursores

Los cursores están sujetos a las siguientes limitaciones:

  • Un cursor solo puede usarse en 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, debes reconstituir la consulta original exactamente, incluidos el mismo de tipo de entidad, filtro de principal, filtros de propiedades y órdenes de clasificación. No es posible recuperar resultados con un cursor sin configurar la misma consulta desde la que se generó originalmente.
  • Debido a que los operadores NOT_EQUAL y IN se implementan con varias consultas, las consultas que los utilizan no admiten cursores ni las consultas compuestas construidas con el método CompositeFilterOperator.or.
  • Los cursores no siempre se comportan de la forma esperada 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 deduplicación para esas propiedades con varios valores no persiste entre las recuperaciones, lo que, posiblemente, hará que el mismo resultado se muestre más de una vez.
  • Las versiones nuevas de App Engine podrían cambiar los detalles de la implementación interna, lo que invalida los cursores que dependen de ellas. Si una aplicación intenta usar un cursor que ya no es válido, Cloud Datastore genera una excepción IllegalArgumentException (API de bajo nivel), JDOFatalUserException (JDO) o PersistenceException (JPA).

Actualizaciones de datos y cursores

La posición del cursor se define como la ubicación en la lista de resultados que le sigue al último resultado que se mostró. Un cursor no es una posición relativa en la lista (no es un desplazamiento); es un marcador al que puede saltar Cloud Datastore cuando se inicia un análisis de índice para obtener resultados. Si los resultados de una consulta cambian entre los usos de un cursor, la consulta notará solo los cambios que se producen en los resultados que aparecen después del cursor. Si aparece un resultado nuevo antes de la posición del cursor para la consulta, este no se mostrará cuando se recuperen los resultados que aparecen después del cursor. De igual modo, si una entidad ya no es un resultado para una consulta, pero ha aparecido antes del cursor, los resultados que aparecen después del cursor no se modifican. Si se quita el último resultado que se muestra del conjunto de resultados, el cursor aún podrá localizar el resultado siguiente.

Cuando se recuperan resultados de consulta, puedes usar un cursor de inicio y un cursor de fin para que se muestre un grupo continuo de resultados de Cloud Datastore. Cuando se usa un cursor de inicio y de fin para recuperar los resultados, no hay garantía de que el tamaño de los resultados será el mismo que cuando se generaron los cursores. Es posible agregar o borrar entidades desde Cloud Datastore entre el momento en que se generan los cursores y en el que se los utiliza en una consulta.

Pasos siguientes

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Entorno estándar de App Engine para Java 8