Hatena::ブログ(Diary)

bufferings

2009-11-23

Slim3 Datastore を日本語に訳してみた

| 22:37 |

2010-01-17

日本語訳はgoogle-sitesに移行しました。今後はそちらに最新の内容を適用していきます。

http://sites.google.com/site/slim3documentja/


ちょっと前にSlim3 Datastore のドキュメントが公開された。

Slim3 Datastore - Slim3


メモしつつ。つぶやきを垂れ流しつつ。読んだ。で。訳してみた。

何かちょっと違和感を感じるとか。ここどういうこと?とか。変だよ。違うよってところがあったら。

つっこんでほしいです。お願いします。


ひがさんをはじめ。

色々教えてくださった僕のTLの素敵な皆さんに深く感謝します。


ちょっと注意点

Slim3 Datastore のことは「Slim3 Datastore」と英語で書いてます。

App Engine のデータストアは「データストア」とカタカナで書いてます。


目次

更新

Slim3 Datastore

| 22:37 |

http://sites.google.com/site/slim3appengine/slim3-datastore


Slim3 Datastore は Datastore Low level API のタイプセーフな薄いラッパーです。Low level API は Bigtable に特化しているので JDO/JPA よりも高速に動作し、JDO/JPA よりも簡単に学ぶことができます。


Slim3のタイプセーフなクエリはこんな感じ。

EmployeeMeta e = new EmployeeMeta();
List<Employee> list = Datastore.query(e)
    .filter(e.job.equal("ANALYST"), e.salary.greaterThan(5000))
    .sort(e.name.asc)
    .asList();

EmployeeMeta のようなモデルのメタデータは Annotation Processing Tool によって自動的に生成されます。


同じことを JDO のクエリでやるとこんな感じ。

List<Employee> list = null;
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
    Query query = pm.newQuery(Employee.class);
    query.setFilter("job == \"ANALYST\") && salary > 5000");
    query.setOrdering("name asc");
    list = (List<Employee>) query.execute();
    list = (List<Employee>) pm.detachCopyAll(list);
} finally {
    pm.close();
}
return list;

JDO のクエリのように文字列を使ってクエリを書くとプロパティ名をリファクタリングしてもコンパイラーは何のエラーも表示しません。

でも Slim3 Datastore のタイプセーフなクエリを使うと、例えば"salary"プロパティの名前を変更したらコンパイラーは「え?"salary"とかもう知らんよ」と教えてくれます。APT によってモデルのメタデータが再生成されるからです。


Slim3 Datastore はエンティティとモデルの間のマッピングロジックを、コンパイル時にモデルメタのソースコードに書き出します。

次のコードは BarMeta の一部です。

@Override
public slim3.demo.model.Bar entityToModel(
    com.google.appengine.api.datastore.Entity entity) {
    slim3.demo.model.Bar model = new slim3.demo.model.Bar();
    model.setKey(entity.getKey());
    model.setSortValue((java.lang.String) entity.getProperty("sortValue"));
    return model;
}

@Override
public com.google.appengine.api.datastore.Entity modelToEntity(
    java.lang.Object model) {
    slim3.demo.model.Bar m = (slim3.demo.model.Bar) model;
    com.google.appengine.api.datastore.Entity entity = null;
    if (m.getKey() != null) {
        entity = new com.google.appengine.api.datastore.Entity(m.getKey());
    } else {
        entity = new com.google.appengine.api.datastore.Entity("Bar");
    }
    entity.setProperty("sortValue", m.getSortValue());
    return entity;
}

Slim3 Datastore はエンティティとモデルのマッピングに実行時リフレクションを必要としないので JDO よりも高速に動作します。

10,000件のシンプルなエンティティを取得するのに、Slim3 は約4,000ミリ秒、JDO は約12,000ミリ秒かかります。

オンラインデモ を見てください。

概要

| 22:37 |

http://sites.google.com/site/slim3appengine/slim3-datastore/overview


Google App Engine データストアは強固でスケーラブルなウェブアプリケーション用ストレージを提供します。

そのデータストアは

  • ウェブアプリケーションのことを考慮して設計されており、読み取りとクエリのパフォーマンスを重要視しています。
  • プロパティを持ったデータエンティティを格納します。エンティティはアプリケーションで定義した kind によってまとめられます。
  • 同じ kind のエンティティに対してクエリを実行することができます。クエリはプロパティ値とキーに対するフィルタとソート順を持っています。
  • 大量のデータセットから高速にデータを取得するために、すべてのクエリには前もってインデックスが定義されています。
  • アプリケーションによって定義されたエンティティグループを、分散ネットワーク上でのトランザクションの単位として使用することによって、トランザクションを使った更新をすることができます。

データストアの紹介

http://sites.google.com/site/slim3appengine/slim3-datastore/overview/introducing-the-datastore


App Engine データストアはデータオブジェクトを保存したり、クエリを実行したりします。

  • このデータオブジェクトのことを「エンティティ」と呼びます。
  • エンティティには1つ以上の「プロパティ」があります。
  • プロパティはサポートされたデータ型の中の1つの型の値に名前をつけたものです。
  • プロパティは他のエンティティに対する参照でも可能です。

データストアは単一のトランザクションで複数の操作を実行することができ、いずれかの操作が失敗した場合にはトランザクション全体をロールバックすることができます。これは、複数のユーザーが同じデータオブジェクトに対して同時にアクセスまたは操作をする可能性のある分散ウェブアプリケーションにとって特に有用です。


