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.


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:


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

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.


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).


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:


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 {
    @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();

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


// ...
    @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.


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.


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.


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.


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


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:


        for (int i = 0; i < NUM_RETRIES; i++) {
            ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345");
            try {
            } 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.


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.


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;

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.


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 {
            Key k = KeyFactory.createKey("SalesAccount", id);
            SalesAccount account;
            try {
                account = pm.getObjectById(Employee.class, k);
            } catch (JDOObjectNotFoundException e) {
                account = new SalesAccount();
        } finally {
            if (tx.isActive()) {

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.


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.


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"
    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
        <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"/>

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 件のコメント: