Google Code offered in: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
You can use JDO to store plain Java data objects (sometimes referred to as "Plain Old Java Objects" or "POJOs") in the datastore. Each object that is made persistent with the PersistenceManager becomes an entity in the datastore. You use annotations to tell JDO how to store and recreate instances of your data classes.
Note: Earlier versions of JDO use .jdo
XML files instead of Java annotations. These still work with JDO 2.3. This documentation only covers using Java annotations with data classes.
Each object saved by JDO becomes an entity in the App Engine datastore. The entity's kind is derived from the simple name of the class (inner classes use the $
path without the package name). Each persistent field of the class represents a property of the entity, with the name of the property equal to the name of the field (with case preserved).
To declare a Java class as capable of being stored and retrieved from the datastore with JDO, give the class a @PersistenceCapable
annotation. For example:
import javax.jdo.annotations.IdentityType; import javax.jdo.annotations.PersistenceCapable; @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Employee { // ... }
Fields of the data class that are to be stored in the datastore must be declared as persistent fields. To declare a field as persistent, give it a @Persistent
annotation:
import java.util.Date; import javax.jdo.annotations.Persistent; // ... @Persistent private Date hireDate;
To declare a field as not persistent (it does not get stored in the datastore, and is not restored when the object is retrieved), give it a @NotPersistent
annotation.
Tip: JDO specifies that fields of certain types are persistent by default if neither the @Persistent
nor @NotPersistent
annotations are specified, and fields of all other types are not persistent by default. See the DataNucleus documentation for a complete description of this behavior. Because not all of the App Engine datastore core value types are persistent by default according to the JDO specification, we recommend explicitly annotating fields as @Persistent
or @NotPersistent
to make it clear.
The type of a field can be any of the following. These are described in detail below.
java.util.List<...>
) or an array of values of a core datastore type@PersistenceCapable
classA data class must have one field dedicated to storing the primary key of the corresponding datastore entity. You can choose between 4 different kinds of key fields, each using a different value type and annotations. (See Creating Data: Keys for more information.) The simplest key field is a long integer value that is automatically populated by JDO with a value unique across all other instances of the class when the object is saved to the datastore for the first time. Long integer keys use a @PrimaryKey
annotation, and a @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
annotation:
Tip: Make all of your persistent fields private
or protected
(or package protected), and only provide public access through accessor methods. Direct access to a persistent field from another class may bypass the JDO class enhancement. Alternatively, you can make other classes @PersistenceAware
. See the DataNucleus documentation for more information.
import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.PrimaryKey; // ... @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Long id;
Here is an example data class:
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; } }
The datastore supports the following core value types:
Type | Java class | Sort order | Notes |
---|---|---|---|
short text string, < 500 bytes | java.lang.String |
Unicode | A value longer than 500 bytes throws a JDOFatalUserException. |
short byte string, < 500 bytes | com.google.appengine.api.datastore.ShortBlob |
byte order | A value longer than 500 bytes throws a JDOFatalUserException. |
Boolean value | boolean or java.lang.Boolean |
false < true |
|
integer | short , java.lang.Short , int , java.lang.Integer , long , java.lang.Long |
Numeric | Stored as long integer, then converted to the field type. Out-of-range values overflow. |
floating point number | float , java.lang.Float , double , java.lang.Double |
Numeric | Stored as double-width float, then converted to the field type. Out-of-range values overflow. |
date-time | java.util.Date |
Chronological | |
Google account | com.google.appengine.api.users.User |
By email address (Unicode) | |
long text string | com.google.appengine.api.datastore.Text |
(not orderable) | Not indexed. |
long byte string | com.google.appengine.api.datastore.Blob |
(not orderable) | Not indexed. |
entity key | com.google.appengine.api.datastore.Key , or the referenced object (as a child) |
By path elements (kind, ID or name, kind, ID or name...) | |
a URL | com.google.appengine.api.datastore.Link |
Unicode |
Note: The datastore supports several additional core value types that are not yet implemented in the Java datastore API, but are implemented in the Python API. If a property has a value with any of these types and the entity is loaded into an object with a field for the property, JDO will behave as if the property does not exist: it will set the field to null
or throw a NullPointerException if the field type is non-nullable. The unsupported types are: Category, Email, IM, PhoneNumber, PostalAddress, Rating, GeoPt.
To represent a property containing a single value of a core type, declare a field of the Java type, and use the @Persistent
annotation:
import java.util.Date; import javax.jdo.annotations.Persistent; // ... @Persistent private Date hireDate;
A field value can contain an instance of a Serializable class, storing the serialized value of the instance in a single property value of the type Blob. To tell JDO to serialize the value, the field uses the annotation @Persistent(serialized=true)
. Blob values are not indexed and cannot be used in query filters or sort orders.
Here is an example of a simple Serializable class that represents a file, including the file contents, a filename and a MIME type. This is not a JDO data class, so there are no persistence annotations.
import java.io.Serializable; public class DownloadableFile implements Serializable { private byte[] content; private String filename; private String mimeType; // ... accessors ... }
To store an instance of a Serializable class as a Blob value in a property, declare a field whose type is the class, and use the @Persistent(serialized = "true")
annotation:
import javax.jdo.annotations.Persistent; import DownloadableFile; // ... @Persistent(serialized = "true") private DownloadableFile file;
A field value that is an instance of a @PersistenceCapable
class creates an owned one-to-one relationship between two objects. A field that is a collection of such references creates an owned one-to-many relationship.
Important: Owned relationships have implications for transactions, entity groups, and cascading deletes. See Transactions and Relationships for more information.
Here is a simple example of an owned one-to-one relationship between an Employee object and a ContactInfo object:
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 ... }
In this example, if the app creates an Employee instance, populates its myContactInfo
field with a new ContactInfo instance, then saves the Employee instance with pm.makePersistent(...)
, the datastore creates two entities. One is of the kind "ContactInfo"
, representing the ContactInfo instance. The other is of the kind "Employee"
. The key of the ContactInfo
entity has the key of the Employee
entity as its entity group parent.
Embedded classes allow you to model a field value using a class without creating a new datastore entity and forming a relationship. The fields of the object value are stored directly in the datastore entity for the containing object.
Any @PersistenceCapable
data class can be used as an embedded object in another data class. The class's @Persistent
fields are embedded in the object. If you give the class to embed the @EmbeddedOnly
annotation, the class can only be used as an embedded class. The embedded class does not need a primary key field because it is not stored as a separate entity.
Here is an example of an embedded class. This example makes the embedded class an inner class of the data class that uses it; this is useful, but not required to make a class embeddable.
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; }
The fields of an embedded class are stored as properties on the entity, using the name of each field and the name of the corresponding property. If you have more than one field on the object whose type is an embedded class, you must rename the fields of one so they do not conflict with another. You specify new field names using arguments to the @Embedded
annotation. For example:
@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;
Similarly, fields on the object must not use names that collide with fields of embedded classes, unless the embedded fields are renamed.
Because the embedded class's persistent properties are stored on the same entity as the other fields, you can use persistent fields of the embedded class in JDOQL query filters and sort orders. You can refer to the embedded field using the name of the outer field, a dot (.
), and the name of the embedded field. This works whether or not the property names for the embedded fields have been changed using @Column
annotations.
select from EmployeeContacts where workContactInfo.zipCode == "98105"
A datastore property can have more than one value. In JDO, this is represented by a single field with a Collection type, where the collection is of one of the core value types or a Serializable class. The following Collection types are supported:
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<...>
If a field is declared as a List, objects returned by the datastore will have an ArrayList value. If a field is declared as a Set, the datastore returns a HashSet. If a field is declared as a SortedSet, the datastore returns a TreeSet.
For example, a field whose type is List<String>
is stored as zero or more string values for the property, one for each value in the List
.
import java.util.List; // ... imports ... // ... @Persistent List<String> favoriteFoods;
A Collection of child objects (of @PersistenceCapable
classes) creates multiple entities with a one-to-many relationship. See Relationships.
Datastore properties with more than one value have special behavior for query filters and sort orders. See Queries and Indexes: Sort Orders and Properties With Multiple Values for more information.
The App Engine datastore makes a distinction between an entity without a given property and an entity with a null
value for a property. JDO does not support this distinction: every field of an object has a value, possibly null
. If a field with a nullable value type (something other than a built-in type like int
or boolean
) is set to null
, when the object is saved, the resulting entity will have the property set with a null value.
If a datastore entity is loaded into an object and doesn't have a property for one of the object's fields and the field's type is a nullable single-value type, the field is set to null
. When the object is saved back to the datastore, the null
property becomes set in the datastore to the null value. If the field is not of a nullable value type, loading an entity without the corresponding property throws an exception. This won't happen if the entity was created from the same JDO class used to recreate the instance, but can happen if the JDO class changes, or if the entity was created using the low-level API instead of JDO.
If a field's type is a Collection of a core data type or a Serializable class and there are no values for the property on the entity, the empty collection is represented in the datastore by setting the property to a single null value. If the field's type is an array type, it is assigned an array of 0 elements. If the object is loaded and there is no value for the property, the field is assigned an empty collection of the appropriate type. Internally, the datastore knows the difference between an empty collection and a collection containing one null value.
If the entity has a property that does not have a corresponding field in the object, that property is inaccessible from the object. If the object is saved back to the datastore, the extra property is deleted.
If an entity has a property whose value is of a different type than the corresponding field in the object, JDO attempts to cast the value to the field type. If the value cannot be cast to the field type, JDO throws a ClassCastException. In the case of numbers (long integers and double-width floats), the value is converted, not cast. If the numeric property value is larger than the field type, the conversion overflows without throwing an exception.