Usar JDO 2.3 con App Engine

Java Data Objects (JDO) es una interfaz estándar para acceder a bases de datos en Java que proporciona una asignación entre clases de Java y tablas de bases de datos. Hay un complemento de código abierto disponible para usar JDO con Datastore. En esta página se explica cómo empezar a usarlo.

Advertencia: Creemos que la mayoría de los desarrolladores disfrutarán de una mejor experiencia si usan la API Datastore de bajo nivel o una de las APIs de código abierto desarrolladas específicamente para Datastore, como Objectify. JDO se diseñó para usarse con bases de datos relacionales tradicionales, por lo que no tiene forma de representar explícitamente algunos de los aspectos de Datastore que lo diferencian de las bases de datos relacionales, como los grupos de entidades y las consultas de ancestros. Esto puede provocar problemas sutiles que son difíciles de entender y solucionar.

El SDK de Java de App Engine incluye una implementación de JDO 2.3 para el almacén de datos de App Engine. La implementación se basa en la versión 1.0 de la plataforma de acceso DataNucleus, la implementación de referencia de código abierto para JDO 2.3.

Nota: Las instrucciones de esta página se aplican a la versión 2.3 de JDO, que usa la versión 1.0 del complemento DataNucleus para App Engine. App Engine ahora ofrece el complemento DataNucleus 2.x, que te permite ejecutar JDO 3.0. El nuevo complemento admite relaciones sin propietario y proporciona varias APIs y funciones nuevas. Esta actualización no es totalmente compatible con la versión anterior. Si recompilas una aplicación con JDO 3.0, debes actualizar y volver a probar el código. Para obtener más información sobre la nueva versión, consulta JDO 3.0. Para obtener más información sobre la actualización, consulta Usar JDO 3.0 con App Engine.

Configurar JDO 2.3

Para usar JDO y acceder al almacén de datos, una aplicación de App Engine necesita lo siguiente:

  • Los archivos JAR del complemento de App Engine de JDO y DataNucleus deben estar en el directorio war/WEB-INF/lib/ de la aplicación.
  • Debe haber un archivo de configuración llamado jdoconfig.xml en el directorio war/WEB-INF/classes/META-INF/ de la aplicación, con una configuración que indique 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 a la implementación de JDO.

Copia de los archivos JAR

Los archivos JAR de JDO y del almacén de datos se incluyen en 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 tu aplicación.

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

Creación del archivo jdoconfig.xml.

La interfaz 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 hacer que tu proceso de compilación copie este archivo desde un directorio de origen.

Debes crear 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>

Definir la política de lectura del almacén de datos y el plazo de llamada

Como se describe en la página Consultas de Datastore, puedes personalizar el comportamiento de Datastore configurando la política de lectura (coherencia fuerte o eventual) y el plazo de llamada. En JDO, esto se hace especificando los valores deseados en el elemento <persistence-manager-factory> del archivo jdoconfig.xml. Todas las llamadas realizadas con una instancia de PersistenceManager determinada usarán los valores de configuración vigentes cuando el gestor se haya creado mediante PersistenceManagerFactory. También puedes anular estos ajustes en un solo objeto Query.

Para definir la política de lectura de un PersistenceManagerFactory, incluye una propiedad llamada datanucleus.appengine.datastoreReadConsistency. Los valores posibles son EVENTUAL y STRONG. Si no se especifica ningún valor, se utiliza STRONG de forma predeterminada. Ten en cuenta que estos ajustes solo se aplican a las consultas de ancestros de un grupo de entidades determinado. Las consultas que no son de ancestros y las consultas entre grupos siempre tienen una coherencia final, independientemente de la política de lectura vigente.

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

Puedes definir distintos tiempos límite en las invocaciones del almacén de datos tanto para procesos de lectura como de escritura. Para las lecturas, usa la propiedad estándar de JDO javax.jdo.option.DatastoreReadTimeoutMillis. Para las escrituras, usa javax.jdo.option.DatastoreWriteTimeoutMillis. El valor es un periodo de tiempo en milisegundos.

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

Si quieres usar transacciones entre grupos (XG), añade la siguiente propiedad:

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

Puedes tener varios elementos <persistence-manager-factory> en el mismo archivo jdoconfig.xml, con diferentes atributos name, para usar instancias de PersistenceManager con diferentes configuraciones en la misma aplicación. 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 la sección Obtener una instancia de PersistenceManager más abajo para obtener información sobre cómo crear un PersistenceManager con un conjunto de configuración con nombre.

