Entités, propriétés et clés

Dans Datastore, les objets de données sont appelés entités. Une entité est associée à une ou plusieurs propriétés nommées, chacune pouvant avoir une ou plusieurs valeurs. Les entités du même genre n'ont pas besoin d'avoir les mêmes propriétés, et il n'est pas nécessaire que les valeurs d'une entité pour une propriété donnée soient toutes du même type de données. (Si nécessaire, une application peut établir et appliquer de telles restrictions dans son propre modèle de données.)

Datastore est compatible avec divers types de données pour les valeurs de propriété, parmi lesquels :

  • Entiers
  • Nombres à virgule flottante
  • Chaînes
  • Dates
  • Données binaires

Pour obtenir la liste complète des types, consultez la section Propriétés et types de valeurs.

Chaque entité dans Datastore possède une clé qui l'identifie de manière unique. Cette clé comprend les composants suivants :

  • L'espace de noms de l'entité, qui permet de définir l'architecture mutualisée.
  • Le genre de l'entité, qui permet de la classer pour les besoins des requêtes Datastore.
  • Un identifiant associé à l'entité de manière individuelle, qui peut être :
    • Une chaîne de nom de clé
    • Un ID numérique sous forme d'entier.
  • Un chemin d'ancêtre facultatif localisant l'entité dans la hiérarchie Datastore

Une application peut extraire une entité individuelle de Datastore à l'aide de la clé de l'entité, ou extraire une ou plusieurs entités en émettant une requête basée sur leurs clés ou leurs valeurs de propriétés.

Le SDK Java App Engine inclut une API simple, fournie dans le package com.google.appengine.api.datastore, qui est directement compatible avec les fonctionnalités de Datastore. Tous les exemples de ce document sont basés sur cette API de bas niveau. Vous pouvez choisir de l'utiliser directement dans votre application ou comme base pour créer votre propre couche de gestion de données.

Datastore lui-même n'applique aucune restriction à la structure des entités, par exemple une propriété donnée ayant une valeur d'un type particulier. Cette tâche revient à l'application.

Genres et identifiants

Chaque entité Datastore est d'un genre particulier, ce qui permet de la classer dans une catégorie pour les besoins des requêtes. Par exemple, une application de ressources humaines peut représenter chaque employé d'une entreprise avec une entité du genre Employee. Dans l'API Datastore Java, vous spécifiez le genre d'une entité lors de sa création en tant qu'argument au constructeur Entity(). Tous les noms de genre commençant par deux traits de soulignement (__) sont réservés et ne peuvent pas être employés.

L'exemple suivant crée une entité du genre Employee, renseigne ses valeurs de propriété et l'enregistre dans Datastore :

Entity employee = new Entity("Employee", "asalieri");
employee.setProperty("firstName", "Antonio");
employee.setProperty("lastName", "Salieri");
employee.setProperty("hireDate", new Date());
employee.setProperty("attendedHrTraining", true);

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
datastore.put(employee);

En plus d'un genre, chaque entité possède un identifiant qui lui est attribué au moment de sa création. Comme il fait partie de la clé de l'entité, l'identifiant est associé de manière permanente à celle-ci et ne peut pas être modifié. Il peut être attribué de deux manières :

  • L'application peut spécifier sa propre chaîne de nom de clé pour l'entité.
  • Datastore peut attribuer automatiquement à l'entité un ID numérique sous forme d'entier.

Pour attribuer un nom de clé à une entité, indiquez le nom comme deuxième argument du constructeur lors de la création de l'entité :

Entity employee = new Entity("Employee", "asalieri");

Pour que Datastore attribue un ID numérique automatiquement, omettez cet argument :

Entity employee = new Entity("Employee");

Attribuer des identifiants

Datastore peut être configuré de sorte à générer des identifiants automatiques à l'aide de deux règles d'ID automatique différentes :

  • La règle default génère une séquence aléatoire d'ID inutilisés distribués d'une manière approximativement uniforme. Chaque ID peut comporter jusqu'à 16 chiffres décimaux.
  • La règle legacy crée une séquence d'ID entiers plus petits non consécutifs.

Si vous souhaitez que les ID d'entité soient visibles par l'utilisateur et/ou si vous souhaitez les afficher en fonction de leur ordre, l'allocation manuelle est la meilleure solution.

Datastore génère une séquence aléatoire d'ID inutilisés distribués de manière approximativement uniforme. Chaque ID peut comporter jusqu'à 16 chiffres décimaux.

Chemins d'ancêtre

