Usa JDO 2.3 con App Engine

Java Data Objects (JDO) es una interfaz estándar para acceder a las bases de datos en Java, lo que proporciona una asignación entre las clases y tablas de base de datos de Java. Hay un complemento de código abierto disponible para usar JDO con Datastore y, en esta página, se proporciona información sobre cómo comenzar a utilizarlo.

Advertencia: Creemos que la mayoría de los desarrolladores tendrá una mejor experiencia con la API de Datastore de bajo nivel, o bien con una de las API de código abierto diseñadas específicamente para Datastore, como Objectify. JDO se diseñó para usarse con bases de datos relacionales tradicionales y, por lo tanto, no puede representar de manera explícita algunos de los aspectos de Datastore que lo diferencian de las bases de datos relacionales, como grupos de entidad y consultas principales. Esto puede ocasionar problemas sutiles que son difíciles de entender y corregir.

El SDK de Java en App Engine incluye una implementación JDO 2.3 para App Engine Datastore. La implementación se basa en la versión 1.0 de DataNucleus Access Platform, la implementación de referencia de código abierto para JDO 2.3.

Nota: Las instrucciones en esta página se aplican a la versión 2.3 de JDO, que usa la versión 1.0 del complemento de DataNucleus para App Engine. App Engine ahora ofrece el complemento 2.x de DataNucleus que te permite ejecutar JDO 3.0. El complemento nuevo admite relaciones sin dueño y proporciona una cantidad de API y características nuevas. Esta actualización no es del todo compatible con la versión anterior. Si vuelves a compilar una aplicación con JDO 3.0, debes actualizar y restablecer tu código. Para obtener más información sobre la versión nueva, consulta JDO 3.0. Para obtener más información sobre la actualización, consulta Cómo usar JDO 3.0 con App Engine.

Cómo configurar JDO 2.3

Si deseas usar JDO para acceder al almacén de datos, una aplicación de App Engine necesita lo siguiente:

  • Los archivos JAR del complemento JDO y DataNucleus de App Engine deben estar en el directorio war/WEB-INF/lib/ de la aplicación.
  • Un archivo de configuración llamado jdoconfig.xml debe estar en el directorio war/WEB-INF/classes/META-INF/ de la app, con una configuración que le indica a JDO que use el almacén de datos de App Engine.
  • El proceso de compilación del proyecto debe realizar un paso de "mejora" posterior a la compilación en las clases de datos compiladas para asociarlas con la implementación de JDO.

Si usas Apache Ant con el fin de compilar tu proyecto, puedes usar una tarea de Ant que se incluye con el SDK para realizar el paso de mejora. Debes copiar los archivos JAR y crear el archivo de configuración cuando configures el proyecto.

Copia archivos JAR

Los JAR de JDO y del almacén de datos se incluyen con el SDK de Java de App Engine. Puedes encontrarlos en el directorio appengine-java-sdk/lib/user/orm/.

Copia los archivos JAR en el directorio war/WEB-INF/lib/ de la aplicación.

Asegúrate de que appengine-api.jar también esté en el directorio war/WEB-INF/lib/. (es posible que ya lo hayas copiado cuando creaste el proyecto). El complemento de DataNucleus en App Engine usa este archivo JAR para acceder al almacén de datos.

Cómo crear el archivo jdoconfig.xml

La interfaz de JDO necesita un archivo de configuración llamado jdoconfig.xml en el directorio war/WEB-INF/classes/META-INF/ de la aplicación. Puedes crear este archivo directamente en esta ubicación o solicitar al proceso de compilación que copie este archivo desde un directorio fuente.

Crea el archivo con el siguiente contenido:

<?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="true"/>
    </persistence-manager-factory>
</jdoconfig>

Cómo configurar la política de lectura y el plazo de llamada de Datastore

Como se describe en la página de Consultas de Datastore, puedes configurar la política de lectura (coherencia sólida vs. coherencia eventual) y el plazo de llamada para personalizar el comportamiento de Datastore. En JDO, puedes hacerlo especificando los valores deseados en el elemento <persistence-manager-factory> del archivo jdoconfig.xml. Todas las llamadas realizadas con una instancia PersistenceManager determinada usarán los valores de configuración vigentes cuando PersistenceManagerFactory creó el administrador. También puedes anular esta configuración para un solo objeto Query.

Para establecer la política de lectura para un PersistenceManagerFactory, incluye una propiedad llamada datanucleus.appengine.datastoreReadConsistency. Los valores posibles son EVENTUAL y STRONG; si no hay especificación, el valor predeterminado es STRONG. Ten en cuenta que esta configuración solo se aplica a las consultas principales dentro de un grupo de entidad determinado. Las consultas no principales y entre grupos siempre son de coherencia eventual, independientemente de la política de lectura que prevalece.

        <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />

Puedes establecer plazos de llamada al almacén de datos de forma individual para lecturas y escrituras. Para las lecturas, usa la propiedad estándar JDO javax.jdo.option.DatastoreReadTimeoutMillis. Para escrituras, usa javax.jdo.option.DatastoreWriteTimeoutMillis. El valor es una cantidad de tiempo en milisegundos.

        <property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />
        <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />

Si deseas usar transacciones entre grupos (XG), agrega la siguiente propiedad:

        <property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />

Puedes tener varios elementos <persistence-manager-factory> en el mismo archivo jdoconfig.xml, con atributos name diferentes, para usar instancias PersistenceManager con configuraciones diferentes en la misma app. Por ejemplo, el siguiente archivo jdoconfig.xml establece dos conjuntos de configuración, uno llamado "transactions-optional" y otro llamado "eventual-reads-short-deadlines":

