Datastore-Abfragen

Bei einer Datastore-Abfrage werden Entitäten aus Cloud Datastore abgerufen, die einen Satz von angegebenen Bedingungen erfüllen.

Eine typische Abfrage umfasst

  • Eine Entitätsart, auf die die Abfrage angewendet wird
  • Optionale Filter, die auf den Attributwerten, Schlüsseln und Ancestors der Entität beruhen
  • Optionale Sortierfolgen zur Anordnung der Ergebnisse
Bei der Ausführung ruft die Abfrage alle Entitäten des angegebenen Typs ab, die alle Filterbedingungen erfüllen, und sortiert sie in der angegebenen Reihenfolge. Abfragen werden schreibgeschützt ausgeführt.

Auf dieser Seite werden die Struktur und Arten von Abfragen beschrieben, die in App Engine zum Abrufen von Daten aus Cloud Datastore verwendet werden.

Filter

Die Filter einer Abfrage legen Einschränkungen für die Attribute, Schlüssel und Ancestors der Entitäten fest, die abgerufen werden sollen.

Attributfilter

In einem Attributfilter wird Folgendes angegeben:

  • Ein Attributname
  • Ein Vergleichsoperator
  • Ein Attributwert
Beispiel:

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);

Der Attributwert muss von der Anwendung angegeben werden; er kann keine anderen Attribute referenzieren oder anhand anderer Attribute berechnet werden. Eine Entität erfüllt den Filter, wenn sie ein Attribut mit dem angegebenen Namen enthält, dessen Wert dem im Filter angegebenen Wert in der vom Vergleichsoperator beschriebenen Weise entspricht.

Es können folgende Vergleichsoperatoren verwendet werden (definiert als Enum-Konstanten in der verschachtelten Klasse Query.FilterOperator):

Operator Bedeutung
EQUAL Gleich
LESS_THAN Kleiner als
LESS_THAN_OR_EQUAL Kleiner als oder gleich
GREATER_THAN Größer als
GREATER_THAN_OR_EQUAL Größer als oder gleich
NOT_EQUAL Ungleich
IN Element von (entspricht einem der Werte in einer angegebenen Liste)

Mit dem Operator NOT_EQUAL werden zwei Abfragen ausgeführt: eine, bei der alle anderen Filter unverändert sind und der Filter NOT_EQUAL durch den Filter LESS_THAN ersetzt wird, und eine, bei der er durch den Filter GREATER_THAN ersetzt wird. Danach werden die Ergebnisse in der entsprechenden Reihenfolge zusammengeführt. Eine Abfrage darf jeweils nur einen Filter NOT_EQUAL enthalten. Wenn dies der Fall ist, darf sie keine anderen Ungleichheitsfilter enthalten.

Mit dem Operator IN werden ebenfalls mehrere Abfragen ausgeführt: eine für jedes Element in der angegebenen Liste, wobei alle anderen Filter unverändert bleiben und der Filter IN durch den Filter EQUAL ersetzt wird. Die Ergebnisse werden in der Reihenfolge der Elemente in der Liste zusammengeführt. Wenn eine Abfrage mehr als einen IN-Filter enthält, werden mehrere Abfragen ausgeführt, und zwar jeweils eine für jede mögliche Kombination von Werten in den IN-Listen.

Eine einzelne Abfrage, die die Operatoren NOT_EQUAL oder IN enthält, ist auf 30 Unterabfragen begrenzt.

Weitere Informationen darüber, wie Abfragen mit NOT_EQUAL und IN in einem JDO/JPA-Framework in mehrere Abfragen umgesetzt werden, finden Sie im Artikel Abfragen mit den Filtern != und IN (nur auf Englisch verfügbar).

Schlüsselfilter

Zum Filtern mit dem Wert eines Entitätsschlüssels verwenden Sie das spezielle Attribut Entity.KEY_RESERVED_PROPERTY:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query("Person").setFilter(keyFilter);

Aufsteigende Sortierungen auf Entity.KEY_RESERVED_PROPERTY werden ebenfalls unterstützt.

Bei einem Vergleich auf Ungleichheit werden die Schlüssel nach den folgenden Kriterien in folgender Reihenfolge angeordnet:

  1. Ancestor-Pfad
  2. Entitätstyp
  3. Kennzeichnung (Schlüsselname oder numerische ID)