Dans Cloud Datastore, les entités forment un espace structuré de manière hiérarchique, semblable à la structure de répertoires d'un système de fichiers. Lorsque vous créez une entité, vous pouvez éventuellement en désigner une autre comme son parent. La nouvelle entité est alors un enfant de l'entité parente. Contrairement à ce qui se produit dans un système de fichiers, l'entité parente n'a pas besoin d'exister réellement. Une entité sans parent est une entité racine. L'association entre une entité et son parent est permanente, et elle ne peut plus être modifiée une fois l'entité créée. Cloud Datastore n'attribue jamais le même ID numérique à deux entités ayant le même parent ou à deux entités racines (celles sans parent).

Les ancêtres d'une entité correspondent à son parent, au parent de son parent et ainsi de suite, de manière récursive. Ses descendants sont ses enfants, les enfants de ses enfants, etc. Une entité racine et tous ses descendants appartiennent au même groupe d'entités. La séquence d'entités commençant par une entité racine, puis allant du parent à l'enfant et menant à une entité donnée, constitue le chemin d'ancêtre de cette entité. La clé complète identifiant l'entité consiste en une séquence de paires genre/identifiant, qui spécifie son chemin d'ancêtre et se termine par les valeurs de l'entité elle-même :

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Pour une entité racine, le chemin d'ancêtre est vide, et la clé est constituée uniquement du genre et de l'identifiant de l'entité :

[Person:GreatGrandpa]

Le schéma suivant illustre ce concept :

Affiche la relation entre l'entité racine et les entités enfants dans le groupe d'entités

Pour désigner le parent d'une entité, fournissez la clé de l'entité parente en tant qu'argument au constructeur Entity() lors de la création de l'entité enfant. Vous pouvez obtenir la clé en appelant la méthode getKey() de l'entité parente :

Entity employee = new Entity("Employee");
datastore.put(employee);

Entity address = new Entity("Address", employee.getKey());
datastore.put(address);

Si la nouvelle entité possède également un nom de clé, fournissez le nom de clé en tant que deuxième argument du constructeur Entity() et la clé de l'entité parent en tant que troisième argument :

Entity address = new Entity("Address", "addr1", employee.getKey());

Transactions et groupes d'entités

Toute tentative de création, de mise à jour ou de suppression d'une entité a lieu dans le cadre d'une transaction. Une même transaction peut inclure un nombre illimité d'opérations de ce type. Pour maintenir la cohérence des données, la transaction garantit que toutes les opérations qu'elle contient sont appliquées à Datastore de manière unitaire ou, en cas d'échec de l'une de ces opérations, qu'aucune d'entre elles n'est appliquée. En outre, toutes les lectures fortement cohérentes (requêtes ascendantes ou opérations "get") effectuées dans la même transaction observent un instantané cohérent des données.

Comme mentionné ci-dessus, un groupe d'entités est un ensemble d'entités connectées par le biais d'un ancêtre à un élément racine commun. L'organisation des données en groupes d'entités peut limiter le nombre de transactions pouvant être effectuées :

  • Toutes les données auxquelles accède une transaction doivent être contenues dans 25 groupes d'entités au maximum.
  • Si vous souhaitez employer des requêtes dans une transaction, les données doivent être organisées en groupes d'entités de sorte que vous puissiez spécifier des filtres d'ancêtres qui correspondent aux données appropriées.
  • Le débit en écriture est limité à environ une transaction par seconde pour un seul groupe d'entités. Cette limite a été définie car Datastore effectue une réplication synchrone sans maître de chaque groupe d'entités sur une vaste zone géographique, afin de fournir une fiabilité et une tolérance aux pannes élevées.