<?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="true"/>
    </persistence-manager-factory>

    <persistence-manager-factory name="eventual-reads-short-deadlines">
        <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="true"/>

        <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
        <property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />
        <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />
    </persistence-manager-factory>
</jdoconfig>

Consulta Cómo obtener una instancia de PersistenceManager a continuación para aprender a crear un PersistenceManager con un conjunto de configuraciones con nombre.

Mejora las clases de datos

En el proceso de compilación, JDO usa un paso de "mejora" posterior a la compilación para asociar las clases de datos con la implementación de JDO.

Si usas Apache Ant, el SDK incluye una tarea de Ant que realiza este paso.

Desde la línea de comandos, puedes realizar el paso de mejora sobre clases compiladas con este comando:

java -cp classpath com.google.appengine.tools.enhancer.Enhance
class-files

La Ruta de clase debe contener el JAR appengine-tools-api.jar del directorio appengine-java-sdk/lib/, así como todas sus clases de datos.

Para obtener más información sobre el enhancer del código de bytes de DataNucleus, consulta la documentación de DataNucleus.

Cómo obtener una instancia de PersistenceManager

Una aplicación interactúa con JDO mediante una instancia de la clase PersistenceManager. Obtienes esta instancia cuando la creas y llamas a un método en una instancia de la clase PersistenceManagerFactory. La fábrica usa la configuración de JDO para crear instancias de PersistenceManager.

Debido a que una instancia de PersistenceManagerFactory demora en inicializarse, la aplicación debe volver a usar una instancia simple. Para ejecutar esto, se genera una excepción si la aplicación crea más de una instancia de PersistenceManagerFactory (con el mismo nombre de configuración). Una forma sencilla de administrar la instancia de PersistenceManagerFactory es crear una clase wrapper singleton con una instancia estática de la siguiente manera:

PMF.java

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

Sugerencia: "transactions-optional" hace referencia al nombre de la configuración establecida en el archivo jdoconfig.xml. Si tu app usa varios conjuntos de configuración, tendrás que extender este código para llamar a JDOHelper.getPersistenceManagerFactory() si lo deseas. Tu código debería almacenar en caché una instancia singleton de cada PersistenceManagerFactory.

La aplicación usa la instancia de fábrica para crear una instancia de PersistenceManager por cada solicitud que accede al almacén de datos.

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import PMF;

// ...
    PersistenceManager pm = PMF.get().getPersistenceManager();

Usa PersistenceManager para almacenar, actualizar y borrar objetos de datos, y realizar consultas sobre el almacén de datos.

Cuando termines de usar la instancia de PersistenceManager, debes llamar a su método close(). Es un error usar la instancia de PersistenceManager después de llamar a su método close().

    try {
        // ... do stuff with pm ...
    } finally {
        pm.close();
    }

Características no compatibles de JDO 2.3

Las siguientes características de la interfaz de JDO no son compatibles con la implementación de App Engine:

  • Relaciones sin dueño. Puedes implementar relaciones sin dueño mediante valores de clave explícitos. La sintaxis de JDO para relaciones sin dueño puede ser compatible en una versión futura.
  • Relaciones de varios a varios con dueño.
  • Consultas del tipo "Join". No puedes usar un campo de una entidad secundaria en un filtro cuando realizas una consulta sobre la entidad principal. Ten en cuenta que puedes probar el campo de relación de la entidad principal directamente en una consulta mediante una clave.
  • Agrupación de JDOQL y otras consultas globales.
  • Consultas polimórficas. No puedes realizar una consulta de una clase para obtener instancias de una subclase. Cada clase se representa con un tipo de entidad individual en el almacén de datos.
  • IdentityType.DATASTORE para la anotación @PersistenceCapable. Solo se admite IdentityType.APPLICATION.
  • Actualmente, existe un error que previene relaciones de uno a varios con nombre en las que la entidad principal y la entidad secundaria son la misma clase, lo que dificulta modelar las estructuras de árbol. Esto se corregirá en una versión futura. Puedes evitar esto si almacenas los valores de clave explícitos para la entidad principal o entidad secundaria.

Cómo inhabilitar transacciones y transferir aplicaciones JDO existentes

La configuración de JDO que recomendamos utilizar establece una propiedad llamada datanucleus.appengine.autoCreateDatastoreTxns en true. Esta es una propiedad específica de App Engine que le indica a la implementación de JDO que asocie las transacciones del almacén de datos con las transacciones de JDO que se administran en el código de la aplicación. Si compilas una aplicación nueva desde cero, probablemente esto es lo que deseas. Sin embargo, si tienes una aplicación existente basada en JDO que quieres que se ejecute en App Engine, puedes usar una configuración de persistencia alternativa que establezca 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 por qué esto puede ser útil, recuerda que solo puedes utilizar objetos que pertenecen al mismo grupo de entidad dentro de una transacción. Las aplicaciones compiladas mediante bases de datos tradicionales normalmente suponen la disponibilidad de las transacciones globales, las cuales te permiten actualizar cualquier conjunto de registros dentro de una transacción. Debido a que App Engine Datastore no admite transacciones globales, App Engine genera excepciones si tu código supone la disponibilidad de transacciones globales. En lugar de revisar toda la base de código (posiblemente grande) y quitar todo el código de administración de transacciones, puedes inhabilitar las transacciones del almacén de datos. Aunque esto no tiene en cuenta las suposiciones que haga tu código acerca de la atomicidad de las modificaciones de varios registros, te permitirá poner la aplicación en funcionamiento para que puedas enfocarte en restablecer el código transaccional de forma gradual, según sea necesario, en lugar de hacerlo de una vez.