Elemente des Ancestor-Pfads werden ähnlich verglichen: nach Art (String), dann nach Schlüsselname oder numerischer ID. Arten und Schlüsselnamen sind Strings und werden nach Bytewert sortiert; numerische IDs sind ganze Zahlen und werden numerisch sortiert. Wenn Entitäten mit derselben übergeordneten Entität und derselben Art eine Mischung aus Schlüsselnamen-Strings und numerischen IDs verwenden, stehen die Entitäten mit numerischen IDs vor den Entitäten mit Schlüsselnamen.

Abfragen auf Schlüssel verwenden Indexe genau wie Abfragen auf Attribute und erfordern in den gleichen Fällen benutzerdefinierte Indexe, mit einigen Ausnahmen: Ungleichheitsfilter oder eine aufsteigende Sortierfolge für Schlüssel erfordern keinen benutzerdefinierten Index, eine absteigende Sortierfolge bei Schlüsseln hingegen schon. Wie bei allen Abfragen erstellt der Entwicklungs-Webserver entsprechende Einträge in der Indexkonfigurationsdatei, wenn eine Abfrage, die einen benutzerdefinierten Index erfordert, getestet wird.

Ancestor-Filter

Sie können Datastore-Abfragen mit einem bestimmten Ancestor filtern, sodass die zurückgegebenen Ergebnisse nur untergeordnete Entitäten von diesem Ancestor enthalten:

Query q = new Query("Person").setAncestor(ancestorKey);

Besondere Abfragetypen

Einige spezifische Abfragetypen müssen besonders erwähnt werden:

Typlose Abfragen

Mit einer typlosen Abfrage ohne Ancestor-Filter werden alle Entitäten einer Anwendung aus Datastore abgerufen. Dazu gehören auch Entitäten, die durch andere App Engine-Features erstellt und verwaltet werden, beispielsweise Statistikentitäten und Blobstore-Metadatenentitäten (sofern vorhanden). Derartige typlose Abfragen können keine Filter oder Sortierfolgen für Attributwerte enthalten. Sie können jedoch zum Filtern von Entitätsschlüsseln verwendet werden, wenn Sie Entity.KEY_RESERVED_PROPERTY als Attributnamen angeben:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setFilter(keyFilter);

Ancestor-Abfragen

Bei einer Abfrage mit einem Ancestor-Filter sind die Ergebnisse auf die angegebene Entität und ihre Nachfolger begrenzt:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity babyPhoto = new Entity("Photo", tomKey);
babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg");

Entity dancePhoto = new Entity("Photo", tomKey);
dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg");

Entity campingPhoto = new Entity("Photo");
campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg");

List<Entity> photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto);
datastore.put(photoList);

Query photoQuery = new Query("Photo").setAncestor(tomKey);

// This returns weddingPhoto, babyPhoto, and dancePhoto,
// but not campingPhoto, because tom is not an ancestor
List<Entity> results =
    datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());

Typlose Ancestor-Abfragen

Mit einer typlosen Abfrage, die einen Ancestor-Filter enthält, werden der angegebene Ancestor und alle seine untergeordneten Entitäten unabhängig vom Typ abgerufen. Für diesen Abfragetyp sind keine benutzerdefinierten Indexe erforderlich. Wie alle typlosen Abfragen dürfen sie keine Filter oder Sortierfolgen für Attributwerte enthalten, das Filtern nach Entitätsschlüssel ist jedoch möglich:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter);

Im folgenden Beispiel wird gezeigt, wie alle untergeordneten Entitäten eines bestimmten Ancestors abgerufen werden:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity weddingVideo = new Entity("Video", tomKey);
weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi");

List<Entity> mediaList = Arrays.asList(weddingPhoto, weddingVideo);
datastore.put(mediaList);

// By default, ancestor queries include the specified ancestor itself.
// The following filter excludes the ancestor from the query results.
Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey);

Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter);

// Returns both weddingPhoto and weddingVideo,
// even though they are of different entity kinds
List<Entity> results =
    datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults());

Ausschließlich schlüsselbasierte Abfragen

Eine ausschließlich schlüsselbasierte Abfrage gibt nur die Schlüssel der Ergebnisentitäten statt der Entitäten selbst mit einer niedrigeren Latenz und niedrigeren Kosten zurück.

Query q = new Query("Person").setKeysOnly();

Häufig ist es wirtschaftlicher, zuerst eine ausschließlich schlüsselbasierte Abfrage durchzuführen und dann einen Teil der Entitäten aus den Ergebnissen abzurufen, als eine allgemeine Abfrage auszuführen, die mehr Entitäten abrufen kann, als tatsächlich benötigt werden.

