Utiliser JDO 3.0 avec App Engine

JDO (Java Data Objects) est une interface standard permettant d’accéder aux bases de données en Java. Elle permet le mappage entre les classes et les tables de base de données Java. Il existe un plug-in open-source permettant d'utiliser JDO avec Datastore. Ici, vous découvrirez comment l'utiliser.

Avertissement : Nous pensons que la plupart des développeurs pourront améliorer leur expérience en tirant parti de l'API Datastore de bas niveau ou de l'une des API Open Source développées spécifiquement pour Datastore, comme Objectify. JDO a été conçu pour être utilisé avec des bases de données relationnelles traditionnelles et ne peut donc pas représenter explicitement certains aspects de Datastore qui le différencient des bases de données relationnelles, comme les groupes d'entités et les requêtes ascendantes. Cela peut entraîner des problèmes subtils, difficiles à comprendre et à résoudre.

Le SDK Java App Engine inclut la version 2.x du plug-in DataNucleus pour App Engine. Ce plug-in correspond à la version 3.0 de DataNucleus Access Plateform. Ainsi, vous pouvez utiliser App Engine DataStore via JDO 3.0.

Consultez la documentation d'Access Platform 3.0 pour en savoir plus sur JDO. Intéressez-vous particulièrement aux sections Mappage JDO et API JDO.

Avertissement : Le plug-in DataNucleus 2.x pour App Engine utilise DataNucleus v3.x. Ce nouveau plug-in n'est pas totalement rétrocompatible avec le précédent plug-in (1.x). Si vous effectuez une mise à niveau vers la nouvelle version, veillez à mettre à jour et à tester votre application.

Outils de compilation compatibles avec JDO 2.x et 3.0

Vous pouvez utiliser Apache Ant ou Maven pour utiliser la version 2.x ou 3.0 du plug-in DataNucleus pour App Engine :

  • Pour les utilisateurs d'Ant : le SDK comprend une tâche Ant qui effectue la phase d'enrichissement. Vous devez copier les fichiers JAR et créer le fichier de configuration au moment où vous configurez votre projet.
  • Pour les utilisateurs de Maven : vous pouvez enrichir les classes avec les configurations suivantes dans votre fichier 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>

Migrer vers la version 2.x du plug-in DataNucleus

Cette section fournit des instructions pour la mise à niveau de votre application afin d'utiliser la version 2.x du plug-in DataNucleus pour App Engine, qui correspond à DataNucleus Access Platform 3.0 et à JDO 3.0. Ce plug-in n'est pas totalement rétrocompatible avec le plug-in 1.x et est susceptible de changer. Si vous effectuez une mise à niveau, veillez à mettre à jour et à tester votre code d'application.

Nouveaux comportements par défaut

