2009/05/10

Relationships

だらだら翻訳 GAE Datastore For Java 最終回(JPAとLow-level APIは今のとこやらない予定。でもLow-level APIをつかわないと面白くなさそう。Javaは型がしょうがないとはいえ、PythonAPIのダイナミックプロパティとかないとDatastoreおいしくなさそう。EntityつかうとMapっぽく使えるのかなぁ。)

それにしても理解不足とはいえ、英語読解力が悲惨なひどい訳なのであまりいじめないでください。寝ぼけながらなんとなく読んで酷い誤読をしてそう。たぶんかなりしてる。 orz

まぁよい英語の勉強になったということで。

公式サイトはさりげなくどんどん更新されてるので、常に公式を追っておくのが吉です。気が向いたら推敲したいと思うけどたぶんしない。

キーの部分がよくわかってない。全体読んでやっとエンティティグループとかリレーションシップでキーの使い方がでてくるのでもう一度見直そう。

それにしてもOwned/Unowned RelationshipとかとかEager Loadingってなんて訳をあてるのがいいのかなぁ。Eager loadingは欲張りローディングとか書いてあったりするけど微妙だなぁ。Lasyが遅延なので、先読みローディングとかだろうか。

ブログで訳すのはめんどくさいし、メンテ性も悪いしなんかいいのないですかね。翻訳エディタのWiki版みたいなの。

このデザイン細長すぎて読みにくいなぁ。デザインも何とかしないと。。。

Relationships

You can model relationships between persistent objects using fields of the object types. A relationship between persistent objects can be described as owned, where one of the objects cannot exist without the other, or unowned, where both objects can exist independently of their relationship with one another. The App Engine implementation of the JDO interface can model owned one-to-one relationships and owned one-to-many relationships, both unidirectional and bidirectional. Unowned relationships are not yet supported with a natural syntax but you can manage these relationships yourself by storing datastore keys in fields directly. App Engine creates related entities in entity groups automatically to support updating related objects together—but it is the app's responsibility to know when to use datastore transactions.

対象のオブジェクト型のフィールドを利用して、永続化オブジェクト間の関連をモデリングすることができます。永続化オブジェクト間の関連は、もう一つのオブジェクトなしでは存在できないOwned、または関連するオブジェクトがお互いに独立して存在することができるUnownedとして記述することができます。JDOインタフェースのApp Engine実装は、Owned1対1関連とOwned1対多関連、それぞれ単方向または双方向関連をモデリングすることができます。Unowned関連は自然な文法ではまだサポートされていませんが、自身でフィールドに直接キーを保存することによって関連を管理することができます。App Engineは、関連するオブジェクトを一緒に更新できるようにするために自動的にエンティティグループに所属した関連するエンティティを作成します。しかし、いつDatastoreのトランザクションを使うかアプリケーションは知る責任があります。

Owned One-to-One Relationships

You create a unidirectional one-to-one owned relationship between two persistent objects by using a field whose type is the class of the related class.

関連するクラスの型のフィールドを使って、2つの永続化オブジェクト間の、単方向1対1 Owned関連を作ることができます。

The following example defines a ContactInfo data class and an Employee data class, with a one-to-one relationship from Employee to ContactInfo.

次の例は、ContactInfoデータクラスとEmployeeデータクラスを定義しています。EmployeeからContactInfoへ1対1関連があります。

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;

    // ...
}

Employee.java

import ContactInfo;
// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private ContactInfo contactInfo;

    ContactInfo getContactInfo() {
        return contactInfo;
    }
    void setContactInfo(ContactInfo contactInfo) {
        this.contactInfo = contactInfo;
    }

    // ...
}

The persistent objects are represented as two distinct entities in the datastore, with two different kinds. The relationship is represented using an entity group relationship: the child's key uses the parent's key as its entity group parent. When the app accesses the child object using the parent object's field, the JDO implementation performs an entity group parent query to get the child.

