Google Code disponible en: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
El almacén de datos de App Engine admite las transacciones. Una transacción es una operación o un conjunto de operaciones que se resuelven correcta o incorrectamente en su totalidad. Una aplicación puede realizar varias operaciones y cálculos en una única transacción.
Una transacción es una operación o un conjunto de operaciones del almacén de datos que se resuelven correcta o incorrectamente en su totalidad. Si la transacción se resuelve correctamente, todos los efectos deseados se aplicarán al almacén de datos. Si la transacción no se resuelve correctamente, no se aplicará ninguno de los efectos.
Todas las operaciones de escritura del almacén de datos son atómicas. Un intento de crear, actualizar o eliminar una entidad se aplica o no se aplica. Una operación puede fallar debido a una elevada tasa de contención provocada por el hecho de que muchos usuarios estén intentando modificar una entidad al mismo tiempo. También se pueden producir fallos en las operaciones si una aplicación alcanza un límite de cuota o si se produce un error interno en el almacén de datos. En cualquiera de los casos, los efectos de la operación no se aplicarán y el API del almacén de datos generará una excepción.
A continuación se muestra un ejemplo de cómo aumentar un campo denominado counter en un objeto denominado ClubMembers (clase no mostrada) que utiliza el API de transacción de JDO:
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();
}
}
Cada entidad pertenece a un grupo de entidades, un conjunto de una o varias entidades que se pueden manipular en una única transacción. Las relaciones de grupos de entidades indican a App Engine que almacene varias entidades de la misma parte de la red distribuida. Una transacción configura las operaciones de almacén de datos para un grupo de entidades, tras lo cual se aplican todas las operaciones o ninguna de ellas (si se genera un error en la transacción).
Cuando la aplicación crea una entidad, puede asignar otra entidad como entidad principal de la entidad nueva. Al asignar una entidad principal a una entidad nueva, se coloca la entidad nueva en el mismo grupo de entidades que la entidad principal.
Una entidad sin una entidad principal es una entidad raíz. Una entidad que sea la entidad principal de otra puede tener a su vez otra entidad principal. Una cadena de entidades principales de una entidad hasta la raíz es la ruta de la entidad; los miembros de la ruta son los ancestros de la entidad. La entidad principal de una entidad se define cuando se crea la entidad y no se puede modificar posteriormente.
Todas las entidades con una entidad raíz dada como ancestro pertenecen al mismo grupo de entidades. Todas las entidades de un grupo se almacenan en el mismo nodo de almacén de datos. Una única transacción puede modificar varias entidades de un grupo o añadir entidades nuevas al grupo haciendo que la entidad principal de la nueva entidad sea una entidad existente en el grupo.
La implementación App Engine de la interfaz JDO representa relaciones de propiedad uno a uno o uno a varios que utilizan grupos de entidades. Esto permite que los cambios realizados a un objeto y a sus objetos secundarios ocurran en la misma transacción. Consulta Relaciones.
En otras situaciones, puedes crear una entidad que contenga una entidad principal del grupo de entidades explícita mediante el establecimiento del campo de clave principal del objeto en la clave completa, incluida la clave de la entidad principal. El campo de clave principal del objeto deberá ser una instancia de Key o una cadena de clave codificada (no un nombre de clave sencillo como Long o String).
Al utilizar ID de cadena asignadas por la aplicación, puedes crear un objeto que contenga una entidad principal del grupo de entidades mediante el establecimiento del campo de clave en el valor de clave completo, que incluye la clave de la entidad principal. Para crear un valor de clave mediante una entidad principal del grupo de entidades, utiliza la clase KeyFactory.Builder, como se muestra a continuación:
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);
Puedes acceder a la clave de la entidad principal del grupo de entidades, independientemente de la clave del objeto que utiliza un campo, como se muestra a continuación:
// ...
@Persistent
@Extension(vendorName="datanucleus", key="gae.parent-pk", value="true")
private Key customerKey;
Para crear un objeto con una ID numérica generada por el sistema y una entidad principal del grupo de entidades, deberás utilizar un campo de clave de la entidad principal del grupo de entidades (como el campo customerKey mostrado anteriormente). Asigna la clave de la entidad principal al campo de clave de la entidad principal y, a continuación, establece el campo de clave del objeto en "null". Al guardar el objeto, el almacén de datos rellenará el campo de clave con la clave completa, incluida la entidad principal del grupo de entidades.
Si una clase contiene un campo de la entidad principal del grupo de entidades, podrás utilizar un filtro de igualdad en una consulta del campo de la entidad principal. (No se admiten filtros de desigualdad de las claves de entidades principales).
El almacén de datos impone una serie de restricciones respecto a las acciones que se pueden realizar en una transacción.
Todas las operaciones del almacén de datos incluidas en una transacción se deben realizar en entidades pertenecientes a un mismo grupo. Esto incluye la recuperación de entidades por clave, la actualización y la eliminación de entidades. Ten en cuenta que cada entidad raíz pertenece a un grupo de entidades independiente, por lo que una transacción no puede crearse ni realizarse en más de una entidad raíz.
Una aplicación no puede realizar ninguna consulta durante una transacción. No obstante, una aplicación puede recuperar entidades del almacén de datos mediante claves durante una transacción y garantizar que la entidad extraída es consistente con el resto de la transacción. Puedes preparar claves antes de la transacción o puedes crear claves dentro de la transacción con nombres de clave o ID.
Una aplicación no puede crear ni actualizar una entidad más de una vez en una sola transacción.
JDO realiza todas las acciones entre la llamada a tx.begin() y la llamada a tx.commit() en una única transacción. Si una acción falla debido a que otro proceso está utilizando el grupo de entidades solicitado, JDO generará una excepción JDODataStoreException o JDOException, provocada por la excepción java.util.ConcurrentModificationException.
En un sistema con simultaneidad optimista, una aplicación suele intentar la transacción varias veces antes de abandonarla. JDO sólo realiza la transacción una vez; la aplicación deberá repetir la transacción (si lo deseas). Por ejemplo:
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;
}
}
}
Si se intenta actualizar más de un grupo de entidades en la misma transacción, se generará una excepción JDOFatalUserException. Ten en cuenta que cada objeto que no contiene una entidad principal del grupo de entidades reside en su propio grupo de entidades, de forma que no puedes crear varias entidades sin entidad principal en una única transacción.
Si se intenta actualizar la misma entidad varias veces en una única transacción (como ocurre con llamadas makePersistent() repetidas), se generará una excepción JDOFatalUserException. En lugar de eso, modifica los objetos persistentes dentro de la transacción y permite que la llamada a commit() aplique los cambios.
Este ejemplo muestra un posible uso de las transacciones: la actualización de una entidad con un valor de propiedad nuevo respecto a su valor actual.
Key k = KeyFactory.createKey("Employee", "k12345");
Employee e = pm.getObjectById(Employee.class, k);
e.counter += 1;
pm.makePersistent(e);
Esto requiere una transacción ya que el valor lo puede actualizar otro usuario después de que este código extraiga el objeto, pero antes de que guarde el objeto modificado. Si no se realiza una transacción, la solicitud del usuario utilizará el valor de counter antes de la actualización del otro usuario y al guardar, se sobrescribirá el valor nuevo. Si se realiza una transacción, la aplicación tendrá constancia de la actualización realizada por el otro usuario. Si la entidad se actualiza durante la transacción, en ese caso, la transacción generará una excepción. La aplicación puede repetir la transacción para utilizar los datos nuevos.
Las transacciones también se suelen utilizar para actualizar una entidad con una clave con nombre o crearla si no existe:
// 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();
}
}
Como en el caso anterior, es necesario realizar una transacción para resolver una situación en la que otro usuario esté intentando crear o actualizar una entidad con la misma ID de cadena. En el caso de que no se realice una transacción, si la entidad no existe y dos usuarios intentan crearla, el segundo sobrescribirá al primero sin saber lo que ha pasado. Si se realiza una transacción, el segundo intento fallará atómicamente y la aplicación puede intentar extraer la entidad nueva para actualizarla.
Sugerencia: las transacciones se deben realizar lo más rápido posible para reducir las probabilidades de que las entidades utilizadas por la transacción cambien y haya que intentar realizar de nuevo la transacción. En la medida de lo posible, te recomendamos que prepares los datos antes de ejecutar la transacción para poder realizar operaciones del almacén de datos que dependan de un estado uniforme. La aplicación deberá preparar claves para los objetos que se utilicen en la transacción y, a continuación, extraerá las entidades de la transacción.
La configuración de JDO que recomendamos establece una propiedad denominada datanucleus.appengine.autoCreateDatastoreTxns en true. Se trata de una propiedad específica de App Engine que indica a la implementación de JDO que asocie transacciones del almacén de datos con las transacciones de JDO que se gestionan en código de aplicación. Si estás creando una nueva aplicación desde el principio, probablemente sea esto lo que desees. No obstante, si tienes una aplicación basada en JDO ya existente que quieres que se ejecute en App Engine, puede que desees utilizar una configuración de persistencia alternativa que establece el valor de esta propiedad en 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>
Para comprender porqué esto puede resultar útil, recuerda que sólo puedes operar en objetos que pertenezcan al mismo grupo de entidad de una transacción. Las aplicaciones creadas que emplean bases de datos tradicionales suelen asumir la disponibilidad de transacciones globales, que te permitirán actualizar cualquier conjunto de registros de una transacción. Dado que el almacén de datos de App Engine no admite transacciones globales, el código que asume la disponibilidad de transacciones globales producirá excepciones. En vez de revisar tu (posiblemente larga) base de código y eliminar todo el código de gestión de transacción, sencillamente puedes inhabilitar las transacciones del almacén de datos. Por supuesto, esto no influye en la dirección de las asunciones que el código realiza sobre la atomicidad de las modificaciones de registros múltiples, pero permite conseguir que tu aplicación funcione, de modo que puedas centrarte en refactorizar tu código transaccional de forma creciente y cuando sea necesario, lo que es mejor que hacerlo en todos al instante.