従来のデータベースとは違って、データストアは非常に大きなデータセットのスケーリングを扱うために、分散アーキテクチャを使用しています。App Engine のアプリケーションはデータオブジェクト間の関連を記述したり、クエリのためのインデックスを定義したりすることによって、データがどのように分散されるかを最適化することができます。


App Engine データストアは強い 一貫性 を持っていますが、リレーショナルデータベースではありません。データストアのインターフェイスはこれまでのデータベースと同じ特徴を多く持っていますが、データストアには独自の特徴があり、自動スケーリング機能を利用するためにはデータの設計方法や扱い方が変わってくるということを暗に意味しています。


データモデリング

http://sites.google.com/site/slim3appengine/slim3-datastore/overview/data-modeling


データストアのエンティティは「スキーマレス」です。同じ kind の二つのエンティティが同じプロパティを持ったり、同じプロパティに対して同じデータ型を使ったりする必要はありません。必要に応じてエンティティがスキーマに従うことをアプリケーションが保障する必要があります。そのため Slim3 には簡単にエンティティをスキーマに従わせることができるようなデータモデリング機能があります。


Slim3 では「モデル」によってエンティティの kind を定義します。

  • モデルにはエンティティのプロパティの型と設定が含まれています。
  • アプリケーションは JavaBeans を使ってモデルを定義します。そのクラスの属性がプロパティとなります。
  • ある kind のエンティティは対応するモデルクラスのインスタンスによって表され、プロパティ値はインスタンスの属性によって表されます。
  • エンティティはクラスのコンストラクターを呼び出すことによって作成することができ put() メソッドを呼び出すことによって保存することができます。
@Model
public class Employee {
    @Attribute(primaryKey = true)
    private Key key;
    private String name;
    private String role;
    private Date hireDate;
    private Boolean newHireTrainingCompleted;
    ...
}
Employee e = new Employee();
e.setRole("manager");
e.setHireDate(new Date());
Datastore.put(e);

Slim3 Datastore API はクエリのインターフェイスを提供します。クエリはモデルクラスのインスタンスの形式でエンティティを返します。そしてそのモデルは変更したりデータストアに戻したりすることができます。 EmployeeMeta のようなモデルのメタデータは Annotation Processing Tool によって自動的に生成されます。

EmployeeMeta e = new EmployeeMeta();
List<Employee> employees = Datastore.query(e).asList();
for (Employee emp : employees) {
    emp.setNewHireTrainingCompleted(true);
}
Datastore.put(employees);

エンティティとプロパティ

http://sites.google.com/site/slim3appengine/slim3-datastore/overview/entities-and-properties


App Engine データストア内のデータオブジェクトは「エンティティ」と呼ばれます。エンティティには1つ以上の「プロパティ」があります。プロパティは整数・浮動小数・文字列・日付・バイナリデータなどのサポートされたデータ型の中の1つの型の値に名前をつけたものです。


各エンティティには、そのエンティティを一意に識別する「キー」も含まれます。最も単純なキーは「kind」とデータストアによって与えられた一意の数値 ID を持っています。ID にはアプリケーションで設定した文字列を使用することもできます。


アプリケーションはキーを使用したり、エンティティのプロパティに合わせたクエリを実行したりして、データストアからエンティティを取得することができます。

クエリは

  • 0個以上のエンティティを返すことができます。
  • プロパティ値によってソートされた結果を返すことができます。
  • メモリや実行時間を浪費しないように結果のエンティティ数を制限することができます。

リレーショナルデータベースと違って App Engine データストアでは、ある kind の全エンティティが同じプロパティを持つ必要はありません。しかし Slim3 を使用すれば特定のデータモデルに従わせることができます。


プロパティは複数の値を持つことができます。

  • 複数値プロパティは複数の型が混ざっていても大丈夫です。
  • 複数値プロパティに対するクエリは、いずれかの値がクエリの条件に合うかどうかを確認します。

クエリとインデックス

http://sites.google.com/site/slim3appengine/slim3-datastore/overview/queries-and-indexes


App Engine データストアのクエリは指定された kind (データクラス) の全てのエンティティに対して処理を行います。クエリにはエンティティのプロパティ値やキーに対する0個以上のフィルタと、0個以上のソート順を指定することができます。

あるエンティティがクエリの結果として返されるのは

  • クエリのフィルタやソート順で触れられている全てのプロパティが少なくとも1つの値(null も可)を持っていて
  • 全てのフィルタ条件にプロパティ値が一致する

場合です。


データストアの全てのクエリはインデックスを使用します。

  • インデックスとはクエリの結果を指定された順番に並べたものを含むテーブルのことです。
  • App Engine アプリケーションは設定ファイルでインデックスを定義します。
  • 開発サーバーはインデックスがまだ作成されていないクエリが実行されると、自動的にこのファイルにインデックス定義の候補を追加します*1
  • アプリケーションをアップロードする前にこのファイルを手動で編集してインデックスをチューニングすることができます。
  • アプリケーションがデータストアのエンティティを変更すると、データストアは正しい結果になるようにインデックスを更新します。
  • アプリケーションがクエリを実行すると、データストアはクエリに一致するインデックスから直接結果を取得します。

この仕組みは広い範囲のクエリをサポートし多くのアプリケーションに適しています。しかし、他のデータベース技術で慣れ親しんでいるクエリの中にはサポートされないものもあります。


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

