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 de App Engine para JDO y DataNucleus deben estar en el directorio war/WEB-INF/lib/ de la aplicación.
  • La configuración del archivo llamado jdoconfig.xml debe estar en el directorio war/WEB-INF/classes/META-INF/ de la aplicación, con la configuración que le indica a JDO usar App Engine Datastore.
  • 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.

Cómo copiar archivos JAR

Los archivos JAR para JDO y Datastore se incluyen con el SDK de Java en 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, para hacer esto, debes especificar 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 cree el administrador. También puedes anular esta configuración para un objeto Query simple.

Para establecer la política de lectura de PersistenceManagerFactory, incluye una propiedad llamada datanucleus.appengine.datastoreReadConsistency. Los valores posibles son EVENTUAL y STRONG: si no se especifican, el valor predeterminado es STRONG. Ten en cuenta que esta configuración se aplica solo 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 de JDO javax.jdo.option.DatastoreReadTimeoutMillis. Para las 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, los cuales usan diferentes atributos name, para usar instancias PersistenceManager con diferentes configuraciones en la misma aplicación. Por ejemplo, el siguiente archivo jdoconfig.xml establece dos conjuntos de configuraciones, uno llamado "transactions-optional" y el otro "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 Obtén una instancia de PersistenceManager a continuación para conocer información acerca de cómo crear un PersistenceManager con un conjunto de configuraciones con nombre.

Cómo mejorar 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 en las clases compiladas con el siguiente comando:

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

La ruta de clase debe contener el archivo JAR appengine-tools-api.jar del directorio appengine-java-sdk/lib/ y todas las clases de datos.

Para obtener más información sobre el optimizador 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 del conjunto de configuraciones en el archivo jdoconfig.xml. Si la aplicación usa varios conjuntos de configuraciones, deberás 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(). Usar la instancia de PersistenceManager después de llamar a su método close() generará un error.

    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 las transacciones y transferir aplicaciones de JDO existentes

La configuración de JDO que recomendamos usar configura una propiedad llamada datanucleus.appengine.autoCreateDatastoreTxns como 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 deseas ejecutar una aplicación existente basada en JDO en App Engine, puede que necesites usar una configuración de persistencia alternativa que configure el valor de esta propiedad como 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.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Entorno estándar de App Engine para Java 8