字段值可以包含一个 Serializable 类的实例,将该实例的序列化值存储在 Blob 类型的单个属性值中。要告知 JDO 序列化该值,该字段需要使用 @Persistent(serialized=true)
批注。Blob 值未被编入索引,且无法在过滤条件或排序顺序中使用。
以下是一个简单的 Serializable 类示例,它代表一个文件,其中包括文件内容、文件名和 MIME 类型。这不是一个 JDO 数据类,因此没有持久批注。
import java.io.Serializable; public class DownloadableFile implements Serializable { private byte[] content; private String filename; private String mimeType; // ... accessors ... }
要将 Serializable 类的实例作为 Blob 值存储在属性中,请声明一个类型为该类的字段,并使用 @Persistent(serialized = "true")
批注:
import javax.jdo.annotations.Persistent; import DownloadableFile; // ... @Persistent(serialized = "true") private DownloadableFile file;
子对象和关系
作为 @PersistenceCapable
类的实例的字段值在两个对象之间创建自有的一对一关系。作为此类引用的集合的字段创建自有的一对多关系。
重要信息:自有的关系具有事务、实体组和级联删除的隐含意义。有关详细信息,请参阅事务和关系。
嵌入类
通过嵌入类,您可以使用类来建模字段值,无需创建新的数据存储区实体并形成关系。该对象值的字段直接存储在含有对象的数据存储区实体中。
任何 @PersistenceCapable
数据类都可用作另一个数据类中的嵌入对象。类的 @Persistent
字段嵌入到对象中。如果让类嵌入@EmbeddedOnly
批注,则该类只能用作嵌入类。嵌入类不需要主键字段,因为它不作为单独的实体存储。
嵌入类的字段作为实体上的属性存储,使用每个字段的名称以及对应属性的名称。如果类型为嵌入类的对象上有多个字段,则必须重命名这些字段以使它们不会互相冲突。您可以使用 @Embedded
批注的参数来指定新的字段名称。
类似地,对象上的字段不可以使用与嵌入类的字段冲突的名称,除非重命名嵌入的字段。
因为嵌入类的持久属性存储在与其他字段相同的实体上,所以您可以在 JDOQL 查询过滤条件和排序顺序中使用嵌入类的持久字段。您可以使用外部字段的名称、点 (.
) 以及嵌入字段的名称来引用嵌入字段。不管是否使用 @Column
批注更改了嵌入字段的属性名称,这都同样适用。
集合
一个数据存储区属性可以具有多个值。在 JDO 中,这是通过集合类型的单一字段来表示的,其中集合为核心值类型之一或 Serializable 类。支持以下集合类型:
java.util.ArrayList<...>
java.util.HashSet<...>
java.util.LinkedHashSet<...>
java.util.LinkedList<...>
java.util.List<...>
java.util.Set<...>
java.util.SortedSet<...>
java.util.Stack<...>
java.util.TreeSet<...>
java.util.Vector<...>
如果某个字段声明为 List,则数据存储区返回的对象将具有 ArrayList 值。如果某个字段声明为 Set,则数据存储区返回 HashSet。如果某个字段声明为 SortedSet,则数据存储区返回 TreeSet。
对象字段和实体属性
App Engine 数据存储区区分不具有指定属性的实体和该属性值为 null
的实体。JDO 不支持这种区分:对象的每个字段都有一个值,可能是 null
。如果将某个具有可设为 null 的值类型(不同于 int
或 boolean
之类的内置类型)的字段设置为 null
,则在保存对象时,所得实体会将该属性设置为 null 值。
如果将一个数据存储区实体载入对象中,对于该对象中的某一个字段,实体中没有对应的属性,且该字段是可设为 null 的单值类型,则该字段将设置为 null
。在将该对象保存回数据存储区时,null
属性将在数据存储区中被设置为 null 值。如果该字段不是可设为 null 的值类型,则载入没有对应属性的实体将引发异常。如果实体是从用于重新创建实例的相同的 JDO 类创建的,则这种情况不会发生,但是如果 JDO 类发生变化,或者实体是使用低级别 API 而非 JDO 创建的,则会出现这种情况。
如果某个字段的类型是核心数据类型的集合或 Serializable 类,而实体上的属性没有值,则会在数据存储区中将该属性设置为单一 null 值来表示该空集合。如果该字段的类型是数组类型,则会为其分配 0 个元素的数组。如果已载入对象而属性没有值,则将为该字段分配相应类型的空集合。在内部,数据存储区已知空集合与包含一个 null 值的集合之间的区别。
如果实体具有一个在对象中没有对应字段的属性,则无法从该对象访问该属性。如果将对象保存回数据存储区,将删除这个额外的属性。
如果某个实体具有一个值类型与对象中对应字段的类型不同的属性,则 JDO 会尝试将该值转换成字段类型。如果无法将该值转换成字段类型,则 JDO 将引发 ClassCastException。在值为数字(长整型或双精度浮点型)的情况下,将对该值进行转换 (convert),而不是对类型进行转换 (cast)。如果数字属性值超出字段类型的表示范围,则转换将溢出,而不会引发异常。
创建、获取和删除数据
将 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(), "[email protected]");
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
对象。例如,要重新创建键名称为 "[email protected]"
的 "Employee"
类型的实体的键(无父实体组):
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
// ...
Key k = KeyFactory.createKey(Employee.class.getSimpleName(), "[email protected]");
要重新创建系统分配的数字 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()
方法。
有关实体组的详细信息,请参阅事务。