Metadata

Datastore bietet programmgesteuerten Zugriff auf einige seiner Metadaten, um die Metaprogrammierung zu unterstützen, Backend-Verwaltungsfunktionen zu implementieren, konsistentes Caching zu vereinfachen und ähnliche Zwecke zu erfüllen. Sie können beispielsweise einen benutzerdefinierten Datastore-Betrachter für Ihre Anwendung erstellen. Die verfügbaren Metadaten umfassen Informationen zu den Entitätsgruppen, Namespaces, Entitätsarten und Attributen, die Ihre Anwendung verwendet, sowie die Attributdarstellungen für die einzelnen Attribute.

Das Datastore-Dashboard in der Google Cloud Console stellt auch einige Metadaten zu Ihrer Anwendung bereit. Die hier angezeigten Daten unterscheiden sich jedoch in einigen wichtigen Bereichen von den Daten, die von diesen Funktionen zurückgegeben werden.

  • Aktualität. Wenn Metadaten mit der API gelesen werden, werden aktuelle Daten abgerufen. Daten im Dashboard werden nur einmal täglich aktualisiert.
  • Inhalt. Einige Metadaten im Dashboard sind über die APIs nicht verfügbar und umgekehrt.
  • Geschwindigkeit. Metadatenabrufe und -abfragen werden wie Datenspeicherabrufe und -abfragen abgerechnet. Die Ausführung von Metadatenabfragen, die Informationen zu Namespaces, Arten und Attributen abrufen, ist im Allgemeinen langsam. Als Faustregel gehen Sie davon aus, dass eine Metadatenabfrage, die n Entitäten zurückgibt, etwa genauso lange dauert wie n normale Abfragen, die jeweils eine einzelne Entität zurückgeben. Außerdem sind Attributdarstellungsabfragen (nicht ausschließlich schlüsselbasierte Attributabfragen) langsamer als ausschließlich schlüsselbasierte Attributabfragen. Abrufe von Metadaten aus Entitätengruppen sind etwas schneller als Abrufe aus regulären Entitäten.

Metadaten aus Entitätengruppen

Cloud Datastore bietet Zugriff auf die "Version" einer Entitätengruppe, eine strikt positive Zahl, die sich bei jeder Änderung der Entitätengruppe garantiert erhöht.

Entitätengruppenversionen werden durch das Aufrufen von get() für eine spezielle Pseudoentität abgerufen, die ein strikt positives __version__-Attribut enthält. Der Schlüssel der Pseudoentität kann mit der Methode Entities.createEntityGroupKey() erstellt werden:

private static long getEntityGroupVersion(DatastoreService ds, Transaction tx, Key entityKey) {
  try {
    return Entities.getVersionProperty(ds.get(tx, Entities.createEntityGroupKey(entityKey)));
  } catch (EntityNotFoundException e) {
    // No entity group information, return a value strictly smaller than any
    // possible version
    return 0;
  }
}

private static void printEntityGroupVersions(DatastoreService ds, PrintWriter writer) {
  Entity entity1 = new Entity("Simple");
  Key key1 = ds.put(entity1);
  Key entityGroupKey = Entities.createEntityGroupKey(key1);

  // Print entity1's entity group version
  writer.println("version " + getEntityGroupVersion(ds, null, key1));

  // Write to a different entity group
  Entity entity2 = new Entity("Simple");
  ds.put(entity2);

  // Will print the same version, as entity1's entity group has not changed
  writer.println("version " + getEntityGroupVersion(ds, null, key1));

  // Change entity1's entity group by adding a new child entity
  Entity entity3 = new Entity("Simple", entity1.getKey());
  ds.put(entity3);

  // Will print a higher version, as entity1's entity group has changed
  writer.println("version " + getEntityGroupVersion(ds, null, key1));
}

Legacy-Verhalten

Im Versionsverhalten der Legacy-Entitätengruppe wird die Version der Entitätengruppe nur bei Änderungen an der Entitätengruppe erhöht. Das Metadatenverhalten der Legacy-Entitätengruppen könnte beispielsweise dazu verwendet werden, einen konsistenten Cache einer komplexen Ancestor-Abfrage für eine Entitätengruppe beizubehalten.

In diesem Beispiel werden Abfrageergebnisse im Cache gespeichert (Anzahl der übereinstimmenden Ergebnisse) und das Legacy-Verhalten von Entitätengruppenversionen verwendet, um den im Cache gespeicherten Wert zu verwenden, wenn er aktuell ist:

// A simple class for tracking consistent entity group counts.
private static class EntityGroupCount implements Serializable {

  long version; // Version of the entity group whose count we are tracking
  int count;

  EntityGroupCount(long version, int count) {
    this.version = version;
    this.count = count;
  }

