データクラスの定義

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 は他のフレームワークよりも高速に動作します。

モデルのメタデータにはタイプセーフプログラミングのための属性定義も含まれています。
public final org.slim3.datastore.StringAttributeMeta<slim3.demo.model.Blog> content =
  new org.slim3.datastore.StringAttributeMeta<slim3.demo.model.Blog>(this, "content", "content");

public final org.slim3.datastore.CoreAttributeMeta<slim3.demo.model.Blog, com.google.appengine.api.datastore.Key> key =
  new org.slim3.datastore.CoreAttributeMeta<slim3.demo.model.Blog, com.google.appengine.api.datastore.Key>(this, "__key__", "key", com.google.appengine.api.datastore.Key.class);

public final org.slim3.datastore.StringAttributeMeta<slim3.demo.model.Blog> title = 
  new org.slim3.datastore.StringAttributeMeta<slim3.demo.model.Blog>(this, "title", "title");

public final org.slim3.datastore.CoreAttributeMeta<slim3.demo.model.Blog, java.lang.Long> version =
  new org.slim3.datastore.CoreAttributeMeta<slim3.demo.model.Blog, java.lang.Long>(this, "version", "version", java.lang.Long.class);

主な値型

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

データストアは以下の主な値型をサポートします。
Java クラス ソート順 備考
短いテキスト(500文字以下) java.lang.String Unicode 500文字よりも長い場合は IllegalArgumentException
短いバイト配列(500文字以下) com.google.appengine.api.datastore.ShortBlob バイト順 500バイトよりも長い場合は IllegalArgumentException
ブール値 boolean or java.lang.Boolean false < 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.Enum Unicode Enum#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.Category Unicode  
メールアドレス com.google.appengine.api.datastore.Email Unicode  
浮動小数点の緯度と経度の座標で表される地点 com.google.appengine.api.datastore.GeoPt 緯度の後に経度  
インスタント メッセージング ハンドル com.google.appengine.api.datastore.IMHandle Unicode  
URL com.google.appengine.api.datastore.Link Unicode  
電話番号 com.google.appengine.api.datastore.PhoneNumber Unicode  
郵便番号 com.google.appengine.api.datastore.PostalAddress Unicode  
コンテンツについてユーザーが入力する評価(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.LinkedList<...>
  • java.util.HashSet<...>
  • java.util.LinkedHashSet<...>
  • 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>();
// ...

Automatic Values


自動的に値を設定する機能を持つAttributeListenerがあります。

@Attribute(listener = ModificationDate.class)を指定すると、モデル保存時に現在日時でプロパティ値を上書きします。これはモデルの最終更新日時をたどりたい場合に便利です。
@Attribute(listener = ModificationDate.class)
Date updatedAt;

@Attribute(listener = CreationDate.class)を指定すると、最初にモデルを保存した場合にだけ現在日時を設定します。その後の保存では値は上書きされません。
@Attribute(listener = CreationDate.class)
Date createdAt;

同様に、CreationUser、ModificationUser、CreationEmail、ModificationEmailが用意されています。

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

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 を使用して作られた場合です。

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

Cipher Text


個人情報や機密情報を安全に扱うために暗号化をする必要がある場合があります。Slim3は暗号化機能をサポートしています。
暗号化アルゴリズムと対象のフィールド型は次の通りです:
  • アルゴリズム: AES (Advanced Encryption Standard)
  • 暗号利用モード(Block cipher mode): CBC (Cipher Block Chaining)
  • 暗号鍵: 128bit (192 bit と 256bit はサポートしていません)
  • フィールド型: java.lang.String, com.google.appengine.api.datastore.Text

Slim3でBigtableのデータを暗号化するのはとても簡単です:
  1. 対象のフィールドに @Attribute(cipher = true) アノテーションを使用します。
  2. 暗号鍵をSlim3 Datastoreに設定します。
  3. Slim3 Datastoreを使用した操作をします。
モデル定義の例です:
@Attribute(cipher=true)
private String myString;

@Attribute(cipher=true, lob=true)
private String myLobString;

@Attribute(cipher=true)
private Text myText;

暗号鍵を設定する例です:
Datastore.setLimitedCipherKey("hogehogehogehoge");

Datastore.setGlobalCipherKey("hogehogehogehoge");

setLimitedCipherKeyは、スレッドローカルの暗号鍵を設定します。その鍵は現在のスレッド内の暗号化・復号化で使用されます。
FrontControllerは各コントローラを呼び出した後に暗号鍵をクリアします。このメソッド(setLimitedCipherKey)はコントローラー毎に異なる暗号鍵を使用する場合を想定しています。

setGlobalCipherKeyは暗号鍵をスタティック領域に設定します。その鍵は全てのスレッドで使用されます。FrontControllerはsetGlobalCipherKeyメソッドで設定された暗号鍵はクリアしません。このメソッドはシステム内で固定の暗号鍵を使用する場合を想定しています。

LimitedCipherKeyの方がGlobalCipherKeyよりも優先されます。さらに両方の値が未設定の場合、例外がスローされます。128bit(16文字)の暗号鍵を設定してください。

GlobalCipherKeyの値は appengine-web.xml で指定できます:
<system-properties>
    <property name="slim3.cipherGlobalKey" value="hogehogehogehoge"/>
</system-properties>


暗号化と復号化は自動的に行われます。
暗号化フィールドに対して、インメモリのフィルタとソートは全てサポートされています。

暗号化フィールドに対するデータストアクエリのフィルタには制限があります。以下のフィルタがサポートされています:
  • EqualCriterion
  • IsNotNullCriterion
  • NotEqualCriterion
データストアクエリのソートはサポートされていません。
Comments