Query Datastore in JDO

Questo documento è incentrato sull'utilizzo del framework di persistenza Java Data Objects (JDO) per le query App Engine Datastore. Per informazioni più generali sulle query, consulta la pagina principale Query del datastore.

Una query recupera le entità da Datastore che soddisfano un insieme specificato di condizioni. La query opera su entità di un determinato kind; può specificare filtri su valori, chiavi e predecessori delle proprietà delle entità e può restituire zero o più entità come risultati. Una query può anche specificare ordini di ordinamento per sequenza i risultati in base ai valori delle rispettive proprietà. I risultati includono tutte le entità che hanno almeno un valore (possibilmente nullo) per ogni proprietà denominata nei filtri e negli ordini di ordinamento e i cui valori delle proprietà soddisfano tutti i criteri del filtro specificati. La query può restituire intere entità, entità previste o solo chiavi di entità.

Una query tipica include quanto segue:

Quando viene eseguita, la query recupera tutte le entità del tipo specificato che soddisfano tutti i filtri indicati, ordinate nell'ordine specificato. Le query vengono eseguite in sola lettura.

Nota: per risparmiare memoria e migliorare le prestazioni, per una query deve, quando possibile, specificare un limite per il numero di risultati restituiti.

Nota: il meccanismo di query basato su indice supporta un'ampia gamma di query ed è adatto alla maggior parte delle applicazioni. Tuttavia, non supporta alcuni tipi di query comuni in altre tecnologie di database: in particolare, i join e le query aggregate non sono supportati all'interno di Datastore Query Engine. Consulta la pagina Query Datastore per conoscere le limitazioni relative alle query di Datastore.

Query con JDOQL

JDO include un linguaggio di query per il recupero degli oggetti che soddisfano un insieme di criteri. Questo linguaggio, denominato JDOQL, fa riferimento direttamente alle classi e ai campi di dati JDO e include il controllo dei tipi di parametri e risultati delle query. JDOQL è simile a SQL, ma è più appropriato per database orientati agli oggetti come App Engine Datastore. (l'implementazione dell'API JDO da parte di App Engine non supporta direttamente le query SQL.)

L'interfaccia JDO Query supporta diversi stili di chiamata: puoi specificare una query completa in una stringa, utilizzando la sintassi delle stringhe JDOQL, oppure specificare alcune o tutte le parti della query richiamando i metodi nell'oggetto Query. L'esempio seguente mostra lo stile del metodo delle chiamate, con un filtro e un ordinamento, utilizzando la sostituzione dei parametri per il valore utilizzato nel filtro. I valori degli argomenti passati al metodo execute() dell'oggetto Query vengono sostituiti nella query nell'ordine specificato:

import java.util.List;
import javax.jdo.Query;

// ...

Query q = pm.newQuery(Person.class);
q.setFilter("lastName == lastNameParam");
q.setOrdering("height desc");
q.declareParameters("String lastNameParam");

try {
  List<Person> results = (List<Person>) q.execute("Smith");
  if (!results.isEmpty()) {
    for (Person p : results) {
      // Process result p
    }
  } else {
    // Handle "no results" case
  }
} finally {
  q.closeAll();
}

Ecco la stessa query che utilizza la sintassi della stringa:

Query q = pm.newQuery("select from Person " +
                      "where lastName == lastNameParam " +
                      "parameters String lastNameParam " +
                      "order by height desc");

List<Person> results = (List<Person>) q.execute("Smith");

Puoi combinare questi stili per definire la query. Ad esempio:

Query q = pm.newQuery(Person.class,
                      "lastName == lastNameParam order by height desc");
q.declareParameters("String lastNameParam");

List<Person> results = (List<Person>) q.execute("Smith");

Puoi riutilizzare una singola istanza Query con valori diversi sostituiti per i parametri richiamando il metodo execute() più volte. Ogni chiamata esegue la query e restituisce i risultati sotto forma di raccolta.

