エンティティ、プロパティ、キー

Datastore のデータ オブジェクトをエンティティと呼びます。エンティティは 1 つ以上の名前付きプロパティを持ち、各プロパティが 1 つ以上の値を持ちます。同じ種類のエンティティが同じプロパティを持つとは限りません。また、エンティティの特定のプロパティの値がすべて同じデータ型である必要はありません(必要に応じて、アプリケーション独自のデータモデルでこのような制限を確立して強制できます)。

Datastore では、プロパティ値でさまざまなデータ型がサポートされています。サポートしているデータ型の例を次に示します。

  • 整数
  • 浮動小数点数
  • 文字列
  • 日付
  • バイナリデータ

すべての型の一覧は、プロパティと値の型をご覧ください。

Datastore 内の各エンティティは、キーによって一意に識別されます。キーは次のコンポーネントで構成されています。

  • エンティティの名前空間マルチテナンシーを可能にします。
  • エンティティの種類。Datastore クエリ用にエンティティを分類します。
  • 個々のエンティティの識別子。次のいずれかになります。
    • キー名の文字列
    • 整数の数値 ID
  • オプションの祖先パス。Datastore 階層内のエンティティの位置を指定します。

アプリケーションではエンティティのキーを使用して Datastore から個々のエンティティを取得できます。また、エンティティのキーやプロパティ値に基づくクエリを実行して 1 つ以上のエンティティを取得することもできます。

Java App Engine SDK には、シンプルな API がパッケージ com.google.appengine.api.datastore に用意されており、この API が Datastore の機能を直接サポートします。このドキュメントの例はすべて、この簡単な Java API に基づいています。アプリケーションで直接この API を使用するか、独自のデータ管理レイヤを構築するベースとして使用できます。

Datastore 自体がエンティティの構造になんらかの制限(特定のプロパティは特定の型の値を持たなければならないなど)を課すことはありません。この役割はアプリケーションが担います。

種類と識別子

Datastore の各エンティティは特定の種類に属しています。種類とは、クエリに対応できるようにエンティティを分類するものです。たとえば、人事アプリケーションでは、社内の各従業員が Employee という種類のエンティティに相当します。Java Datastore API では、作成するエンティティの種類を Entity() コンストラクタの引数として指定します。2 つのアンダースコア(__)で始まる種類名はすべて予約済みであり、使用できません。

次の例では、種類が 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);

種類に加え、各エンティティは作成時に割り当てられた識別子を持ちます。識別子はエンティティのキーの一部であるため、エンティティに永続的に割り当てられており、変更できません。識別子を割り当てる方法は 2 通りあります。

  • アプリケーションからエンティティの固有のキー名文字列を指定する。
  • エンティティに整数の数値 ID が自動的に割り当てられるように Datastore を設定する。

エンティティにキー名を設定するには、エンティティの作成時にコンストラクタの 2 番目の引数として、その名前を指定します。

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

Datastore で数値の ID が自動的に設定されるようにするには、この引数を省略します。

Entity employee = new Entity("Employee");

識別子の割り当て

Datastore は、次の 2 種類の自動 ID ポリシーを使用して自動 ID を生成するように構成できます。

  • default ポリシーは、ほぼ均等に分布する未使用の ID をランダムに生成します。各 ID は最大 16 桁の 10 進数になります。
  • legacy ポリシーは、連続しない小さい整数の ID を作成します。

エンティティの ID をユーザーに表示する場合や、ID の順序に従ってなんらかの処理を行う場合は、手動で割り当てることをおすすめします。

Datastore では、ほぼ一様に分散されたランダムな未使用 ID のシーケンスが生成されます。各 ID は最大 16 桁の 10 進数になります。

祖先パス

Cloud Datastore 内の各エンティティは、ファイル システムのディレクトリ構造と同様の階層的に構造化された空間を形成します。エンティティを作成するときに、別のエンティティを親として設定することもできます。新しいエンティティは、この親エンティティの子になります(ファイル システムとは異なり、親エンティティが実際に存在している必要はありません)。親を持たないエンティティは「ルート エンティティ」となります。エンティティと親との割り当ては永続的であり、エンティティの作成後は変更できません。同じ親を持つ 2 つのエンティティ、または 2 つのルート エンティティ(親を持たないエンティティ)に同じ数値 ID が割り当てられることはありません。

エンティティの親、親の親、さらにその親などは順に「祖先」として位置付けられ、エンティティの子、子の子、さらにその子などは順に「子孫」として位置付けられます。ルート エンティティとその子孫のエンティティは、すべて同じエンティティ グループに属します。ルート エンティティから始まり、親から子を経由して対象のエンティティに至るまでのエンティティの連なりのことを、エンティティの「祖先パス」といいます。エンティティを識別する完全なキーは、エンティティの祖先パスから始まってそのエンティティ自身で終わる一連の「種類と識別子のペア」で構成されています。

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

ルート エンティティの場合は祖先パスが空で、エンティティ自身の種類と識別子だけでキーが構成されています。