Projektionsabfragen

Manchmal benötigen Sie aus den Ergebnissen einer Abfrage eigentlich nur die Werte einiger bestimmter Attribute. In solchen Fällen haben Sie die Möglichkeit, mit einer Projektionsabfrage nur die Attribute abzurufen, die Sie tatsächlich interessieren. Dies führt zu niedrigerer Latenz und geringeren Kosten als beim Abruf ganzer Entitäten. Weitere Informationen dazu finden Sie auf der Seite zu Projektionsabfragen.

Sortierfolgen

Eine Abfrage-Sortierfolge gibt Folgendes an:

  • Ein Attributname
  • Eine Sortierfolge (aufsteigend oder absteigend)

Beispiel:

// 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);

Wenn eine Abfrage mehrere Sortierfolgen enthält, werden diese in der angegebenen Abfolge angewendet. Im folgenden Beispiel wird zuerst aufsteigend nach Nachname und dann absteigend nach Körpergröße sortiert:

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);

Wenn keine Sortierfolgen angegeben werden, werden die Ergebnisse in der Reihenfolge zurückgegeben, in der sie aus Datastore abgerufen werden.

Hinweis: Aufgrund der Art, wie Datastore Abfragen ausführt, muss Folgendes beachtet werden: Wenn in einer Abfrage Ungleichheitsfilter für ein Attribut und Sortierfolgen für andere Attribute angegeben sind, muss das Attribut, das mit Ungleichheitsfiltern verwendet wird, vor den anderen Attributen sortiert werden.

Indexe

Bei jeder Datastore-Abfrage werden die Ergebnisse anhand eines oder mehrerer Indexe berechnet, die Entitätsschlüssel in einer von den Indexattributen vorgegebenen Reihenfolge und optional auch Ancestors der Entität enthalten. Diese Indexe werden inkrementell aktualisiert, um die von den Anwendungen durchgeführten Änderungen an den Entitäten widerzuspiegeln und so ohne weitere Berechnung exakte Ergebnisse für alle Abfragen zur Verfügung zu stellen.

App Engine gibt für jede Property einer Entität einen einfachen Index vor. Eine App Engine-Anwendung kann weitere benutzerdefinierte Indexe in der Indexkonfigurationsdatei datastore-indexes.xml definieren, die im Verzeichnis /war/WEB-INF/appengine-generated Ihrer Anwendung generiert wird. Der Entwicklungsserver fügt dieser Datei automatisch Vorschläge hinzu, wenn er Abfragen ermittelt, die mit den vorhandenen Indexen nicht ausgeführt werden können. Sie können die Indexe vor dem Hochladen der Anwendung von Hand optimieren.

Beispiel einer Abfrageschnittstelle

Die Low-Level Java Datastore API bietet die Klasse Query zum Erstellen von Abfragen und die Schnittstelle PreparedQuery zum Abrufen von Entitäten aus Datastore:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Filter heightMaxFilter =
    new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);

// Use CompositeFilter to combine multiple filters
CompositeFilter heightRangeFilter =
    CompositeFilterOperator.and(heightMinFilter, heightMaxFilter);

// Use class Query to assemble a query
Query q = new Query("Person").setFilter(heightRangeFilter);

// Use PreparedQuery interface to retrieve results
PreparedQuery pq = datastore.prepare(q);

for (Entity result : pq.asIterable()) {
  String firstName = (String) result.getProperty("firstName");
  String lastName = (String) result.getProperty("lastName");
  Long height = (Long) result.getProperty("height");

  out.println(firstName + " " + lastName + ", " + height + " inches tall");
}

Beachten Sie die Verwendung von FilterPredicate und CompositeFilter zum Erstellen von Filtern. Wenn Sie nur einen Filter pro Abfrage festlegen möchten, verwenden Sie nur FilterPredicate:

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Query q = new Query("Person").setFilter(heightMinFilter);

Wenn Sie jedoch mehr als einen Filter für eine Abfrage festlegen möchten, müssen Sie CompositeFilter verwenden, wobei mindestens zwei Filter erforderlich sind. Im Beispiel oben wird der Shortcut-Helper CompositeFilterOperator.and verwendet. Im folgenden Beispiel wird eine Möglichkeit gezeigt, einen zusammengesetzten OR-Filter zu erstellen:

Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight);

Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight);

Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter);

Query q = new Query("Person").setFilter(heightOutOfRangeFilter);

Nächste Schritte