http://sites.google.com/site/slim3appengine/slim3-datastore/overview/transactions-and-entity-groups


トランザクションは

  • エンティティに対する全ての変更がデータストアに保存される
  • 失敗時には、全ての変更が保存されない

のどちらかであることを保障します。これによってエンティティのデータの一貫性が保障されます。


トランザクション API を使って、単一トランザクションで複数のアクションをエンティティに適用することができます。例えば、あるオブジェクトのカウンターをインクリメントしたいとしましょう。そうするためには、まずカウンターの値を取得して、新しい値を計算して、保存する必要があります。トランザクションを使用しなかったら、値を読み込んでから更新するまでの間に別のプロセスがカウンターをインクリメントしてしまい、アプリケーションがそのインクリメントされた値を上書き更新してしまうということが起こりえます。読み込み、計算、書き込みを単一トランザクション内で行うことで、他のプロセスの影響を受けずにカウンターをインクリメントすることができます。


単一トランザクションで複数のエンティティに対して変更を加えることもできます。そのためには、トランザクション内で複数のエンティティを保存することができるように、あらかじめどのエンティティが一緒に更新されるのかを App Engine に知らせておく必要があります。それは、あるエンティティを作成するときに別のエンティティと同じ「エンティティグループ」に所属することを宣言するということです。単一トランザクション内で取得・作成・更新・削除を行う全てのエンティティは同じエンティティグループに所属していなければなりません。


エンティティグループはエンティティ間の関連の階層構造によって定義されます。

  • グループ内に既に存在する別のエンティティの「子」であることを宣言すると、そのエンティティはグループ内に作成されます。
  • そして、その別のエンティティが「親」になります。
  • 親を指定せずに作成されたエンティティは「ルートエンティティ」になります。
  • 子のないルートエンティティはそれ自身がエンティティグループとなります。
  • 各エンティティにはルートエンティティからそのエンティティまでの親子関係のパスがあります(親のないエンティティが最も短いパスになります)。
  • このパスはエンティティの完全キーに対して非常に重要な役割を持っています。完全キーはパス内の各エンティティの「kind と ID」または「kind と キー名」で表されます*2

データストアはトランザクションを扱うのに「楽観的並列処理」を行います。あるアプリケーションのインスタンスがエンティティグループのエンティティに変更を適用している間は、グループ内のエンティティに対するその他の更新は全てすぐに失敗します。アプリケーションは更新されたデータに対してトランザクションの適用を再実行することができます。

Slim3 Datastore の使用

| 22:37 |

http://sites.google.com/site/slim3appengine/slim3-datastore/using-slim3-datastore


Slim3 Datastore は Datastore Low level API のタイプセーフな薄いラッパーです。Slim3 Datastore には Java オブジェクトに注釈をつけたり、クエリでオブジェクトを検索したり、トランザクションを使用してデータストアとやりとりをしたりするためのインターフェイスがあります。


注:Eclipse3.4 に pleiades をあてて訳しました。画面ショットは Slim3 のドキュメントのものをコピーして使わせていただいてます。

Slim3 Datastore の新規プロジェクトを作成するための設定

1. 最新の Slim3 blank project(slim3-blank-xxx.zip) を ダウンロード して解凍します。

2. slim3-blank プロジェクトをエクリプスのワークスペースにインポートします。

3. エクリプスにテスト用の設定を行います。

ウィンドウメニュー > 設定 > Java > コード・スタイル > インポートの編成 で「.* に必要な静的インポート数」を1にします。

f:id:bufferings:20091123220833j:image


ウィンドウメニュー > 設定 > Java > エディタ > コンテンツ・アシスト > お気に入り で「新規タイプ」ボタンをクリックして

  • org.hamcrest.CoreMatchers
  • org.junit.Assert
  • org.junit.matchers.JUnitMatchers

を追加します。

f:id:bufferings:20091123220834j:image


ウィンドウメニュー > 設定 > 一般 > ワークスペース で「自動的にリフレッシュ」をチェックします。

f:id:bufferings:20091227225442j:image


4. プロジェクト名を「tutorial」(好きな名前)に変更します。(リファクタリング > 名前変更)

5. slim3-gen-xxx.jar を 注釈処理のファクトリー・パスに追加します。

プロジェクトプロパティダイアログで、Java コンパイラー > 注釈処理 > ファクトリー・パス を選択します。

f:id:bufferings:20091123220835j:image


slim3-gen-xxx.jar をパスに追加します。

f:id:bufferings:20091123220836j:image


既存プロジェクトで Slim3 Datastore を使用するための設定

1. 最新の Slim3 blank project(slim3-blank-xxx.zip) を ダウンロード して解凍します。

2. war/WEB-INF/lib/slim3-xxx.jar を "あなたのプロジェクト"/war/WEB-INF/lib にコピーします。

3. slim3-xxx.jar を Java のビルドパスに追加します。

f:id:bufferings:20091123220837j:image


4. エクリプスにテスト用の設定を行います。

ウィンドウメニュー > 設定 > Java > コード・スタイル > インポートの編成 で「.* に必要な静的インポート数」を1にします。

f:id:bufferings:20091123220833j:image


ウィンドウメニュー > 設定 > Java > エディタ > コンテンツ・アシスト > お気に入り で「新規タイプ」ボタンをクリックして

  • org.hamcrest.CoreMatchers
  • org.junit.Assert
  • org.junit.matchers.JUnitMatchers