[Person:GreatGrandpa]

このコンセプトを次の図に示します。

エンティティ グループ内のルート エンティティと子エンティティの関係を示す図

エンティティの親を指定するには、子エンティティの作成時に、親エンティティのキーを Entity() コンストラクタの引数として指定します。キーを取得するには、親エンティティの getKey() メソッドを呼び出します。

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

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

新しいエンティティにもキー名がある場合は、Entity() コンストラクタの 2 番目の引数にそのキー名を指定し、3 番目の引数に親エンティティのキーを指定します。

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

トランザクションとエンティティ グループ

エンティティの作成、更新、削除は、すべてトランザクションのコンテキストで行われます。1 つのトランザクションで、このようなオペレーションが複数実行される場合もあります。トランザクションでは、データの整合性を維持するため、すべてのオペレーションを 1 つの単位として Datastore に適用します。いずれかのオペレーションが失敗した場合、すべてのオペレーションが適用されません。また同一のトランザクション内で実行されるすべての強整合性読み取り(祖先クエリまたは取得)は、データの一貫性のあるスナップショットを維持します。

上記のとおり、エンティティ グループとは、祖先から共通のルート要素までが連結された一連のエンティティです。データをエンティティ グループに編成する場合、実行可能なトランザクションの種類が制限されることがあります。

  • トランザクションがアクセスするすべてのデータは、最大 25 のエンティティ グループに制限されます。
  • トランザクション内でクエリを使用する場合は、適切なデータと一致する祖先フィルタを指定できるように、データをエンティティ グループに編成する必要があります。
  • 1 つのエンティティ グループに対し、1 秒につき約 1 つのトランザクションという書き込みスループットの制限があります。これは高い信頼性とフォールト トレラントを実現するため、Datastore が広範な地域にわたって個々のエンティティ グループをマスターなしで同期複製するために生じる制限です。

多くのアプリケーションでは、相互に関連性のないデータに対する広範なビューを取得するのであれば、結果整合性(複数のエンティティ グループに対する非祖先クエリ、多少古いデータが返される場合もある)の使用で対応できます。一方、関連性の高いデータからなる単一データセットを表示または編集する場合は、強整合性の使用が適します(祖先クエリ、または単一エンティティに対する get)。このようなアプリケーションでは通常、関連性の高い個々のデータセットに対し、個別のエンティティ グループを作成すると効率的です。詳細については、強整合性に対応するデータ構造をご覧ください。

プロパティと値の型

エンティティに関連付けられたデータ値は 1 つ以上のプロパティで構成されます。各プロパティには名前と 1 つ以上の値があります。プロパティは異なる型の複数の値を持つことができるため、2 つのエンティティの同じプロパティに異なる型の値が存在する場合もあります。プロパティはインデックス付けされている場合とされていない場合があります(プロパティ P で並べ替えまたはフィルタリングを行うクエリでは、P がインデックス付けされていないエンティティは無視されます)。1 つのエンティティに、インデックスが付けられたプロパティを最大 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 最大 1,500 バイト

1,500 バイトを超える値は IllegalArgumentException をスローします。
テキスト文字列(長い) com.google.appengine.api.datastore.Text なし 最大 1 メガバイト

インデックス付けなし
バイト文字列(短) com.google.appengine.api.datastore.ShortBlob バイト順 最大 1,500 バイト

1,500 バイトを超える値は IllegalArgumentException をスローします。
バイト文字列(長) com.google.appengine.api.datastore.Blob なし 最大 1 メガバイト

インデックス付けなし
日時 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 数値
データストアのキー com.google.appengine.api.datastore.Key
または参照先オブジェクト(子として)
パス要素順
(種類、識別子、
種類、識別子...)
最大 1,500 バイト

1,500 バイトを超える値は IllegalArgumentException をスローします。
Blobstore のキー com.google.appengine.api.blobstore.BlobKey バイト順
埋め込みエンティティ com.google.appengine.api.datastore.EmbeddedEntity なし インデックス未登録
Null null なし

重要: プロパティ値として users.User を保存しないようにすることを強くおすすめします。これには一意の ID とともにメールアドレスが含まれるためです。ユーザーがメールアドレスを変更すると、そのユーザーの古いメールアドレス(user.User に格納されている)と新しい user.User の値を比較したときに一致しません。代わりに、ユーザーの安定した一意の識別子として、User のユーザー ID 値を使用してください。

テキスト文字列とエンコードされていないバイナリデータ(バイト文字列)については、Datastore は次の 2 つの値の型をサポートします。

  • 短い文字列(最大 1,500 バイト)は、インデックスに登録され、クエリのフィルタ条件や並べ替えの順序付けに使用できます。
  • 長い文字列(最大 1 メガバイト)は、インデックスに登録されず、クエリのフィルタや並べ替えの順序付けには使用できません。
注: 長いバイト文字列型は、Datastore API では Blob と呼ばれています。この型は、Blobstore API で使用される blob とは関係ありません。