  // Display count of entities in an entity group, with consistent caching
  void showEntityGroupCount(
      DatastoreService ds, MemcacheService cache, PrintWriter writer, Key entityGroupKey) {
    EntityGroupCount egCount = (EntityGroupCount) cache.get(entityGroupKey);
    // Reuses getEntityGroupVersion method from the previous example.
    if (egCount != null && egCount.version == getEntityGroupVersion(ds, null, entityGroupKey)) {
      // Cached value matched current entity group version, use that
      writer.println(egCount.count + " entities (cached)");
    } else {
      // Need to actually count entities. Using a transaction to get a consistent count
      // and entity group version.
      Transaction tx = ds.beginTransaction();
      PreparedQuery pq = ds.prepare(tx, new Query(entityGroupKey));
      int count = pq.countEntities(FetchOptions.Builder.withLimit(5000));
      cache.put(
          entityGroupKey,
          new EntityGroupCount(getEntityGroupVersion(ds, tx, entityGroupKey), count));
      tx.rollback();
      writer.println(count + " entities");
    }
  }
}

__entity_group__-Entitäten sind möglicherweise nicht für Entitätengruppen vorhanden, in die noch nie geschrieben wurde.

Metadatenabfragen

Die im Paket com.google.appengine.api.datastore definierte Java-Klasse Entities bietet drei spezielle Entitätstarten, die für Metadatenabfragen reserviert sind. Sie werden durch statische Konstanten der Klasse Entities angegeben:

Statische Konstante Entitätsart
Entities.NAMESPACE_METADATA_KIND __namespace__
Entities.KIND_METADATA_KIND __kind__
Entities.PROPERTY_METADATA_KIND __property__

Diese Arten stehen nicht in Konflikt mit anderen Arten derselben Namen, die möglicherweise bereits in Ihrer Anwendung vorhanden sind. Durch Abfragen dieser besonderen Arten können Sie Entitäten abrufen, die die gewünschten Metadaten enthalten.

Die von Metadatenabfragen zurückgegebenen Entitäten werden basierend auf dem aktuellen Status von Datastore dynamisch generiert. Sie können zwar lokale Entity-Objekte der Arten __namespace__, __kind__ oder __property__ erstellen, aber jeder Versuch, sie im Datenspeicher zu speichern, schlägt mit IllegalArgumentException fehl.

Die einfachste Methode zum Ausgeben von Metadatenabfragen ist die Datastore API niedriger Stufe. Im folgenden Beispiel werden die Namen aller Namespaces in einer Anwendung ausgegeben:

void printAllNamespaces(DatastoreService ds, PrintWriter writer) {
  Query q = new Query(Entities.NAMESPACE_METADATA_KIND);

  for (Entity e : ds.prepare(q).asIterable()) {
    // A nonzero numeric id denotes the default namespace;
    // see <a href="#Namespace_Queries">Namespace Queries</a>, below
    if (e.getKey().getId() != 0) {
      writer.println("<default>");
    } else {
      writer.println(e.getKey().getName());
    }
  }
}

Namespace-Abfragen

Wenn Ihre Anwendung die Namespaces API verwendet, können Sie mithilfe einer Namespace-Abfrage alle Namespaces ermitteln, die in den Entitäten der Anwendung verwendet werden. Auf diese Weise können Sie Aktivitäten, wie administrative Funktionen, über mehrere Namespaces hinweg ausführen.

Namespace-Abfragen geben Entitäten der speziellen Art __namespace__ zurück, deren Schlüsselname der Name eines Namespace ist. Eine Ausnahme bildet der Standard-Namespace, der mit dem leeren String "" angegeben wird. Weil der leere String kein gültiger Schlüsselname ist, wird dieser Namespace stattdessen mit der numerischen ID 1 eingegeben. Bei Abfragen dieses Typs wird die Filterung nur für Bereiche über dem speziellen Pseudoattribut __key__ unterstützt, dessen Wert der Schlüssel der Entität ist. Die Ergebnisse können nach aufsteigendem (nicht jedoch nach absteigendem) __key__-Wert sortiert werden. Weil __namespace__-Entitäten keine Attribute haben, geben sowohl Abfragen, die ausschließlich schlüsselbasiert sind, als auch Abfragen, die nicht ausschließlich schlüsselbasiert sind, dieselben Informationen zurück.

Im folgenden Beispiel wird eine Liste der Namespaces einer Anwendung im Bereich zwischen den beiden angegebenen Namen start und end zurückgegeben:

