Utilisation de JDO 2.3 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 intègre une mise en œuvre de JDO 2.3 pour App Engine Datastore. La mise en œuvre repose sur la version 1.0 de la plate-forme d'accès DataNucleus, qui est la mise en œuvre de référence Open Source pour JDO 2.3.

Remarque : Les instructions de cette page s'appliquent à JDO version 2.3, qui utilise la version 1.0 du plug-in DataNucleus pour App Engine. App Engine propose désormais le plug-in DataNucleus 2.x qui permet d'exécuter JDO 3.0. Le nouveau plug-in prend en charge les relations indépendantes et propose un certain nombre de nouvelles API et fonctionnalités. Cette mise à niveau n'est pas totalement rétrocompatible avec la version précédente. Si vous recompilez une application à l'aide de JDO 3.0, vous devez mettre à jour et retester votre code. Pour plus d'informations sur la nouvelle version, consultez la section JDO 3.0. Pour plus d'informations sur la mise à niveau, consultez la section Utiliser JDO 3.0 avec App Engine.

Configurer JDO

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

  • Les fichiers JAR du plug-in JDO et DataNucleus App Engine doivent se trouver dans le répertoire war/WEB-INF/lib/ de l'application.
  • Un fichier de configuration nommé jdoconfig.xml doit figurer dans le répertoire war/WEB-INF/classes/META-INF/ de l'application, avec une configuration qui indique à 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 JDO.

Si vous compilez votre projet à l'aide d'Apache Ant, vous pouvez utiliser une tâche Ant incluse dans le SDK pour exécuter la phase d'enrichissement du code. Vous devez copier les fichiers JAR et créer le fichier de configuration au moment où vous configurez votre projet.

Copier des fichiers JAR

Les fichiers JAR de JDO et du datastore sont inclus dans le SDK Java App Engine. Vous pouvez les trouver dans le répertoire appengine-java-sdk/lib/user/orm/.

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

Définir des règles de lecture et de 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 la politique de lecture d'un PersistenceManagerFactory , incluez une propriété nommée datanucleus.appengine.datastoreReadConsistency. Les valeurs possibles sont EVENTUAL et STRONG : si elles ne sont pas spécifiées, la valeur par défaut est STRONG. Notez que ces paramètres s'appliquent uniquement aux requêtes ascendantes d'un groupe d'entités donné. Les requêtes non-ascendantes et entre les groupes finissent toujours par être cohérentes, quelle que soit la règle 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.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>

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.

Enrichissement du code des classes de données

JDO utilise une étape "d'enrichissement" post-compilation dans le processus de construction pour associer des classes de données à la mise en œuvre de JDO.

Si vous utilisez Apache Ant, le SDK intègre une tâche Ant permettant d'effectuer cette opération.

Pour appliquer l'opération d'enrichissement du code aux 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.

Étant donné que l'initialisation d'une instance PersistenceManagerFactory demande un certain temps, une application doit réutiliser une instance. Pour appliquer cette règle, une exception est levée si l'application instancie plus d'une instance PersistenceManagerFactory (avec le même nom de configuration). Un moyen simple de gérer l'instance PersistenceManagerFactory consiste à créer une classe wrapper unique avec une instance statique à l'aide du code suivant :

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 figurant 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(). L'instance PersistenceManager ne doit pas être utilisée après avoir appelé sa méthode close().

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

Fonctionnalités incompatibles de JDO 2.3

Les fonctionnalités de l'interface JDO non prises en charge par la mise en œuvre d'App Engine sont les suivantes :

  • Relations indépendantes. Vous pouvez mettre en œuvre des relations indépendantes à l'aide de valeurs Key explicites. La syntaxe de JDO relative aux relations indépendantes sera éventuellement prise en charge dans une version ultérieure.
  • 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.
  • IdentityType.DATASTORE pour l'annotation @PersistenceCapable. Seul IdentityType.APPLICATION est pris en charge.
  • À ce jour, un bug empêche les relations d'appartenance un à plusieurs dans lesquelles le parent et l'enfant correspondent à la même classe, ce qui complique la modélisation des arborescences. Ce problème sera résolu dans une version ultérieure. Vous pouvez contourner cette difficulté en stockant des valeurs Key explicites pour le parent ou pour les enfants.

Désactiver les transactions et assurer la portabilité d'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.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>

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