型が混合した値を持つプロパティをクエリで扱う場合、Datastore では内部表現に基づく決定論的な順序付けが使用されます。

  1. Null 値
  2. 固定小数点数
    • 整数
    • 日時型
    • 評価
  3. ブール値
  4. バイト列
    • バイト文字列
    • Unicode 文字列
    • Blobstore のキー
  5. 浮動小数点数
  6. 地理的座標
  7. Google アカウントのユーザー
  8. Datastore のキー

長いテキスト文字列、長いバイト文字列、埋め込みエンティティはインデックスに登録されないため、順序付けは定義されていません。

エンティティの操作

アプリケーションは Datastore API を使用してエンティティを作成、取得、更新、削除できます。エンティティの完全なキーがわかっている場合(または親のキー、種類、識別子からキーを導出できる場合)、アプリケーションはそのキーを使用してエンティティを直接操作できます。また、Datastore クエリの結果としてエンティティのキーを取得することもできます。詳細については、Datastore のクエリをご覧ください。

Java Datastore API では、DatastoreService インターフェースのメソッドを使用してエンティティを処理します。DatastoreService オブジェクトを取得するには、静的メソッド DatastoreServiceFactory.getDatastoreService() を呼び出します。

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

エンティティの作成

新しいエンティティを作成するには、そのエンティティの種類を Entity() コンストラクタの引数として指定して、クラス Entity のインスタンスを作成します。

必要に応じてエンティティのプロパティを入力してから、そのエンティティを DatastoreService.put() メソッドの引数として渡してデータストアに保存します。エンティティのキー名を指定するには、コンストラクタの 2 番目の引数として渡します。

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

キー名を指定しない場合は、Datastore によって数値 ID がエンティティのキーとして自動的に生成されます。

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

エンティティの取得

指定したキーで識別されるエンティティを取得するには、Key オブジェクトを DatastoreService.get() メソッドに渡します。

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

エンティティの更新

既存のエンティティを更新するには、Entity オブジェクトの属性を変更してから、それを DatastoreService.put() メソッドに渡します。そのオブジェクト データで既存のエンティティが上書きされます。put() を呼び出すたびに、オブジェクト全体が Datastore に送信されます。

エンティティの削除

エンティティを削除するには、エンティティのキーを指定して、DatastoreService.delete() メソッドを実行します。

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

反復プロパティ

1 つのプロパティに複数の値を保存できます。

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

エンティティの埋め込み

あるエンティティを別のエンティティのプロパティとして埋め込むと便利な場合があります。たとえば、1 つのエンティティ内でプロパティ値の階層構造を作成する場合などです。これは 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);

バッチ オペレーション

DatastoreService のメソッド put()get()delete()(および AsyncDatastoreService の対応する各メソッド)にはバッチ バージョンがあり、反復可能なオブジェクト(put() の場合は Entity クラス、get()delete() の場合は Key クラス)を 1 つ受け取ります。このオブジェクトを使用すると、1 回の 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);

このバッチ オペレーションでは、すべてのエンティティまたはキーがエンティティ グループごとにグループ化され、エンティティ グループごとにオペレーションが並列に行われます。このようなバッチ呼び出しでは、サービス呼び出しの 1 回分のオーバーヘッドしか発生しないため、エンティティごとに個別の呼び出しを行うよりも高速に処理できます。また、複数のエンティティ グループを対象とする場合は、サーバー側で各エンティティ グループの処理が並列に行われます。

キーの生成

アプリケーションで 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 メソッドでは、1 つのエンティティがパスに追加され、ビルダーそのものが返されます。したがって、これを連続して呼び出すことにより、ルート エンティティから始めて、エンティティを 1 つずつ追加してパスを構築できます。完全なパスを作成した後、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 や URL で特殊文字とみなされる文字は含まれません。

空のリストの使用

これまで、Datastore には空のリストを表すプロパティの表記がありませんでした。Java SDK では、この問題を回避するために空のコレクションを null 値として保存していました。そのため、null 値と空のリストを区別する方法がありません。下位互換性を維持するために、これはデフォルトの動作として残されています。次に、その概要を示します。

  • null のプロパティは、null として Datastore に書き込まれます。
  • 空のコレクションは、null として Datastore に書き込まれます。
  • null は、null として Datastore から読み取られます。
  • 空のコレクションは、null として読み取られます。

ただし、デフォルトの動作を変更すれば、Java SDK で空のリストの保存がサポートされます。アプリケーションのデフォルトの動作を変更した場合の影響について考慮したうえで、空のリストのサポートを有効にしてください

空のリストを使用できるようにデフォルトの動作を変更するには、アプリの初期化時に、次に示すように DATASTORE_EMPTY_LIST_SUPPORT プロパティを設定します。

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

上記のように、このプロパティを true に設定すると、次のようになります。

  • null のプロパティは、null として Datastore に書き込まれます。
  • 空のコレクションは、空のリストとして Datastore に書き込まれます。
  • null は、null として Datastore から読み取られます。
  • 空のリストを Datastore から読み取ると、空の Collection として返されます。