将 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。您可以使用字段类型和批注来选择所需的键的行为。
如果类用作关系中的“子”类,则键字段必须为能够代表父实体组的类型:Key 实例或编码为字符串的键值。有关实体组的详细信息,请参阅事务;有关关系的详细信息,请参阅关系。
提示:如果应用程序创建一个新对象,并为其指定了与类型相同(且具有相同父实体组)的另一对象相同的字符串 ID,则保存该新对象时将覆盖数据存储区中的另一对象。要在创建新对象前检测字符串 ID 是否已经被使用,您可以使用事务尝试获取具有指定 ID 的实体,如果该实体不存在,则您可以继续创建。请参阅事务。
主键字段有 4 种类型:
长整型 (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;
Key 实例 (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 类创建 Key 实例:
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);
与键类似,但值是键的编码字符串形式。编码字符串键允许您以便携方式编写应用程序且仍可以利用 App Engine 数据存储区实体组。
import javax.jdo.annotations.Extension;
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)
@Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
private String encodedKey;
应用程序可以在保存前使用带名称的键填充该值,或将它留为 null。如果编码的键字段为 null,则将在保存对象时使用系统生成的键填充该字段。
Key 实例和编码字符串表示可以通过 KeyFactory 方法 keyToString() 和 stringToKey() 进行相互转换。
当使用编码键字符串时,您可以通过一个额外的字段提供对对象的字符串 ID 或数字 ID 的访问:
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
private String encodedKey;
@Extension(vendorName="datanucleus", key="gae.pk-name", value="true")
private String keyName;
// OR:
@Extension(vendorName="datanucleus", key="gae.pk-id", value="true")
private Long keyId;
"gae.pk-name" 字段可以在保存对象前设置为键名称。保存对象时,将使用包括键名称的完整键填充编码键字段。其类型必须为 String。
"gae.pk-id" 字段在对象保存时填充,且无法修改。其类型必须为 Long。
创建带生成的键(使用 valueStrategy = IdGeneratorStrategy.IDENTITY 的键字段)的新对象时,其键值初始为 null。键字段在对象写入数据存储区时填充。如果使用事务,则对象在事务提交时写入。否则,如果创建对象,则对象在调用 makePersistent() 方法时写入;如果更新对象,则对象在调用 PersistenceManager 实例的 close() 方法时写入。
如果应用程序已知实体的完整键的所有元素,则应用程序可以在没有对象的情况下创建对应的 Key 对象。
对于没有父实体组的实体的键,您可以使用 KeyFactory 类的 createKey() 静态方法。该方法采用类型(类的简单名称)和应用程序分配的字符串 ID 或系统分配的数字 ID,并返回 Key 对象。例如,要重新创建键名称为 "Alfred.Smith@example.com" 的 "Employee" 类型的实体的键(无父实体组):
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
// ...
Key k = KeyFactory.createKey(Employee.class.getSimpleName(), "Alfred.Smith@example.com");
要重新创建系统分配的数字 ID 为 52234 的 "Employee" 类型的实体的键(无父实体组):
Key k = KeyFactory.createKey(Employee.class.getSimpleName(), 52234);
可以使用 KeyFactory 类的 keyToString() 方法将键转换为字符串表示,使用 stringToKey() 方法将字符串表示转换为键。(请注意,这与 Key 类的 toString() 方法不同,该方法返回适用于调试的用户可读的值。)
对于具有父实体组的实体的键,您可以使用 KeyFactory.Builder 类:
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
// ...
Key k = new KeyFactory.Builder(Employee.class.getSimpleName(), 52234).addChild(ExpenseReport.class.getSimpleName(), "A23Z79").getKey();
Builder 实例的 addChild() 方法返回 Builder,因此您可以连锁调用以添加键路径的每一个元素。要获取指定 Builder 的完整键值,您可以调用该 Builder 的 getKey() 方法。
有关实体组的详细信息,请参阅事务。
要通过对象的键检索对象,请使用 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();
}
}
因为 Employee 实例是由 PersistenceManager 返回的,所以 PersistenceManager 已知对 Employee 上的持久字段所做的任何修改,并将在 PersistenceManager 关闭时自动使用这些修改更新数据存储区。PersistenceManager 已知进行这些修改的原因是 Employee 实例已“附加”到 PersistenceManager。
您可以通过将类声明为“可分离”,在 PersistenceManager 关闭后对该对象进行修改。为此,请将 detachable 属性添加到 @PersistenceCapable 批注:
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class Employee {
// ...
}
现在您可以在载入 Employee 对象的 PersistenceManager 关闭后读取和写入该对象的字段。下例说明分离对象的作用:
public Employee getEmployee(User user) {
PersistenceManager pm = PMF.get().getPersistenceManager();
pm.setDetachAllOnCommit(true);
try {
e = pm.getObjectById(Employee.class, "Alfred.Smith@example.com");
} finally {
pm.close();
}
return e;
}
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);
如果某个对象的字段同样包含持久的子对象,则这些子对象也将被删除。有关详细信息,请参阅关系。