元数据

Datastore 能够以编程方式访问部分元数据以支持元编程,从而实现后端管理功能、简化一致性缓存以及实现类似的目标;例如,您可以使用该功能为应用构建自定义 Datastore 查看器。可用的元数据包括应用所用的实体组、命名空间、实体种类和属性的相关信息,以及每个属性的属性表示法

Google Cloud 控制台中的 Datastore 信息中心还提供一些有关应用的元数据,但其中显示的数据在某些重要方面与这些函数返回的数据有所不同。

  • 时效性。信息中心内的数据每天只更新一次,而使用 API 读取元数据会获取当前数据。
  • 内容。信息中心内的某些元数据不能通过 API 获得;反之亦然。
  • 速度。元数据获取和查询的计费方式与 Datastore 获取和查询相同。元数据查询用于获取命名空间、种类和属性的相关信息,但执行起来通常较慢。根据经验,返回 N 个实体的元数据查询预计与各返回单个实体的 N 个普通查询的时间大致相同。此外,属性表示法查询(非仅限于键的属性查询)比仅限于键的属性查询速度要慢。对实体组元数据进行的元数据获取比获取常规实体要快一些。

实体组元数据

Cloud Datastore 提供实体组“版本”信息。版本必定是正数,且随实体组的每次更改而提高。

对包含正数 __version__ 属性的特殊伪实体进行 get() 调用可获得实体组版本信息。伪实体的键可以使用 Entities.createEntityGroupKey() 方法创建:

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

旧版行为

在旧版的实体组版本行为中,实体组版本仅在实体组发生更改时才会提高。旧版实体组元数据行为可用于多种用途,例如,可以始终如一地缓存对实体组的复杂祖先查询。

以下示例缓存查询结果(匹配结果的计数),并通过实体组版本的旧版行为来使用当前的缓存值:

// 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__ 实体。

元数据查询

com.google.appengine.api.datastore 软件包中定义的 Java 类 Entities 提供了三种为元数据查询预留的特殊实体种类。它们通过 Entities 类的静态常量表示:

静态常量 实体种类
Entities.NAMESPACE_METADATA_KIND __namespace__
Entities.KIND_METADATA_KIND __kind__
Entities.PROPERTY_METADATA_KIND __property__

这些种类不会与应用中可能已存在的其他同名种类发生冲突。通过查询这些特殊种类,您可以检索包含所需元数据的实体。

元数据查询返回的实体是根据 Datastore 的当前状态动态生成的。虽然您可以创建种类为 __namespace____kind____property__ 的本地 Entity 对象,但任何将它们存储在 Datastore 中的尝试都将失败,并抛出 IllegalArgumentException 异常。

发出元数据查询最简单的方法是使用低层级 Datastore API。以下示例输出应用中所有命名空间的名称:

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

命名空间查询

如果您的应用使用 Namespaces API,则您可以使用命名空间查询来查找应用实体中所用的所有命名空间。这样您就可以跨多个命名空间执行管理功能等活动。

命名空间查询返回特殊种类 __namespace__ 的实体,其键名为命名空间的名称。(由空字符串 "" 指定的默认命名空间例外:由于空字符串不是有效的键名,因此此命名空间改为以数字 ID 1 为键。)此类查询支持通过特殊伪属性 __key__ 仅对范围进行过滤,该伪属性的值就是实体的键。结果可以按 __key__ 值升序(但不能降序)排序。由于 __namespace__ 实体没有属性,仅限于键的查询和非仅限于键的查询返回的信息相同。

以下示例返回应用的命名空间列表,其范围介于 startend 两个指定名称之间:

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

种类查询

种类查询返回种类为 __kind__ 的实体,其键名为实体种类的名称。此类型的查询隐式地限制于当前命名空间,并且仅支持对 __key__ 伪属性的范围进行过滤。结果可以按 __key__ 值升序(但不能降序)排序。由于 __kind__ 实体没有属性,仅限于键的查询和非仅限于键的查询返回的信息相同。

以下示例输出名称以小写字母开头的所有种类:

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

属性查询

属性查询返回种类为 __property__ 的实体,该实体表示与某实体种类关联的属性。代表种类 K 的属性 P 的实体按以下方式构建:

  • 对于该实体的键,种类为 __property__,键名为 p
  • 父实体的键具有种类 __kind__ 和键名 K

属性查询的行为取决于它是仅限于键还是非仅限于键(属性表示法)的查询,如下文的各个子部分中所述。

属性查询:仅限于键

仅限于键的属性查询会为指定实体种类的每个编入索引的属性返回一个键(不包括未编入索引的属性)。以下示例将输出应用所有实体种类的名称以及与各种类相关联的属性:

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

此类型的查询隐式地限制于当前命名空间,并且仅支持对伪属性 __key__ 的范围进行过滤,在这种情况下,键表示 __kind____property__ 实体。结果可以按 __key__ 值升序(但不能降序)排序。过滤适用于“种类属性对”,先按种类排序,再按属性排序:例如,假定您有一个具有以下属性的实体:

  • 种类 Account,其属性为:
    • balance
    • company
  • 种类 Employee,其属性为:
    • name
    • ssn
  • 种类 Invoice,其属性为:
    • date
    • amount
  • 种类 Manager,其属性为:
    • name
    • title
  • 种类 Product,其属性为:
    • description
    • price

返回属性数据的查询会如下所示:

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

以上查询会返回以下结果:

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

请注意,结果既不包括种类 Employeename 属性和种类 Managertitle 属性,也不包括种类 AccountProduct 的任何属性,因为这些属性不在为查询指定的范围内。

属性查询还支持对 __kind____property__ 键进行祖先过滤,以将查询结果限为单一种类或属性。例如,您可以借助这一点获取与给定的实体种类关联的属性,如以下示例所示:

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

属性查询:非仅限于键(属性表示法)

非仅限于键的属性查询称为“属性表示法查询”,可返回各“种类属性对”所用表示法的相关额外信息(不包括未编入索引的属性)。为种类为 K 的属性 P 返回的实体具有的键与相应仅限于键的查询返回的键相同,同时具有返回属性表示法的额外 property_representation 属性。此属性的值是类 java.util.Collection<String> 的实例,对于在种类为 K 的任何实体中找到的属性 P 的每个表示法,这个类都包含一个对应的字符串。

请注意,表示法与属性类不同;多个属性类可以对应同一种表示法。(例如,java.lang.Stringcom.google.appengine.api.datastore.PhoneNumber 都使用 STRING 表示法。)

属性类与其表示法的对应关系如下表所示:

属性类 表示法
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> T 的表示法

以下示例查找给定实体种类特定属性的所有表示法:

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