實體、屬性和金鑰

Cloud Datastore 中的資料物件又稱為「實體」。實體具有一或多個命名的「屬性」,每個屬性可以有一或多個值。相同種類的實體不需要具有相同屬性,而實體的特定屬性值並不全都需要屬於相同的資料類型 (如有必要,應用程式可在本身的資料模型建立及強制執行這類限制)。

Cloud Datastore 支援屬性值的各種資料類型。這些資料類型列舉如下:

  • 整數
  • 浮點數
  • 字串
  • 日期
  • 二進位資料

如需這些類型的完整清單,請參閱屬性和值類型一節。

Cloud Datastore 中每個實體都有專屬識別「索引鍵」。金鑰由下列元件組成:

  • 實體的「命名空間」,可允許多租戶架構
  • 實體的「種類」,可將實體分類以便進行 Cloud Datastore 查詢
  • 個別實體的「ID」,可以是以下其中一種:
    • 「索引鍵名稱」字串
    • 整數「數字 ID」
  • 選用的「祖系路徑」,可將實體置於 Cloud Datastore 階層之中

應用程式可使用實體的金鑰從 Cloud Datastore 擷取個別實體,或依據實體的金鑰或屬性值發布「查詢」,以擷取一或多個實體。

Java App Engine SDK 在直接支援 Cloud Datastore 功能的 com.google.appengine.api.datastore 套件中提供簡易的 API。本文件中的所有範例均以這個低階 API 為基礎;您可以選擇在應用程式中直接使用這個 API,或以其為基礎來建置自己的資料管理層。

Cloud Datastore 本身不會對實體結構強制執行任何限制,例如指定屬性是否具有特定類型的值;這項工作將由應用程式負責。

種類及 ID

每個 Cloud Datastore 實體都屬於特定「種類」,可將實體分類以便查詢。舉例來說,人力資源應用程式可能會以 Employee 種類的實體代表公司的每位員工。您在 Java Datastore API 中建立實體時,可透過 Entity() 建構函式的引數來指定實體的種類。以兩條底線 (__) 為開頭的所有種類名稱均保留不得使用。

以下示例會建立 Employee 種類的實體、填入屬性值,並將其儲存至 Datastore:

Entity employee = new Entity("Employee", "asalieri");
employee.setProperty("firstName", "Antonio");
employee.setProperty("lastName", "Salieri");
employee.setProperty("hireDate", new Date());
employee.setProperty("attendedHrTraining", true);

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
datastore.put(employee);

除了種類以外,每個實體在建立時都會獲指派「ID」。因為 ID 屬於實體索引鍵的一部分,所以會與實體建立永久的關聯,且無法變更。ID 可利用兩種方式指派:

  • 您的應用程式可以指定自身的實體「金鑰名稱」字串。
  • 您可讓 Cloud Datastore 自動指派整數的「數字 ID」給實體。

如要指派索引鍵名稱給實體,請在建立實體時,將名稱當做第二個引數提供給建構函式:

Entity employee = new Entity("Employee", "asalieri");

如要讓 Cloud Datastore 自動指派數字 ID,請省略這個引數:

Entity employee = new Entity("Employee");

指派 ID

Cloud Datastore 可使用兩種不同的自動 ID 政策設定自動產生 ID:

  • default 政策可產生隨機順序的未使用 ID,近乎均勻分佈。每個 ID 最長可達 16 位數。
  • legacy 政策可建立一系列非連續的較小整數 ID。

如果您要向使用者顯示實體 ID,並/或依據其順序顯示,最理想的方式就是使用手動分配。

Cloud Datastore 會產生隨機順序的未使用 ID,近乎均勻分佈。每個 ID 最長可達 16 個十進位數字。

系統分配的 ID 值保證為實體群組獨有。如果您由一個實體群組或命名空間將實體複製到其他實體群組或命名空間,並希望保留索引鍵的 ID 部分,請務必先分配 ID,避免讓 Cloud Datastore 未來指派時選擇這個 ID。

祖系路徑

Cloud Datastore 中的實體會形成階層結構空間,與檔案系統的目錄結構相似。建立實體時,可選擇將其他實體指定為「父項」;新實體則為父系實體的「子項」(請注意,不同於檔案系統,父項實體不需要實際存在)。沒有父系的實體則是「根實體」。 實體與父項實體之間具有永久關聯性,一旦實體建立後就無法變更。Cloud Datastore 絕對不會將相同的數字 ID 指派給父項相同的兩個實體,也不會指派給兩個根實體 (即沒有父項的實體)。

實體的父項、父項的父項等以此類推,全都是這個實體的「祖系」;實體的子項、子項的子項等等,則都是其「子系」。 根實體及所有子系都屬於相同的「實體群組」。 從根實體開始,再從父項到子項,最後到指定實體的實體序列,即構成該實體的「祖系路徑」。 完整金鑰代表包含種類-ID 組合的實體,該組合指定其祖系路徑,並且止於實體本身的這類組合:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