を追加します。

f:id:bufferings:20091123220834j:image


ウィンドウメニュー > 設定 > 一般 > ワークスペース で「自動的にリフレッシュ」をチェックします。

f:id:bufferings:20091227225442j:image


5. slim3-gen-xxx.jar を 注釈処理のファクトリー・パスに追加します。

プロジェクトプロパティダイアログで、Java コンパイラー > 注釈処理 > ファクトリー・パス を選択します。

f:id:bufferings:20091123220835j:image


slim3-gen-xxx.jar をパスに追加します。

f:id:bufferings:20091123220836j:image


6. DatastoreFilter を war/WEB-INF/web.xml に定義します。このフィルターは例外が発生した際に自動的にロールバックを行います。

<filter>
    <filter-name>datastoreFilter</filter-name>
    <filter-class>org.slim3.datastore.DatastoreFilter</filter-class>
</filter>
<filter-mapping>
        <filter-name>datastoreFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
</filter-mapping>

モデルとテストの作成

プロジェクトフォルダ直下の build.xml ファイルをダブルクリックして開きます。

f:id:bufferings:20091123220838j:image


アウトラインビューで model-controller タスクを右クリックをして 「実行 > 1 Ant ビルド」を選択します。

f:id:bufferings:20091123220839j:image


Ant の入力ダイアログで"お好きな名前"を入力して、OK ボタンをクリックします。

f:id:bufferings:20091123220840j:image


プロジェクトが自動的にリフレッシュする設定になっていない場合は

ウィンドウメニュー > 設定 > 一般 > ワークスペース で「自動的にリフレッシュ」をチェックしてください。


"お好きな名前"に"Message"と入力した場合は、"src/tutorial/model/Message.java" と "test/tutorial/model/MessageTest.java" が生成されます。

f:id:bufferings:20091123220842j:image


"src/tutorial.model/Message.java" を見てみましょう。

Message.java

package tutorial.model;

import java.io.Serializable;

import com.google.appengine.api.datastore.Key;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

@Model
public class Message implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;

    @Attribute(version = true)
    private Long version;

    private Integer schemaVersion = 1;

    /**
     * Returns the key.
     *
     * @return the key
     */
    public Key getKey() {
        return key;
    }

    /**
     * Sets the key.
     *
     * @param key
     *            the key
     */
    public void setKey(Key key) {
        this.key = key;
    }

    /**
     * Returns the version.
     *
     * @return the version
     */
    public Long getVersion() {
        return version;
    }

    /**
     * Sets the version.
     *
     * @param version
     *            the version
     */
    public void setVersion(Long version) {
        this.version = version;
    }

    /**
     * Returns the schema version.
     *
     * @return the schema version
     */
    public Integer getSchemaVersion() {
        return schemaVersion;
    }

    /**
     * Sets the schema version.
     *
     * @param schemaVersion
     *            the schema version
     */
    public void setSchemaVersion(Integer schemaVersion) {
        this.schemaVersion = schemaVersion;
    }
}

"test/tutorial.model/MessageTest.java" を見てみましょう。

MessageTest.java

package tutorial.model;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class MessageTest {

    private Message model = new Message();

    @Test
    public void test() throws Exception {
        assertThat(model, is(notNullValue()));
    }
}

"MessageTest.java"のテストを実行します。結果はグリーン( OK )になります。

データクラスの定義

| 22:37 |

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes


Slim3 Datastore を使うと、普通の Java データオブジェクト(POJOと呼ばれたりするもの)をデータストアに保存できます。Slim3 Datastore によって永続化されたオブジェクトは、データストア内のエンティティになります。モデルインスタンスの保存方法や再生成の方法を Slim3 Datastore に指定するためにアノテーションを使用します。


クラスとフィールドのアノテーション

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/annotations


Slim3 Datastore によって保存されたオブジェクトは App Engine データストア内のエンティティになります。エンティティの kind はクラスの単純名から取得します(内部クラスはパッケージ名を除いて $ のパスを使用します)。クラスの永続化フィールドがエンティティのプロパティを表し、大文字小文字をそのままにしたフィールド名がエンティティのプロパティ名になります。


Java クラスをモデルとして宣言するためには @Model アノテーションをつけます。

import org.slim3.datastore.Model;

@Model
public class Employee {
    // ...
}

データクラスのフィールドはデータストアに保存されます。永続化フィールドは getter と setter メソッドを持たなければなりません。

import java.util.Date;

// ...
    private Date hireDate;

    public Date getHireDate() {
        return hireDate;
    }

    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }

永続化しないフィールド(データストアに保存したりデータストアから取得したりしないフィールド)を宣言するためには @Attribute(persistent = false) アノテーションをつけます。

import java.util.Date;
import org.slim3.datastore.Attribute;


// ...
    @Attribute(persistent = false)
    private Date hireDate;

フィールドの型は次に示す型のいずれかです。詳細は後述します。

  • データストアでサポートされているコア型の中の一つ
  • コア型のコレクション (java.util.List, java.util.Set, java.util.SortedSet)
  • シリアライズ可能なクラスのインスタンスまたはそのコレクション

データクラスは、対応するデータストアエンティティに主キーを保存するためのフィールドを1つ持たなければなりません。キーのフィールドは com.google.appengine.api.datastore.Key でなければなりません。キーには @Attribute(primaryKey = true) アノテーションを使用します。

