2009/05/10

Transactions

つづき。やっとエンティティグループがでてくる。でもキーの理解が足りないのでよくわからない。

Transactions

The App Engine datastore supports transactions. A transaction is an operation or set of operations that either succeeds completely, or fails completely. An application can perform multiple operations and calculations in a single transaction.

App Engine datastoreはトランザクションをサポートしています。トランザクションは完全に成功するか、完全に失敗する一つの操作、または操作のセットです。アプリケーションは複数の操作と計算を単一のトランザクションで実行することができます。

Using Transactions

A transaction is a datastore operation or a set of datastore operations that either succeed completely, or fail completely. If the transaction succeeds, then all of its intended effects are applied to the datastore. If the transaction fails, then none of the effects are applied.

トランザクションは、完全に成功するか完全に失敗する、Datastoreの操作または操作のセットです。そのトランザクションが成功した場合、全ての意図する結果がDatastoreに適用されます。もしトランザクションが失敗した場合結果は一切適用されません。

Every datastore write operation is atomic. An attempt to create, update or delete an entity either happens, or it doesn't. An operation may fail due to a high rate of contention, with too many users trying to modify an entity at the same time. Or an operation may fail due to the application reaching a quota limit. Or there may be an internal error with the datastore. In all cases, the operation's effects are not applied, and the datastore API raises an exception.

全てのDatastoreの書込み操作はアトミックです。エンティティを作成、更新、削除使用とすると、それは実行されるか、またはされません。とても大量のユーザが同時に一つのエンティティを変更しようとする操作は高い確率で競合により失敗します。また、アプリケーションがクオータの制限に達した場合、Datastoreでインターナルエラーが発生した場合も失敗します。全てのケースで、その操作の結果は適用されません。そして、Datastore APIは例外を投げます。

Here is an example of incrementing a field named counter in an object named ClubMembers (class not shown) using the JDO transaction API:

次の例は、JDOのトランザクションAPIを使用して、ClubMembers(クラスは記載してません)という名前のオブジェクトのcounterというフィールドをインクリメントします。

 
import javax.jdo.Transaction;
 
import ClubMembers;   // not shown
 