La version 2.x du plug-in App Engine DataNucleus a des valeurs par défaut différentes de la version précédente :

  • Les appels non transactionnels vers PersistenceManager.makePersistent() et PersistenceManager.deletePersistent() sont désormais exécutés de manière atomique. (Auparavant, l'exécution était effectuée lors de la transaction suivante ou avec PersistenceManager.close().)
  • Il n'y a plus d'exception sur l'allocation dupliquée de PersistenceManagerFactory (PMF). Désormais, si la propriété de persistance datanucleus.singletonPMFForName est définie sur la valeur true, elle renvoie l'objet EMF singleton actuellement alloué pour ce nom.
  • Les relations sans propriétaire sont désormais prises en charge. Consultez la section Relations sans propriétaire.

Modifications des fichiers de configuration

Pour mettre à niveau votre application vers la version 2.x du plug-in App Engine DataNucleus, vous devez modifier les paramètres de configuration dans build.xml et jdoconfig.xml.

Avertissement : Après avoir mis à jour votre configuration, vous devez tester le code de votre application pour garantir la compatibilité avec les versions antérieures. Si vous configurez une nouvelle application et que vous souhaitez utiliser la dernière version du plug-in, consultez la section Configuration de JDO 3.0 .

  1. La propriété PersistenceManagerFactoryClass a été modifiée. Modifiez la ligne suivante dans jdoconfig.xml :

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

    pour qu'elle ressemble à ceci :
    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

Dans build.xml

La cible copyjars doit être modifiée pour assurer la compatibilité avec DataNucleus 2.x :

  1. La cible copyjars a été modifiée. Modifiez la section ci-après :
    <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>
    pour qu'elle ressemble à ceci :
    <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. La cible datanucleusenhance a été modifiée. Modifiez la section ci-après :
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
      <enhance_war war="war" />
    </target>
    pour qu'elle ressemble à ceci :
    <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>

Configurer JDO 3.0

Pour accéder au datastore au moyen de JDO, une application App Engine a besoin que les conditions suivantes soient remplies :

  • Les fichiers JAR de JDO et du plug-in DataNucleus doivent se trouver dans le répertoire war/WEB-INF/lib/ de l'application.
  • Un fichier de configuration nommé jdoconfig.xml, indiquant à JDO d'utiliser le magasin de données App Engine, doit être stocké dans le répertoire de l'application war/WEB-INF/classes/META-INF/.
  • Le processus de compilation du projet doit exécuter une étape "d'enrichissement" post-compilation sur les classes de données compilées pour les associer à la mise en œuvre JDO.

Copie des fichiers JAR

Les fichiers JAR de JDO et du magasin de données sont disponibles dans le SDK Java App Engine. Ils sont stockés dans le répertoire appengine-java-sdk/lib/opt/user/datanucleus/v2/.

Copiez les fichiers JAR dans le répertoire war/WEB-INF/lib/ de votre application.

Assurez-vous que le fichier appengine-api.jar se trouve également dans le répertoire war/WEB-INF/lib/. (Vous l'avez peut-être déjà copié au moment de la création du projet.) Le plug-in DataNucleus App Engine utilise ce fichier JAR pour accéder au datastore.

Création du fichier jdoconfig.xml

L'interface JDO requiert la présence d'un fichier de configuration nommé jdoconfig.xml dans le répertoire de l'application war/WEB-INF/classes/META-INF/. Vous pouvez créer directement ce fichier à cet emplacement ou paramétrer votre processus de compilation afin de copier ce fichier à partir d'un répertoire source.

Créez le fichier en y incluant le contenu suivant :

<?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>

Définir les règles de lecture et la durée maximale de l'appel au datastore

Comme décrit sur la page Requêtes Datastore, vous pouvez personnaliser le comportement du Datastore en définissant les règles de lecture (cohérence forte ou à terme) et la durée maximale de l'appel. Pour ce faire, dans JDO, spécifiez les valeurs souhaitées dans l'élément <persistence-manager-factory> du fichier jdoconfig.xml. Tous les appels passés avec une instance PersistenceManager donnée utilisent les valeurs de configuration en vigueur lors de la création du gestionnaire par PersistenceManagerFactory. Vous pouvez également ignorer ces paramètres pour un objet Query unique.

Pour définir la règle de lecture pour une classe PersistenceManagerFactory, spécifiez une propriété nommée datanucleus.appengine.datastoreReadConsistency. Les valeurs possibles sont EVENTUAL et STRONG. Si aucune valeur n'est spécifiée, la valeur par défaut est STRONG. ( Notez cependant que ces paramètres s'appliquent uniquement aux requêtes ascendantes d'un groupe d'entités donné. Les requêtes non ascendantes sont toujours cohérentes à terme, quelles que soient les règles de lecture en vigueur.)

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

Le délai d'appel vers le datastore que vous définissez pour les lectures peut différer de celui défini pour les écritures. Pour les lectures, utilisez la propriété standard JDO javax.jdo.option.DatastoreReadTimeoutMillis. Pour les écritures, utilisez javax.jdo.option.DatastoreWriteTimeoutMillis. Cette valeur correspond à une durée exprimée en millisecondes.

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

Si vous souhaitez utiliser des transactions entre groupes (XG), ajoutez la propriété suivante :

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

Vous pouvez avoir plusieurs éléments <persistence-manager-factory> dans le même fichier jdoconfig.xml, en utilisant différents attributs name, pour utiliser des instances PersistenceManager avec des configurations différentes dans la même application. Par exemple, le fichier jdoconfig.xml suivant établit deux ensembles de configuration, l'un nommé "transactions-optional" et l'autre nommé "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>

Pour en savoir plus sur la création d'une instance PersistenceManager avec un ensemble de configuration nommé, consultez la section Obtenir une instance PersistenceManager ci-dessous.

Enrichissement du code des classes de données

JDO utilise une phase "d'enrichissement" post-compilation dans le processus de compilation pour associer des classes de données à l'implémentation JDO.

Pour effectuer l'opération d'enrichissement sur des classes compilées, exécutez la commande ci-après à partir de la ligne de commande :

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

Le paramètre classpath doit inclure le fichier JAR appengine-tools-api.jar situé dans le répertoire appengine-java-sdk/lib/, ainsi que l'ensemble de vos classes de données.

Pour en savoir plus sur l'outil d'enrichissement de bytecode DataNucleus, consultez la documentation de DataNucleus.

Obtenir une instance PersistenceManager

Une application interagit avec JDO à l'aide d'une instance de la classe PersistenceManager. Pour récupérer cette instance, vous devez instancier et appeler une méthode sur une instance de la classe PersistenceManagerFactory. L'usine utilise la configuration JDO pour créer des instances PersistenceManager.

L'initialisation d'une instance PersistenceManagerFactory demande un certain temps. Par conséquent, l'application doit réutiliser une même instance. Pour gérer facilement l'instance PersistenceManagerFactory, vous pouvez, par exemple, créer une classe wrapper singleton avec une instance statique, comme suit :

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

Conseil : "transactions-optional" fait référence au nom de l'ensemble de configuration défini dans le fichier jdoconfig.xml. Si votre application utilise plusieurs ensembles de configuration, vous devrez étendre ce code pour qu'il appelle la méthode JDOHelper.getPersistenceManagerFactory() autant de fois que nécessaire. Votre code doit mettre en cache une instance singleton de chaque PersistenceManagerFactory.

L'application utilise l'instance d'usine afin de créer une instance PersistenceManager pour chaque requête qui accède au datastore.

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

import PMF;

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

Utilisez l'instance PersistenceManager pour stocker, mettre à jour et supprimer des objets de données, ainsi que pour exécuter des requêtes datastore.

Lorsque vous avez fini d'utiliser l'instance PersistenceManager, vous devez appeler sa méthode close(). Vous ne devez pas utiliser l'instance PersistenceManager après avoir appelé sa méthode close().

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

Fonctionnalités de JDO 3.0 non prises en charge

Les fonctionnalités de l'interface JDO répertoriées ci-après ne sont pas prises en charge par l'implémentation d'App Engine :

  • Relations d'appartenance plusieurs-à-plusieurs.
  • Requêtes "Join". Vous ne pouvez pas utiliser le champ d'une entité enfant dans un filtre lorsque vous exécutez une requête sur le genre parent. Notez que vous pouvez tester directement le champ de relation du parent dans une requête à l'aide d'une clé.
  • Groupement JDOQL et autres requêtes d'agrégation.
  • Requêtes polymorphes. Vous ne pouvez pas exécuter de requête d'une classe pour récupérer des instances d'une sous-classe. Chaque classe est représentée par un genre d'entité distinct dans le datastore.

Désactiver des transactions et porter des applications JDO existantes

La configuration JDO que nous recommandons d'utiliser définit une propriété nommée datanucleus.appengine.autoCreateDatastoreTxns sur true. Cette propriété, propre à App Engine, indique à la mise en œuvre de JDO d'associer les transactions datastore aux transactions JDO qui sont gérées dans le code de l'application. Si vous compilez une nouvelle application dans son intégralité, vous cherchez probablement à obtenir ce résultat. Toutefois, si vous disposez déjà d'une application reposant sur JDO que vous voulez exécuter sur App Engine, il est possible que vous souhaitiez utiliser une autre configuration de persistance qui définit la valeur de cette propriété sur 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>

Pour comprendre l'utilité de cette opération, rappelez-vous qu'au sein d'une transaction, vous ne pouvez agir que sur des objets qui appartiennent au même groupe d'entités. Les applications compilées à l'aide de bases de données traditionnelles supposent généralement que les transactions globales sont disponibles, ce qui vous permet de mettre à jour n'importe quel ensemble d'enregistrements dans le cadre d'une transaction. Le datastore AppEngine ne prend pas en charge les transactions globales. Par conséquent, App Engine lève des exceptions si votre code repose sur la disponibilité de ces transactions. Plutôt que de parcourir votre codebase (potentiellement volumineux) et de supprimer la totalité du code relatif à la gestion des transactions, vous pouvez simplement désactiver les transactions du datastore. Cette solution ne résout pas le problème des suppositions de votre code concernant l'atomicité des modifications d'enregistrements multiples, mais elle vous permet de faire fonctionner votre application tout en vous laissant vous concentrer sur le réusinage de votre code transactionnel, de manière incrémentielle et selon les besoins, plutôt qu'en une seule fois.