Cursori di query

I cursori di query consentono a un'applicazione di recuperare i risultati di una query in comodi batch e sono consigliati rispetto all'utilizzo di offset interi per la paginazione. Consulta la sezione Query per ulteriori informazioni su come strutturare le query per la tua app.

Cursori di query

I cursori di query consentono a un'applicazione di recuperare i risultati di una query senza dover sostenere l'overhead di un offset di query. Dopo aver eseguito un operazione di recupero, l'applicazione può ottenere un cursore, che è una stringa opaca codificata in base64 che indica la posizione di indice è l'ultimo risultato recuperato. L'applicazione può salvare questa stringa, ad esempio Datastore, in Memcache, in un payload di attività della coda di attività o incorporato in una pagina web come parametro HTTP GET o POST e può quindi utilizzare il cursore il punto di partenza per una successiva operazione di recupero allo scopo di ottenere il batch di risultati dal punto in cui è terminato il recupero precedente. Un recupero può anche specificare un cursore di fine per limitare l'ambito del set di risultati restituito.

Differenza tra offset e cursori

Sebbene Datastore supporti gli offset interi, dovresti evitare a utilizzarli. Utilizza invece i cursori. L'utilizzo di un offset evita solo di restituire le entità saltate alla tua applicazione, ma queste entità vengono comunque recuperate internamente. Le entità ignorate influiscono sulla latenza della query e alla tua applicazione vengono addebitate le operazioni di lettura necessarie per recuperarle. Utilizzo i cursori al posto degli offset consente di evitare tutti questi costi.

Esempio di cursore di query

Nell'API di basso livello, l'applicazione può utilizzare i cursori tramite le interfacce QueryResultList, QueryResultIterable e QueryResultIterator, rispettivamente restituite dai metodi PreparedQuery asQueryResultList(), asQueryResultIterable(), e asQueryResultIterator(). Ciascuno di questi oggetti risultato fornisce getCursor() che a sua volta restituisce un valore Cursor . L'applicazione può ottenere una stringa sicura per il web che rappresenta il cursore chiama l'oggetto Cursor toWebSafeString() e potrà in seguito utilizzare il metodo statico Cursor.fromWebSafeString() ricostituire il cursore dalla stringa.

L'esempio seguente mostra l'utilizzo dei cursori per l'impaginazione:


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

Limiti dei cursori

I cursori sono soggetti alle seguenti limitazioni:

  • Un cursore può essere utilizzato solo dalla stessa applicazione che ha eseguito l'originale e solo per continuare la stessa query. Per utilizzare il cursore in una sequenza di recupero, devi ricostituire esattamente la query originale inclusi lo stesso tipo di entità, filtro predecessore, filtri di proprietà e ordini. Non è possibile recuperare i risultati utilizzando un cursore senza impostare la stessa query da cui è stata inizialmente generata.
  • Poiché gli operatori NOT_EQUAL e IN vengono se vengono implementate più query, le query che le utilizzano non supportano i cursori, né le query composte create con CompositeFilterOperator.or.
  • I cursori non funzionano sempre come previsto con una query che utilizza un filtro di disuguaglianza o un ordine di ordinamento per una proprietà con più valori. La deduplicazione la logica per queste proprietà a più valori non persiste tra i recuperi, causando probabilmente la restituzione dello stesso risultato più di una volta.
  • Le nuove release di App Engine potrebbero modificare i dettagli di implementazione interna, invalidando i cursori che dipendono da questi. Se un'applicazione tenta di utilizzare un cursore non più valido, Datastore genera un IllegalArgumentException (API di basso livello), JDOFatalUserException (JDO) o PersistenceException (JPA).

Cursori e aggiornamenti dei dati

La posizione del cursore è definita come la posizione nell'elenco dei risultati dopo l'ultimo risultato restituito. Un cursore non è una posizione relativa nell'elenco (non è un offset); è un indicatore a cui Datastore può passare quando si avvia una scansione dell'indice per trovare i risultati. Se i risultati di una query cambiano tra gli utilizzi di un cursore, la query rileva solo le modifiche che si verificano nei risultati dopo il cursore. Se un nuovo risultato viene visualizzato prima della posizione del cursore per la query, non verrà restituito quando vengono recuperati i risultati successivi al cursore. Analogamente, se un'entità non è più il risultato di una query, ma è stata visualizzata prima del cursore, i risultati visualizzati dopo il cursore non cambiano. Se l'ultimo risultato restituito viene rimosso dall'insieme di risultati, il cursore sa ancora come trovare il risultato successivo.

Quando recuperi i risultati delle query, puoi utilizzare sia un cursore di inizio sia un cursore di fine per restituire un gruppo continuo di risultati da Datastore. Quando utilizzi un cursore di inizio e di fine per recuperare i risultati, non è garantito che le dimensioni dei risultati siano le stesse di quando hai generato i cursori. Le entità possono essere aggiunte o eliminate da Datastore tra l'ora in cui vengono generati i cursori e il momento in cui vengono utilizzati in una query.

Passaggi successivi