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 Apache Ant o Maven con las versiones 2.x o 3.0 del complemento DataNucleus para App Engine:

  • Para usuarios de Ant: el SDK incluye una tarea de Ant que realiza el paso de mejora. Cuando configures el proyecto, debes copiar los archivos JAR y crear el archivo de configuración.
  • 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>

Cómo migrar 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 manera atómica. (Estas funciones antes se ejecutaban en la siguiente transacción o durante PersistenceManager.close()).
  • Ya no hay una excepción relativa a la asignación duplicada de PersistenceManagerFactory (PMF). En cambio, si la propiedad de persistencia datanucleus.singletonPMFForName está configurada como verdadera, mostrará el singleton de PMF asignado actualmente a ese nombre.
  • Ahora se admiten relaciones sin propietario. Consulta Relaciones sin propietario.

Cambios en los archivos de configuración

Si quieres actualizar tu aplicación a la versión 2.x del complemento DataNucleus para 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ó. Reemplaza en jdoconfig.xml la siguiente línea:

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

    por:
    <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>
    por:
    <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 archivos JAR para JDO y el complemento DataNucleus deben encontrarse 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, donde se le 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" en la poscompilación sobre las clases de los datos compilados para asociarlos con la implementación de JDO.

Cómo copiar los archivos JAR

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

Copia los archivos JAR al 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/ (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 cree el administrador. También puedes anular esta configuración para un objeto Query simple.

Si quieres configurar la política de lectura para 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 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.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.

Cómo mejorar 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 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.

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 del conjunto de configuraciones del 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 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 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.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 influye en los distintos presupuestos del código acerca de la atomicidad de las modificaciones de registros múltiples, te permitirá poner tu aplicación en funcionamiento para que puedas enfocarte en refactorizar el código transaccional de forma gradual y según sea necesario en lugar de hacerlo de una sola vez.

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

Enviar comentarios sobre...

Entorno estándar de App Engine para Java 8