Java Persistence API (JPA) est une interface standard permettant d'accéder à des bases de données en Java. Elle fournit un mappage automatique entre les classes Java et les tables de base de données. Un plug-in Open Source est disponible pour utiliser JPA avec Datastore. Cette page explique comment faire vos premiers pas avec ce plug-in.
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. JPA a été conçue pour être utilisée avec des bases de données classiques et n'a donc aucun moyen de représenter explicitement certains des aspects qui différencient Datastore des bases de données relationnelles, tels que 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 la version 2.x du plug-in DataNucleus pour Datastore. Ce plugin correspond à la version 3.0 de la plate-forme d'accès DataNucleus, ce qui vous permet d'utiliser App Engine Datastore via JPA 2.0.
Pour plus d'informations sur JPA, consultez la documentation d'Access Platform 3.0, en particulier la section concernant JPA.
Avertissement : La version 2.x du plug-in DataNucleus pour App Engine utilise DataNucleus en version 3.x. Le plug-in 2.x n'est pas totalement rétrocompatible avec le plug-in précédent 1.x. Si vous effectuez une mise à niveau vers la nouvelle version, veillez à mettre à jour votre application et à la tester.
Compiler des outils compatibles avec JPA 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-api-jdo</artifactId> <version>3.1.3</version> </dependency> </dependencies> </plugin>
Migrer vers la version 2.x du plug-in DataNucleus
Cette section fournit des instructions vous permettant de mettre à niveau votre application pour qu'elle utilise la version 2.x du plug-in DataNucleus pour App Engine, qui correspond à la plate-forme d'accès DataNucleus 3.0 et à JPA 2.0. La version 2.x du plug-in n'est pas totalement rétrocompatible avec la version 1.x, et elle est susceptible d'être modifiée sans préavis. Si vous effectuez une mise à niveau, veillez à mettre à jour le code de votre application et à le tester.
Nouveaux comportements par défaut
La version 2.x du plug-in DataNucleus pour App Engine possède des valeurs par défaut différentes de la version précédente 1.x :
- Le "fournisseur de persistance" JPA est désormais
org.datanucleus.api.jpa.PersistenceProviderImpl
. - La mise en cache de niveau 2 est activée par défaut. Pour obtenir le comportement par défaut précédent, définissez la propriété de persistance
datanucleus.cache.level2.type
sur none. (Vous pouvez également inclure le plug-in datanucleus-cache dans le chemin de classe et définir la propriété de persistancedatanucleus.cache.level2.type
sur javax.cache pour utiliser Memcache pour la mise en cache L2.) - Datastore
IdentifierFactory
est désormais configuré par défaut sur datanucleus2. Pour obtenir le comportement précédent, définissez la propriété de persistancedatanucleus.identifierFactory
sur datanucleus1. - Les appels non transactionnels vers
EntityManager.persist()
,EntityManager.merge()
etEntityManager.remove()
sont désormais exécutés de manière atomique. (Auparavant, l'exécution était effectuée lors de la transaction suivante ou avec l'appel deEntityManager.close()
.) - La fonction
retainValues
est activée pour JPA, ce qui signifie que les valeurs des champs chargés sont conservées dans les objets après une validation. - La fonction
javax.persistence.query.chunkSize
n'est plus utilisée. Utilisez plutôtdatanucleus.query.fetchSize
. - Il n'y a plus d'exception sur l'allocation EMF en double. Si la propriété de persistance
datanucleus.singletonEMFForName
est définie sur la valeur true, alors elle renvoie l'objet EMF singleton actuellement alloué pour ce nom. - Les relations sans propriétaire sont désormais prises en charge.
- Datastore Identity est désormais pris en charge.
Pour obtenir la liste complète des nouvelles fonctionnalités, consultez les notes de version.
Modifications des fichiers de configuration
Pour mettre à niveau votre application afin qu'elle utilise la version 2.0 du plug-in DataNucleus pour App Engine, vous devez modifier certains paramètres de configuration dans build.xml
et persistence.xml
. Si vous configurez une nouvelle application et souhaitez utiliser la dernière version du plug-in DataNucleus, passez à la section Configurer JPA 2.0.
Avertissement : Après avoir mis à jour votre configuration, vous devez tester le code de votre application pour en garantir la rétrocompatibilité.
Dans build.xml
La cible copyjars
doit être modifiée pour assurer la compatibilité avec DataNucleus 2.x :
- 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>
- 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>
Dans persistence.xml
La cible <provider>
a été modifiée. Mettez à jour la section suivante :
<provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>
vers :
<provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
Configurer JPA 2.0
Pour accéder au magasin de données au moyen de JPA, une application App Engine a besoin que les conditions suivantes soient remplies :
- Les fichiers JAR de JPA et du datastore doivent se trouver dans le répertoire
war/WEB-INF/lib/
de l'application. - Un fichier de configuration nommé
persistence.xml
, indiquant à JPA d'utiliser le datastore App Engine, doit être stocké dans le répertoirewar/WEB-INF/classes/META-INF/
de l'application. - 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 JPA.
Copie des fichiers JAR
Les fichiers JAR de JPA 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 magasin de données.
Créer le fichier persistence.xml
L'interface JPA requiert la présence d'un fichier de configuration nommé persistence.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" ?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="transactions-optional"> <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider> <properties> <property name="datanucleus.NontransactionalRead" value="true"/> <property name="datanucleus.NontransactionalWrite" value="true"/> <property name="datanucleus.ConnectionURL" value="appengine"/> <property name="datanucleus.singletonEMFForName" value="true"/> </properties> </persistence-unit> </persistence>
Règles de lecture et durée maximale de l'appel au Datastore
Comme indiqué sur la page Requêtes Datastore, vous pouvez définir les règles de lecture (cohérence forte ou cohérence à terme) et le délai d'appel du datastore pour EntityManagerFactory
dans le fichier persistence.xml
.
Ces paramètres sont stockés dans l'élément <persistence-unit>
. Tous les appels effectués avec une instance EntityManager
donnée utilisent la configuration sélectionnée au moment de la création du gestionnaire par la classe EntityManagerFactory
. Vous pouvez également ignorer ces options pour un objet Query
spécifique (comme décrit ci-après).
Pour définir les règles de lecture, spécifiez une propriété nommée datanucleus.appengine.datastoreReadConsistency
. Ses valeurs possibles sont EVENTUAL
(pour les lectures avec cohérence à terme) et STRONG
(pour les lectures avec cohérence forte). Si aucune valeur n'est spécifiée, la valeur par défaut de cet argument est STRONG
.
<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 JPA javax.persistence.query.timeout
. Pour les écritures, utilisez datanucleus.datastoreWriteTimeout
. La valeur du délai d'appel correspond à une durée exprimée en millisecondes.
<property name="javax.persistence.query.timeout" value="5000" /> <property name="datanucleus.datastoreWriteTimeout" 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 dans le même fichier persistence.xml
plusieurs éléments <persistence-unit>
utilisant différents attributs name
, afin d'exploiter des instances EntityManager
ayant des configurations différentes dans la même application. Par exemple, le fichier persistence.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" ?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="transactions-optional"> <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider> <properties> <property name="datanucleus.NontransactionalRead" value="true"/> <property name="datanucleus.NontransactionalWrite" value="true"/> <property name="datanucleus.ConnectionURL" value="appengine"/> </properties> </persistence-unit> <persistence-unit name="eventual-reads-short-deadlines"> <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider> <properties> <property name="datanucleus.NontransactionalRead" value="true"/> <property name="datanucleus.NontransactionalWrite" value="true"/> <property name="datanucleus.ConnectionURL" value="appengine"/> <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" /> <property name="javax.persistence.query.timeout" value="5000" /> <property name="datanucleus.datastoreWriteTimeout" value="10000" /> <property name="datanucleus.singletonEMFForName" value="true"/> </properties> </persistence-unit> </persistence>
Pour en savoir plus sur la création d'une instance EntityManager
avec un ensemble de configuration nommé, consultez la section Obtenir une instance EntityManager ci-dessous.
Vous pouvez passer outre les règles de lecture et le délai d'appel pour un objet Query
spécifique. Pour ignorer les règles de lecture d'un objet Query
, appelez sa méthode setHint()
à l'aide du code suivant :
Query q = em.createQuery("select from " + Book.class.getName()); q.setHint("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");
Comme indiqué ci-dessus, les valeurs possibles de cette règle sont "EVENTUAL"
et "STRONG"
.
Pour passer outre le délai de lecture, appelez la méthode à l'aide du code setHint()
suivant :
q.setHint("javax.persistence.query.timeout", 3000);
Il n'existe aucun moyen d'ignorer la configuration de ces options lorsque vous récupérez des entités par leur clé.
Enrichissement du code des classes de données
La mise en œuvre DataNucleus de JPA ajoute au processus de compilation une étape "d'enrichissement" post-compilation, afin d'associer des classes de données à la mise en œuvre de JPA.
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 org.datanucleus.enhancer.DataNucleusEnhancer class-files
Le paramètre classpath doit inclure les fichiers JAR datanucleus-core-*.jar
, datanucleus-jpa-*
, datanucleus-enhancer-*.jar
, asm-*.jar
et geronimo-jpa-*.jar
(où *
correspond au numéro de version approprié de chaque fichier JAR) situés dans le répertoire appengine-java-sdk/lib/tools/
, 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.
Récupérer une instance EntityManager
Une application interagit avec JPA à l'aide d'une instance de la classe EntityManager
. Pour récupérer cette instance, vous devez instancier la classe EntityManagerFactory
et appeler une méthode sur cet objet. La fabrique utilise la configuration JPA (identifiée par le nom "transactions-optional"
) pour créer des instances EntityManager
.
Étant donné qu'une instance EntityManagerFactory
met du temps à s'initialiser, il est judicieux de réutiliser une même instance autant que possible. Un moyen simple d'effectuer cette opération consiste à créer une classe wrapper singleton avec une instance statique à l'aide du code suivant :
EMF.java
import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public final class EMF { private static final EntityManagerFactory emfInstance = Persistence.createEntityManagerFactory("transactions-optional"); private EMF() {} public static EntityManagerFactory get() { return emfInstance; } }
Conseil : "transactions-optional"
fait référence au nom de l'ensemble de configuration défini dans le fichier persistence.xml
. Si votre application utilise plusieurs ensembles de configuration, vous devrez étendre ce code pour qu'il appelle la méthode Persistence.createEntityManagerFactory()
autant de fois que nécessaire. Votre code doit mettre en cache une instance singleton de chaque EntityManagerFactory
.
L'application utilise l'instance de fabrique afin de créer une instance EntityManager
pour chaque requête qui accède au datastore.
import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import EMF; // ... EntityManager em = EMF.get().createEntityManager();
Utilisez l'instance EntityManager
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 EntityManager
, vous devez appeler sa méthode close()
. Vous ne devez pas utiliser l'instance EntityManager
après avoir appelé sa méthode close()
.
try { // ... do stuff with em ... } finally { em.close(); }
Annotations de classe et de champ
Chaque objet enregistré par JPA devient une entité du magasin de données App Engine. Le genre de l'entité est dérivé du nom simple de la classe (sans le nom du package). Chaque champ persistant de la classe représente une propriété de l'entité, dont le nom correspond au nom du champ (en respectant la distinction majuscules-minuscules).
Pour indiquer qu'une classe Java peut être stockée et extraite du datastore à l'aide de JPA, attribuez-lui une annotation @Entity
. Exemple :
import javax.persistence.Entity; @Entity public class Employee { // ... }
Les champs de la classe de données qui doivent être stockés dans le magasin de données doivent être d'un type persistant par défaut ou être explicitement déclarés comme persistants.
Vous trouverez un graphique détaillant le comportement de persistance par défaut de JPA sur le site Web de DataNucleus. Pour déclarer explicitement un champ comme persistant, attribuez-lui une annotation @Basic
:
import java.util.Date; import javax.persistence.Enumerated; import com.google.appengine.api.datastore.ShortBlob; // ... @Basic private ShortBlob data;
Les types de champs possibles sont répertoriés ci-dessous :
- L'un des types de base pris en charge par le magasin de données
- Une collection (par exemple,
java.util.List<...>
) de valeurs d'un type de datastore de base - Une instance ou une collection d'instances d'une classe
@Entity
- Une classe encapsulée, stockée sous forme de propriétés dans l'entité
Une classe de données doit disposer d'un constructeur par défaut public ou protégé, ainsi que d'un champ dédié au stockage de la clé primaire de l'entité correspondante dans le magasin de données. Vous pouvez choisir parmi quatre genres de champs de clé, chacun utilisant un type de valeur et des annotations distincts. (Pour plus d'informations, consultez la page Créer des données : clés.) Le champ de clé le plus simple correspond à un entier long qui est automatiquement renseigné par JPA avec une valeur unique pour l'ensemble des instances de la classe lorsque l'objet est enregistré pour la première fois dans le magasin de données. Les clés de type entier long utilisent une annotation @Id
, ainsi qu'une annotation @GeneratedValue(strategy = GenerationType.IDENTITY)
:
import com.google.appengine.api.datastore.Key; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; // ... @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Key key;
Voici un exemple de classe de données :
import com.google.appengine.api.datastore.Key; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Key key; private String firstName; private String lastName; private Date hireDate; // Accessors for the fields. JPA doesn't use these, but your application does. public Key getKey() { return key; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Date getHireDate() { return hireDate; } public void setHireDate(Date hireDate) { this.hireDate = hireDate; } }
Héritage
JPA prend en charge la création de classes de données qui utilisent des stratégies d'héritage. Avant d'aborder le fonctionnement de l'héritage JPA dans App Engine, nous vous recommandons de consulter la documentation DataNucleus sur ce sujet, puis de reprendre votre lecture de cette documentation. C'est fait ? D'accord. L'héritage JPA dans App Engine fonctionne comme décrit dans la documentation de DataNucleus, avec quelques restrictions supplémentaires. Cette section spécifie ces restrictions, puis en présente quelques exemples concrets.
La stratégie d'héritage "JOINED" (par jointure) vous permet de fractionner les données d'un même objet de données dans différentes "tables". Toutefois, du fait que le magasin de données d'App Engine n'est pas compatible avec les jointures, l'utilisation d'un objet de données avec cette stratégie d'héritage nécessite un appel de procédure à distance pour chaque niveau d'héritage. C'est un fonctionnement potentiellement très inefficace. Par conséquent, la stratégie d'héritage "JOINED" n'est pas compatible avec les classes de données.
Par ailleurs, la stratégie d'héritage "SINGLE_TABLE" (table unique) vous permet de stocker les données d'un objet de données dans une "table" unique associée à la classe persistante figurant à la racine de votre hiérarchie d'héritage. Bien que cette stratégie ne présente pas de risque d'inefficacité, elle n'est pas prise en charge à ce jour. Cet aspect sera éventuellement modifié dans les versions ultérieures.
Maintenant, voici la bonne nouvelle : les stratégies "TABLE_PER_CLASS" et "MAPPED_SUPERCLASS" fonctionnent comme décrit dans la documentation de DataNucleus. Voyons un exemple :
Worker.java
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; @Entity @MappedSuperclass public abstract class Worker { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Key key; private String department; }
Employee.java
// ... imports ... @Entity public class Employee extends Worker { private int salary; }
Intern.java
import java.util.Date; // ... imports ... @Entity public class Intern extends Worker { private Date internshipEndDate; }
Dans cet exemple, nous avons ajouté une annotation @MappedSuperclass
à la déclaration de classe Worker
. Cela indique à JPA de stocker tous les champs persistants de la classe Worker
dans les entités du datastore correspondant à ses sous-classes. L'entité du datastore créée à la suite de l'appel de persist()
avec une instance Employee
aura deux propriétés nommées "department" "et "salary". L'entité du datastore créée à la suite de l'appel de persist()
avec une instance Intern
possède deux propriétés nommées "department" et "internshipEndDate". Le magasin de données ne contient aucune entité du genre "Worker".
Passons maintenant à un aspect un peu plus intéressant. Supposons qu'en plus des instances Employee
et Intern
, nous souhaitions également disposer d'une spécialisation de l'instance Employee
décrivant les employés qui ont quitté l'entreprise :
FormerEmployee.java
import java.util.Date; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; // ... imports ... @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class FormerEmployee extends Employee { private Date lastDay; }
Dans cet exemple, nous avons ajouté une annotation @Inheritance
à la déclaration de classe FormerEmployee
en définissant son attribut strategy
sur InheritanceType.TABLE_PER_CLASS
. Cette opération indique à JPA de stocker tous les champs persistants de la classe FormerEmployee
et de ses super-classes dans les entités du datastore correspondant aux instances FormerEmployee
. L'entité du datastore créée à la suite de l'appel de persist()
avec une instance FormerEmployee
possède trois propriétés nommées "department", "salary" et "lastDay". Il n'y aura jamais d'entité du genre "Employee" qui correspond à un FormerEmployee
, mais si vous appelez persist()
avec un objet dont le type d'exécution est Employee
, vous créez une entité de type "Employee".
L'utilisation combinée de relations et de stratégies d'héritage fonctionne tant que les types déclarés de vos champs de relations correspondent aux types d'exécution des objets que vous attribuez à ces champs. Pour plus d'informations, reportez-vous à la section sur les relations polymorphes. Bien que cette section présente des exemples relatifs à JDO, les concepts et les restrictions qui s'appliquent à JDO sont les mêmes que pour JPA.
Fonctionnalités non compatibles de JPA 2.0
Les fonctionnalités de l'interface JPA non prises en charge par l'implémentation App Engine sont les suivantes :
- Relations d'appartenance plusieurs à plusieurs.
- Requêtes "Join". Vous ne pouvez pas utiliser un 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é.
- Requêtes d'agrégation (GROUP BY, HAVING, SUM, AVG, MAX, MIN).
- 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.