// ...
        // PersistenceManager pm = ...;
 
        Transaction tx = pm.currentTransaction();
 
        try {
            tx.begin();
    
            ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345");
            members.incrementCounterBy(1);
            pm.makePersistent(members);
    
            tx.commit();
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

Entity Groups

Every entity belongs to an entity group, a set of one or more entities that can be manipulated in a single transaction. Entity group relationships tell App Engine to store several entities in the same part of the distributed network. A transaction sets up datastore operations for an entity group, and all of the operations are applied as a group, or not at all if the transaction fails.

全てのエンティティはエンティティグループという、単一のトランザクションで操作することのできる、1つ以上のエンティティのセットに所属します。エンティティグループの関連はApp Engineに、いくつかのエンティティが分散ネットワークの同じ部分に格納されるように命じます。トランザクションはエンティティグループ用にDatastore操作をセットアップし、全ての操作をグループとして適用します。トランザクションに失敗した場合は何もしません。

When the application creates an entity, it can assign another entity as the parent of the new entity. Assigning a parent to a new entity puts the new entity in the same entity group as the parent entity.

アプリケーションがエンティティを作成するとき、新しいエンティティの親として別のエンティティをアサインすることができます。新しいエンティティを親にアサインすると、親のエンティティとして、その新しいエンティティを同じエンティティグループにおきます。

An entity without a parent is a root entity. An entity that is a parent for another entity can also have a parent. A chain of parent entities from an entity up to the root is the path for the entity, and members of the path are the entity's ancestors. The parent of an entity is defined when the entity is created, and cannot be changed later.

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

Every entity with a given root entity as an ancestor is in the same entity group. All entities in a group are stored in the same datastore node. A single transaction can modify multiple entities in a single group, or add new entities to the group by making the new entity's parent an existing entity in the group.

ルートエンティティが祖先として与えられた各エンティティは同じエンティティグループとなります。そのグループの全てのエンティティは同じDatastoreのノードに保存されます。単一のトランザクションは、単一のグループの複数のエンティティを変更することができます。また、そのグループに属するエンティティを新しいエンティティの親とすることで、新しいエンティティをそのエンティティグループに追加することができます。

Creating Entities With Entity Groups

The App Engine implementation of the JDO interface represents owned one-to-one or one-to-many relationships using entity groups. This allows changes to an object and changes to its child objects to occur in the same transaction. See Relationships.

JDOインターフェースのApp Engine実装は、エンティティグループを利用してOwned1対1または1対多関連を表すことができます。これにより、対象のオブジェクトの変更と、その個オブジェクトへの変更を同一のトランザクションで行うことができます。Relationshipsを参照してください。

For other situations, you can create an entity with an explicit entity group parent by setting the object's primary key field to the complete key, including the parent's key. The object's primary key field must be either a Key instance or an encoded key string (not a simple Long or String key name).

その他のシチュエーションのため、オブジェクトのプライマリキーフィールドに、親のキーを含む完全なキーを設定することにより、エンティティグループの親を明示してエンティティを作成することができます。そのオブジェクトのプライマリキーフィールドはKeyインスタンスかエンコードされたキー文字列でなければなりません(単純なLongまたはStringキー名は不可)。

When using app-assigned string IDs, you can create an object with an entity group parent by setting its key field to the complete key value, which includes the key of the parent. To create a key value using an entity group parent, use the KeyFactory.Builder class, as follows:

アプリケーションでアサインされた文字列IDを使ったとき、親のキーを含む完全なキーの値をキーのフィールドに設定することによって、エンティティグループの親を指定したオブジェクトを作成することができます。エンティティグループの親を使ってキーの値を作るには、KeyFactoru.Builderクラスを使います。

 
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
 
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class AccountInfo {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;
 
    public void setKey(Key key) {
        this.key = key;
    }
}
 
// ...
        KeyFactory.Builder keyBuilder = new KeyFactory.Builder(Customer.class.getSimpleName(), "custid985135");
        keyBuilder.addChild(AccountInfo.class.getSimpleName(), "acctidX142516");
        Key key = keyBuilder.getKey();
 
        AccountInfo acct = new AccountInfo();
        acct.setKey(key);
        pm.makePersistent(acct);

You can access the entity group parent key separately from the object's key using a field, as follows:

フィールドを使って、オブジェクトのキーとは別にエンティティグループの親のキーにアクセスすることができます。

 
// ...
    @Persistent
    @Extension(vendorName="datanucleus", key="gae.parent-pk", value="true")
    private Key customerKey;

To create an object with a system-generated numeric ID and an entity group parent, you must use an entity group parent key field (such as customerKey, above). Assign the key of the parent to the parent key field, then leave the object's key field set to null. When the object is saved, the datastore populates the key field with the complete key, including the entity group parent.

システムが生成した数値IDとエンティティグループの親でオブジェクトを作るには、エンティティグループの親のキーフィールド(上記ではcustomerKey)を使う必要があります。

If a class has an entity group parent field, you can use an equality filter in a query on the parent field. (Inequality filters on parent keys are not supported.)

エンティティグループの親のフィールドを持つクラスの場合、その親のフィールドに等号フィルタをクエリに使用することができます。(不等号フィルタを親のキーに使用することはサポートされていません。)

What Can Be Done In a Transaction

The datastore imposes several restrictions on what can be done inside a single transaction.

Datastoreは単一のトランザクション内でできることにいくつかの制限を課します。

All datastore operations in a transaction must operate on entities in the same entity group. This includes retrieving entities by key, updating entities, and deleting entities. Notice that each root entity belongs to a separate entity group, so a single transaction cannot create or operate on more than one root entity.

トランザクション内の全てのDatastore操作は、同じエンティティグループのエンティティへの操作でなければなりません。これには、エンティティをキーにより抽出する、エンティティを更新する、エンティティを削除することが含まれます。ルートエンティティは、別のエンティティグループに所属しています。よって単一のトランザクションは1つ以上のルートエンティティを作成したり操作したりすることができません。

An app cannot perform a query during a transaction. However, an app can retrieve datastore entities using keys during a transaction, and be guaranteed that the fetched entity is consistent with the rest of the transaction. You can prepare keys prior to the transaction, or you can build keys inside the transaction with key names or IDs.

アプリケーションはトランザクションの中でクエリを実行することができません。しかし、アプリケーションはトランザクション中にキーを使うことでDatastoreのエンティティを抽出することができます。そして、そのようにして取得したエンティティはその後のトランザクションで一貫性が保たれることが保障されます。トランザクションの前にキーを準備したり、トランザクションの中で、キー名かIDを使ってキーを組み立てることもできます。

An application cannot create or update an entity more than once in a single transaction.

アプリケーションは単一のトランザクションで1回以上エンティティを作成したり更新することができません。

JDO performs all actions between the call to tx.begin() and the call to tx.commit() in a single transaction. If any action fails due to the requested entity group being in use by another process, JDO throws a JDODataStoreException or a JDOException, cause by a java.util.ConcurrentModificationException.

JDOはx.begine()を呼び出してからtx.commit()を呼び出す間で行う全ての操作を単一のトランザクション内で実行します。要求したエンティティグループが、他のプロセスで使われていると、いかなる操作も失敗し、 java.util.ConcurrentModificationExceptionが原因で、JDOはJDODataStoreExceptionかJDOExceptionをスローします。

In a system with optimistic concurrency, it is typical for an application to try the transaction again several times before giving up. JDO only performs the transaction once; the application must repeat the transaction, if desired. For example:

楽観的並行性のシステムでは、アプリケーションはランザクションをあきらめる前に何回かト試行するのが一般的です。JDOはトランザクションを一回しか実行しません。アプリケーションはもし望むならそのトランザクションを繰り返す必要があります。

 
        for (int i = 0; i < NUM_RETRIES; i++) {
            pm.currentTransaction().begin();
 
            ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345");
            members.incrementCounterBy(1);
 
            try {
                pm.currentTransaction().commit();
                break;
 
            } catch (JDOCanRetryException ex) {
                if (i == (NUM_RETRIES - 1)) { 
                    throw ex;
                }
            }
        }

An attempt to update more than one entity group in the same transaction will throw a JDOFatalUserException. Notice that each object that doesn't have an entity group parent resides in its own entity group, so you cannot create multiple parent-less entities in a single transaction.

同じトランザクションで、1つ以上のエンティティグループを更新しようとすると、JDOFatalUserExceptionがスローされます。エンティティグループの親をもたない各オブジェクトは、エンティティグループを所有しています。よって、親のないエンティティを単一トランザクションで複数作成することはできません。

An attempt to update the same entity multiple times in a single transaction (such as with repeated makePersistent() calls) will throw a JDOFatalUserException. Instead, just modify the persistent objects within the transaction, and allow the call to commit() to apply the changes.

単一のトランザクションで、同じエンティティを複数回アップデート(例えばmakePersistent()を繰り返し呼ぶ)しようとすると、JDOFatalUserExceptionがスローされます。その代わり、トランザクション内で対象の永続化オブジェクトを変更し、変更を適用するためcommit()を呼んでください。

Uses For Transactions

This example demonstrates one use of transactions: updating an entity with a new property value relative to its current value.

次にエンティティから現在の値を取り出し、新しいプロパティで更新するトランザクションの例を示します。

 
        Key k = KeyFactory.createKey("Employee", "k12345");
        Employee e = pm.getObjectById(Employee.class, k);
        e.counter += 1;
        pm.makePersistent(e);

This requires a transaction because the value may be updated by another user after this code fetches the object, but before it saves the modified object. Without a transaction, the user's request will use the value of counter prior to the other user's update, and the save will overwrite the new value. With a transaction, the application is told about the other user's update. If the entity is updated during the transaction, then the transaction fails with an exception. The application can repeat the transaction to use the new data.

この例はトランザクションを必要とします。なぜなら、オブジェクトを取得したあとオブジェクトの変更を保存する前に、他のユーザによって対象の値が更新される可能性があるためです。トランザクションがない場合、ユーザの要求は他のユーザが更新する前にcounterの値を使い、そして保存したときに新しい値を上書きしてしまいます。トランザクションを使用した場合、アプリケーションは他のユーザの更新を伝えます。トランザクションの間に対象のエンティティが更新された場合、トランザクションは失敗し例外が発生します。アプリケーションは新しいデータを使うため、トランザクションを繰り返すことができます。

Another common use for transactions is to update an entity with a named key, or create it if it doesn't yet exist:

他のトランザクションの一般的な使い方は、名前付けられたキーでエンティティを更新したり、存在しないときに作成する場合です。

 
        // PersistenceManager pm = ...;
 
        Transaction tx = pm.currentTransaction();
 
        String id = "jj_industrial";
        String companyName = "J.J. Industrial";
       
        try {
            tx.begin();
 
            Key k = KeyFactory.createKey("SalesAccount", id);
            SalesAccount account;
            try {
                account = pm.getObjectById(Employee.class, k);
            } catch (JDOObjectNotFoundException e) {
                account = new SalesAccount();
                account.setId(id);
            }
 
            account.setCompanyName(companyName);
            pm.makePersistent(account);
 
            tx.commit();
 
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

As before, a transaction is necessary to handle the case where another user is attempting to create or update an entity with the same string ID. Without a transaction, if the entity does not exist and two users attempt to create it, the second will overwrite the first without knowing that it happened. With a transaction, the second attempt will fail atomically, and can be retried by the application to fetch the new entity and update it.

前述の通り、トランザクションは他のユーザが同じ文字列IDを持つエンティティを作成したり更新したりしようとするのをハンドリングする必要があります。トランザクションを使用しない場合、対象のエンティティが存在せず、2人のユーザがそれを作成しようとすると、2人目のユーザが最初の値を知らずに上書きしてしまいます。トランザクションを使用した場合、2人目のユーザの作成は自動的に失敗し、アプリケーションによりリトライを行い、新しいエンティティを取得してそれを更新することができます。

Tip: A transaction should happen as quickly as possible to reduce the likelihood that the entities used by the transaction will change, requiring the transaction be retried. As much as possible, prepare data outside of the transaction, then execute the transaction to perform datastore operations that depend on a consistent state. The application should prepare keys for objects used inside the transaction, then fetch the entities inside the transaction.

Tip:トランザクションはできるだけ早く、そのトランザクションで使われているエンティティがかわるような状態を減らし、トランザクションがリトライされるように要求するべきです。可能性はありますが、トランザクションの外でデータを準備し、一貫性のある状態が必要なDatastoreの操作を実行するトランザクションを実行してくさい。アプリケーションはトランザクション内で使用するオブジェクトのキーを用意し、トランザクションの中でエンティティを取得するべきです。

Disabling Transactions and Porting Existing JDO Apps

The JDO configuration we recommend using sets a property named datanucleus.appengine.autoCreateDatastoreTxns to true. This is an App Engine-specific property that tells the JDO implementation to associate datastore transactions with the JDO transactions that are managed in application code. If you are building a new app from scratch, this is probably what you want. However, if you have an existing, JDO-based application that you want to get running on App Engine, you may want to use an alternate persistence configuration which sets the value of this property to false:

我々が推奨するJDOの設定はdatanucleus.appengine.autoCreateDatastoreTxnsプロパティをtrueに設定することです。これはApp Engine特有のプロパティで、JDO実装にアプリケーションコードで管理されたJDOトランザクションとDatastoreのトランザクションを連携させるように命じます。スクラッチから新しいアプリケーションを構築している場合、これはおそらくあなたが望んできることです。しかし、既にあるJDOベースのアプリケーションをAPP Engineで動作させたい場合、他の永続化設定を使いたいと思うかもしれません。その場合は、このプロパティをfalseに設定してください。

 
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
 
    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="false"/>
    </persistence-manager-factory>
</jdoconfig>

In order to understand why this may be useful, remember that you can only operate on objects that belong to the same entity group within a transaction. Applications built using traditional databases typically assume the availability of global transactions, which allow you to update any set of records inside a transaction. Since the App Engine datastore does not support global transactions, code that assumes the availability of global transactions will yield exceptions. Instead of going through your (potentially large) codebase and removing all your transaction management code, you can simply disable datastore transactions. This of course does nothing to address assumptions your code makes about atomicity of multi-record modifications, but it allows you to get your app working so that you can then focus on refactoring your transactional code incrementally and as needed, rather than all at once.

これがなぜ役に立つのかを理解するために、トランザクション内では同一のエンティティグループに所属するエンティティのみ操作できることを思い出してください。従来のデータベースを使っているアプリケーションは一般的にグローバルトランザクションが使えると仮定するでしょう。それはトランザクション内でどんなレコードでも更新できます。しかしApp Engine datastoreはグローバルトランザクションをサポートしていません。グローバルトランザクションが使えることを仮定しているコードは例外を発生させます。あなたの(とても大きな)コードベースを読み返し、全てのトランザクション管理コードを削除する代わりに、データストアのトランザクションを無効化してください。これはもちろんあなたのコードが複数のレコードの変更をアトミックにすることを仮定して処理するものではありませんが、アプリケーションを動作させることができるようになります。よって、全て一度に行うのではなく、必要であればトランザクションのコードに集中してリファクタリングすることができるようになります。

0 件のコメント: