您可以使用 JDO 在数据存储区中存储普通 Java 数据对象(有时也称为“普通旧 Java 对象”或“POJO”)。每个通过 PersistenceManager 变为持久的对象都成为数据存储区中的一个实体。您可以使用批注告诉 JDO 如何存储和重新创建数据类的实例。
注意:早期版本的 JDO 使用 .jdo XML 文件,而不是 Java 批注。这些 XML 文件仍适用于 JDO 2.3。本文档仅介绍对数据类使用 Java 批注。
JDO 保存的每个对象都将成为 App Engine 数据存储区中的实体。实体的类型从类的简单名称派生(内部类使用不带包名称的 $ 路径)。类的每个持久字段代表实体的一个属性,其中属性的名称与字段的名称相同(保留大小写)。
要将 Java 类声明为能够通过 JDO 在数据存储区中存储或检索,请为该类指定 @PersistenceCapable 批注。例如:
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
// ...
}
要存储到数据存储区中的数据类的字段必须声明为持久字段。要将字段声明为持久,请为其指定 @Persistent 批注:
import java.util.Date;
import javax.jdo.annotations.Persistent;
// ...
@Persistent
private Date hireDate;
要将字段声明为非持久(该字段不会存储到数据存储区中,也不会在检索对象时还原),请为其指定 @NotPersistent 批注。
提示:对于某些类型的字段,如果既未指定 @Persistent 批注,也未指定 @NotPersistent 批注,且所有其他类型的字段在默认情况下为非持久,则默认情况下,JDO 会将这些类型的字段指定为持久。有关此行为的完整说明,请参阅 DataNucleus 文档。因为根据 JDO 规范,在默认情况下并非所有的 App Engine 数据存储区核心值类型都是持久的,所以为了明确起见,我们建议为字段显式指定 @Persistent 批注或 @NotPersistent 批注。
字段的类型可以是以下任意一种。下文详细说明了这些类型。
java.util.List<...>)或核心数据存储区类型的值的数组@PersistenceCapable 类的一个实例或实例集合数据类必须具有一个专用于存储相应数据存储区实体的主键的字段。您可以在 4 种不同类型的键字段中选择,每一种都使用不同的值类型和批注。(有关详细信息,请参阅创建数据:键。)最简单的键字段是在对象首次保存到数据存储区时,由 JDO 使用在类的所有其他实例中唯一的值自动填充的长整型值。长整型键使用 @PrimaryKey 批注,以及 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) 批注:
提示:请将您的所有持久字段指定为 private 或 protected(或 package protected),且只通过访问器方法提供公共访问。直接从另一个类访问持久字段可能无法体验到 JDO 类的增强功能。或者,您可以将其他类指定为 @PersistenceAware。有关详细信息,请参阅 DataNucleus 文档。
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PrimaryKey;
// ...
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
以下是一个示例数据类:
import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private String firstName;
@Persistent
private String lastName;
@Persistent
private Date hireDate;
public Employee(String firstName, String lastName, Date hireDate) {
this.firstName = firstName;
this.lastName = lastName;
this.hireDate = hireDate;
}
// Accessors for the fields. JDO doesn't use these, but your application does.
public Long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getHireDate() {
return hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
}
数据存储区支持以下核心值类型:
| 类型 | Java 类 | 排序顺序 | 注释 |
|---|---|---|---|
| 短文本字符串,< 500 字节 | java.lang.String |
Unicode | 超过 500 字节的值将引发 JDOFatalUserException。 |
| 短字节字符串,< 500 字节 | com.google.appengine.api.datastore.ShortBlob |
字节顺序 | 超过 500 字节的值将引发 JDOFatalUserException。 |
| 布尔值 | boolean 或 java.lang.Boolean |
false < true |
|
| 整数 | short、java.lang.Short、int、java.lang.Integer、long、java.lang.Long |
数字 | 作为长整型存储,然后转换为字段类型。超出范围的值将溢出。 |
| 浮点数字 | float、java.lang.Float、double、java.lang.Double |
数字 | 作为双精度浮点数存储,然后转换为字段类型。超出范围的值将溢出。 |
| 日期时间 | java.util.Date |
按时间顺序 | |
| Google 帐户 | com.google.appengine.api.users.User |
按电子邮件地址 (Unicode) | |
| 长文本字符串 | com.google.appengine.api.datastore.Text |
(不可排序) | 未编入索引。 |
| 长字节字符串 | com.google.appengine.api.datastore.Blob |
(不可排序) | 未编入索引。 |
| 实体键 | com.google.appengine.api.datastore.Key,或引用的对象(作为子对象) |
按路径元素(类型、ID 或名称、类型、ID 或名称...) | |
| 网址 | com.google.appengine.api.datastore.Link |
Unicode |
注意:数据存储区还支持几种其他的核心值类型,这些核心值类型尚未在 Java 数据存储区 API 中实现,但已在 Python API 中实现。如果某个属性具有这些类型中任意一种类型的值并且实体与该属性的字段一起载入到对象中,则 JDO 会将这个属性当作不存在:它会将这个字段设置为 null,或在这个字段类型无法设置为 null 的情况下引发 NullPointerException。不支持的类型包括:Category、Email、IM、PhoneNumber、PostalAddress、Rating、GeoPt。
要表示包含单个核心类型的值的属性,请声明一个 Java 类型的字段,并使用 @Persistent 批注:
import java.util.Date;
import javax.jdo.annotations.Persistent;
// ...
@Persistent
private Date hireDate;
字段值可以包含一个 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 类的实例的字段值在两个对象之间创建自有的一对一关系。作为此类引用的集合的字段创建自有的一对多关系。
重要信息:自有的关系具有事务、实体组和级联删除的隐含意义。有关详细信息,请参阅事务和关系。
以下是 Employee 对象和 ContactInfo 对象之间自有的一对一关系的简单示例:
ContactInfo.java
import com.google.appengine.api.datastore.Key;
// ... imports ...
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class ContactInfo {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private String streetAddress;
@Persistent
private String city;
@Persistent
private String stateOrProvince;
@Persistent
private String zipCode;
// ... accessors ...
}
Employee.java
import ContactInfo;
// ... imports ...
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private ContactInfo myContactInfo;
// ... accessors ...
}
在此例中,如果应用程序创建 Employee 实例,并使用新的 ContactInfo 实例填充它的 myContactInfo 字段,然后通过 pm.makePersistent(...) 保存该 Employee 实例,则数据存储区将创建两个实体。其中一个为 "ContactInfo" 类型,代表 ContactInfo 实例。另一个为 "Employee" 类型。ContactInfo 实体的键将 Employee 实体的键作为它的父实体组。
通过嵌入类,您可以使用类来建模字段值,无需创建新的数据存储区实体并形成关系。该对象值的字段直接存储在含有对象的数据存储区实体中。
任何 @PersistenceCapable 数据类都可用作另一个数据类中的嵌入对象。类的 @Persistent 字段嵌入到对象中。如果让类嵌入 @EmbeddedOnly 批注,则该类只能用作嵌入类。嵌入类不需要主键字段,因为它不作为单独的实体存储。
以下是一个嵌入类示例。此示例使嵌入类成为使用该类的数据类的内部类;这很有用,但不是使类可嵌入所必需的。
import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.EmbeddedOnly;
// ... imports ...
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class EmployeeContacts {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Long id;
@PersistenceCapable
@EmbeddedOnly
public static class ContactInfo {
@Persistent
private String streetAddress;
@Persistent
private String city;
@Persistent
private String stateOrProvince;
@Persistent
private String zipCode;
// ... accessors ...
}
@Persistent
@Embedded
private ContactInfo homeContactInfo;
}
嵌入类的字段作为实体上的属性存储,使用每个字段的名称以及对应属性的名称。如果类型为嵌入类的对象上有多个字段,则必须重命名这些字段以使它们不会互相冲突。您可以使用 @Embedded 批注的参数来指定新的字段名称。例如:
@Persistent
@Embedded
private ContactInfo homeContactInfo;
@Persistent
@Embedded(members = {
@Persistent(name="streetAddress", columns=@Column(name="workStreetAddress")),
@Persistent(name="city", columns=@Column(name="workCity")),
@Persistent(name="stateOrProvince", columns=@Column(name="workStateOrProvince")),
@Persistent(name="zipCode", columns=@Column(name="workZipCode")),
})
private ContactInfo workContactInfo;
类似地,对象上的字段不可以使用与嵌入类的字段冲突的名称,除非重命名嵌入的字段。
因为嵌入类的持久属性存储在与其他字段相同的实体上,所以您可以在 JDOQL 查询过滤条件和排序顺序中使用嵌入类的持久字段。您可以使用外部字段的名称、点 (.) 以及嵌入字段的名称来引用嵌入字段。不管是否使用 @Column 批注更改了嵌入字段的属性名称,这都同样适用。
select from EmployeeContacts where workContactInfo.zipCode == "98105"
一个数据存储区属性可以具有多个值。在 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。
例如,类型为 List<String> 的字段存储为属性的零个或多个字符串值,每一个对应于 List 中的一个值。
import java.util.List;
// ... imports ...
// ...
@Persistent
List<String> favoriteFoods;
(@PersistenceCapable 类的)子对象的集合创建具有一对多关系的多个实体。请参阅关系。
对于查询过滤条件和排序顺序,具有多个值的数据存储区属性具有特殊行为。有关详细信息,请参阅查询和索引:排序顺序和具有多个值的属性。
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)。如果数字属性值超出字段类型的表示范围,则转换将溢出,而不会引发异常。