使用 JDO 创建、获取和删除数据

将 JDO 数据对象保存到数据存储区十分简单,只需调用 PersistenceManager 实例的 makePersistent() 方法即可实现。App Engine JDO 实现使用对象的主键字段来跟踪与数据对象相对应的数据存储区实体,并自动为新对象生成键。您可以使用键来快速检索实体,还可以基于已知的值(如账号 ID)来构造键。

使对象持久化

要在数据存储区中存储一个简单的数据对象,可以调用的 PersistenceManager 的 makePersistent() 方法,将实例传递给它。

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

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

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

makePersistent() 的调用是同步的,直到保存对象并更新索引之后才返回。

要在 JDO 中保存多个对象,可以使用一个对象集合调用 makePersistentAll(...) 方法。此方法将使用单个低级批量保存操作,操作效率高于一系列单独的 makePersistent(...) 调用。

注意:如果数据对象的任意持久字段是对其他持久数据对象的引用,并且那些对象从未保存过,或者自载入后发生过更改,那么系统还会将所引用的对象保存到数据存储区。请参阅关系

每个实体都有一个键,该键在 App Engine 的所有实体中是唯一的。一个完整的键包括多个信息段,其中包括应用 ID、类型和实体 ID。(键还包含有关实体组的信息;如需了解详细信息,请参阅事务。)

对象的键存储在实例上的某个字段中。您可以使用 @PrimaryKey 注释来标识主键字段。

在创建对象时,应用会以字符串形式提供键的 ID 部分,也可能会让数据存储区自动生成一个数字 ID。完整的键在数据存储区中的所有实体之间必须保持唯一。换句话说,对象 ID 在相同类型且具有相同父级实体组(如果有)的所有对象中必须是唯一的。您可以使用字段类型和注释选择所需的键行为。

如果该类在关系中用作“子”类,则键字段必须是能够表示父级实体组的类型:即一个键值实例,或者编码为字符串的一个键值。如需详细了解实体组,请参阅事务;如需详细了解关系,请参阅关系

提示:如果应用创建了一个新对象,并为它指定了与另一个同类型(且具有相同的父级实体组)对象相同的字符串 ID,则保存新对象时,系统会覆盖数据存储区中的另一个对象。要在创建新对象之前检测是否已经使用了某个字符串 ID,可以使用一个事务来尝试获取具有指定 ID 的实体,如果该实体不存在,则创建一个实体。请参阅事务

有四种主键字段类型:

一个长整数 (java.lang.Long),表示由数据存储区自动生成的实体 ID。对于没有父级实体组且其 ID 应该由数据存储区自动生成的对象,请使用此类型。实例的长整数键字段是在保存实例时填充的。

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

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
未编码的字符串

一个字符串 (java.lang.String),表示创建对象时由应用提供的实体 ID(“键名”)。对于没有父级实体组且其 ID 应该由应用提供的对象,请使用此类型。应用在执行保存操作之前将此字段设置为所需的 ID。

import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    private String name;

一个键实例 (com.google.appengine.api.datastore.Key)。键值包括父级实体组的键(如果有),以及应用分配的字符串 ID 或系统生成的数字 ID。要使用应用分配的字符串 ID 创建对象,可以使用该 ID 创建键值并将字段设置为此值。要使用系统分配的数字 ID 创建对象,可以将键字段保留为 null。(有关如何使用父级实体组的信息,请参阅事务。)

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

应用可以使用 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);
编码字符串形式的键

类似于 Key,但值为键的编码字符串形式。编码字符串形式的键允许您用可移植的方式编写应用,同时仍然利用 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;

应用可以在执行保存操作之前使用具有某个名称的键来填充此值,也可以将它保留为 null。如果编码键字段为 null,则在保存对象时使用系统生成的键来填充该字段。

可以使用 KeyFactory 方法 keyToString()stringToKey() 在键实例与编码字符串表示形式之间转换。

在使用编码键字符串时,您可以通过一个额外的字段来提供对于对象的字符串或数字 ID 的访问:

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

在保存对象之前,可以将 "gae.pk-name" 字段设置为键名。保存对象时,系统会使用包含键名的完整键来填充编码的键字段。它的类型必须是 String

保存对象时,系统会填充 "gae.pk-id" 字段,该字段的内容无法修改。它的类型必须是长整型。

创建具有所生成的键(使用 valueStrategy = IdGeneratorStrategy.IDENTITY 的键字段)的新对象时,其键值最初是 null。键字段是在将对象写入数据存储区时填充的。如果您使用了事务,系统会在提交事务时写入该对象。否则,如果在创建对象时调用了 makePersistent() 方法,或者在更新对象时调用了 PersistenceManager 实例的 close() 方法,则会写入对象。

有关创建键的更多信息,请参阅实体、属性和密键

通过键获取对象

要在给定键的情况下检索对象,可以使用 PersistenceManager 的 getObjectById() 方法。该方法接受对象的类以及键:

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

如果类使用未编码的字符串 ID (String) 或数字 ID (Long) 形式的键字段,则 getObjectByID() 可以将简单值作为键参数:

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

键参数可以是任何受支持的键字段类型(字符串 ID、数字 ID、键值和编码键字符串),也可以是与类中的键字段不同的类型。App Engine 必须能够根据类名称和所提供的值派生出完整的键。字符串 ID 和数字 ID 彼此互斥,因此使用数字 ID 的调用永远不会返回具有字符串 ID 的实体。如果使用键值或编码键字符串,则键必须引用由该类表示其类型的实体。

更新对象

通过 JDO 来更新对象的一种方法是提取该对象,然后在返回该对象的 PersistenceManager 仍然处于打开状态时修改该对象。关闭 PersistenceManager 后,更改就会持久保留。例如:

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

由于 PersistenceManager 返回了 Employee 实例,因此 PersistenceManager 了解对于 Employee 上的持久保留字段所做的任何修改,并在 PersistenceManager 关闭时自动使用这些修改更新数据存储区。而它之所以能了解此信息,是由于 Employee 实例已“附加”到 PersistenceManager。

通过将类声明为“可分离”,即可在关闭 PersistenceManager 后修改对象。为此,请将 detachable 特性添加到 @PersistenceCapable 注释:

import javax.jdo.annotations.PersistenceCapable;

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

现在,您可以在已经关闭了载入 Employee 对象的 PersistenceManager 之后,对该对象的字段执行读写操作。下面的示例展示了分离式对象的有用之处:

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

采用分离式对象可以很好地取代创建数据传输对象。如需详细了解分离式对象的使用,请参阅 DataNucleus 文档

删除对象

要从数据存储区中删除对象,请使用以下对象调用 PersistenceManager 的 deletePersistent() 方法:

pm.deletePersistent(e);

要在 JDO 中删除多个对象,请使用对象集合调用 deletePersistentAll(...) 方法。此方法将使用单个低级批量删除操作,操作效率高于一系列单独的 deletePersistent(...) 调用。

如果某个对象的字段包含持久子对象,那么系统也会删除这些子对象。如需了解详情,有请参阅关系

要删除与查询匹配的所有对象,可以使用 JDOQL 的“利用查询进行删除”功能。如需了解详情,请参阅利用查询删除实体