Google Code offered in: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
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.
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.
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 { tx.begin(); ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345"); members.incrementCounterBy(1); pm.makePersistent(members); tx.commit(); } finally { if (tx.isActive()) { tx.rollback(); } }
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.
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.
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.
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 { @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.
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.)
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.
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++) { 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.
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.
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.
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.
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.
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
:
<?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.