import com.google.appengine.api.datastore.Key;
import org.slim3.datastore.Attribute;

// ...
    @Attribute(primaryKey = true)
    private Key key;

// ... accessors ...

モデルのメタデータ

http://google.com/site/slim3appengine/slim3-datastore/defining-data-classes/meta-data-of-model


モデルを定義したら Annotation Processing Tool によってモデルのメタデータが自動的に生成されます。例えば slim3.demo.model.Employee モデルを定義したら slim3.demo.meta.EmployeeMeta が生成されます。モデルの定義に変更を加えたら、モデルのメタデータも再生成されます。


モデルのメタデータにはモデルとエンティティのマッピングメソッドが含まれています。

@Override
public slim3.demo.model.Foo
    entityToModel(com.google.appengine.api.datastore.Entity entity) {
        slim3.demo.model.Employee model =
            new slim3.demo.model.Employy();
        model.setKey(entity.getKey());
        model.setFirstName((java.lang.String) entity.getProperty("firstName"));
        model.setLastName((java.lang.String) entity.getProperty("lastName"));
        model.setHireDate((java.util.Date) entity.getProperty("hireDate"));
        return model;
}

@Override
public com.google.appengine.api.datastore.Entity
    modelToEntity(java.lang.Object model) {
        slim3.demo.model.Employee m = (slim3.demo.model.Employee) model;
        com.google.appengine.api.datastore.Entity entity = null;
        if (m.getKey() != null) {
            entity = new com.google.appengine.api.datastore.Entity(m.getKey());
        } else {
            entity = new com.google.appengine.api.datastore.Entity("Employee");
        }
        entity.setProperty("firstName", m.getFirstName());
        entity.setProperty("lastName", m.getLastName());
        entity.setProperty("hireDate", m.getHireDate());
        return entity;
}

Slim3 はマッピングメソッドをコンパイル時に生成して実行時にはそのメソッドを使用するので、マッピングに実行時リフレクションを必要としません。そのため、 Slim3 は他のフレームワークよりも高速に動作します。


主な値型

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/core-value-types


データストアは以下の主な値型をサポートします。

Java クラスソート順備考
短いテキスト文字列(500文字以下)java.lang.StringUnicode500文字よりも長い場合は IllegalArgumentException
短いバイト文字列(500バイト以下)com.google.appengine.api.datastore.ShortBlobバイト順500バイトよりも長い場合は IllegalArgumentException
ブール値boolean or java.lang.Booleanfalse < true
整数short, java.lang.Short, int, java.lang.Integer, long, java.lang.Long数値長整数として格納された後、フィールド型に変換されます。範囲外の値はオーバーフローします。
浮動小数点数float, java.lang.Float, double, java.lang.Double数値倍精度浮動小数点数として格納された後、フィールド型に変換されます。範囲外の値はオーバーフローします。
日付/時刻java.util.Date時系列
列挙java.lang.EnumUnicodeEnum#name()の文字列が格納されます。
Google アカウントcom.google.appengine.api.users.Userメール アドレス(Unicode)
長いテキスト文字列com.google.appengine.api.datastore.Text(順序付け不可)インデックス化されません。
長いバイト文字列com.google.appengine.api.datastore.Blob(順序付け不可)インデックス化されません。
エンティティ キーcom.google.appengine.api.datastore.Key, または子要素として参照されているオブジェクトパス要素単位(種類、IDか名前、種類、IDか名前...)
カテゴリーcom.google.appengine.api.datastore.CategoryUnicode
メール アドレスcom.google.appengine.api.datastore.EmailUnicode
浮動小数点の緯度と経度の座標で表される地点com.google.appengine.api.datastore.GeoPt緯度の後に経度
インスタント メッセージング ハンドルcom.google.appengine.api.datastore.IMHandleUnicode
URLcom.google.appengine.api.datastore.LinkUnicode
電話番号com.google.appengine.api.datastore.PhoneNumberUnicode
郵便番号com.google.appengine.api.datastore.PostalAddressUnicode
コンテンツについてユーザーが入力する評価(0 〜 100 の整数)com.google.appengine.api.datastore.Rating数値

シリアライズ可能なオブジェクト

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/serializable-objects


シリアライズ可能なクラスのインスタンスは Blob 値として保存することができます。 Slim3 Datastore では、シリアライズ可能なクラスのインスタンスを Blob 値として保存する場合はフィールドに @Attribute(lob = true) アノテーションをつけます。 Blob 値はインデックスづけできないのでクエリのフィルタやソート順に使用することはできません。


次に示すのはシンプルなシリアライズ可能クラスの例です。ファイルの内容とファイル名とMIMEタイプを持った、ファイルを表すクラスです。

import java.io.Serializable;

public class DownloadableFile implements Serializable {
    private static final long serialVersionUID = 1L;
    private byte[] content;
    private String filename;
    private String mimeType;

    // ... accessors ...
}

シリアライズ可能クラスのインスタンスを Blob 値としてプロパティに保存する場合は、クラス型のフィールドを宣言して @Attribute(lob = true) アノテーションをつけます。

import DownloadableFile;
import org.slim3.datastore.Attribute;
// ...
    @Attribute(lob = true)
    private DownloadableFile file;

バイト配列

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/array-of-bytes


