2011年2月10日木曜日

Slim3でDatastoreを使う - データ操作編

 

この記事はこちらへ移動しました。
Slim3でDatastoreを使う

 

キーの作成

アプリ指定の値で作成
Datastore.createKey(…);

値を自動割り当て
Datastore.allocateId(…);

親エンティティを指定して作成
Datastore.createKey(parentKey, Child.class, value);
Datastore.allocateId(parentKey, Clind.class);

データオブジェクトの新規追加

org.slim3.datastore.DataStore.put(object);

非同期での保存も可能。
DataStore.putAsync(object);

複数のオブジェクトの保存はコレクションか配列をputに渡すか、putに複数のパラメータを並べる

オブジェクトのロード

Datastore.get(Class, Key);

複数のオブジェクトの取り出しもできる。
DataStore.get(Class, コレクション or 配列);
Datastore.get(Class, key1, key2, …);

オブジェクトの更新

オブジェクトを更新するときは、ロード(get)、オブジェクトの値の変更、保存(put)の順で行う。

オブジェクトの削除

Datastore.delete(Key);

ロードや更新と同様、複数オブジェクトを同時に削除できる。
子孫エンティティごと削除するには

Datastore.deleteAll(Key);

クエリ

メタデータ

データクラスをEclipseで作成すると、自動的にメタデータが作成される。クエリの作成の際にこのメタデータを使う。

DataClassのメタデータのインスタンスは次のように取得できる。

DataClassMeta meta = DataClassMeta.get();

フィルタ

EntityQuery query = Datastore.query(meta);
List results = query.filter( meta.property1.equal(value1), meta.progerty2.greaterThan(value2), … ).asList();

インメモリでフィルタ。
endsWith や contains が使える。
query.filterInMemory(meta.property.endsWith("XYZ"))

ソート

query.sort(meta.property1.asc, meta.property2.desc)

インメモリでソート
query.sortInMemory(meta.property.asc)

offset, limit

query.offset(5).limit(10)

クエリの実行と結果

query.asList()
query.asIterator()
query.asSingle()
query.asKeyList()
query.asKeyIterator()

祖先クエリ

[TODO] 後で調べる
List<Child> list = Datastore.query(Child.class, ancestorKey).asList();

kind なしの祖先クエリ

[TODO] 後で調べる
List<Entity> list = Datastore.query(ancestorKey).asList();

クエリの制限

不等式フィルタはクエリ中で1つのプロパティだけにしか使用できない。
不等式で使われたプロパティは最初のソート項目に無ければならない(ソートを指定しなければならない)。

インデックス

フィルタで指定したプロパティを持っていないエンティティは、クエリに引っかからない。
インデックスされないプロパティはクエリに引っかからない。
Text、Blob、@Attribute(unindexed = true) アノテーションをつけたプロパティはインデックスされない。
同じプロパティ名で別の型の値を持つエンティティがあると、型でソートされたあと値でソートされる。
対応するインデックス定義が無いクエリは失敗する。

インデックスの定義

単純なクエリに対するインデックスは自動作成される。

  • フィルタとソート順を使用しないクエリ
  • 等式フィルタと祖先フィルタのみを使用するクエリ
  • 単一のプロパティに対する不等式フィルタのみを使用するクエリ
  • フィルタなしで、プロパティに昇順か降順のどちらかのソート順が設定されているクエリ
  • 等式フィルタをプロパティに使用して、不等式または範囲フィルタをキーに使用しているクエリ

インデックスはxmlで定義。
クエリに必要なインデックス定義は開発環境でクエリを実行したときに自動的に作成される。

手作業で定義するときは WEB-INF/datastore-indexes.xml に書く。

datastore-indexes.xml の autoGenerate が true になっている場合、WEB-INF/appengine-generated/datastore-indexes-auto.xml にインデックスが自動生成される。

クエリカーソル

カーソルとは、フェッチ処理後の次の位置を表す文字列。
S3QueryResultList を通じてカーソルを使用する。

S3QueryResultList results = query.limit(20).asQueryResultList();
results.getEncodedCursor();
results.getEncodedFilters();
results.getEncodedSorts();
results.hasNext();

query.encodedCursor(encodedCursor)
.encodedFilters(encodedFilters)
.encodedSorts(encodedSorts)
.limit(20).asQueryResultList();

カーソルの制限
  • in や != フィルタのクエリには使えない
  • カーソルは同じクエリにしか使えない。kind、フィルタ、フィルタ値、祖先フィルタ、ソートが同じであること。
  • インデックス設定を変更するとクエリは無効になる

 

トランザクション

Transaction tx = Datastore.beginTransaction();
DataClass data = Datastore.get(tx, Dataclass.class, key);
data.setProperty(value);
Datastore.put(tx, data);
tx.commit();

単一のトランザクションでできることは

  • 1つのグループ内の複数のエンティティを変更すること
  • グループに新しいエンティティを追加すること

1つのトランザクション内では1つのエンティティグループの操作だけができる
(エンティティのロードも含めて)。
楽観的並列処理なのでトランザクションが失敗したらアプリ側で何度かリトライする。
トランザクションの外側のトランザクション分離性はRead committedに近いがトランザクションはSerializableで実行される。
トランザクション内の祖先クエリやgetはトランザクション内での変更があってもトランザクション開始時点のスナップショットを返す。

バージョンによる楽観的ロック

トランザクションを超える更新(Web画面のためのGETリクエストの後、更新のためのPOSTリクエストでDatastoreにputするなど)を行う場合、データのバージョンにより競合を検出できる。

エンティティに @Attribute(version = true) を付けたプロパティを用意することで、書き込みためのgetの際にすでに他の書き込みによってバージョンが変更されていたら ConcurrentModificationException が投げられる。

Datastore.get(Transaction, Class, Key, version)

グローバルトランザクション

複数のエンティティグループにまたがったトランザクションを実行できる。

GlobalTransaction gtx = Datastore.beginGlobalTransaction();
gtx.get(…);
gtx.put(…);
gtx.commit();

同じエンティティグループに対してローカルトランザクションとグローバルトランザクションを混ぜて使ってはいけない。グローバルトランザクションはパフォーマンス的にはローカルトランザクションと大きな違いはないそうなので、グローバルトランザクションを使う可能性があればそのエンティティグループの操作には常にグローバルトランザクションを使うと良い。