根實體的祖系路徑是空白路徑,其金鑰只包含實體本身的種類與 ID。

[Person:GreatGrandpa]

本概念以下圖說明:

顯示實體群組中根實體與子實體的關係

如要指定實體的父項,請在建立子系實體時,提供父系實體的金鑰做為 Entity() 建構函式的引數。您可以呼叫父系實體的 getKey() 方法來取得金鑰:

Entity employee = new Entity("Employee");
datastore.put(employee);

Entity address = new Entity("Address", employee.getKey());
datastore.put(address);

如果新實體也擁有索引鍵名稱,請提供索引鍵名稱做為 Entity() 建構函式的第二個引數,並提供父系實體的索引鍵做為第三個引數:

Entity address = new Entity("Address", "addr1", employee.getKey());

交易和實體群組

每次嘗試建立、更新或刪除實體時,都是在「交易」的背景下進行。單一交易可包含任何數量的前述作業。為了維持資料一致性,交易會確保其中的所有作業會以單元方式套用至 Cloud Datastore;如有任何作業失敗,則全部作業均不會套用。此外,在相同交易內執行的所有同步一致性讀取作業 (祖系查詢或是「get」) 將出現一致的資料快照。

如前所述,實體群組是一組實體,透過祖系連接至共同根元素。將資料組織為實體群組,可限制執行的交易:

  • 一項交易所存取的所有資料,最多只能存在於 25 個實體群組中。
  • 如果您想在交易中使用查詢,必須將資料歸納成實體群組,才可指定能夠比對出正確資料的祖系篩選條件。
  • 單一實體群組的寫入總處理量大約是每秒一次交易。這項限制是因為 Cloud Datastore 會在廣大的地理區域範圍內,針對各個實體群組執行對等同步的複製,以提供高可靠性及容錯。

在許多應用程式中,可在廣泛檢視無關聯資料時使用最終一致性 (亦即涵蓋多個實體群組的非祖系查詢,有時可能傳回稍微過時的資料),然後在檢視或編輯單組高度關聯資料時使用同步一致性 (祖系查詢或單一實體的 get)。在這類應用程式中,通常適合針對每組高度關聯資料使用獨立實體群組。詳情請參閱建立同步一致性結構的說明。

屬性和值類型

與實體相關聯的資料值由一或多個「屬性」組成。 每個屬性都有一個名稱及一或多個值。一個屬性可能會有多個類型的值,而兩個實體的相同屬性可能會有不同類型的值。屬性可能已建立索引或未建立索引 (排序或篩選屬性「P」的查詢將忽略「P」未建立索引的實體)。一個實體最多可有 20,000 個已建立索引的屬性。

支援的值類型如下:

值類型 Java 類型 排序順序 附註
整數 short
int
long
java.lang.Short
java.lang.Integer
java.lang.Long
數字 以長整數形式儲存,然後轉換為欄位類型

超出範圍值溢出
浮點數 float
double
java.lang.Float
java.lang.Double
數字 64 位元雙精準度,
IEEE 754
布林值 boolean
java.lang.Boolean
false<true
文字字串 (短) java.lang.String Unicode 最多 1500 個位元組

超過 1500 個位元組的值會擲回 IllegalArgumentException
文字字串 (長) com.google.appengine.api.datastore.Text 最多 1 MB

未建立索引
位元組字串 (短) com.google.appengine.api.datastore.ShortBlob 位元組順序 最多 1500 個位元組

超過 1500 個位元組的值會擲回 IllegalArgumentException
位元組字串 (長) com.google.appengine.api.datastore.Blob 最多 1 MB

未建立索引
日期與時間 java.util.Date 依時間順序
地理點 com.google.appengine.api.datastore.GeoPt 依照緯度、
接著經度
郵遞地址 com.google.appengine.api.datastore.PostalAddress Unicode
電話號碼 com.google.appengine.api.datastore.PhoneNumber Unicode
電子郵件地址 com.google.appengine.api.datastore.Email Unicode
Google 帳戶使用者 com.google.appengine.api.users.User 電子郵件地址
依 Unicode 順序
即時通訊控點 com.google.appengine.api.datastore.IMHandle Unicode
連結 com.google.appengine.api.datastore.Link Unicode
類別 com.google.appengine.api.datastore.Category Unicode
評分 com.google.appengine.api.datastore.Rating 數字
Cloud Datastore 索引鍵 com.google.appengine.api.datastore.Key
或參考物件 (當做子項)
依路徑元素
(種類、ID、
種類、ID...)
最多 1500 個位元組