La sintassi della stringa JDOQL supporta la specifica letterale dei valori stringa e numerici; tutti gli altri tipi di valori devono utilizzare la sostituzione dei parametri. I valori letterali all'interno della stringa di query possono essere racchiusi tra virgolette singole (') o doppie ("). Ecco un esempio utilizzando il valore letterale stringa:

Query q = pm.newQuery(Person.class,
                      "lastName == 'Smith' order by height desc");

Filtri

Un filtro proprietà specifica

  • Il nome di una proprietà
  • Un operatore di confronto
  • Un valore proprietà
Ad esempio:

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);
Query q = pm.newQuery(Person.class);
q.setFilter("height <= maxHeight");

Il valore della proprietà deve essere fornito dall'applicazione; non può fare riferimento ad altre proprietà né può essere calcolato. Un'entità soddisfa il filtro se ha una proprietà del nome specificato il cui valore viene confrontato con quello specificato nel filtro nel modo descritto dall'operatore di confronto.

L'operatore di confronto può essere:

Operatore Significato
== Uguale a
< Minore di
<= Minore o uguale a
> Maggiore di
>= Maggiore o uguale a
!= Diverso da

Come descritto nella pagina principale Query, una singola query non può utilizzare filtri di disuguaglianza (<, <=, >, >=, !=) su più di una proprietà. Sono consentiti più filtri di disuguaglianza sulla stessa proprietà, ad esempio l'esecuzione di query per un intervallo di valori. I filtri contains(), corrispondenti ai filtri IN in SQL, sono supportati utilizzando la seguente sintassi:

// Query for all persons with lastName equal to Smith or Jones
Query q = pm.newQuery(Person.class, ":p.contains(lastName)");
q.execute(Arrays.asList("Smith", "Jones"));

L'operatore non uguale (!=) in realtà esegue due query: una in cui tutti gli altri filtri sono invariati e il filtro non uguale viene sostituito con un filtro minore di (<) e l'altro in cui viene sostituito con un filtro maggiore di (>) . I risultati vengono poi uniti, in ordine. Una query non può avere più di un filtro non uguale mentre una query che ne ha uno non può avere altri filtri di disuguaglianza.

L'operatore contains() esegue inoltre più query: una per ogni elemento nell'elenco specificato, con tutti gli altri filtri invariati e il filtro contains() sostituito con un filtro di uguaglianza (==) . I risultati vengono uniti in ordine di elementi nell'elenco. Se una query ha più di un filtro contains(), viene eseguita come più query, una per ogni possibile combinazione di valori negli elenchi contains().

Una singola query contenente operatori non uguali (!=) o contains() è limitata a un massimo di 30 sottoquery.

Per saperne di più su come le query != e contains() si traducono in più query in un framework JDO/JPA, consulta l'articolo Query con filtri != e IN.

Nella sintassi della stringa JDOQL, puoi separare più filtri con gli operatori && ( "and logico") e || ("or" logico):

q.setFilter("lastName == 'Smith' && height < maxHeight");

La negazione ("non logico") non è supportata. Tieni presente, inoltre, che l'operatore || può essere utilizzato solo quando i filtri che separa tutti hanno lo stesso nome proprietà (ossia quando possono essere combinati in un unico filtro contains()):

// Legal: all filters separated by || are on the same property
Query q = pm.newQuery(Person.class,
                      "(lastName == 'Smith' || lastName == 'Jones')" +
                      " && firstName == 'Harold'");

// Not legal: filters separated by || are on different properties
Query q = pm.newQuery(Person.class,
                      "lastName == 'Smith' || firstName == 'Harold'");

Ordina ordini

Un ordinamento delle query specifica

  • Il nome di una proprietà
  • Una direzione di ordinamento (ordine crescente o decrescente)

Ad esempio:

// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);

// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);

Ad esempio:

// Order alphabetically by last name:
Query q = pm.newQuery(Person.class);
q.setOrdering("lastName asc");

// Order by height, tallest to shortest:
Query q = pm.newQuery(Person.class);
q.setOrdering("height desc");

Se una query include più ordini, questi vengono applicati nella sequenza specificata. L'esempio seguente ordina prima in base al cognome crescente e poi in base all'altezza decrescente:

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);
Query q = pm.newQuery(Person.class);
q.setOrdering("lastName asc, height desc");