バイト配列は Blob 値として保存することができます。Slim3 Datastore では、バイト配列を Blob 値として保存する場合はフィールドに @Attribute(lob = true) アノテーションをつけます。Blob 値はインデックスづけできないのでクエリのフィルタやソート順に使用することはできません。

@Attribute(lob = true)
private byte[] bytes;

長いテキスト文字列

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/long-text-string


長いテキスト文字列(500バイト以上)は Text 値として保存することができます。Slim3 Datastore では、長いテキスト文字列を Text 値として保存する場合はフィールドに @Attribute(lob = true) アノテーションをつけます。Text 値はインデックスづけできないのでクエリのフィルタやソート順に使用することはできません。

@Attribute(lob = true)
private String content;

コレクション

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/collections


データストアのプロパティは1つ以上の値をもつことができます。Slim3 Datastore では、これはコレクション型の単一フィールドによって表されます。コレクションはコア型の1つかシリアライズ可能クラスからなります。以下のコレクション型がサポートされています。

  • java.util.ArrayList<...>
  • java.util.HashSet<...>
  • java.util.TreeSet<...>
  • java.util.List<...>
  • java.util.Set<...>
  • java.util.SortedSet<...>

フィールドが List として宣言されている場合は、 Slim3 Datastore は ArrayList を返します。 Set で宣言されている場合は HashSet を、 SortedSet で宣言されている場合は TreeSet を返します。


コレクションはインデックスづけすることができるので、クエリのフィルタやソート順に使用することができます。


例えば、 List<String> 型のフィールドは0個以上の文字列値としてプロパティに保存されます。その1つ1つはリストの値です。

import java.util.List;

// ...

    private List<String> favoriteFoods;

// ... accessors ...

配列はコレクションとしてはサポートされていません。つまり配列はインデックスづけされず、クエリのフィルタやソート順で使えません。配列をデータストアに保存したければそのフィールドをシリアライズ可能なクラスとして指定するべきです。

import org.slim3.datastore.Attribute;

// ...

    @Attribute(lob = true)
    private String[] favoriteFoods;

// ... accessors ...

インデックスづけされないプロパティ

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/unindexed-properties


デフォルトでは App Engine データストアはエンティティの全てのプロパティ(Blob と Text 以外)に対してそれぞれインデックスのレコードを2つずつ作成します(single property index を昇順・降順の2つ作成)。このインデックスレコードのおかげで、複合インデックスを作成せずにそのプロパティを含む様々なクエリを実行することができます。


このインデックスは書き込みに時間がかかりディスク領域を使用します。あるプロパティをフィルタやソートで決して使用しないことが完全に明らかなのであれば、デフォルトのインデックスづけを中止することができます。


@Attribute(unindexed = true)
private String unindexedString;

@Attribute(unindexed = true)
List<String> favoriteColorList = new ArrayList<String>();
// ...

モデルの属性とエンティティのプロパティ

http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/attributes-and-properties


App Engine データストアでは「プロパティがないエンティティ」と「プロパティに null が入っているエンティティ」が区別されます。 一方 Slim3 Datastore では区別されません。それは、モデルの全ての属性に(null かもしれませんが)値があるからです。 null を許容する型(ビルトインの int や boolean などではない型)の属性に null が入っていたら、モデルが保存されるときにエンティティのプロパティに null が設定されます。


データストアのエンティティがモデルにロードされ、モデルのある属性に対応するプロパティが存在しなくて、その属性が null を入れることのできる単値の型だった場合、その属性には null が入ります。モデルがデータストアに戻されるときには、存在しなかったプロパティには null が設定されてデータストアに保存されます。属性が null 不可の場合に、対応するプロパティがないエンティティを読み込んだ場合は、その属性にはデフォルト値が設定されます。エンティティの生成と再生成に同じモデルを使用している場合、このようなことは起こりません。このようなことが起こるのは、モデルが変更された場合か、エンティティが Slim3 Datastore ではなく low-level API を使用して作られた場合です。


エンティティがモデルのフィールドにないプロパティを持っている場合、そのプロパティはモデルからはアクセスできません。モデルがデータストアに戻された場合は、そのプロパティは削除されます。

キーとエンティティグループ

| 22:37 |

http://sites.google.com/site/slim3appengine/slim3-datastore/keys-and-entity-groups


データストア内の全エンティティに「キー」があります。キーとはアプリケーションの全エンティティに対してユニークな識別子です。キーにはいくつかの要素があります。

  • エンティティ同士の親子関係を記述する「パス」
  • エンティティの「kind」
  • アプリケーションによって設定された「名前」またはデータストアによって設定された数値の「ID」のどちらか

Kind と Name と ID

http://sites.google.com/site/slim3appengine/slim3-datastore/keys-and-entity-groups/kinds-names-and-ids


全てのエンティティには特定の kind があります。kind とはテーブルのようなものです。Slim3 Datastore はモデルクラスの単純名を kind 名として使用します。


例えば、このモデルの kind は"Employee"です。

import org.slim3.datastore.Model;

@Model
public class Employee {
    ...
}

@Model(kind = "...") アノテーションを使用して kind 名を指定することができます。

import org.slim3.datastore.Model;

@Model(kind = "Emp")
public class Employee {
    ...
}

キーの ID の部分は、アプリケーションが文字列で指定することも、データストアに数値IDを自動生成してもらうようにすることもできます。


アプリケーションで文字列 ID を生成するためには、Key 型の値を ID で作ってその値をフィールドに設定します。