超過 1500 個位元組的值會擲回 IllegalArgumentException
Blobstore 索引鍵 com.google.appengine.api.blobstore.BlobKey 位元組順序
嵌入實體 com.google.appengine.api.datastore.EmbeddedEntity 未建立索引
空值 null

重要事項:強烈建議您避免將 users.User 當做屬性值儲存,因為其中含有電子郵件地址和唯一識別碼。如果使用者變更電子郵件地址,但您還是使用先前儲存的 user.User 來比對新的 user.User 值時,兩者將無法配對。請改為採用 User 的「使用者 ID 值」做為使用者的固定唯一識別碼。

Cloud Datastore 針對文字字串和未編碼的二進位資料 (位元組字串) 支援兩種值類型:

  • 短字串 (不超過 1500 個位元組) 會建立索引,可用於查詢篩選器條件與排序順序。
  • 長字串 (最多 1 MB) 不會建立索引,也不能用於查詢篩選器和排序順序。
注意事項:長位元組字串類型在 Cloud Datastore API 中稱為「Blob」,這個類型與 Blobstore API 使用的 blob 無關。

當查詢的屬性具有混和類型的值時,Cloud Datastore 會根據內部表示法來決定排序:

  1. 空值
  2. 固定點數
    • 整數
    • 日期和時間
    • 評分
  3. 布林值
  4. 位元組序列
    • 位元組字串
    • Unicode 字串
    • Blobstore 索引鍵
  5. 浮點數
  6. 地理點
  7. Google 帳戶使用者
  8. Cloud Datastore 索引鍵

長文字字串、長位元組字串和嵌入實體不會建立索引,因此未定義排序。

處理實體

應用程式可使用 Cloud Datastore API 建立、擷取、更新及刪除實體。如果應用程式知道實體的完整索引鍵 (或由其父項索引鍵、種類及 ID 衍生),就可利用索引鍵直接在實體作業。應用程式也可依據 Cloud Datastore 查詢結果取得實體索引鍵;詳情請參閱 Datastore 查詢頁面。

Java Datastore API 會使用 DatastoreService 介面的方法處理實體。您可藉由呼叫 DatastoreServiceFactory.getDatastoreService() 靜態方法來取得 DatastoreService 物件:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

建立實體

建立新實體的方法如下:建構 Entity 類別的執行個體,然後將實體的種類當做引數提供給 Entity() 建構函式。

視需求填入實體的屬性後,將實體以引數的形式傳送給 DatastoreService.put() 方法,即可在資料儲存庫中儲存實體。您可以將實體的索引鍵名稱當做第二個引數傳送給建構函式,藉此為實體指定索引鍵名稱。

Entity employee = new Entity("Employee", "asalieri");
// Set the entity properties.
// ...
datastore.put(employee);

如果您未提供索引鍵名稱,Cloud Datastore 會自動產生實體索引鍵的數字 ID:

Entity employee = new Entity("Employee");
// Set the entity properties.
// ...
datastore.put(employee);

擷取實體

如要擷取特定金鑰指定的實體,請將 Key 物件傳送至 DatastoreService.get() 方法:

// Key employeeKey = ...;
Entity employee = datastore.get(employeeKey);

更新實體

如要更新現有實體,請修改實體物件的屬性,然後將物件傳送至 DatastoreService.put() 方法。物件資料會覆寫現有實體,每次呼叫 put() 時,系統會將整個物件傳送至 Cloud Datastore。

刪除實體

如果某個實體的金鑰已存在,您可以透過 DatastoreService.delete() 方法刪除該實體:

// Key employeeKey = ...;
datastore.delete(employeeKey);

重複屬性

您可以在單一屬性中儲存多個值。

Entity employee = new Entity("Employee");
ArrayList<String> favoriteFruit = new ArrayList<String>();
favoriteFruit.add("Pear");
favoriteFruit.add("Apple");
employee.setProperty("favoriteFruit", favoriteFruit);
datastore.put(employee);

// Sometime later
employee = datastore.get(employee.getKey());
@SuppressWarnings("unchecked") // Cast can't verify generic type.
    ArrayList<String> retrievedFruits = (ArrayList<String>) employee
    .getProperty("favoriteFruit");

嵌入實體

有時您會發現將實體當做另一個實體的屬性來嵌入是一種方便的做法。比方說,這種做法很適合用於在實體中建立屬性值的階層結構。Java 類別 EmbeddedEntity 可讓您執行這項操作:

// Entity employee = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678");
embeddedContactInfo.setProperty("phoneNumber", "555-555-5555");
embeddedContactInfo.setProperty("emailAddress", "test@example.com");

employee.setProperty("contactInfo", embeddedContactInfo);

如果索引中包含嵌入實體,您就可以查詢子屬性。如果索引排除了嵌入實體,則會一併將所有子屬性排除於在外。您可以選擇建立索引鍵與嵌入實體的關聯,但 (和完善的實體不同) 索引鍵並非必要,而且即使有索引鍵,也不能用來擷取實體。