Se non vengono specificati ordini di ordinamento, i risultati vengono restituiti nell'ordine in cui sono stati recuperati da Datastore.

Nota: a causa del modo in cui Datastore esegue le query, se una query specifica filtri di disuguaglianza su una proprietà e ordina ordini su altre proprietà, la proprietà utilizzata nei filtri di disuguaglianza deve essere ordinata prima delle altre.

Intervalli

Una query può specificare un intervallo di risultati da restituire all'applicazione. L'intervallo indica quali risultati devono essere i primi e gli ultimi restituiti nell'insieme di risultati completo. I risultati sono identificati dai rispettivi indici numerici, dove 0 indica il primo risultato dell'insieme. Ad esempio, un intervallo di 5, 10 restituisce i risultati dal sesto al decimo:

q.setRange(5, 10);

Nota: l'utilizzo degli intervalli può influire sulle prestazioni, poiché Datastore deve recuperare e quindi ignorare tutti i risultati che precedono l'offset iniziale. Ad esempio, una query con un intervallo di 5, 10 recupera dieci risultati da Datastore, ignora i primi cinque e restituisce i cinque rimanenti all'applicazione.

Query basate su chiave

Le chiavi di entità possono essere oggetto di un filtro di query o di un ordinamento. Datastore prende in considerazione il valore chiave completo per queste query, inclusi il percorso predecessore dell'entità, il tipo e la stringa del nome della chiave assegnata dall'applicazione o l'ID numerico assegnato dal sistema. Poiché la chiave è univoca in tutte le entità del sistema, le query chiave semplificano il recupero delle entità di un determinato tipo in batch, ad esempio per un dump batch dei contenuti di Datastore. A differenza degli intervalli JDOQL, funziona in modo efficiente per un numero qualsiasi di entità.

Quando si confronta la disuguaglianza, le chiavi vengono ordinate in base ai seguenti criteri:

  1. Percorso predecessore
  2. Tipo di entità
  3. Identificatore (nome chiave o ID numerico)

Gli elementi del percorso predecessore vengono confrontati in modo simile: per tipo (stringa), poi per nome chiave o ID numerico. I tipi e i nomi delle chiavi sono stringhe e sono ordinati per valore byte; gli ID numerici sono numeri interi in ordine numerico. Se le entità con lo stesso tipo e l'elemento principale utilizzano una combinazione di stringhe di nomi chiave e ID numerici, quelle con ID numerici le antepongono ai nomi delle chiavi.

In JDO, fai riferimento alla chiave di entità nella query utilizzando il campo di chiave primaria dell'oggetto. Per utilizzare una chiave come filtro di query, devi specificare il tipo di parametro Key nel metodo declareParameters(). Quanto segue trova tutte le entità Person con un determinato alimento preferito, supponendo una relazione one-to-one senza proprietà tra Person e Food:

Food chocolate = /*...*/;

Query q = pm.newQuery(Person.class);
q.setFilter("favoriteFood == favoriteFoodParam");
q.declareParameters(Key.class.getName() + " favoriteFoodParam");

List<Person> chocolateLovers = (List<Person>) q.execute(chocolate.getKey());

Una query solo con chiavi restituisce solo le chiavi delle entità risultati anziché le entità stesse, con una latenza e un costo inferiori rispetto al recupero di intere entità:

Query q = new Query("Person").setKeysOnly();
Query q = pm.newQuery("select id from " + Person.class.getName());
List<String> ids = (List<String>) q.execute();

Spesso è più economico eseguire prima una query basata solo su chiavi e poi recuperare un sottoinsieme di entità dai risultati, anziché eseguire una query generale che potrebbe recuperare più entità di quelle di cui hai effettivamente bisogno.

Estensioni

Un'extent JDO rappresenta ogni oggetto nel datastore di una determinata classe. Puoi crearla passando la classe desiderata al metodo getExtent() di Persistence Manager. L'interfaccia Extent estende l'interfaccia Iterable per accedere ai risultati, recuperandoli in batch secondo necessità. Quando hai terminato di accedere ai risultati, chiami il metodo closeAll() dell'estensione.