import com.google.appengine.api.datastore.Key;
import org.slim3.datastore.Datastore;

// ...
    Key key = Datastore.createKey(Employee.class, "Alfred.Smith@example.com");
    Employee emp = new Employee();
    emp.setKey(key);
    ...
    Datastore.put(emp);

システムで設定される数値IDを使う場合は、キーのフィールドを null のままにしておきます。そうすると、キーのフィールドは Datastore.put() メソッドを使用してデータストアに書き込まれる際に設定されます。

import com.google.appengine.api.datastore.Key;
import org.slim3.datastore.Datastore;

// ...
    Employee e = new Employee();
    ...
    Datastore.put(e);

システムで設定される数値IDをデータストアに保存する前に取得したい場合は Datastore.allocateId() を使います。

import com.google.appengine.api.datastore.Key;
import org.slim3.datastore.Datastore;

// ...
    Key key = Datastore.allocateId(Employee.class);
    Employee e = new Employee();
    e.setKey(key);
    Datastore.put(e);

システムで設定される数値IDをデータストアに保存する前に複数取得したい場合は Datastore.allocateIds() を使います。

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyRange;
import org.slim3.datastore.Datastore;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

// ...
    List<Employee> list = new ArrayList<Employee>();
    Iterator<Key> keys = Datastore.allocateIds(Employee.class, 100).iterator();
    while (keys.hasNext() {    
        Employee e = new Employee();
        e.setKey(keys.next());
        ...
        list.add(e);
    }
    Datastore.put(list);

Datastore.allocateId(s) はアプリケーションのトランザクションとは無関係ということです。

(id:higayasuo さんありがとうございます。)


エンティティグループと祖先とパス

http://sites.google.com/site/slim3appengine/slim3-datastore/keys-and-entity-groups/entity-groups-ancestors-and-paths


全てのエンティティは「エンティティグループ」に所属しています。エンティティグループとは、単一のトランザクションで扱うことのできる1つ以上のエンティティのセットです。App Engine はエンティティグループの関連に基づいて、複数のエンティティを分散ネットワークの同じ部分に格納します。トランザクションはエンティティグループに対してデータストアの操作を開始し、全ての操作が実行されるか、または失敗時には全ての操作が実行されないかのどちらかとなります。


アプリケーション指定の文字列IDを使用して子のキーを生成する場合は「親」のキーを次のように指定することができます。

import com.google.appengine.api.datastore.Key;
import org.slim3.datastore.Datastore;

// ...
    Key parentKey = ...;
    Key childKey = Datastore.createKey(parentKey, Child.class, "...");

自動生成のIDを使用する場合は「親」のキーを次のように指定することができます。

import com.google.appengine.api.datastore.Key;
import org.slim3.datastore.Datastore;

// ...
    Key parentKey = ...;
    Key childKey = Datastore.allocateId(parentKey, Child.class);

自動生成IDの場合に複数の子のキーを設定する際には「親」のキーを次のように指定することができます。

import com.google.appengine.api.datastore.Key;
import org.slim3.datastore.Datastore;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

// ...
    Key parentKey = ...;
    List<Child> list = new ArrayList<Child>();
    Iterator<Key> childKeys = Datastore.allocateIds(parentKey, Child.class, 100).iterator();
    while(childKeys.hasNext()) {
        Child child = new Child();
        child.setKey(childKeys.next());
        ...
        list.add(child);
    }
    Datastore.put(list);

親を持たないエンティティは「ルート」エンティティとなります。別のエンティティの親であるエンティティも親を持つことができます。あるエンティティからルートまでの親エンティティのチェーンがエンティティの「パス」となります。パスのメンバーはエンティティの「祖先」となります。エンティティの親はエンティティの作成時に定義され、後で変更することはできません。


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

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

です。


トランザクションについての詳しい情報は、トランザクション を参照してください。


あるエンティティの祖先エンティティが削除されても、子孫エンティティは削除されません。子孫エンティティは完全キーを使用してアクセスすることができます。


エンティティグループを使用する際の Tips :

  • トランザクションのために必要な場合だけエンティティグループを使用します。
  • エンティティグループが多いほど、つまり、ルートエンティティが多いほど、データストアは効率よくノードをまたいだエンティティグループの分散を行うことができます。うまく分散されればデータの生成や更新のパフォーマンスが改善されます。さらに、処理がトランザクションの一部ではない場合に同じエンティティグループに対する同時更新が行われて処理が失敗するとデータストアは自動的にリトライしますが、トランザクションの一部の処理に対してはリトライをしません。その代わりにすぐに java.util.ConcurrentModificationException を投げます。
  • エンティティグループに対する経験から言うと、1人のユーザーにとって意味のあるデータサイズにするか、それより小さくするべきです。
  • エンティティグループはクエリの速度に大した影響を与えません。

モデルの作成、取得、削除

| 22:37 |

http://sites.google.com/site/slim3appengine/slim3-datastore/creating-getting-and-deleting-data


単純に Datastore.put() を呼び出せば、モデルをデータストアに保存することができます。 Slim3 Datastore はデータストアのどのエンティティがそのデータオブジェクトと一致するのかを見失わないようにオブジェクトの主キーフィールドを使用し、新しいオブジェクトに対しては自動的にキーを生成することができます。エンティティを素早く取得するためには、キーを使用します。キーは(アカウント ID のような)既知の値から生成することができます。


モデルの作成

http://sites.google.com/site/slim3appengine/slim3-datastore/creating-getting-and-deleting-data/put


データストアにモデルを保存するには Datastore.put() にインスタンスを渡して呼び出します。

Employee employee = new Employee();
...
Datastore.put(employee);

トランザクション外では Datastore.put() の呼び出しは同期的に行われ、オブジェクトが保存されてインデックスが更新されるまで処理は戻りません。


複数のモデルを保存するにはモデルのコレクションか配列を Datastore.put() に渡します。この方が1つずつ呼び出すよりも効率が良いです。

List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 100; i++) {
    Employee emp = new Employee();
    ...
    list.add(emp);
}
Datastore.put(list);
Employee emp = new Employee();
...
Employee emp2 = new Employee();
...
Datastore.put(emp, emp2);