この永続化オブジェクトは、Datastoreでは2つの異なる種類の、異なったエンティティとして表されます。この関連はエンティティグループを使って表されます。子どものキーには、エンティティグループの親としてその親のキーを使います。アプリケーションが親オブジェクトのフィールドを使って子オブジェクトにアクセスすると、JDO実装は子オブジェクトを取得するため、エンティティグループの親クエリを実行します。

The child class must have a key field whose type can contain the parent key information: either a Key, or a Key value encoded as a string. See Creating Data: Keys for information on key field types.

子どものクラスは親キーの情報を含むことができる型のキーフィールドを持たなければなりません。それはKeyかKeyの値を文字列としてエンコードしたものです。キーフィールドの型についての情報はCreating Data: Keysを参照してください。

You create a bidirectional one-to-one relationship using fields on both classes, with an annotation on the child class's field to declare that the fields represent a bidirectional relationship. The field of the child class must have a @Persistent annotation with the argument mappedBy = "...", where the value is the name of the field on the parent class. If the field on one object is populated, then the corresponding reference field on the other object is populated automatically.

両方のクラスのフィールドを使うことにより双方向1対1関連を作ることができます。その子クラスのフィールドには双方向関連を表すフィールドであることをアノテーションで宣言します。子クラスのそのフィールドは、親クラスのフィールドの名前を値としたmappedBy = "..." 引数をもった@Persistentアノテーションを付与しなければなりません。あるオブジェクトのこのフィールドが設定されると、もう一方のオブジェクトの対応する参照フィールドが自動的に設定されます。

ContactInfo.java

import Employee;

// ...
    @Persistent(mappedBy = "contactInfo")
    private Employee employee;