Dans de nombreuses applications, il est acceptable de recourir à la cohérence à terme (à savoir à une requête non ascendante couvrant plusieurs groupes d'entités et pouvant parfois renvoyer des données légèrement obsolètes) lors de l'obtention d'une vue d'ensemble de données non liées, puis de passer à la cohérence forte (requête ascendante ou opération get d'une seule entité) lors de la visualisation ou de la modification d'un seul ensemble de données étroitement liées. Dans ces applications, il est généralement recommandé d'utiliser un groupe d'entités distinct pour chaque ensemble de données étroitement liées. Pour en savoir plus, consultez la page Structurer des données pour renforcer la cohérence.

Propriétés et types de valeurs

Les valeurs de données associées à une entité consistent en une ou plusieurs propriétés. Chaque propriété a un nom, et une ou plusieurs valeurs. Une propriété peut avoir des valeurs de plus d'un type, et deux entités peuvent avoir des valeurs de types différents pour la même propriété. Les propriétés peuvent être indexées ou non indexées (les requêtes qui ordonnent ou filtrent une propriété P ignorent les entités pour lesquelles P n'est pas indexée.) Une entité peut avoir 20 000 propriétés indexées au maximum.

Les types de valeurs suivants sont acceptés :

Type de valeur Type Java Ordre de tri Remarques
Entier short
int
long
java.lang.Short
java.lang.Integer
java.lang.Long
Numérique Stocké sous forme d'entier long, puis converti au type de champ

Dépassement des valeurs hors plage
Nombre à virgule flottante float
double
java.lang.Float
java.lang.Double
Numérique Double précision 64 bits,
IEEE 754.
Booléen boolean
java.lang.Boolean
false<true
Chaîne de texte (courte) java.lang.String Unicode Jusqu'à 1 500 octets

Valeurs supérieures à 1 500 octets renvoient IllegalArgumentException
Chaîne de texte (longue) com.google.appengine.api.datastore.Text Aucun Jusqu'à 1 mégaoctet

Non indexé
Chaînes d'octets (courtes) com.google.appengine.api.datastore.ShortBlob Ordre des octets Jusqu'à 1 500 octets

Valeurs supérieures à 1 500 octets renvoient IllegalArgumentException
Chaîne d'octets (longue) com.google.appengine.api.datastore.Blob Aucun Jusqu'à 1 mégaoctet

Non indexé
Date et heure java.util.Date Chronologique
Point géographique com.google.appengine.api.datastore.GeoPt En fonction de la latitude,
puis de la longitude
Adresse postale com.google.appengine.api.datastore.PostalAddress Unicode
Numéro de téléphone com.google.appengine.api.datastore.PhoneNumber Unicode
Adresse e-mail com.google.appengine.api.datastore.Email Unicode
Utilisateur de Google Accounts com.google.appengine.api.users.User Adresse e-mail
dans l'ordre Unicode
Descripteur de messagerie instantanée com.google.appengine.api.datastore.IMHandle Unicode
Lien com.google.appengine.api.datastore.Link Unicode
Catégorie com.google.appengine.api.datastore.Category Unicode
Note com.google.appengine.api.datastore.Rating Numérique
Clé Datastore com.google.appengine.api.datastore.Key
ou l'objet référencé (en tant qu'enfant)
Par éléments de chemin d'accès
(genre, identifiant,
genre, identifiant, etc.)
Jusqu'à 1 500 octets

Valeurs supérieures à 1 500 octets renvoient IllegalArgumentException
Clé Blobstore com.google.appengine.api.blobstore.BlobKey Ordre des octets
Entité intégrée com.google.appengine.api.datastore.EmbeddedEntity Aucun Pas indexé
Null null Aucun

Important : Nous vous recommandons vivement de ne pas enregistrer de valeur de propriété users.User, car elle inclut l'adresse e-mail et l'ID unique. Si un utilisateur change d'adresse e-mail et que vous comparez l'ancienne valeur user.User stockée à la nouvelle valeur user.User, elles ne correspondront pas. Envisagez plutôt d'utiliser la valeur d'ID d'utilisateur User comme identifiant unique stable de l'utilisateur.