List<String> getNamespaces(DatastoreService ds, String start, String end) {

  // Start with unrestricted namespace query
  Query q = new Query(Entities.NAMESPACE_METADATA_KIND);
  List<Filter> subFilters = new ArrayList();
  // Limit to specified range, if any
  if (start != null) {
    subFilters.add(
        new FilterPredicate(
            Entity.KEY_RESERVED_PROPERTY,
            FilterOperator.GREATER_THAN_OR_EQUAL,
            Entities.createNamespaceKey(start)));
  }
  if (end != null) {
    subFilters.add(
        new FilterPredicate(
            Entity.KEY_RESERVED_PROPERTY,
            FilterOperator.LESS_THAN_OR_EQUAL,
            Entities.createNamespaceKey(end)));
  }

  q.setFilter(CompositeFilterOperator.and(subFilters));

  // Initialize result list
  List<String> results = new ArrayList<String>();

  // Build list of query results
  for (Entity e : ds.prepare(q).asIterable()) {
    results.add(Entities.getNamespaceFromNamespaceKey(e.getKey()));
  }

  // Return result list
  return results;
}

Artabfragen

Artabfragen geben Entitäten der Art __kind__ zurück, deren Schlüsselname der Name einer Entitätsart ist. Abfragen dieses Typs sind implizit auf den aktuellen Namespace begrenzt und unterstützen die Filterung nur für Bereiche über dem Pseudoattribut __key__. Die Ergebnisse können nach aufsteigendem (nicht jedoch nach absteigendem) __key__-Wert sortiert werden. Weil __kind__-Entitäten keine Attribute haben, geben sowohl Abfragen, die ausschließlich schlüsselbasiert sind, als auch Abfragen, die nicht ausschließlich schlüsselbasiert sind, dieselben Informationen zurück.

Im folgenden Beispiel werden alle Arten ausgegeben, deren Namen mit einem Kleinbuchstaben beginnen:

void printLowercaseKinds(DatastoreService ds, PrintWriter writer) {

  // Start with unrestricted kind query
  Query q = new Query(Entities.KIND_METADATA_KIND);

  List<Filter> subFils = new ArrayList();

  // Limit to lowercase initial letters
  subFils.add(
      new FilterPredicate(
          Entity.KEY_RESERVED_PROPERTY,
          FilterOperator.GREATER_THAN_OR_EQUAL,
          Entities.createKindKey("a")));

  String endChar = Character.toString((char) ('z' + 1)); // Character after 'z'

  subFils.add(
      new FilterPredicate(
          Entity.KEY_RESERVED_PROPERTY,
          FilterOperator.LESS_THAN,
          Entities.createKindKey(endChar)));

  q.setFilter(CompositeFilterOperator.and(subFils));

  // Print heading
  writer.println("Lowercase kinds:");

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println("  " + e.getKey().getName());
  }
}

Attributabfragen

Attributabfragen geben Entitäten der Art __property__ zurück. Diese kennzeichnen die indexierten Attribute, die mit einer Entitätsart verknüpft sind. Die Entität zur Darstellung von Attribut P der Art K wird so erstellt:

  • Der Entitätsschlüssel hat die Art __property__ und den Schlüsselnamen P.
  • Der Schlüssel der übergeordneten Entität ist vom Typ __kind__ und hat den Schlüsselnamen K.

Das Verhalten einer Attributabfrage hängt davon ab, ob es sich um eine ausschließlich schlüsselbasierte oder um eine nicht ausschließlich schlüsselbasierte (Attributsdarstellung) Abfrage handelt, wie in den folgenden Unterabschnitten beschrieben.

Attributabfragen: ausschließlich schlüsselbasiert

Ausschließlich schlüsselbasierte Attributabfragen geben für jedes indexierte Attribut einer angegebenen Entitätsart einen Schlüssel zurück. Nicht indexierte Attribute werden nicht einbezogen. Im folgenden Beispiel werden die Namen aller Entitätsarten einer Anwendung und die mit jeder Art verknüpften Attribute ausgegeben:

void printProperties(DatastoreService ds, PrintWriter writer) {

  // Create unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName());
  }
}

Abfragen dieser Art sind implizit auf den aktuellen Namespace beschränkt und unterstützen nur das Filtern für Bereiche oberhalb des Pseudoattributs __key__, wobei die Schlüssel __kind__- oder __property__-Entitäten kennzeichnen. Die Ergebnisse können nach aufsteigendem (nicht jedoch nach absteigendem) __key__-Wert sortiert werden. Die Filterung wird auf Art/Attribut-Paare angewendet, die zuerst nach Art und dann nach Attribut sortiert sind. Angenommen, Sie haben eine Entität mit den folgenden Attributen:

  • Art Account mit Attributen
    • balance
    • company
  • Art Employee mit Attributen
    • name
    • ssn
  • Art Invoice mit Attributen
    • date
    • amount
  • Art Manager mit Attributen
    • name
    • title
  • Art Product mit Attributen
    • description
    • price

Die Abfrage zum Zurückgeben der Attributdaten würde so aussehen:

