Curseurs de requêtes

Les curseurs de requêtes permettent à une application de récupérer les résultats d'une requête sous forme de lots pratiques. Il est recommandé de privilégier leur utilisation à celle de décalages d'entiers pour la pagination. Consultez la section Requêtes pour savoir comment structurer des requêtes pour l'application.

Curseurs de requêtes

Les curseurs de requêtes permettent à une application de récupérer les résultats d'une requête sous forme de lots pratiques sans entraîner la surcharge d'un décalage de requête. Après avoir effectué une opération de récupération, l'application peut obtenir un curseur, qui est une chaîne opaque codée en base64 indiquant la position d'index du dernier résultat récupéré. L'application peut enregistrer cette chaîne, par exemple dans Cloud Datastore, dans Memcache, dans une charge utile de tâche de la file d'attente de tâches, ou intégrée dans une page Web comme paramètre HTTP GET ou POST. Elle peut ensuite utiliser le curseur comme point de départ d'une session ultérieure de récupération pour obtenir le prochain lot de résultats à partir du point où la récupération précédente s'est terminée. Une récupération peut également spécifier un curseur de fin afin de limiter l'étendue de l'ensemble des résultats renvoyés.

Décalages et curseurs

Bien que Cloud Datastore accepte les décalages d'entiers, vous devriez éviter de les utiliser. Privilégiez plutôt les curseurs. L'utilisation d'un décalage permet uniquement d'éviter de renvoyer les entités ignorées à l'application, mais ces dernières sont tout de même récupérées en interne. Les entités ignorées ont une incidence sur la latence de la requête, et l'application est facturée pour les opérations de lecture nécessaires à leur récupération. En faisant appel à des curseurs plutôt qu'à des décalages, vous évitez tous ces coûts.

Exemple de curseur de requête

Dans l'API de bas niveau, l'application peut utiliser des curseurs via les interfaces QueryResultList, QueryResultIterable et QueryResultIterator, qui sont renvoyées par les méthodes PreparedQuery asQueryResultList(), asQueryResultIterable() et asQueryResultIterator(), respectivement. Chacun de ces objets de résultat fournit une méthode getCursor(), qui renvoie à son tour un objet Cursor. L'application peut obtenir une chaîne Web sécurisée représentant le curseur en appelant la méthode toWebSafeString() de l'objet Cursor et peut, par la suite, utiliser la méthode statique Cursor.fromWebSafeString() pour reconstituer le curseur à partir de la chaîne.

L'exemple qui suit illustre l'utilisation de curseurs pour la pagination.

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

Limites relatives aux curseurs

Les curseurs sont soumis aux limites suivantes :

  • Un curseur ne peut être utilisé que par l'application qui a exécuté la requête d'origine et uniquement pour poursuivre la même requête. Pour utiliser le curseur dans une opération de récupération ultérieure, vous devez reconstituer exactement la requête d'origine, avec le même genre d'entité, le même filtre d'ancêtre, les mêmes filtres de propriétés et les mêmes ordres de tri. Il est impossible de récupérer des résultats à l'aide d'un curseur sans configurer une requête identique à celle à partir de laquelle il a été généré.
  • Étant donné que les opérateurs NOT_EQUAL et IN sont mis en œuvre avec plusieurs requêtes, les requêtes qui les utilisent ne sont pas compatibles avec les curseurs, pas plus que les requêtes composites construites avec la méthode CompositeFilterOperator.or.
  • Les curseurs ne fonctionnent pas toujours comme prévu avec une requête utilisant un filtre d'inégalité ou un ordre de tri sur une propriété comportant plusieurs valeurs. Comme la logique de déduplication de ces propriétés à valeurs multiples ne persiste pas entre les récupérations, le même résultat peut être renvoyé plusieurs fois.
  • Les nouvelles versions d'App Engine peuvent modifier des détails de mise en œuvre interne, et ainsi invalider les curseurs qui en dépendent. Si une application tente d'utiliser un curseur qui n'est plus valide, Cloud Datastore déclenche une exception IllegalArgumentException (API de bas niveau), JDOFatalUserException (JDO) ou PersistenceException (JPA).

Mises à jour des curseurs et des données

La position du curseur se définit comme l'emplacement dans la liste de résultats après renvoi du dernier résultat. Un curseur n'est pas une position relative dans la liste (ce n'est pas un décalage). Il s'agit d'un marqueur auquel Cloud Datastore peut accéder lors du lancement d'une analyse des résultats dans un index. Si les résultats d'une requête changent entre les différentes utilisations d'un curseur, la requête remarque uniquement les modifications apportées aux résultats situés après le curseur. Si un nouveau résultat apparaît avant la position du curseur pour cette requête, il n'est pas renvoyé lors de la récupération des résultats situés après le curseur. De la même façon, si une entité ne constitue plus un résultat d'une requête, alors qu'elle apparaissait avant le curseur, les résultats situés après le curseur ne changent pas. Si le dernier résultat renvoyé est supprimé de l'ensemble de résultats, le curseur est toujours capable de localiser le résultat suivant.

Lors de la récupération des résultats de la requête, vous pouvez utiliser un curseur de début et un curseur de fin pour renvoyer un groupe continu de résultats à partir de Cloud Datastore. Lorsque vous utilisez un curseur de début et de fin pour récupérer les résultats, il n'est pas certain que vous obteniez la même taille de résultats que celle obtenue lorsque vous avez généré les curseurs. Des entités peuvent être ajoutées ou supprimées de Cloud Datastore entre le moment où les curseurs sont générés et utilisés dans une requête.

Étapes suivantes

Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Environnement standard App Engine pour Java 8