中繼資料

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

請注意,結果不包含 Employee 種類的 name 屬性和 Manager 種類的 title 屬性,也不包含 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> 類別的例項,其中包含每個實體屬性 P 的表示法字串,這些實體屬性可在任何 K 類型實體中找到。

請注意,表示法跟屬性類別不同;同時可以有多個屬性類別對應至相同的表示法 (例如,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");
}