void printPropertyRange(DatastoreService ds, PrintWriter writer) {

  // Start with unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Limit range
  q.setFilter(
      CompositeFilterOperator.and(
          new FilterPredicate(
              Entity.KEY_RESERVED_PROPERTY,
              Query.FilterOperator.GREATER_THAN_OR_EQUAL,
              Entities.createPropertyKey("Employee", "salary")),
          new FilterPredicate(
              Entity.KEY_RESERVED_PROPERTY,
              Query.FilterOperator.LESS_THAN_OR_EQUAL,
              Entities.createPropertyKey("Manager", "salary"))));
  q.addSort(Entity.KEY_RESERVED_PROPERTY, SortDirection.ASCENDING);

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName());
  }
}

Bei der oben aufgeführten Abfrage würde Folgendes zurückgegeben:

Employee: ssn
Invoice: date
Invoice: amount
Manager: name

Beachten Sie, dass die Ergebnisse weder das name-Attribut der Art Employee und das title-Attribut der Art Manager noch die Attribute der Arten Account und Product enthalten, weil sie außerhalb des für die Abfrage angegebenen Bereichs liegen.

Attributabfragen unterstützen auch die Ancestor-Filterung nach einem __kind__- oder __property__-Schlüssel, um die Abfrageergebnisse auf eine einzige Art oder ein einziges Attribut zu beschränken. Sie können diese Filterung beispielsweise verwenden, um die Attribute abzurufen, die einer bestimmten Entitätsart zugeordnet sind, wie im folgenden Beispiel gezeigt:

List<String> propertiesOfKind(DatastoreService ds, String kind) {

  // Start with unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Limit to specified kind
  q.setAncestor(Entities.createKindKey(kind));

  // Initialize result list
  ArrayList<String> results = new ArrayList<String>();

  //Build list of query results
  for (Entity e : ds.prepare(q).asIterable()) {
    results.add(e.getKey().getName());
  }

  // Return result list
  return results;
}

Attributabfragen: nicht ausschließlich schlüsselbasiert (Attributdarstellung)

Nicht ausschließlich schlüsselbasierte Attributabfragen, die als Attributdarstellungsabfragen bezeichnet werden, liefern zusätzliche Informationen zu den Darstellungen, die für jedes Art/Attribut-Paar verwendet werden. Nicht indexierte Attribute werden nicht einbezogen. Die Entität, die für das Attribut P der Art K zurückgegeben wird, hat denselben Schlüssel wie für eine entsprechende ausschließlich schlüsselbasierte Abfrage sowie ein weiteres property_representation-Attribut, das die Darstellungen des Attributs zurückgibt. Der Wert dieses Attributs ist eine Instanz der Klassejava.util.Collection<String>, das alle Darstellungen des Attributs von P in einer Entität der Art K enthält.

Beachten Sie, dass Darstellungen nicht mit Attributklassen identisch sind. Mehrere Attributklassen können derselben Darstellung zugeordnet sein. Beispiel: java.lang.String und com.google.appengine.api.datastore.PhoneNumber verwenden beide die STRING-Darstellung.

Die folgende Tabelle enthält die Zuordnungen von Attributklassen zu ihren Darstellungen:

Attributklasse Darstellung
java.lang.Byte INT64
java.lang.Short INT64
java.lang.Integer INT64
java.lang.Long INT64
java.lang.Float DOUBLE
java.lang.Double DOUBLE
java.lang.Boolean BOOLEAN
java.lang.String STRING
com.google.appengine.api.datastore.ShortBlob STRING
java.util.Date INT64
com.google.appengine.api.datastore.GeoPt POINT
com.google.appengine.api.datastore.PostalAddress STRING
com.google.appengine.api.datastore.PhoneNumber STRING
com.google.appengine.api.datastore.Email STRING
com.google.appengine.api.users.User USER
com.google.appengine.api.datastore.IMHandle STRING
com.google.appengine.api.datastore.Link STRING
com.google.appengine.api.datastore.Category STRING
com.google.appengine.api.datastore.Rating INT64
com.google.appengine.api.datastore.Key REFERENCE
com.google.appengine.api.blobstore.BlobKey STRING
java.util.Collection<T> Darstellung von T

Im folgenden Beispiel werden alle Darstellungen einer angegebenen Property für eine bestimmte Entitätsart ermittelt:

Collection<String> representationsOfProperty(DatastoreService ds, String kind, String property) {

  // Start with unrestricted non-keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND);

  // Limit to specified kind and property
  q.setFilter(
      new FilterPredicate(
          "__key__", Query.FilterOperator.EQUAL, Entities.createPropertyKey(kind, property)));

  // Get query result
  Entity propInfo = ds.prepare(q).asSingleEntity();

  // Return collection of property representations
  return (Collection<String>) propInfo.getProperty("property_representation");
}