Cómo usar JDO 3.0 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 la versión 2.x del complemento DataNucleus para App Engine. Este complemento corresponde a la versión 3.0 de DataNucleus Access Platform, que te permite usar App Engine Datastore a través de JDO 3.0.

Consulta la documentación de Access Platform 3.0 para obtener más información acerca de JDO. En particular, visita Asignación de correspondencias en JDO y API de JDO.

Advertencia: El complemento DataNucleus 2.x para App Engine usa DataNucleus v3.x. Este nuevo complemento no es del todo compatible con el anterior (1.x). Si instalas la versión nueva, asegúrate de actualizar y probar tu aplicación.

Herramientas de compilación compatibles con JDO 2.x y 3.0

Puedes usar Maven con la versión 2.x o 3.0 del complemento de DataNucleus para App Engine:

  • Para usuarios de Maven: puedes mejorar las clases con las siguientes configuraciones en el archivo pom.xml:
    <plugin>
        <groupId>org.datanucleus</groupId>
        <artifactId>maven-datanucleus-plugin</artifactId>
        <version>3.2.0-m1</version>
        <configuration>
            <api>JDO</api>
            <props>${basedir}/datanucleus.properties</props>
            <verbose>true</verbose>
            <enhancerName>ASM</enhancerName>
        </configuration>
        <executions>
            <execution>
                <phase>process-classes</phase>
                <goals>
                    <goal>enhance</goal>
                </goals>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>org.datanucleus</groupId>
                <artifactId>datanucleus-core</artifactId>
                <version>3.1.3</version>
            </dependency>
        </dependencies>
    </plugin>

Migra a la versión 2.x del complemento DataNucleus

Esta sección proporciona instrucciones para actualizar tu aplicación de modo que use la versión 2.x del complemento DataNucleus para App Engine, que corresponde a DataNucleus Access Platform 3.0 y JDO 3.0. Este complemento no es del todo compatible con la versión 1.x y posiblemente cambie. Si haces la actualización, asegúrate de actualizar también y probar el código de tu aplicación.

Nuevos comportamientos predeterminados

La versión 2.x del complemento DataNucleus para App Engine incorpora algunas diferencias en las configuraciones predeterminadas respecto de la versión anterior:

  • Las llamadas no transaccionales a PersistenceManager.makePersistent() y PersistenceManager.deletePersistent() ahora se ejecutan de forma atómica. (Estas funciones antes se ejecutaban en la siguiente transacción o en PersistenceManager.close()).
  • Ya no hay una excepción relativa a la asignación duplicada de PersistenceManagerFactory (PMF). En su lugar, si tienes la propiedad de persistencia datanucleus.singletonPMFForName configurada en verdadero, se mostrará el PMF del singleton actualmente asignado a ese nombre.
  • Ahora se admiten relaciones sin propietario. Consulta Relaciones sin propietario.

Cambios en los archivos de configuración

Para actualizar tu aplicación a la versión 2.x del complemento de DataNucleus de App Engine, debes cambiar la configuración en build.xml y jdoconfig.xml.

Advertencia Tras actualizar la configuración, prueba el código de tu aplicación para garantizar la compatibilidad con versiones anteriores. Si estás configurando una aplicación nueva y deseas usar la versión más reciente del complemento, dirígete a Cómo configurar JDO 3.0.

  1. La propiedad PersistenceManagerFactoryClass cambió. Cambia esta línea en jdoconfig.xml:

    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>

    a:
    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

En build.xml

El objetivo copyjars debe cambiar para ajustarse a DataNucleus 2.x:

  1. El objetivo copyjars cambió. Actualiza esta sección:
    <target name="copyjars"
        description="Copies the App Engine JARs to the WAR.">
      <mkdir dir="war/WEB-INF/lib" />
      <copy
          todir="war/WEB-INF/lib"
          flatten="true">
        <fileset dir="${sdk.dir}/lib/user">
          <include name="**/*.jar" />
        </fileset>
      </copy>
    </target>
    a:
    <target name="copyjars"
        description="Copies the App Engine JARs to the WAR.">
      <mkdir dir="war/WEB-INF/lib" />
      <copy
          todir="war/WEB-INF/lib"
          flatten="true">
        <fileset dir="${sdk.dir}/lib/user">
            <include name="**/appengine-api-1.0-sdk*.jar" />
        </fileset>
        <fileset dir="${sdk.dir}/lib/opt/user">
          <include name="appengine-api-labs/v1/*.jar" />
          <include name="jsr107/v1/*.jar" />
          <include name="datanucleus/v2/*.jar" />
        </fileset>
      </copy>
    </target>
  2. El objetivo datanucleusenhance cambió. Actualiza esta sección:
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
      <enhance_war war="war" />
    </target>
    a:
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
        <enhance_war war="war">
                <args>
                <arg value="-enhancerVersion"/>
                <arg value="v2"/>
            </args>
        </enhance_war>
    </target>

Cómo configurar JDO 3.0

Para acceder al almacén de datos con JDO, las aplicaciones de App Engine necesitan lo siguiente:

  • Los JAR para JDO y el complemento de DataNucleus deben estar en el directorio war/WEB-INF/lib/ de la app.
  • 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.

Copia archivos JAR

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

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.api.jdo.JDOPersistenceManagerFactory"/>
        <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.singletonPMFForName" 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 creó el administrador. También puedes anular esta configuración para un objeto Query único.

Para establecer la política de lectura de una 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. Sin embargo, ten en cuenta que esta configuración solo se aplica a consultas principales dentro de un grupo de entidad determinado. Las consultas no principales siempre tienen coherencia eventual sin importar la política de lectura aplicable).

<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 javax.jdo.option.DatastoreReadTimeoutMillis de JDO. 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.api.jdo.JDOPersistenceManagerFactory"/>
        <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.api.jdo.JDOPersistenceManagerFactory"/>
        <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" />
        <property name="datanucleus.singletonPMFForName" value="true" />
    </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

JDO usa un paso de "mejora" de poscompilación como parte del proceso de compilación para asociar clases de datos con la implementación de JDO.

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 classpath 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.

Como la inicialización de instancias de PersistenceManagerFactory demora, una aplicación debería reutilizar una instancia individual. La instancia de PersistenceManagerFactory puede administrarse con facilidad creando 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(). Usar la instancia de PersistenceManager luego de llamar a su método close() es un error.

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

Características no compatibles de JDO 3.0

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

  • Relaciones de varios a varios con propietario.
  • 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 independiente en el almacén de datos.

Cómo inhabilitar transacciones y transferir aplicaciones JDO existentes

La configuración de JDO que recomendamos usar 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 ejecutar 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.api.jdo.JDOPersistenceManagerFactory"/>
        <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.