L'esempio seguente esegue l'iterazione su ogni oggetto Person nel datastore:

import java.util.Iterator;
import javax.jdo.Extent;

// ...

Extent<Person> extent = pm.getExtent(Person.class, false);
for (Person p : extent) {
  // ...
}
extent.closeAll();

Eliminazione di entità per query

Se stai inviando una query con l'obiettivo di eliminare tutte le entità che corrispondono al filtro della query, puoi risparmiare un po' di codice utilizzando la funzionalità "Elimina per query" di JDO. Con la seguente azione vengono eliminate tutte le persone che superano una determinata altezza:

Query q = pm.newQuery(Person.class);
q.setFilter("height > maxHeightParam");
q.declareParameters("int maxHeightParam");
q.deletePersistentAll(maxHeight);

Noterai che l'unica differenza qui è che stiamo chiamando q.deletePersistentAll() anziché q.execute(). Tutte le regole e le restrizioni descritte in precedenza per filtri, ordini e indici si applicano alle query indipendentemente dal fatto che tu stia selezionando o eliminando il set di risultati. Tuttavia, tieni presente che, proprio come se avessi eliminato queste entità Person con pm.deletePersistent(), verranno eliminati anche tutti i figli dipendenti delle entità eliminate dalla query. Per maggiori informazioni sui figli dipendenti, consulta la pagina Relazioni delle entità in JDO.

Cursori di query

In JDO, puoi usare un'estensione e la classe JDOCursorHelper per usare i cursori con le query JDO. I cursori funzionano quando recuperano i risultati come elenco o utilizzano un iteratore. Per ottenere un cursore, passi l'elenco di risultati o l'iteratore al metodo statico JDOCursorHelper.getCursor():

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jdo.Query;
import com.google.appengine.api.datastore.Cursor;
import org.datanucleus.store.appengine.query.JDOCursorHelper;

Query q = pm.newQuery(Person.class);
q.setRange(0, 20);

List<Person> results = (List<Person>) q.execute();
// Use the first 20 results

Cursor cursor = JDOCursorHelper.getCursor(results);
String cursorString = cursor.toWebSafeString();
// Store the cursorString

// ...

// Query q = the same query that produced the cursor
// String cursorString = the string from storage
Cursor cursor = Cursor.fromWebSafeString(cursorString);
Map<String, Object> extensionMap = new HashMap<String, Object>();
extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, cursor);
q.setExtensions(extensionMap);
q.setRange(0, 20);

List<Person> results = (List<Person>) q.execute();
// Use the next 20 results

Per ulteriori informazioni sui cursori delle query, consulta la pagina Query Datastore.

Norme di lettura di Datastore e scadenza chiamate

Puoi impostare il criterio per la lettura (elevata coerenza o coerenza finale) e la scadenza della chiamata Datastore per tutte le chiamate effettuate da un'istanza PersistenceManager utilizzando la configurazione. Puoi anche eseguire l'override di queste opzioni per un singolo oggetto Query. Tuttavia, tieni presente che non è possibile eseguire l'override della configurazione per queste opzioni quando recuperi le entità per chiave.

Quando viene selezionata la coerenza finale per una query Datastore, è possibile accedere anche agli indici utilizzati dalla query per raccogliere i risultati con coerenza finale. Le query a volte restituiscono entità che non corrispondono ai criteri di query, anche se questo è vero anche con un criterio per la lettura a elevata coerenza. Se la query utilizza un filtro dei predecessori, puoi utilizzare le transazioni per garantire un set di risultati coerente.

Per eseguire l'override del criterio per la lettura per una singola query, chiama il metodo addExtension() corrispondente:

Query q = pm.newQuery(Person.class);
q.addExtension("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");

I valori possibili sono "EVENTUAL" e "STRONG"; il valore predefinito è "STRONG", se non diversamente impostato nel file di configurazione jdoconfig.xml.

Per eseguire l'override della scadenza della chiamata Datastore per una singola query, chiama il metodo setDatastoreReadTimeoutMillis() corrispondente:

q.setDatastoreReadTimeoutMillis(3000);

Il valore è un intervallo di tempo espresso in millisecondi.