Pour les chaînes de texte et les données binaires non codées (chaînes d'octets), Datastore prend en charge deux types de valeurs :

  • Les chaînes courtes (jusqu'à 1 500 octets) sont indexées et peuvent être utilisées dans les conditions de filtre de requêtes et les ordres de tri.
  • Les chaînes longues (jusqu'à 1 Mo) ne sont pas indexées et ne peuvent pas être utilisées dans les filtres de requête et les ordres de tri.
Remarque : Le type d'une chaîne d'octets longue est appelé Blob dans l'API Datastore. Ce type n'est pas lié aux objets blob utilisés dans l'API Blobstore.

Lorsqu'une requête implique une propriété avec des valeurs de types mixtes, Datastore utilise un ordre déterministe basé sur les représentations internes :

  1. Valeurs Null
  2. Nombres à virgule fixe
    • Entiers
    • Dates et heures
    • Notes
  3. Valeurs booléennes
  4. Séquences d'octets
    • Chaîne d'octets
    • Chaîne Unicode
    • Clés Blobstore
  5. Nombres à virgule flottante
  6. Points géographiques
  7. Utilisateurs disposant d'un compte Google
  8. Clés Datastore

Aucun ordre n'est défini pour les chaînes de texte longues et les chaînes d'octets longues, car elles ne sont pas indexées.

Utiliser des entités

Les applications peuvent utiliser l'API Datastore pour créer, récupérer, mettre à jour et supprimer des entités. Si l'application connaît la clé complète d'une entité (ou si elle peut la déduire de sa clé parente, de son genre et de son identifiant), elle peut s'en servir pour effectuer des opérations directement sur l'entité. Une application peut également obtenir la clé d'une entité à la suite d'une requête Datastore. Pour plus d'informations, consultez la page Requêtes Datastore.

L'API Datastore Java utilise des méthodes de l'interface DatastoreService pour effectuer des opérations sur des entités. Vous obtenez un objet DatastoreService en appelant la méthode statique DatastoreServiceFactory.getDatastoreService() :

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Créer une entité

Vous pouvez créer une entité en générant une instance de classe Entity, en fournissant le genre de l'entité en tant qu'argument au constructeur Entity().

Après avoir renseigné les propriétés de l'entité si nécessaire, vous devez l'enregistrer dans le datastore en la transmettant en tant qu'argument à la méthode DatastoreService.put(). Vous pouvez spécifier le nom de la clé de l'entité en le transmettant comme second argument au constructeur.

Entity employee = new Entity("Employee", "asalieri");
// Set the entity properties.
// ...
datastore.put(employee);

Si vous ne fournissez pas de nom de clé, Datastore génère automatiquement un ID numérique pour la clé de l'entité :

Entity employee = new Entity("Employee");
// Set the entity properties.
// ...
datastore.put(employee);

Récupérer une entité

Pour récupérer une entité identifiée par une clé donnée, transmettez l'objet Key à la méthode DatastoreService.get() :

// Key employeeKey = ...;
Entity employee = datastore.get(employeeKey);

Mettre à jour une entité

Pour mettre à jour une entité existante, modifiez les attributs de l'objet Entity, puis transmettez-le à la méthode DatastoreService.put(). Les données de l'objet écrasent l'entité existante. L'objet entier est envoyé à Datastore à chaque appel de put().

Supprimer une entité

Selon la clé d'une entité, vous pouvez supprimer l'entité à l'aide de la méthode DatastoreService.delete().

// Key employeeKey = ...;
datastore.delete(employeeKey);

Propriétés répétées

Vous pouvez stocker plusieurs valeurs dans une même propriété.

Entity employee = new Entity("Employee");
ArrayList<String> favoriteFruit = new ArrayList<String>();
favoriteFruit.add("Pear");
favoriteFruit.add("Apple");
employee.setProperty("favoriteFruit", favoriteFruit);
datastore.put(employee);

// Sometime later
employee = datastore.get(employee.getKey());
@SuppressWarnings("unchecked") // Cast can't verify generic type.
    ArrayList<String> retrievedFruits = (ArrayList<String>) employee
    .getProperty("favoriteFruit");

Entités intégrées

Il peut parfois être utile d'intégrer une entité en tant que propriété d'une autre entité. Cela peut être utile, par exemple, pour créer une structure hiérarchique de valeurs de propriété au sein d'une entité. La classe Java EmbeddedEntity vous permet d'effectuer les opérations suivantes :

// Entity employee = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678");
embeddedContactInfo.setProperty("phoneNumber", "555-555-5555");
embeddedContactInfo.setProperty("emailAddress", "test@example.com");

employee.setProperty("contactInfo", embeddedContactInfo);

Lorsqu'une entité intégrée est incluse dans les index, vous pouvez interroger les sous-propriétés. Si vous excluez une entité intégrée de l'indexation, toutes les sous-propriétés en sont également exclues. Si vous le souhaitez, vous pouvez associer une clé à une entité intégrée, mais contrairement à une entité complète, la clé n'est pas obligatoire. De plus, même si elle est présente, elle ne peut pas être utilisée pour récupérer l'entité.

Au lieu de renseigner manuellement les propriétés de l'entité intégrée, vous pouvez utiliser la méthode setPropertiesFrom() pour les copier à partir d'une entité existante :

// Entity employee = ...;
// Entity contactInfo = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setKey(contactInfo.getKey()); // Optional, used so we can recover original.
embeddedContactInfo.setPropertiesFrom(contactInfo);

employee.setProperty("contactInfo", embeddedContactInfo);

Vous pouvez utiliser ultérieurement la même méthode pour récupérer l'entité d'origine à partir de l'entité intégrée :

Entity employee = datastore.get(employeeKey);
EmbeddedEntity embeddedContactInfo = (EmbeddedEntity) employee.getProperty("contactInfo");

Key infoKey = embeddedContactInfo.getKey();
Entity contactInfo = new Entity(infoKey);
contactInfo.setPropertiesFrom(embeddedContactInfo);

Opérations par lot

Les méthodes DatastoreService put(), get() et delete() (et leurs équivalents AsyncDatastoreService) ont des versions par lot acceptant un objet iterable (de classe Entity pour put(), Key pour get() et delete()) et l'utilisent pour intervenir sur plusieurs entités via un seul appel Datastore :

Entity employee1 = new Entity("Employee");
Entity employee2 = new Entity("Employee");
Entity employee3 = new Entity("Employee");
// ...

List<Entity> employees = Arrays.asList(employee1, employee2, employee3);
datastore.put(employees);

Ces opérations par lots regroupent toutes les entités ou clés par groupe d'entités, puis exécutent l'opération demandée sur chaque groupe d'entités en parallèle. Il est plus rapide d'effectuer des appels par lot que des appels distincts pour chaque entité individuelle, car ils entraînent une surcharge pour un seul appel de service. Si plusieurs groupes d'entités sont impliqués, les opérations pour tous les groupes sont effectuées en parallèle côté serveur.

Générer des clés

Les applications peuvent utiliser la classe KeyFactory afin de créer un objet Key pour une entité à partir de composants connus, tels que le genre et l'identifiant de l'entité. Pour une entité sans parent, transmettez le genre et l'identifiant (chaîne de nom de clé ou ID numérique) à la méthode statique KeyFactory.createKey() pour créer la clé. Les exemples suivants créent une clé pour une entité de genre Person avec le nom de clé "GreatGrandpa" ou l'ID numérique 74219 :

Key k1 = KeyFactory.createKey("Person", "GreatGrandpa");
Key k2 = KeyFactory.createKey("Person", 74219);

Si la clé inclut un composant de chemin, vous pouvez utiliser la classe d'assistance KeyFactory.Builder pour créer le chemin. La méthode addChild de cette classe ajoute une seule entité au chemin et renvoie le générateur lui-même. Vous pouvez ainsi exécuter une série d'appels à la suite, en commençant par l'entité racine, afin de créer le chemin une entité à la fois. Après avoir créé le chemin complet, appelez getKey pour récupérer la clé obtenue :

Key k =
    new KeyFactory.Builder("Person", "GreatGrandpa")
        .addChild("Person", "Grandpa")
        .addChild("Person", "Dad")
        .addChild("Person", "Me")
        .getKey();

La classe KeyFactory inclut également les méthodes statiques keyToString et stringToKey pour effectuer une conversion entre les clés et leurs représentations sous forme de chaîne :

String personKeyStr = KeyFactory.keyToString(k);

// Some time later (for example, after using personKeyStr in a link).
Key personKey = KeyFactory.stringToKey(personKeyStr);
Entity person = datastore.get(personKey);

La représentation sous forme de chaîne d'une clé est adaptée au Web : elle ne contient aucun caractère considéré comme spécial en HTML ou dans les URL.

Utiliser une liste vide

Auparavant, Datastore n'avait pas de représentation pour une propriété représentant une liste vide. Le SDK Java a résolu ce problème en stockant les collections vides en tant que valeurs Null. Il n'y a donc aucun moyen de distinguer les valeurs Null des listes vides. Pour assurer la rétrocompatibilité, ce comportement par défaut s'applique toujours, comme résumé ci-dessous :

  • Les propriétés Null sont écrites comme nulles dans Datastore
  • Les collections vides sont écrites comme nulles dans Datastore.
  • Une valeur Null est lue comme étant nulle à partir de Datastore.
  • Les collections vides sont lues comme étant Null.

Toutefois, si vous modifiez le comportement par défaut, le SDK pour Java prendra en charge le stockage de listes vides. Nous vous recommandons de prendre en compte les conséquences de la modification du comportement par défaut de votre application, puis d'activer la prise en charge des listes vides.

Pour modifier le comportement par défaut afin d'utiliser des listes vides, définissez la propriété DATASTORE_EMPTY_LIST_SUPPORT lors de l'initialisation de votre application comme suit :

System.setProperty(DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT, Boolean.TRUE.toString());

Avec cette propriété définie sur true, comme illustré ci-dessus :

  • Les propriétés Null sont écrites comme nulles dans Datastore
  • Les collections vides sont écrites sous forme de liste vide dans Datastore
  • Une valeur Null est lue comme étant nulle à partir de Datastore.
  • Lors de la lecture depuis Datastore, une liste vide est renvoyée sous forme de collection vide.