Child objects are loaded from the datastore when they are accessed for the first time. If you do not access the child object on a parent object, the entity for the child object is never loaded. (The datastore interface does not support the "eager" loading of child objects. The datastore does not support join queries, so an implementation of eager loading wouldn't save the app a call to the datastore.)

子オブジェクトが最初にアクセスされたときに、Datastoreからロードされます。親オブジェクトの子オブジェクトに開く背しない場合、そのエンティティの子オブジェクトはロードされません。(Datastoreインタフェースは子オブジェクトの「eager」ローディングをサポートしていません。DatastoreはJoinクエリをサポートしないので、eagerローディングの実装はDatastoreの呼び出しを防ぎません。)

Owned One-to-Many Relationships

To create a one-to-many relationship between objects of one class and multiple objects of another, you use a Collection of the related class:

あるクラスのオブジェクトと、他の複数のオブジェクト間の1対多関連を作るには、関連先クラスのコレクションを使います。

Employee.java

import java.util.List;

// ...
    @Persistent
    private List<ContactInfo> contactInfoSets;

A one-to-many bidirectional relationship is similar to a one-to-one, with a field on the parent class using the annotation @Persistent(mappedBy = "..."), where the value is the name of the field on the child class:

1対多の双方向関連は1対1関連と似ています。親クラスのフィールドに@Persistent(mappedBy = "..."アノテーションを付与します。値は子クラスのフィールドの名前です。

Employee.java

import java.util.List;

// ...
    @Persistent(mappedBy = "employee")
    private List<ContactInfo> contactInfoSets;

ContactInfo.java


import Employee;

// ...
    @Persistent
    private Employee employee;

The collection types listed in Defining Data Classes: Collections are supported for one-to-many relationships. However, arrays are not supported for one-to-many relationships.

Defining Data Classes: Collectionsに一覧されているコレクションの型が1対多関連でサポートされています。

App Engine does not support join queries: you cannot query a parent entity using an attribute of a child entity. (You can query a property of an embedded class, because embedded classes store properties on the parent entity. See Defining Data Classes: Embedded Classes.)

App EngineはJoinクエリをサポートしていません。子エンティティの属性を使って親エンティティを探すクエリを実行することはできません。(埋め込みクラスのプロパティはクエリに使うことができます。なぜなら、埋め込みクラスは親エンティティのプロパティとして保存されるためです。Defining Data Classes: Embedded Classesを参照してください。)

How Ordered Collections Maintain Their Order

Ordered collections, such as List<...>, preserve the order of objects when the parent object is saved. JDO requires that databases preserve this order by storing the position of each object as a property of the object. App Engine stores this as a property of the corresponding entity, using a property name equal to the name of the parent's field followed by _INTEGER_IDX. Position properties are inefficient. If an element is added, removed or moved in the collection, all entities subsequent to the modified place in the collection must be updated. This can be slow, and error prone if not performed in a transaction.

List<...>のような、順序付きコレクションは永続化オブジェクトが保存されたときにオブジェクトの順番を保持します。JDOはデータベースがオブジェクトのプロパティとして、各オブジェクトの場所を保存することによって、この順番を保存することを要求します。App Engineは対応するエンティティのプロパティとしてこれを保存します。それは親のフィールドの名前のあとに_INTEGER_IDXという名前のプロパティ名となります。順序プロパティは効率がよくありません。要素が追加されたとき、コレクションは削除または移動され、続く全てのエンティティはコレクション内の場所を移動するために更新されなければなりません。これはとても遅く、トランザクション内でない場合エラーになる可能性があります。

If you do not need to preserve an arbitrary order in a collection, but need to use an ordered collection type, you can specify an ordering based on properties of the elements using an annotation, an extension to JDO provided by DataNucleus:

コレクションの順番を保持する必要がないが、順序づきのコレクション型を使う必要がある場合は、DataNucleusによってJDOに提供されている拡張アノテーションを使って、要素のプロパティによる順序付けを指定することができます。

import java.util.List;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.Order;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    @Order(extensions = @Extension(vendorName="datanucleus", key="list-ordering", value="state asc, city asc"))
    private List<ContactInfo> contactInfoSets = new List<ContactInfo>();

The @Order annotation (using the list-ordering extension) specifies the desired order of the elements of the collection as a JDOQL ordering clause. The ordering uses values of properties of the elements. As with queries, all elements of a collection must have values for the properties used in the ordering clause.

@Orderアノテーション(list-ordering拡張を使っています)で、JDOQLの順序句としてコレクションの要素の順番を希望するとおり指定します。この順序は要素のプロパティの値を利用します。クエリと同様、コレクションの全ての要素は、順序句で使うプロパティの値を持っていなければなりません。

Accessing a collection performs a query. If the ordering clause of a field uses more than one sort order, the query requires a datastore index. See Queries and Indexes for more information on indexes.

クエリによってコレクションにアクセスします。フィールドの順序句が1つ以上のソート順を使っている場合、クエリはDatastoreのインデックスを必要とします。インデックスの詳細な情報はQueries and Indexesを参照してください。

For efficiency, always use an explicit ordering clause for one-to-many relationships of ordered collection types, if possible.

効率のため、可能であれば順序付きコレクション型の1対多関連のために、常に明示的に順序句を使ってください。

Unowned Relationships

In addition to owned relationships, the JDO API also provides a facility for managing unowned relationships. The App Engine implementation of JDO does not yet implement this facility, but don't worry, you can still manage these relationships using Key values in place of instances (or Collections of instances) of your model objects. You can think of storing Key objects as modeling an arbitrary "foreign key" between two objects. The datastore does not guarantee referential integrity with these Key references, but the use of Key makes it very easy to model (and then fetch) any relationship between two objects. However, if you go this route there are a few additional things to keep in mind. First, it is the app's responsibility to make sure the Keys are of the appropriate kind - JDO and the compiler will not do any type checking for you. Second, all objects must belong to the same entity group in order to perform an atomic update of objects on both sides of the relationship.

Owned関連に加え、JDO APIはUnowned関連の管理をする機能も提供します。JDOのApp Engine実装ではこの機能は未だ実装されていません。しかし、心配しないでください。モデルオブジェクトのインスタンス(もしくはインスタンスのコレクション)のKey値を使って、この関連を管理することができます。2つのオブジェクト間の「外部キー」として、Keyオブジェクトを保存すると考えることができます。DatastoreはこれらのKeyの参照の完全性を保障しませんが、Keyを使うと2つのオブジェクト間のどんな関連も簡単にモデル(そして取得)できるようにします。しかし、この方法を使うとき、いくつか意識しておくことがあります。まず、Keyが適切な種類であることを確かめるのはアプリケーションの責任です。JDOとコンパイラは型チェックを行いません。次に、関連する双方のオブジェクトでアトミックに更新するため、全てのオブジェクトは同じエンティティグループに所属する必要があります。

Tip: In some cases you may find it necessary to model an owned relationship as if it is unowned. This is because all objects involved in an owned relationship are automatically placed in the same entity group, and an entity group can only support 1 to 10 writes per second. So for example if a parent object is receiving .75 writes per second and a child object is receiving .75 writes per second, it may make sense to model this relationship as unowned so that both parent and child reside in their own, independent entity groups.

Tip:いくつかの場合、Owned関連をUnowned関連のようにモデリングする必要があることに気付くかもしもしれません。Owned関連の全てのオブジェクトは自動的に同じエンティティグループに置かれます。そして、エンティティグループは1秒間に1から10回の書き込みしかサポートしてません。よって親オブジェクト親オブジェクトが1秒間に.75回の書き込みをすると、子オブジェクトも1秒間に.75回の書き込みになります。Unownedの関連として、親と子はそれぞれ独立で、独立なエンティティグループに存在するようにみなすかもしれません、

Unowned One-to-One Relationships

Suppose we want to model person and food, where a person can only have one favorite food but a favorite food does not belong to the person because it can be the favorite food of any number of people:

PersonとFootをモデル化したいとします。Personは1つの好きな食べ物を持つことができ、好きな食べ物はPersonに所属しません。なぜなら、その好きな食べ物は何人にでも持つことができるからです。

Person.java

// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Person {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private Key favoriteFood;

    // ...
}

Food.java

import Person;
// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Food {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    // ...
}

