中繼資料

Cloud Datastore 針對其部分中繼資料提供程式化的存取機制,進而支援元程式設計、實作後端管理函式、簡化一致性快取等類似目的;舉例來說,您可以將其用於為應用程式建構自訂 Cloud Datastore 檢視器。這些中繼資料包括實體群組、命名空間、實體種類、應用程式使用的屬性,以及各個屬性的屬性表示法等相關資訊。

另外在 GCP 主控台的 Cloud Datastore 資訊主頁也提供應用程式的部分中繼資料,但該處顯示的資料與這些函式傳回的資料在某些重要層面有所不同。

  • 時效性: 使用 API 讀取中繼資料會傳回最新資料,資訊主頁中的資料每天只更新一次。
  • 內容: 無法透過 API 讀取資訊主頁中的某些中繼資料;反之亦然。
  • 速度: 中繼資料的取得與查詢作業計費方式與 Cloud Datastore 取得與查詢作業相同。擷取命名空間、種類及屬性相關資訊的中繼資料查詢作業,執行速度通常較慢。根據經驗法則,一項中繼資料查詢傳回 N 個實體所需的時間,大約相當於 N 項一般查詢各自傳回單一實體所需的時間。再者,屬性表示法查詢 (非純金鑰屬性查詢) 的速度比純金鑰屬性查詢慢。實體群組中繼資料的取得中繼資料作業,比取得一般實體更快。

實體群組中繼資料

Cloud Datastore 提供實體群組的「版本」存取,每次變更實體群組,僅為正值的版本數字一定會增加。

對於包含僅為正值之 __version__ 屬性的特殊虛擬實體呼叫 get(),可取得實體群組版本。虛擬實體的金鑰可以使用 Entities.createEntityGroupKey() 方法建立:

Java 8

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

Java 7

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

舊版行為

在舊版實體群組版本行為中,實體群組的版本數字僅會隨實體群組的變更而增加。舉例來說,舊版實體群組中繼資料行為可用來確保實體群組的複雜祖系查詢快取的一致性。

以下範例會快取查詢結果 (相符結果數),並藉由實體群組版本的舊版行為來使用最新的快取值:

Java 8

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

Java 7

// 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__

這些種類不會與應用程式中可能已存在的同名種類發生衝突。您可以藉由查詢這些特殊種類,而擷取包含所需中繼資料的實體。

中繼資料查詢傳回的實體會根據 Cloud Datastore 的目前狀態而動態產生。雖然您可以建立種類為 __namespace____kind____property__ 的本機 Entity 物件,但嘗試將這些物件儲存於 Cloud Datastore 時會發生 IllegalArgumentException 的失敗。

發出中繼資料查詢最簡單的方法是使用低階 Datastore API。以下範例會輸出應用程式中所有命名空間的名稱:

Java 8

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

Java 7

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 兩個指定名稱之間的範圍內,應用程式命名空間的清單:

Java 8

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

Java 7

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__ 實體沒有任何屬性,所以僅限金鑰和非僅限金鑰的查詢都會傳回相同的資訊。

以下範例可輸出名稱開頭為小寫字母的所有種類:

Java 8

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

Java 7

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」

屬性查詢的行為取決於其為僅限金鑰非僅限金鑰 (屬性表示法) 的查詢,詳細說明如以下子節所述。

屬性查詢:僅限金鑰

「僅限金鑰的屬性查詢」會針對指定實體種類的各個已建立索引的屬性傳回金鑰 (不含未建立索引的屬性)。以下範例會輸出應用程式所有實體種類的名稱,以及與各個實體種類相關屬性的名稱:

Java 8

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

Java 7

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

要傳回屬性資料的查詢應如下所示:

Java 8

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

Java 7

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__ 金鑰的祖系篩選作業,藉此將查詢結果限制為單一種類或屬性。舉例來說,您可以使用屬性查詢取得與指定實體種類相關的屬性,如以下範例所示:

Java 8

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

Java 7

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 的表示法

以下範例可找出特定實體種類指定屬性的所有表示法:

Java 8

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

Java 7

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");
}
本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Java 8 適用的 App Engine 標準環境