キーによるモデルの取得

http://sites.google.com/site/slim3appengine/slim3-datastore/creating-getting-and-deleting-data/getting-a-model-by-key


モデルをキーから取得する場合は Datastore.get() メソッドを使用します。このメソッドにはモデルのクラスとキーを渡します。

Key key = ...;
Employee emp = Datastore.get(Employee.class, key);

複数のモデルをキーから取得する場合は Datastore.get() にモデルのクラスと複数のキーを渡します。この方が1つずつ呼び出すよりも効率が良いです。

List<Key> keys = ...;
List<Employee> list = Datastore.get(Employee.class, keys);

put() や delete() の例みたいに get() も可変長引数いけるということです。

(shin1ogawa さんありがとうございます。)


モデルの更新

http://sites.google.com/site/slim3appengine/slim3-datastore/creating-getting-and-deleting-data/updating-a-model


モデルを更新するには、モデルを取得し、変更し、データストアに保存( put )します。

public void updateEmployeeTitle(Key key, String newTitle) {
    EmployeeMeta e = new EmployeeMeta();
    Employee emp = Datastore.get(e, key);
    emp.setTitle(newTitle);
    Datastore.put(emp);
}

複数のモデルの更新は次のように行います。この方が1つずつ呼び出すよりも効率が良いです。

public void updateEmployeesTitle(List<Key> keys, String newTitle) {
    EmployeeMeta e = new EmployeeMeta();
    List<Employee> list = Datastore.get(e, keys);
    for (Employee emp : list) {
        emp.setTitle(newTitle);
    }
    Datastore.put(list);
}

モデルの削除

http://sites.google.com/site/slim3appengine/slim3-datastore/creating-getting-and-deleting-data/deleting-a-model


モデルをデータストアから削除する場合は Datastore.delete() にキーを渡します。

Key key = ...;
Datastore.delete(key);

複数のモデルを削除するにはキーのコレクションか配列を Datastore.delete() に渡します。この方が1つずつ呼び出すよりも効率が良いです。

List<Key> keys = ...;
Datastore.delete(keys);
Datastore.delete(key, key2, key3);

*1この文だけだと「何もしなくても勝手にインデックス設定ファイルに候補が追加される」という意味に取れるけど、別の部分では「自動インデックスの設定を true にしてたら自動生成用の設定ファイルに候補が追加される」と書いてある。たぶん後者が正確なんだと思うけど。実際に動作をみたわけじゃないので直訳のままにしとく。 → 何もしていなければ自動生成がONになっていて、自動生成用のインデックスファイルに候補が追加される。という意味で文の通り。

*2:自信ない。A complete key can be represented by the kind and ID or key name of each entity in the path.

shin1oshin1o 2009/11/23 23:44 *1の箇所の「自動インデックスの設定を true にしてたら自動生成用の設定ファイルに候補が追加される」というのは、逆かも。「自動インデックスの設定をfalseにしたら自動生成用の設定ファイルに候補が追加されない」つまり「何もしなければ勝手にインデックス設定ファイルに候補が追加される」というのが正解。

bufferingsbufferings 2009/11/24 00:22 > shin1o さん
コメントありがとうございます。

なるほど。
「何もしなければ(自動生成がONになっていて)勝手に(自動生成用の)インデックス設定ファイルに候補が追加される。」
ということですね。文の通りなんですね。ありがとうございます。

kamiru78kamiru78 2009/12/20 22:51 既存プロジェクトで・・・とある手順で試してみましたがMetaデータのソースが自動生成できませんでした。
slim3-blankプロジェクトから.projectや.settingsや.externalToolBuildersを既存プロジェクトにコピーして.apt_generatedというソースフォルダを作成したらMetaデータが自動生成できるようになりました。
どれが必要でどれが不必要だったかはわかりませんが報告まで。

bufferingsbufferings 2009/12/20 23:05 > kamiru78さん
ご報告ありがとうございます。

「slim3-gen-xxx.jar を 注釈処理のファクトリー・パスに追加します。」
の部分がうまく動作できていなかったのかなと思います。設定ファイルでいうと「.factorypath」です。
でも、自動生成できるようになって良かったです。

slim3 について質問や意見などあれば
http://groups.google.co.jp/group/slim3-user-japan
に投稿するとみなさんが答えてくれますよ。

shimanpshimanp 2010/04/28 12:36 私もMetaクラスが生成されなかったのですが、
slim3-gen-xxx.jar を 注釈処理のファクトリー・パスから一度削除して、
再度追加し直したら.apt_generatedフォルダに生成されました。