In this example, rather than giving Person a member of type Food to represent the person's favorite food we instead give Person a member of type Key, where the Key is the unique identifier of a Food object. Note that unless an instance of Person and the instance of Food referred to by Person.favoriteFood are in the same entity group, it is not possible to update the person and that person's favorite food in a single transaction.

この例では、PersonのメンバにFood型を与えず、Personの好きな食べ物をKey型のメンバで表現しています。このKeyはFoodオブジェクトのユニークな識別氏です。Personのインスタンスと、Person.favoriteFoodで参照されるFoodのインスタンスは同じエンティティグループに所属していなければ、PersonとPersonの好きな食べ物を単一のトランザクションで更新できないことに注意してください。

Unowned One-to-Many Relationships

Now suppose we want to let a person have multiple favorite foods. Again, a favorite food does not belong to the person because it can be the favorite food of any number of people:

Personが複数の好きな食べ物を持つようにしたいとします。今回も好きな食べ物は何人もの人がもつことができるため、好きな食べ物はPersonに所属していません。

Person.java

// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Person {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private Set<Key> favoriteFoods;

    // ...
}

In this example, rather than giving Person a member of type Set<Food> to represent the person's favorite foods we instead give Person a member of type Set<Key>, where the set contains the unique identifiers of Food objects. Note that unless an instance of Person and an instance of Food contained in Person.favoriteFoods are in the same entity group, it is not possible to update the person and that favorite food in a single transaction.

この例では、PersonにSet型のメンバではなく、好きな食べ物を表すのにSet型のメンバを使っています。これはFoodオブジェクトのユニークな識別しのセットを格納します。Personのインスタンスと、Person.favoriteFoodで参照されるFoodのインスタンスは同じエンティティグループに所属していなければ、PersonとPersonの好きな食べ物を単一のトランザクションで更新できないことに注意してください。

