Creating, Getting and Deleting Data in JDO

Saving a JDO data object to the datastore is a simple matter of calling the makePersistent() method of the PersistenceManager instance. The App Engine JDO implementation uses the primary key field of the object to keep track of which datastore entity corresponds with the data object, and can generate keys for new objects automatically. You can use keys to retrieve entities quickly, and you can construct keys from known values (such as account IDs).

Making Objects Persistent

To store a simple data object in the datastore, you call the PersistenceManager's makePersistent() method, passing it the instance.

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

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

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

The call to makePersistent() is synchronous, and doesn't return until the object is saved and indexes are updated.

To save multiple objects in JDO, call the makePersistentAll(...) method with a Collection of objects. This method will use a single low-level batch save operation that is more efficient than a series of individual makePersistent(...) invocations.

Note: If any of the data object's persistent fields are references to other persistent data objects, and any of those objects have never been saved or have changed since they were loaded, the referenced objects are also saved to the datastore. See Relationships.

Keys

Every entity has a key that is unique over all entities in App Engine. A complete key includes several pieces of information, including the application ID, the kind, and an entity ID. (Keys also contain information about entity groups; see Transactions for more information.)

An object's key is stored in a field on the instance. You identify the primary key field using the @PrimaryKey annotation.

The app can provide the ID portion of the key as a string when the object is created, or it can allow the datastore to generate a numeric ID automatically. The complete key must be unique across all entities in the datastore. In other words, an object must have an ID that is unique across all objects of the same kind and with the same entity group parent (if any). You select the desired behavior of the key using the type of the field and annotations.

If the class is used as a "child" class in a relationship, the key field must be of a type capable of representing an entity group parent: either a Key instance, or a Key value encoded as a string. See Transactions for more information on entity groups, and Relationships for more information on relationships.

Tip: If the app creates a new object and gives it the same string ID as another object of the same kind (and the same entity group parent), saving the new object overwrites the other object in the datastore. To detect whether a string ID is already in use prior to creating a new object, you can use a transaction to attempt to get an entity with a given ID, then create one if it doesn't exist. See Transactions.

There are four types of primary key fields:

Long

A long integer (java.lang.Long), an entity ID automatically generated by the datastore. Use this for objects without entity group parents whose IDs should be generated automatically by the datastore. The long key field of an instance is populated when the instance is saved.

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

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
Unencoded String

A string (java.lang.String), an entity ID ("key name") provided by the application when the object is created. Use this for objects without entity group parents whose IDs should be provided by the application. The application sets this field to the desired ID prior to saving.

import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    private String name;
Key

A Key instance (com.google.appengine.api.datastore.Key). The key value includes the key of the entity group parent (if any) and either the app-assigned string ID or the system-generated numeric ID. To create the object with an app-assigned string ID, you create the Key value with the ID and set the field to the value. To create the object with a system-assigned numeric ID, you leave the key field null. (For information on how to use entity group parents, see 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;
    }

The app can create a Key instance using the KeyFactory class:

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);
Key as Encoded String

Similar to Key, but the value is the encoded string form of the key. Encoded string keys allow you to write your application in a portable manner and still take advantage of App Engine datastore entity groups.

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;

The app can populate this value prior to saving using a key with a name, or it can leave it null. If the encoded key field is null, the field is populated with a system-generated key when the object is saved.

Key instances can be converted to and from the encoded string representation using the KeyFactory methods keyToString() and stringToKey().

When using encoded key strings, you can provide access to an object's string or numeric ID with an additional field:

    @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;

A "gae.pk-name" field can be set to a key name prior to saving the object. When the object is saved, the encoded key field is populated with the complete key that includes the key name. Its type must be String.

A "gae.pk-id" field is populated when the object is saved, and cannot be modified. Its type must be Long.

When a new object with a generated key (a key field using valueStrategy = IdGeneratorStrategy.IDENTITY) is created, its key value starts out null. The key field is populated when the object is written to the datastore. If you are using a transaction, the object is written when the transaction is committed. Otherwise, the object is written when the makePersistent() method is called if the object is being created, or when the PersistenceManager instance's close() method is called if the object is being updated.

For more information about making keys, see Entities, Properties, and Keys.

Getting an Object by Key

To retrieve an object given its key, use the PersistenceManager's getObjectById() method. The method takes the class for the object, and key:

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

If the class uses a key field that is an unencoded string ID (String) or numeric ID (Long), getObjectByID() can take the simple value as the key parameter:

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

The key argument can be of any of the supported key field types (string ID, numeric ID, Key value, encoded key string), and can be of a different type than the key field in the class. App Engine must be able to derive the complete key from the class name and the provided value. String IDs and numeric IDs are exclusive, so a call using a numeric ID never returns an entity with a string ID. If a Key value or encoded key string is used, the key must refer to an entity whose kind is represented by the class.

Updating an Object

One way to update an object with JDO is to fetch the object, then modify it while the PersistenceManager that returned the object is still open. Changes are persisted when the PersistenceManager is closed. For example:

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();
    }
}

Since the Employee instance was returned by the PersistenceManager, the PersistenceManager knows about any modifications that are made to Persistent fields on the Employee and automatically updates the datastore with these modifications when the PersistenceManager is closed. It knows this because the Employee instance is "attached" to the PersistenceManager.

You can modify an object after the PersistenceManager has been closed by declaring the class as "detachable." To do this, add the detachable attribute to the @PersistenceCapable annotation:

import javax.jdo.annotations.PersistenceCapable;

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

Now you can read and write the fields of an Employee object after the PersistenceManager that loaded it has been closed. The following example illustrates how a detached object might be useful:

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);
    }
}

Detached objects are a nice alternative to creating data transfer objects. For more information on working with detached objects please see the DataNucleus documentation.

Deleting an Object

To delete an object from the datastore, call the PersistenceManager's deletePersistent() method with the object:

pm.deletePersistent(e);

To delete multiple objects in JDO, call the deletePersistentAll(...) method with a Collection of objects. This method will use a single low-level batch delete operation that is more efficient than a series of individual deletePersistent(...) invocations.

If an object has fields containing child objects that are also persistent, the child objects are also deleted. See Relationships for more information.

To delete all objects that match a query you can use JDOQL's "delete by query" feature. See Deleting Entities By Query for more information.