Création, récupération et suppression de données dans JDO

L'enregistrement d'un objet de données JDO dans le datastore nécessite simplement d'appeler la méthode makePersistent() de l'instance PersistenceManager. L'implémentation de JDO par App Engine utilise le champ de clé primaire de l'objet pour déterminer l'entité du magasin de données qui correspond à l'objet de données. Cette implémentation peut générer automatiquement des clés pour les nouveaux objets. Vous pouvez utiliser des clés pour récupérer rapidement des entités, et construire des clés à partir de valeurs connues (telles que les identifiants de compte).

Déclarer des objets comme persistants

Pour stocker un objet de données simple dans le datastore, vous appelez la méthode makePersistent() de PersistenceManager en lui transmettant l'instance.

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

Employee e = new Employee("Alfred", "Smith", new Date());

try {
    pm.makePersistent(e);
} finally {
    pm.close();
}

L'appel de la méthode makePersistent() s'effectue en mode synchrone et ne renvoie aucun résultat tant que l'objet n'a pas été enregistré et que les index n'ont pas été mis à jour.

Pour enregistrer plusieurs objets dans JDO, appelez la méthode makePersistentAll(...) avec une collection d'objets. Cette méthode utilise une seule opération d'enregistrement simultané de bas niveau, qui se révèle plus efficace qu'une série d'invocations distinctes de la méthode makePersistent(...).

Remarque : Si des champs persistants d'objets de données constituent des références à d'autres objets de données persistants, et que certains de ces objets n'ont jamais été enregistrés ni modifiés depuis leur chargement, les objets référencés sont également enregistrés dans le magasin de données. Consultez la page relative aux Relations.

Clés

Chacune des entités dans App Engine est associée à une clé unique. Une clé complète intègre différentes informations, telles que l'identifiant d'application, le kind et un identifiant d'entité. (Les clés contiennent également des informations sur les groupes d'entités. Pour plus d'informations, consultez la documentation sur les Transactions.)

La clé d'un objet est stockée dans un champ de l'instance. Vous identifiez le champ de clé primaire à l'aide de l'annotation @PrimaryKey.

L'application peut fournir la partie identifiant de la clé sous la forme d'une chaîne lorsque l'objet est créé, ou elle peut autoriser le magasin de données à générer automatiquement un identifiant numérique. La clé complète de chacune des entités du magasin de données doit être unique. En d'autres termes, tous les objets de même genre et dotés du même parent de groupe d'entités (s'il y a lieu) doivent disposer d'un identifiant qui leur est propre. Vous sélectionnez le comportement souhaité de la clé à l'aide du type du champ et des annotations.

Si la classe est utilisée en tant que classe "enfant" dans une relation, le champ de clé doit être d'un type capable de représenter un parent de groupe d'entités : soit une instance de Key, soit une valeur de type Key codée sous forme de chaîne. Pour obtenir plus d'informations sur les groupes d'entités, consultez la page Transactions. Reportez-vous également à la page concernant les relations pour en savoir plus sur ces dernières.