Many-to-Many Relationships

We can model a many-to-many relationship by maintaining collections of keys on both sides of the relationship. Let's adjust our example to let Food keep track of the people that consider it a favorite:

関連の両方でキーのコレクションを管理することによって多対多関連をモデル化することができます。例のFoodに対して、好んでる人を追跡できるようにしてみましょう。

Person.java

import java.util.Set;
import com.google.appengine.api.datastore.Key;

// ...
    @Persistent
    private Set<Key> favoriteFoods;

Food.java

import java.util.Set;
import com.google.appengine.api.datastore.Key;

// ...
    @Persistent
    private Set<Key> foodFans;

In this example the Person maintains a Set of Key values that uniquely identify the Food objects that are favorites, and the Food maintains a Set of Key values that uniquely identify the Person objects that consider it a favorite.

この例ではPersonは好きなFoodオブジェクトのユニークな識別子のKeyのセットを管理し、Foodはそれを好きだとしているPersonのユニークな識別子のKeyのセットを管理します。

When modeling a many-to-many using Key values, be aware that it is the app's responsibility to maintain both sides of the relationship:

Album.java

Keyの値を用いて、多対多関連をモデリングしたとき、関連の双方を管理する責任がアプリケーションにあります。


// ...
public void addFavoriteFood(Food food) {
    favoriteFoods.add(food.getKey());
    food.getFoodFans().add(getKey());
}

public void removeFavoriteFood(Food food) {
    favoriteFoods.remove(food.getKey());
    food.getFoodFans().remove(getKey());
}

Note that unless an instance of Person and an instance of Food contained in Person.favoriteFoods are in the same entity group, it is not possible to update the person and that favorite food in a single transaction. If it is not feasible to colocate the objects in the same entity group, the application must account for the possibility that a person's favorite foods will get updated without the corresponding update to the food's set of fans or, conversely, that a food's set of fans will get updated without the corresponding update to the fan's set of favorite foods.

Personのインスタンスと、Person.favoriteFoodで参照されるFoodのインスタンスは同じエンティティグループに所属していなければ、PersonとPersonの好きな食べ物を単一のトランザクションで更新できないことに注意してください。もしそのオブジェクトを同じエンティティグループに配置できない場合、アプリケーションは、Personの好きな食べ物を更新するときに対応する食べ物のファンのセットを更新されない可能性があることに責任をとる必要があります。反対にFoodのファンのセットが更新されときに対応するファンの好きな食べ物のセットが更新されない場合もあります。

Relationships, Entity Groups, and Transactions

When an object with owned relationships is saved to the datastore, all other objects that can be reached via relationships and need to be saved (they are new or have been modified since they were last loaded) are saved automatically. This has important implications for transactions and entity groups.

DatastoreにOwned関連をもつオブジェクトが保存されるとき、関連を通じてアクセスでき、保存する必要のある(新規またはロードされてから変更された)全てのほかのオブジェクトは自動的に保存されます。これはトランザクションとエンティティグループに関する重要な影響です。

Consider the following example using a unidirectional relationship between the Employee and ContactInfo classes above:

上記のEmployeeとContactInfoクラス間の単方向関連の例を考えて見ましょう。


    Employee e = new Employee();
    ContactInfo ci = new ContactInfo();
    e.setContactInfo(ci);

    pm.makePersistent(e);

When the new Employee object is saved using the pm.makePersistent() method, the new related ContactInfo object is saved automatically. Since both objects are new, App Engine creates two new entities in the same entity group, using the Employee entity as the parent of the ContactInfo entity. Similarly, if the Employee object has already been saved and the related ContactInfo object is new, App Engine creates the ContactInfo entity using the existing Employee entity as the parent.