Mejora de las clases de datos

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

Puede realizar el paso de mejora en las clases compiladas desde la línea de comandos con el siguiente comando:

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

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

Para obtener más información sobre el optimizador de bytecode de DataNucleus, consulta la documentación de DataNucleus.

Obtener una instancia de PersistenceManager

Una aplicación interactúa con JDO utilizando una instancia de la clase PersistenceManager. Para obtener esta instancia, debes crear una instancia de la clase PersistenceManagerFactory y llamar a un método en ella. La fábrica usa la configuración de JDO para crear instancias de PersistenceManager.

Como una instancia de PersistenceManagerFactory tarda en inicializarse, una aplicación debe reutilizar una sola instancia. Para aplicar esta medida, se genera una excepción si la aplicación crea más de un PersistenceManagerFactory (con el mismo nombre de configuración). Una forma sencilla de gestionar la instancia de PersistenceManagerFactory es crear una clase de envoltorio singleton con una instancia estática, como se muestra a continuación:

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;
    }
}

Nota: "transactions-optional" hace referencia al nombre del conjunto de configuración del archivo jdoconfig.xml. Si tu aplicación usa varios conjuntos de configuración, tendrás que ampliar este código para llamar a JDOHelper.getPersistenceManagerFactory() como quieras. Tu código debe 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 acceda al almacén de datos.

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

import PMF;

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

Utilizas PersistenceManager para almacenar, actualizar y eliminar objetos de datos, así como para realizar consultas de almacén de datos.

Cuando hayas terminado con 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();
    }

Funciones no admitidas de JDO 2.3

La implementación de App Engine no admite las siguientes funciones de la interfaz JDO:

  • Relaciones sin propiedad. Puedes implementar relaciones sin propietario mediante valores de clave explícitos. Es posible que la sintaxis de JDO para las relaciones no propias se admita en una versión futura.
  • Relaciones de propiedad multidireccionales.
  • Consultas "Join". No puedes usar un campo de una entidad secundaria en un filtro cuando realices una consulta en el tipo de entidad principal. Ten en cuenta que puedes probar el campo de relación de los padres directamente en la consulta mediante una clave.
  • Consultas de agrupación JDOQL y otras consultas de agrupación conjunta.
  • Consultas polimórficas. No puedes realizar una consulta de una clase para obtener instancias de una subclase. Cada clase se representa mediante un tipo de entidad independiente en el almacén de datos.
  • IdentityType.DATASTORE de la anotación @PersistenceCapable. Solo se admite IdentityType.APPLICATION.
  • Actualmente, hay un error que impide las relaciones de propiedad de uno a muchos en las que el elemento superior y el secundario son de la misma clase, lo que dificulta la modelización de estructuras de árbol. Esto se solucionará en futuras versiones. Puedes solucionar este problema almacenando valores de clave explícitos para el elemento principal o los secundarios.

Inhabilitar transacciones y migrar aplicaciones JDO

La configuración de JDO que recomendamos usar asigna el valor true a una propiedad llamada datanucleus.appengine.autoCreateDatastoreTxns. Se trata de una propiedad específica de App Engine que indica a la implementación de JDO que asocie las transacciones del almacén de datos con las transacciones de JDO que se gestionan en el código de la aplicación. Si vas a crear una aplicación desde cero, probablemente sea lo que buscas. Sin embargo, si tienes una aplicación basada en JDO y quieres que se ejecute en App Engine, puedes usar una configuración de persistencia alternativa que asigne el valor false a esta propiedad:

<?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 entender por qué puede ser útil, recuerda que solo puedes operar con objetos que pertenezcan al mismo grupo de entidades en una transacción. Las aplicaciones creadas con bases de datos tradicionales suelen presuponer la disponibilidad de transacciones globales, que te permiten actualizar cualquier conjunto de registros dentro de una transacción. Como el almacén de datos de App Engine no admite transacciones globales, App Engine genera excepciones si tu código asume la disponibilidad de transacciones globales. En lugar de revisar tu base de código (que puede ser grande) y eliminar todo el código de gestión de transacciones, puedes inhabilitar las transacciones de Datastore. Esto no hace nada para abordar las suposiciones que hace tu código sobre la atomicidad de las modificaciones de varios registros, pero te permite hacer que tu aplicación funcione para que puedas centrarte en refactorizar tu código transaccional de forma incremental y según sea necesario, en lugar de hacerlo todo a la vez.