Conseil : Si l'application crée un objet auquel elle attribue le même identifiant de chaîne qu'un autre objet de même genre (et doté du même parent de groupe d'entités), l'enregistrement de ce nouvel objet écrase l'autre objet dans le magasin de données. Afin de déterminer si un identifiant de chaîne est déjà en cours d'utilisation avant de créer un objet, vous pouvez utiliser une transaction pour tenter de récupérer une entité disposant d'un identifiant spécifique, puis créer cette entité si elle n'existe pas. Pour en savoir plus, consultez la page Transactions.

Il existe quatre types de champs de clé primaire :

Long

Entier long (java.lang.Long) correspondant à un identifiant d'entité automatiquement généré par le datastore. Utilisez ce type de champ pour les objets dépourvus de parent de groupe d'entités dont l'identifiant doit être généré automatiquement par le magasin de données. Le champ de clé long d'une instance est renseigné lorsque l'instance est enregistrée.

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
Chaîne non encodée

Chaîne (java.lang.String), identifiant d'entité ("nom de clé") fourni par l'application lors de la création de l'objet. Utilisez ce type de champ pour les objets dépourvus de parent de groupe d'entités dont l'identifiant doit être fourni par l'application. L'application définit ce champ sur l'identifiant souhaité avant l'enregistrement.

import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    private String name;
Clé

Instance Key (com.google.appengine.api.datastore.Key). La valeur clé inclut la clé du parent de groupe d'entités (le cas échéant) et soit l'ID de chaîne attribué par l'application, soit l'ID numérique généré par le système. Pour créer l'objet avec un identifiant de chaîne attribué par l'application, vous créez la valeur Key avec l'identifiant, puis vous définissez le champ sur cette valeur. Pour créer l'objet avec un identifiant numérique généré par le système, laissez le champ de clé associé à la valeur null. (Pour plus d'informations sur l'utilisation des parents de groupes d'entités, consultez la documentation sur les Transactions.)

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    public void setKey(Key key) {
        this.key = key;
    }

L'application peut créer une instance Key à l'aide de la classe KeyFactory :

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

// ...
        Key key = KeyFactory.createKey(Employee.class.getSimpleName(), "Alfred.Smith@example.com");
        Employee e = new Employee();
        e.setKey(key);
        pm.makePersistent(e);
Clé sous forme de chaîne encodée

Type de champ semblable au type clé, mais doté d'une valeur correspondant à la forme chaîne encodée de la clé. Les clés au format de chaîne encodée vous permettent d'écrire votre application sous forme portable tout en tirant parti des groupes d'entités de magasin de données App Engine.

import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
    private String encodedKey;

L'application peut renseigner cette valeur avant l'enregistrement à l'aide d'une clé dotée d'un nom, ou bien laisser le champ défini sur null. Si le champ de clé encodée est associé à la valeur null, il est renseigné avec une clé générée par le système lors de l'enregistrement de l'objet.

Les instances de clé peuvent être converties en représentation de chaîne encodée (et vice versa) à l'aide des méthodes keyToString()et stringToKey() de la classe KeyFactory.

Si vous utilisez des chaînes de clé encodées, vous pouvez donner accès à la chaîne ou à l'identifiant numérique d'un objet à travers un champ supplémentaire :

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
    private String encodedKey;

    @Persistent
    @Extension(vendorName="datanucleus", key="gae.pk-name", value="true")
    private String keyName;

    // OR:

    @Persistent
    @Extension(vendorName="datanucleus", key="gae.pk-id", value="true")
    private Long keyId;

Vous pouvez définir un champ "gae.pk-name" sur un nom de clé avant d'enregistrer l'objet. Lorsque l'objet est enregistré, le champ de clé encodée est renseigné avec la clé complète, qui inclut notamment le nom de la clé. Ce champ doit être de type String.

Un champ "gae.pk-id" est renseigné lors de l'enregistrement de l'objet, et n'est pas modifiable. Ce champ doit être de type Long.

Lorsqu'un nouvel objet doté d'une clé générée (champ de clé utilisant valueStrategy = IdGeneratorStrategy.IDENTITY) est créé, sa valeur de clé initiale est null. Le champ de clé est renseigné au moment où l'objet est écrit dans le magasin de données. Si vous utilisez une transaction, l'objet est écrit lorsque la transaction est validée. Dans le cas contraire, l'objet est écrit lors de l'appel de la méthode makePersistent() en cas de création de l'objet, ou lors de l'appel de la méthode close() de l'instance PersistenceManager en cas de mise à jour de l'objet.

Pour plus d'informations sur la création de clés, consultez la documentation Entités, Propriétés et Clés.

Récupérer un objet par sa clé

Pour récupérer un objet en fonction de sa clé, utilisez la méthode getObjectById() de l'instance PersistenceManager. Cette méthode utilise la classe de l'objet, ainsi que la clé :

        Key k = KeyFactory.createKey(Employee.class.getSimpleName(), "Alfred.Smith@example.com");
        Employee e = pm.getObjectById(Employee.class, k);

Si la classe utilise un champ de clé qui correspond à un identifiant de chaîne non encodée (String) ou à un identifiant numérique (Long), la méthode getObjectByID() peut accepter la valeur simple en guise de paramètre de clé :

        Employee e = pm.getObjectById(Employee.class, "Alfred.Smith@example.com");

L'argument de clé peut être de l'un des types de champs de clé pris en charge (identifiant de chaîne, identifiant numérique, valeur Key, chaîne de clé encodée), ce type pouvant être différent de celui du champ de clé dans la classe. App Engine doit être en mesure de déterminer la clé complète à partir du nom de la classe et de la valeur fournie. Les identifiants de chaîne et les identifiants numériques s'excluent mutuellement ; en d'autres termes, un appel utilisant un identifiant numérique ne renvoie jamais d'entité avec un identifiant de chaîne. En cas d'utilisation d'une valeur Key ou d'une chaîne de clé encodée, la clé doit faire référence à une entité dont le genre est représenté par la classe.

Mettre un objet à jour

L'une des méthodes possibles pour mettre à jour un objet avec JDO consiste à récupérer ce dernier, puis à le modifier pendant que le gestionnaire PersistenceManager ayant renvoyé l'objet est encore ouvert. Les modifications sont conservées après la fermeture du gestionnaire PersistenceManager. Exemple :

public void updateEmployeeTitle(User user, String newTitle) {
    PersistenceManager pm = PMF.get().getPersistenceManager();
    try {
        Employee e = pm.getObjectById(Employee.class, user.getEmail());
        if (titleChangeIsAuthorized(e, newTitle) {
            e.setTitle(newTitle);
        } else {
            throw new UnauthorizedTitleChangeException(e, newTitle);
        }
    } finally {
        pm.close();
    }
}

Étant donné que l'instance Employee a été renvoyée par le gestionnaire PersistenceManager, ce dernier a connaissance des modifications apportées aux champs persistants de l'objet Employee et met automatiquement à jour le datastore avec ces modifications lorsqu'il est fermé. En effet, l'instance de classe Employee est "attachée" à l'objet PersistenceManager.

Vous pouvez modifier un objet après la fermeture du PersistenceManager en déclarant la classe comme "détachable". Pour ce faire, ajoutez l'attribut detachable à l'annotation @PersistenceCapable :

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable(detachable="true")
public class Employee {
    // ...
}

Vous êtes alors en mesure de lire et de modifier les champs d'un objet Employee après la fermeture du gestionnaire PersistenceManager qui a chargé cet objet. L'exemple qui suit illustre l'utilité d'un objet détaché :

public Employee getEmployee(User user) {
    PersistenceManager pm = PMF.get().getPersistenceManager();
    Employee employee, detached = null;
    try {
        employee = pm.getObjectById(Employee.class,
            "Alfred.Smith@example.com");

        // If you're using transactions, you can call
        // pm.setDetachAllOnCommit(true) before committing to automatically
        // detach all objects without calls to detachCopy or detachCopyAll.
        detached = pm.detachCopy(employee);
    } finally {
        pm.close();
    }
    return detached;
}

public void updateEmployeeTitle(Employee e, String newTitle) {
    if (titleChangeIsAuthorized(e, newTitle) {
        e.setTitle(newTitle);
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(e);
        } finally {
            pm.close();
        }
    } else {
        throw new UnauthorizedTitleChangeException(e, newTitle);
    }
}

Les objets détachés constituent une alternative efficace à la création d'objets de transfert de données. Pour plus d'informations sur l'utilisation d'objets détachés, consultez la documentation de DataNucleus.

Suppression d'un objet

Pour supprimer un objet du datastore, appelez la méthode deletePersistent() du gestionnaire PersistenceManager avec l'objet :

pm.deletePersistent(e);

Pour supprimer plusieurs objets dans JDO, appelez la méthode deletePersistentAll(...) avec une collection d'objets. Cette méthode utilise une seule opération de suppression simultanée de bas niveau, qui se révèle plus efficace qu'une série d'invocations distinctes de la méthode deletePersistent(...).

Si un objet comporte des champs contenant des objets enfants eux-mêmes persistants, ces derniers sont également supprimés. Pour plus d'informations, consultez la page des Relations.

Pour supprimer tous les objets correspondant à une requête, vous pouvez utiliser la fonction "Supprimer par requête" de JDOQL. Pour plus d'informations, consultez la page Supprimer des entités par requête.