新しいEmployeeオブジェクトがpm.makePersistent()メソッドで保存されたとき、新しい関連するContactInfoオブジェクトも自動的に保存されます。両方のオブジェクトが新しい場合、App Engineは同じエンティティグループに、ContactInfoエンティティの親としてEmployeeエンティティを使って、2つの新しいエンティティを作成します。同様にEmployeeオブジェクトが既に保存されていて、関連するContactInfoオブジェクトが新しい場合、App Engineは既存のEmployeeオブジェクトを親として、ContactInfoエンティティを作成します。

Notice, however, that the call to pm.makePersistent() in this example does not use a transaction. Without an explicit transaction, both entities are created using separate atomic actions. In this case, it is possible for the creation of the Employee entity to succeed, but the creation of the ContactInfo entity to fail. To ensure that either both entities are created successfully or neither entity is created, you must use a transaction:

しかし、この例ではトランザクションを使用せず、pm.makePersistent()を呼んでいることに注意してください。明示的なトランザクションを使用せず、双方のエンティティが別々のアトミックな操作で作成されています。この場合、Employeeエンティティの作成が成功し、ContactInfoエンティティの作成に失敗する場合があります。両方のエンティティの作成に成功したか、作成されないかを確かにするにはトランザクションを使う必要があります。

    Employee e = new Employee();
    ContactInfo ci = new ContactInfo();
    e.setContactInfo(ci);

    try {
        Transaction tx = pm.currentTransaction();
        tx.begin();
        pm.makePersistent(e);
        tx.commit();
    } finally {
        if (tx.isActive()) {
            tx.rollback();
        }
    }

If both objects were saved before the relationship was established, App Engine cannot "move" the existing ContactInfo entity into the Employee entity's entity group, because entity groups can only be assigned when the entities are created. App Engine can establish the relationship with a reference, but the related entities will not be in the same group. In this case, the two entities cannot be updated or deleted in the same transaction. An attempt to update or delete entities of different groups in the same transaction will throw a JDOFatalUserException.

両方のオブジェクトが関連が成立する前に保存された場合、App Engineは既存のContactInfoエンティティをEmployeeエンティティのエンティティグループに「移動」することはできません。なぜなら、エンティティグループはエンティティが作られたときにのみアサインされるからです。App Engineは参照で関連を張ることができますが、関連するエンティティは同じグループではありません。この場合、2つのエンティティは単一のトランザクションで更新や削除することができません。異なるグループのエンティティを、単一のトランザクション内で更新したり削除したり使用とするとJDOFatalUserExceptionがスローされます。

Saving a parent object whose child objects have been modified will save the changes to the child objects. It's a good idea to allow parent objects to maintain persistence for all related child objects in this way, and to use transactions when saving changes.

子オブジェクトが変更された親オブジェクトを保存すると、子オブジェクトの変更も保存されます。この方法で全ての関連する子オブジェクトを、親オブジェクトで管理するのは良いアイデアです。そして、変更を保存するときにはトランザクションを使用してください。

Dependent Children and Cascading Deletes

The App Engine implementation of JDO makes all owned relationships "dependent." If a parent object is deleted, all child objects are also deleted. Breaking an owned relationship by assigning a new value to the dependent field on the parent also deletes the old child.

JDOのApp Engine実装は全てのOwned関連を「依存」にします。親オブジェクトが削除されると、全ての子オブジェクトもまた削除されます。依存するフィールドに新しい値を代入して、Owned関連を壊すと、古い子は削除されます。

As with creating and updating objects, if you need every delete in a cascading delete to occur in a single atomic action, you must perform the delete in a transaction.

作成や更新も同様に、単一のアトミックな操作でカスケード削除をしたい場合、トランザクションで実行してください。

Note: The JDO implementation does the work to delete dependent child objects, not the datastore. If you delete a parent entity using the low-level API or the Admin Console, the related child objects will not be deleted.

注:JDO実装が、関連する子オブジェクトを削除します、これはDatastoreの処理ではありません。もし低レベルAPIやAdmin Consoleから親エンティティを削除した場合、関連する子オブジェクトは削除されません。

0 件のコメント: