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 :

  • Désormais, les appels non transactionnels à PersistenceManager.makePersistent() et à PersistenceManager.deletePersistent() sont exécutés de manière atomique. (Ces fonctions ont déjà été exécutées dans 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 true, elle renvoie l'objet PMF singleton actuellement attribué pour ce nom.
  • Les relations sans propriétaire sont maintenant 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 DataNucleus App Engine, 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. Remplacez la ligne dans jdoconfig.xml :

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

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

Dans build.xml

La cible copyjars doit être modifiée afin de prendre en charge DataNucleus 2.x :

  1. La cible copyjars a été modifiée. Remplacez 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>
    par :
    <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. Remplacez la section ci-après :
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
      <enhance_war war="war" />
    </target>
    par :
    <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 pour JDO et le plug-in DataNucleus doivent se trouver dans le répertoire war/WEB-INF/lib/.
  • Le répertoire war/WEB-INF/classes/META-INF/ doit inclure un fichier de configuration nommé jdoconfig.xml doté d'une configuration indiquant à JDO d'utiliser le datastore App Engine.
  • 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 de JDO.

Copie des fichiers JAR

Les fichiers JAR de JDO et du datastore sont inclus dans le SDK Java d'App Engine. Ils sont disponibles 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 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

Un fichier de configuration nommé jdoconfig.xml doit être inclus dans le répertoire war/WEB-INF/classes/META-INF/ pour l'interface JDO. 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, vous devez spécifier les valeurs souhaitées dans l'élément <persistence-manager-factory> du fichier jdoconfig.xml, dans JDO. 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 une instance PersistenceManagerFactory. Vous pouvez également remplacer ces paramètres pour un objet Query unique.

Pour définir les règles de lecture d'une instance PersistenceManagerFactory, incluez 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" />

Un même fichier jdoconfig.xml peut inclure plusieurs éléments <persistence-manager-factory> dotés d'attributs name différents, afin d'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 PersistentManager.

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 contenir le fichier JAR appengine-tools-api.jar du répertoire appengine-java-sdk/lib/, ainsi que toutes les 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 devez étendre ce code pour appeler JDOHelper.getPersistenceManagerFactory(), selon les besoins. Votre code doit mettre en cache une instance singleton de chaque instance 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 terminé avec l'instance de PersistenceManager, vous devez appeler sa méthode close(). Vous faites erreur si vous utilisez 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 vous recommandons d'utiliser définit la propriété 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 souhaitez exécuter une application existante basée sur JDO sur App Engine, il convient d'utiliser une autre configuration de persistance définissant 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.

Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Environnement standard App Engine pour Java 8