您可以使用 setPropertiesFrom() 方法從現有實體複製嵌入實體的屬性,而不是手動填入這些屬性:

// Entity employee = ...;
// Entity contactInfo = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setKey(contactInfo.getKey()); // Optional, used so we can recover original.
embeddedContactInfo.setPropertiesFrom(contactInfo);

employee.setProperty("contactInfo", embeddedContactInfo);

您稍後可以使用相同的方法從嵌入實體復原原本的實體:

Entity employee = datastore.get(employeeKey);
EmbeddedEntity embeddedContactInfo = (EmbeddedEntity) employee.getProperty("contactInfo");

Key infoKey = embeddedContactInfo.getKey();
Entity contactInfo = new Entity(infoKey);
contactInfo.setPropertiesFrom(embeddedContactInfo);

批次作業

DatastoreServiceput()get()delete() 方法 (及其 AsyncDatastoreService 對應項目) 均有批次版本,可接受可疊代物件 (屬於 put() 的類別 Entityget()delete() 的類別 Key),並用於單一 Cloud Datastore 呼叫中對多個實體執行作業:

Entity employee1 = new Entity("Employee");
Entity employee2 = new Entity("Employee");
Entity employee3 = new Entity("Employee");
// ...

List<Entity> employees = Arrays.asList(employee1, employee2, employee3);
datastore.put(employees);

這些批次作業會將所有實體或索引鍵分組為實體群組,然後在每個實體群組上並行執行要求的作業。這類批次呼叫僅會產生單次服務呼叫的負擔,因此比個別呼叫每個實體的速度還快。如果涉及多個實體群組,則會在伺服器端針對所有群組並行執行作業。

產生金鑰

應用程式可以使用類別 KeyFactory,藉由實體種類與 ID 等已知元件,為實體建立 Key 物件。如果實體沒有父項,請將種類與 ID (金鑰名稱字串或數字 ID) 傳送至靜態方法 KeyFactory.createKey(),藉以建立金鑰。以下範例會為種類為 Person 且金鑰名稱為 "GreatGrandpa" 或數字 ID 為 74219 的實體建立金鑰:

Key k1 = KeyFactory.createKey("Person", "GreatGrandpa");
Key k2 = KeyFactory.createKey("Person", 74219);

如果金鑰包含路徑元件,您可以使用輔助類別 KeyFactory.Builder 建構路徑。這個類別的 addChild 方法可將單一實體新增至路徑,並傳回建構函式本身,讓您可以將一系列呼叫鏈結在一起,從根實體開始,一次為一個實體建構路徑。建構完整路徑之後,請呼叫 getKey 以擷取產生的金鑰:

Key k =
    new KeyFactory.Builder("Person", "GreatGrandpa")
        .addChild("Person", "Grandpa")
        .addChild("Person", "Dad")
        .addChild("Person", "Me")
        .getKey();

KeyFactory 類別還包含靜態方法 keyToStringstringToKey,用來在金鑰及其字串表示法之間進行轉換:

String personKeyStr = KeyFactory.keyToString(k);

// Some time later (for example, after using personKeyStr in a link).
Key personKey = KeyFactory.stringToKey(personKeyStr);
Entity person = datastore.get(personKey);

金鑰的字串表示法為「可在網路上安全使用」:這種表示法並不包含在 HTML 或網址中被視為特殊的字元。

使用空白清單

Cloud Datastore 之前沒有可適當表示空白清單的表示法。Java SDK 的解決方式是將空白集合儲存為空值,但也因此無法區分空值和空白清單。為了維持回溯相容性,會繼續以這種方法做為預設行為,運作方式摘要如下:

  • 空值屬性以空值寫入 Cloud Datastore
  • 空白集合以空值寫入 Cloud Datastore
  • 從 Cloud Datastore 讀取的空值會被當做空值
  • 空白集合會被當成空值讀取。

但是,如果您變更預設行為,Java 適用的 SDK 就會支援儲存空白清單。建議您對變更應用程式預設行為一事深思熟慮之後,再開啟空白清單的支援

如果要變更預設行為以能使用空白清單,請在應用程式初始化期間,按照下列方式設定 DATASTORE_EMPTY_LIST_SUPPORT 屬性:

System.setProperty(DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT, Boolean.TRUE.toString());

將此屬性如上所示設為 true 之後,運作方式如下:

  • 空值屬性以空值寫入 Cloud Datastore
  • 空白集合以空白清單寫入 Cloud Datastore
  • 從 Cloud Datastore 讀取的空值會被當做空值
  • 從 Cloud Datastore 讀取時,會以空白集合傳回空白清單。
本頁內容對您是否有任何幫助?請提供意見:

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

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