データストアにJDOを使用してplain java data objects(Plain Old Java Object:POJO)を保存することができます。 PersistenceManagerを使って永続化されたオブジェクトは、データストア上でエンティティとなります。 JDOにデータクラスのインスタンスの保存・復元方法を伝えるためには、アノテーションを使用します。
Note:初期のJDOのバージョンでは、.jdoというXMLファイルをアノテーションの代わりに使用していました。 JDO2.3でもその方法を用いることは出来ます。このドキュメントでは、アノテーションを使った方法のみ説明します。
JDOで保存されたオブジェクトはApp Engine データストア上でエンティティとなります。 エンティティのkindは、クラス名です(内部クラスは、パッケージ名の無い$pathを使用します)。 クラスの各永続化フィールドはエンティティのプロパティを表します。 プロパティ名はフィールド名と同じになります。
JavaクラスをJDOで保存・復元可能とするためには、クラスアノテーション@PersistenceCapable を付ける必要があります。例を示します。
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
// ...
}
保存されるデータクラスのフィールドは、永続化フィールドとして宣言されなければなりません。 @Persistentアノテーションを付けます。
import java.util.Date;
import javax.jdo.annotations.Persistent;
// ...
@Persistent
private Date hireDate;
永続化されないフィールドを宣言するためには(データストアに保存・復元されないフィールドです)、 @NotPersistentアノテーションを付けます。
Tip:@Persistent・@NotPersistentがついてない場合、 JDOは、特定の型のフィールドはデフォルトで永続化し、他の型は永続化しません。 このあたりの詳細な動作に付いては、DataNucleus documentationを参照ください。 App Engine はJDOの仕様通りにデフォルトの永続化を行わないため、 フィールドには@Persistentか@NotPersistentのどちらかを付けることをお勧めします。
フィールドの型は以下のいずれかです。詳細は以降で説明します。
データクラスは、対応するエンティティの主キーとなる、1つのフィールドを持たなければなりません。 主キーフィールドは4種類あり、そこから1つを選択することができ、 それぞれが異なるアノテーションを使用します。(詳しくはCreateing Data Keysを参照ください。) もっとも単純なのは、long integer型のフィールドで、データの最初の挿入時に JDOにより自動的によって、ユニークな番号が振り与えられます。
long integer型のキーには、@PrimaryKeyアノテーションと @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) アノテーションを付けます。
Tip:永続化フィールドはprivateかprotected(もしくはパッケージprotected)とし、 publicなアクセッサメソッドによってのみアクセスしてください。 他クラスから永続化フィールドへ直接アクセスすると、JDOクラスの拡張が無視されます。 代わりの手段として、他のクラスに@PersistenceAwareを付けることができます。 詳しくはDataNucleus documentationを参照ください。
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PrimaryKey;
// ...
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
データクラスの例です
import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private String firstName;
@Persistent
private String lastName;
@Persistent
private Date hireDate;
public Employee(String firstName, String lastName, Date hireDate) {
this.firstName = firstName;
this.lastName = lastName;
this.hireDate = hireDate;
}
// Accessors for the fields. JDO doesn't use these, but your application does.
public Long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getHireDate() {
return hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
}
データストアは以下のcore value型をサポートします。
型 | Javaクラス | 並び順 | 備考 |
---|---|---|---|
500バイトより 小さいテキスト |
java.lang.String | Unicode | 500バイト以上の場合、 JDOFatalUserExceptionをスローする。 |
500バイトより 小さいテキスト |
com.google.appengine.api.datastore.ShortBlob | バイト順 | 500バイト以上の場合、 JDOFatalUserExceptionをスローする。 |
boolean | booleanまたはjava.lang.Boolean | false < true | |
整数 | short, java.lang.Short, int, java.lang.Integer, long, java.lang.Long | 数値順 | long Integerに変換される。 範囲外はオーバーフロー |
浮動小数点数 | float, java.lang.Float, double, java.lang.Double | 数値順 | 倍精度浮動小数点数に変換される。 範囲外はオーバーフロー |
日時 | java.util.Date | Chronological | |
googleアカウント | com.google.appengine.api.users.User | Eメールアドレス(Unicode) | |
長いバイト文字列 | com.google.appengine.api.datastore.Text | 並べ替え不可能 | インデックスを付けられない |
長いバイト文字列 | com.google.appengine.api.datastore.Blob | 並べ替え不可能 | インデックスを付けられない |
エンティティキー | com.google.appengine.api.datastore.Key もしくは被参照オブジェクト(子オブジェクト) |
By path elements (kind, ID or name, kind, ID or name ...) |
|
URL | com.google.appengine.api.datasotre.Link | Unicode |
重要:他にも、Java datastore APIには実装されておらず、python APIには実装されているcore value型がいくつか存在します。 それらの型のプロパティを持つエンティティがオブジェクトのフィールドにロードされたとき、JDOはそれらを無視します。 (nullをセットするか、nullを許容しないフィールドの場合はNullPointerExceptionをスローします。) サポートされない型は、Category、Email、IM、PhoneNumber、PostalAddress、Rating、GeoPt。
1つのcore型のプロパティを表すためには、(上の表で示した)Javaの型を宣言し、@Persistentアノテーションを付けます。
import java.util.Date;
import javax.jdo.annotations.Persistent;
// ...
@Persistent
private Date hireDate;
直列化可能クラスのインスタンスをフィールドに含めることが可能で、 直列化された値はBlob型のプロパティとして保存されます。 JDOに値の直列化を通知するためには、@Persistent(serialized=true) アノテーションを付けます。 Blob値はインデックスを付けることができず、クエリフィルタを通すことができず、 また、ソートもできません。
簡単な直列化可能クラスの例を示します。 このクラスはファイルを表し、ファイルコンテンツとファイル名、MIMEタイプを含みます。 このクラスはJDOクラスではないので、アノテーションは必要ありません。
import java.io.Serializable;
public class DownloadableFile implements Serializable {
private byte[] content;
private String filename;
private String mimeType;
// ... accessors ...
}
直列化可能クラスのインスタンスをBlob型のプロパティとして保存するためには、 そのクラスのフィールドを宣言し、@Persistent(serialized="true")アノテーションを付けます。
import javax.jdo.annotations.Persistent;
import DownloadableFile;
// ...
@Persistent(serialized = "true")
private DownloadableFile file;
@PersistenceCapableクラスのフィールド値は、オブジェクト間のownedな1対1関係を表します。 @PersistenceCapableクラスのコレクションのフィールドは、 オブジェクト間のownedな1対多の関係を表します。
重要:Owned関係は、トランザクション、エンティティグループ、カスケード削除に関連します。 詳しくはトランザクション、関連の項目を参照ください。
ownedな1対1の関係の例(EmployeeとContactinfo)を以下に示します。
ContactInfo.java
import com.google.appengine.api.datastore.Key;
// ... imports ...
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class ContactInfo {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private String streetAddress;
@Persistent
private String city;
@Persistent
private String stateOrProvince;
@Persistent
private String zipCode;
// ... accessors ...
}
Employee.java
import ContactInfo;
// ... imports ...
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private ContactInfo myContactInfo;
// ... accessors ...
}
この例では、アプリケーションがEmployeeインスタンスを作成し、 (myContactInfoフィールドがContactInfoのインスタンスを参照している状態で) pm.makePersistent(...)メソッドを用いてEmployeeインスタンスを保存すると、 2つのエンティティが作成されます。 1つは"ContactInfo"で、Contactinfoインスタンスを表します。 もう1つは"Employee"です。ContactInfoエンティティのキーは、 エンティティグループの親として、Employeeエンティティのキーを持っています。
組み込みクラスを用いると、データストア上に新たなエンティティや関係を生成せずに、 クラスを用いたフィールドの値を作成できます。 オブジェクトの値を持ったフィールドは、それを保有するオブジェクトのエンティティとして 直接データストアに保存されます。
@PersistenceCapableデータクラスは、別のデータクラス内で組み込みクラスとして使用可能です。 そのクラスの@Persistentフィールドは、オブジェクトの中に組み込まれます。 クラスに@EmbeddedOnlyアノテーションをつけると、それは組み込みクラスとしてのみ利用されます。 組み込みクラスは独立したエンティティではないので、主キーが必要ではありません。
組み込みクラスの例を示します。例では、組み込みクラスをデータクラスの内部クラスとして扱っています。 この方法は便利ですが、必ずしもクラスを組み込み可能にする必要はありません。
import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.EmbeddedOnly;
// ... imports ...
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class EmployeeContacts {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Long id;
@PersistenceCapable
@EmbeddedOnly
public static class ContactInfo {
@Persistent
private String streetAddress;
@Persistent
private String city;
@Persistent
private String stateOrProvince;
@Persistent
private String zipCode;
// ... accessors ...
}
@Persistent
@Embedded
private ContactInfo homeContactInfo;
}
組み込みクラスの各フィールドは、エンティティのプロパティとして保存されます。 プロパティの名前は、組み込みクラスのプロパティと同じです。 データオブジェクト上に複数の組み込みクラスのフィールドを持つ場合は、 名前の衝突を避けるために、フィールド名をリネームする必要があります。 @Embeddedアノテーションの引数を使って新たなフィールド名を指定できます。
@Persistent
@Embedded
private ContactInfo homeContactInfo;
@Persistent
@Embedded(members = {
@Persistent(name="streetAddress", columns=@Column(name="workStreetAddress")),
@Persistent(name="city", columns=@Column(name="workCity")),
@Persistent(name="stateOrProvince", columns=@Column(name="workStateOrProvince")),
@Persistent(name="zipCode", columns=@Column(name="workZipCode")),
})
private ContactInfo workContactInfo;
同じように、オブジェクトのフィールドも、組み込みクラスのフィールドと同じ名前を使用しては いけません。(組み込みクラスのフィールドをリネームしない限り)
組み込みクラスの永続化プロパティは別のフィールドと同じエンティティ上に保存されるため、 組み込みクラスのフィールドをJDOQLクエリフィルタやソートオーダに指定することができます。 これらのフィールドは、 「外側のクラスのフィールド名、ドット、組み込みクラスのフィールド名」で参照できます。 これは、@Columnアノテーションにより組み込みクラスのフィールド名が変更されていても同じです。
select from EmployeeContacts where workContactInfo.zipCode == "98105"
データストアプロパティは、1つ以上の値を持つことが可能です。 JDOでは、これをコレクション型の1つのフィールドとして表します。 コレクションは、core value型あるいは直列化可能クラスから成ります。 以下のコレクションがサポートされます。
フィールドがListとして宣言された場合、オブジェクトはデータストアからArrayListとして返されます。 フィールドがSetとして宣言された場合HashSetとして、SortedSetとして宣言された場合TreeSetとして 返されます。
例えば、List
import java.util.List;
// ... imports ...
// ...
@Persistent
List favoriteFoods;
子オブジェクト(@PersistenceCapableクラス)のコレクションは、1対多の関係をもつ複数個のエンティティを 生成します。関係を参照ください。
複数個の値をもつプロパティはクエリフィルタとソートオーダで特別な振る舞いをします。 クエリとインデックス:複数の値をもつプロパティのソートオーダー を参照ください。
App Engineデータストアは、プロパティ(自体)が無い事とプロパティの値がnullである事を区別します。 JDOはこの区別をサポートしません。 オブジェクトの全フィールドは値を持ちます(nullの可能性もある)。 null値を取りうる型(int、booleanなどの基本型でない型)のフィールドにnullがセットされ、 オブジェクトが保存されると、作成されるエンティティのプロパティはnullになります。
データストアのエンティティがオブジェクトにロードされる際、 オブジェクトのあるフィールドに対応するエンティティのプロパティが無い場合、 そのフィールドにはnullがセットされます(null値を取り得る型の場合)。 オブジェクトが再びデータストアにセーブされたとき、 データストアには、nullプロパティとしてセットされます。 null値を取り得ない型の場合は例外をスローします。 この状況は、同じJDOクラスを用いてエンティティを作成・使用した場合起こりえませんが、 JDOクラスが変更されたり、low-levelAPIを用いてエンティティが作成された場合発生します。
フィールドの型がcore value型や直列化可能クラスのコレクションであり、エンティティのプロパティが無い場合、 プロパティには単一のnull値がセットされる事により、データストア内では空のコレクションとして表現されます。 フィールドの型が配列型の場合、0個の要素をもつ配列になります。 オブジェクトがロードされ、プロパティに値が存在しない場合、 フィールドには適切な型の0個のコレクションが割り当てられます。 内部的には、データストアは空のコレクションとnull値をもつコレクションの違いを知っています。
エンティテイのもつプロパティに対応するフィールドをオブジェクトがもっていない場合、 そのフィールドはオブジェクトからアクセスできません。 そのオブジェクトがデータストアに保存されると、プロパティは削除されます。
エンティティのプロパティの型と、オブジェクトのフィールドの型が異なる場合、 JDOはプロパティの値をフィールド型にキャストしようとします。 キャストができない場合、JDOはClassCastExceptionをスローします。 数値型(long integer,dobule width floats)の場合、値はキャストではなく変換されます。 数値型のプロパティの値がフィールド型を超える場合、エラーをスローせずにオーバーフローします。