The Java Persistence API - Programming Google App Engine (2012)

Programming Google App Engine

Chapter 10. The Java Persistence API

The App Engine Java SDK includes implementations of two data access interface standards: the Java Persistence API (JPA) and Java Data Objects (JDO). These interfaces provide two essential features.

For one, these interfaces define a mechanism for describing the structure of data objects in terms of Java classes. You can use them to define and enforce consistent data schemas on top of App Engine’s schemaless datastore, and take advantage of type safety in the Java language. These interfaces serve as a data modeling layer.

Because each interface is a standard supported by other popular data storage solutions, using a standard interface makes it easier to port an application to and from these other solutions. Different databases have varying degrees of support for these standards. Since the standards were developed with SQL-based relational databases in mind, the App Engine datastore can only be said to support a portion of the standard, and it is often easier to port away from App Engine than to it. But this alone adds value, as you can reserve the right to move your app to your company’s own servers at any time. These interfaces are a portability layer.

The App Engine SDK uses an open source product called DataNucleus Access Platform as the basis for its implementations of JDO and JPA. Access Platform uses an adapter layer that translates both standards to an underlying implementation. The App Engine SDK includes an Access Platform adapter based on its low-level datastore API.

The JDO and JPA standards are similar, and share similar roots. The concepts that apply to the App Engine datastore have similar interfaces in both standards but with different terminology and minor behavioral differences. Which one you choose may depend on how familiar you are with it, or how well it is implemented for your most likely porting target, if you have one in mind.

In this chapter, we look at how to use JPA with App Engine. If you’d prefer to use JDO, check out the official documentation for App Engine, which includes a JDO tutorial.

A quick note on terminology: JPA refers to data objects as “entities.” This similarity to datastore entities is convenient in some ways, and not so convenient in others. For this chapter, we’ll refer to JPA entities as “data objects” (or just “objects”) to avoid confusion with datastore entities.

Setting Up JPA

To use JPA, you must perform a few steps to set up the library.

JPA needs a configuration file that specifies that you want to use the App Engine implementation of the interface. This file is named persistence.xml, and should appear in your WAR’s WEB-INF/classes/META-INF/ directory. If you’re using Eclipse, you can create this file in the src/META-INF/ directory, and Eclipse will copy it to the final location automatically. It should look like this:

<?xml version="1.0" encoding="UTF-8" ?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/persistence

http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

<persistence-unit name="transactions-optional">

<provider>

org.datanucleus.api.jpa.PersistenceProviderImpl

</provider>

<properties>

<property name="datanucleus.NontransactionalRead" value="true"/>

<property name="datanucleus.NontransactionalWrite" value="true"/>

<property name="datanucleus.ConnectionURL" value="appengine"/>

</properties>

</persistence-unit>

</persistence>

This configuration tells Access Platform to use the "appengine" adapter. It also says to allow reads and writes outside of transactions (NontransactionalRead and NontransactionalWrite are true), which fits the semantics of the datastore that we described earlier. We named this configuration set "transactions-optional" to match.

Your application uses an EntityManager object to perform a set of datastore operations. The application creates an EntityManager, using an EntityManagerFactory. The factory loads the configuration file and uses it for subsequent datastore interactions. You get an instance of the factory by calling a static method and passing it the name of the configuration set ("transactions-optional"):

import javax.persistence.EntityManagerFactory;

import javax.persistence.Persistence;

// ...

EntityManagerFactory emfInstance =

Persistence.createEntityManagerFactory("transactions-optional");

The createEntityManagerFactory() static method performs a nontrivial amount of work. You can think of the factory as a connection pool, and each EntityManager as an individual connection. Since you only need one factory for the entire existence of the application, a best practice is to call the method only once, store the factory in a static member, and reuse it for multiple web requests.

package myapp; // where "myapp" is your app's package

import javax.persistence.EntityManagerFactory;

import javax.persistence.Persistence;

public final class EMF {

private static final EntityManagerFactory emfInstance =

Persistence.createEntityManagerFactory("transactions-optional");

private EMF() {}

public static EntityManagerFactory get() {

return emfInstance;

}

}

Access Platform hooks up the persistence plumbing to your JPA data classes in a post-compilation process that it calls “enhancement.” If you are using Eclipse with the Google Plugin, the plug-in performs this step automatically. If you are not using Eclipse, you must add the enhancement step to your build process. See the official documentation for information about performing this build step with Apache Ant.

Entities and Keys

In JPA, you define data classes as plain old Java objects (POJOs). You use annotations to tell JPA which classes to persist to the datastore, and how to store its members. Defining your data exclusively in terms of the Java classes your application uses makes it easy to manipulate your persistent data. It also makes it easy to test your application, since you can create mock data objects directly from the classes.

JPA also lets you use an XML file instead of annotations to describe how to persist data classes. We’ll only cover the annotation style here, but if you are familiar with the XML file mechanism, you can use it with Access Platform.

Here’s a simple example of a data class:

import java.util.Date;

import javax.persistence.Entity;

import javax.persistence.Id;

@Entity(name = "Book")

public class Book {

@Id

private String isbn;

private String title;

private String author;

private int copyrightYear;

private Date authorBirthdate;

// ... constructors, accessors ...

}

JPA knows instances of the Book class can be made persistent (saved to the datastore) because of the @Entity annotation. This annotation takes a name argument that specifies the name to be used in JPA queries for objects of this class. The name must be unique across all data classes in the application.

By default, the name of the datastore kind is derived from the name of the class. Specifically, this is the simple name of the class, without the package path (everything after the last ., e.g., "Book"). If you have two data classes with the same simple name in different packages, you can specify an alternate kind name by using the @Table annotation. (JPA was designed with tabular databases in mind, but the concept is equivalent.)

import javax.persistence.Entity;

import javax.persistence.Table;

@Entity(name = "Book")

@Table(name = "BookItem")

public class Book {

// ...

}

The Book class has five members. Four of these members are stored as properties on the datastore entity: title, author, copyrightYear, and authorBirthdate. The fifth member, isbn, represents the key name for the entity. JPA knows this because the member has the @Idannotation, and because the type of the member is String.

Every data class needs a member that represents the object’s primary key, annotated with @Id. If the type of this member is String and it has no other annotations, then the key has no ancestors, and the value of the member is the string key name. The application must set this field before saving the object for the first time.

To tell JPA to let the datastore assign a unique numeric ID instead of using an app-provided key name string, you declare the member with a type of Long and give it the annotation @GeneratedValue(strategy = GenerationType.IDENTITY), like so:

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

@Entity(name = "Book")

public class Book {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

// ...

}

The member is set with the system-assigned ID when the object is saved to the datastore for the first time.

These simple key member types are sufficient for entities without ancestors. Together with the entity kind ("Book"), the member represents the complete key of a root entity. If an instance of the class may represent an entity with ancestors, the key member must be able to represent the full key path. There are two ways to do this.

One way is to declare the type of the key member to be the com.google.appengine.api.datastore.Key class:

import javax.persistence.Entity;

import javax.persistence.Id;

import com.google.appengine.api.datastore.Key;

@Entity(name = "Book")

public class Book {

@Id

private Key id;

// ...

}

You can use this key member type to create a complete Key with a string name. You can also use system-assigned numeric IDs with ancestors by using the @GeneratedValue annotation, then assigning a Key value with neither the name nor the ID set.

If you’d prefer not to create a dependency on an App Engine–specific class, there is another way to implement a key with ancestors. Simply declare the ID field’s type as String and use a DataNucleus JPA extension that encodes the complete key as a string value, like so:

import javax.persistence.Entity;

import javax.persistence.Id;

import org.datanucleus.api.jpa.annotations.Extension;

import com.google.appengine.api.datastore.Key;

@Entity(name = "Book")

public class Book {

@Id

@Extension(vendorName = "datanucleus",

key = "gae.encoded-pk",

value = "true")

private String id;

// ...

}

You can convert between a Key and a string-encoded key using the KeyFactory class’s keyToString() and stringToKey() methods. (Note that the Key class’s toString() method returns something else.)

You can use a Key ID field or a string-encoded ID field in combination with the @GeneratedValue annotation to produce keys with ancestors and system-assigned numeric IDs.

Entity Properties

The fields of the object become the properties of the corresponding entity. The name of a field is used as the name of the property. The @Id field is not stored as a property value, only as the key.

JPA and App Engine support many types of fields. Any of the types mentioned in Table 5-1 can be used as a field type. A field can contain a serializable object, stored as a single property. A field can also be a collection of one of the core datastore types or a serializable class, to be stored as a multivalued property. Additionally, App Engine supports JPA embedded data objects and relationships between entities using fields.

In some cases, JPA must be told which fields to save to the datastore. For the Java standard types (such as Long or String or Date), JPA assumes that fields of those types should be saved. For other types, especially the datastore-specific classes such as datastore.ShortBlob, you must tell JPA to save the field by giving it the @Basic annotation. If you have a field that should not be saved to the datastore, give it the @Transient annotation:

import java.util.List;

import javax.persistence.Basic;

import javax.persistence.Id;

import javax.persistence.Transient;

import com.google.appengine.api.datastore.ShortBlob;

@Entity(name = "Book")

public class Book {

// ...

private String title; // saved

@Basic // saved

private ShortBlob coverIcon;

@Basic // saved

private List<String> tags;

@Transient // not saved

private int debugAccessCount;

}

As with the low-level API, some types are widened before being stored. int and Integer are converted to Long, and float and Float become Double. With the JPA interface, these values are converted back to the declared field types when loaded into an object.

A Serializable class can be used as a field type, using the @Lob annotation. These values are stored in serialized form as datastore.Blob values. As such, these values are not indexed, and cannot be used in queries.

Collection types are stored as multivalued properties in iteration order. When loaded into the data class, multivalued properties are converted back into the specified collection type.

By default, the name of a field is used as the name of the corresponding property. You can override this by using the @Column annotation:

import javax.persistence.Column;

import javax.persistence.Entity;

@Entity(name = "Book")

public class Book {

// ...

@Column(name = "long_description")

private String longDescription;

}

You can declare that the datastore property of a field should not be mentioned in indexes—the property of each entity should be created as a nonindexed property—using an @Extension annotation:

import org.datanucleus.api.jpa.annotations.Extension;

@Entity(name = "Book")

public class Book {

// ...

@Extension(vendorName = "datanucleus",

key = "gae.unindexed",

value = "true")

private String firstSentence;

}

Embedded Objects

App Engine supports JPA embedded classes by storing the fields of the embedded class as properties on the same datastore entity as the fields of the primary class. You must declare the class to embed using the @Embeddable annotation:

import javax.persistence.Embeddable;

@Embeddable

public class Publisher {

private String name;

private String address;

private String city;

private String stateOrProvince;

private String postalCode;

// ...

}

To embed the class, simply use it as a field type:

import javax.persistence.Entity;

import Publisher;

@Entity(name = "Book")

public class Book {

// ...

private Publisher publisher;

}

Because fields of embedded classes are stored as separate properties, they are queryable just like other properties. You can refer to an embedded field in a property with the name of the outer field with a dot-notation, such as publisher.name. The actual property name is just the name of the inner field, and you can change this if needed, using an @Column annotation.

Saving, Fetching, and Deleting Objects

To start a session with the datastore, you use the EntityManagerFactory to create an EntityManager. You must create a new EntityManager for each request handler, and close it when you’re done:

import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory;

import myapp.EMF; // where "myapp" is your app's package

// ...

EntityManagerFactory emf = EMF.get();

EntityManager em = null;

try {

em = emf.createEntityManager();

// ... do datastore stuff ...

} finally {

if (em != null)

em.close();

}

To create a new data object, you construct the data class and then call the EntityManager’s persist() method with the object:

import myapp.Book; // our data class

// ...

EntityManager em = null;

try {

em = emf.createEntityManager();

Book book = new Book();

book.setTitle("The Grapes of Wrath");

// ...

em.persist(book);

} finally {

if (em != null)

em.close();

}

If you create an object with a complete key, and an entity with that key already exists in the datastore, saving the new object will overwrite the old one. In App Engine’s implementation, JPA’s merge() method is equivalent to persist() in this way. (Other implementations may do something different in this case.)

To fetch an entity with a known key, you use the find() method. This method takes the class of the object in which to load the entity, and the key of the object. The key can be any appropriate type: a string key name, a numeric ID, a datastore.Key object, or a string-encoded complete key. The method returns an object of the given class, or null if no object with that key is found:

Book book = em.find(Book.class, "9780596156732");

if (book == null) {

// not found

}

TIP

The ability of find() to accept all four key types is nonstandard. To make your code more portable, only call find(), using the type of key you used in the data class.

When you create or fetch an entity (or get an entity back from a query), the data object becomes “attached” to (or managed by) the entity manager. If you make changes to an attached object and do not save them by calling the persist() method explicitly, the object is saved automatically when you close the entity manager. As we’ll see in the next section, if you need the entity to be updated in a transaction, you pass the updated object to the persist() method at the moment it needs to be saved.

To delete an entity, you call the remove() method. This method takes the data object as its sole argument. The object still exists in memory after it is removed from the datastore:

em.remove(book);

The remove() method requires a loaded data object. There is no way to delete an entity with this method without fetching its object first. (You can delete entities without fetching them by using a JPQL delete query. See the section Queries and JPQL.)

TIP

Remember to close the EntityManager by calling its close() method. If you don’t, changes to objects will not be saved to the datastore. The best way to do this is in a finally block, as shown previously, so the manager still gets closed in the event of an uncaught exception.

Transactions in JPA

The API for performing transactions in JPA is similar to the low-level datastore API. You call a method on the entity manager to create a Transaction object, then call methods on the object to begin and commit or roll back the transaction:

import javax.persistence.EntityTransaction;

// ...

EntityTransaction txn = em.getTransaction();

txn.begin();

try {

Book book = em.find(Book.class, "9780596156732");

BookReview bookReview = new BookReview();

bookReview.setRating(5);

book.getBookReviews().add(bookReview);

// Persist all updates and commit.

txn.commit();

} finally {

if (txn.isActive() {

txn.rollback();

}

}

The JPA transaction interface was designed for databases that support global transactions, so it knows nothing of App Engine’s local transactions and entity groups. It’s up to the application to know which operations are appropriate to perform in a single transaction. You can manage entity groups and ancestors by using App Engine’s extensions to JPA.

One way to set up a data class that can represent entities with parents is to use either a datastore.Key or a string-encoded key for the @Id field. When you create a new object, you can construct the complete key, including ancestors, and assign it to this field.

Alternatively, you can establish a second field to contain the parent key as either a Key or string-encoded key, using an extension:

import javax.persistence.Basic;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import org.datanucleus.api.jpa.annotations.Extension;

@Entity

public class BookReview {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

@Extension(vendorName = "datanucleus",

key = "gae.encoded-pk",

value = "true")

private String keyString;

@Basic

@Extension(vendorName = "datanucleus",

key = "gae.parent-pk",

value = "true")

private String bookKeyString;

}

The parent key field makes it easier to port your application to another database at a later time. It declares a slot in the data class for the ancestor relationship that is separate from the entity’s key name.

The parent key field is required if you want to perform queries with ancestor filters. As we’ll see in the next section, JPA queries must refer to fields on the data class.

The App Engine implementation of JPA includes features for managing entity groups automatically using object relationships. We’ll discuss relationships later in this chapter.

Queries and JPQL

JPA includes a SQL-like query language called JPQL. JPQL provides access to the underlying database’s query functionality at the level of abstraction of JPA data objects. You form queries for data objects in terms of the data classes, and get objects as results.

To perform a query, you call the entity manager’s createQuery() method with the text of the JPQL query. This returns a Query object. To get the results, you call getResultList() on the Query object:

import java.util.List;

import javax.persistence.Query;

// ...

Query query = em.createQuery("SELECT b FROM Book b");

@SuppressWarnings("unchecked")

List<Book> results = (List<Book>) query.getResultList();

In this example, the cast to List<Book> generates a compiler warning, so we suppress this warning by using an @SuppressWarnings annotation.

JPA knows which class to use for each result from the @Entity(name = "...") annotation on the class. You can also use the full package path of the class in the query.

You can use parameters in your JPQL query, and replace the parameters with values by calling setParameter():

Query query = em.createQuery(

"SELECT b FROM Book b WHERE copyrightYear >= :earliestYear");

query.setParameter("earliestYear", 1923);

getResultList() returns a special App Engine–specific implementation of List that knows how to fetch results in batches. If you iterate over the entire list, the List implementation may make multiple calls to the datastore to fetch results.

If you are only expecting one result, you can call getSingleResult() instead. This gets the first result if any, or null if there are no results:

Book book = (Book) query.getSingleResult();

You can fetch a range of results by setting an offset and a maximum number of results, using the setFirstResult() and setMaxResults() methods before calling getResultList():

// Get results 5-15.

query.setFirstResult(4);

query.setMaxResults(10);

@SuppressWarnings("unchecked")

List<Book> results = (List<Book>) query.getResultList();

The syntax of JPQL is straightforward, and similar to SQL or the Python API’s GQL. JPQL keywords can be all uppercase or all lowercase, and are shown as uppercase here, as is tradition. Class and field names are case-sensitive. The query begins by identifying the simple name of the class of objects to query, corresponding to the kind of the entities:

SELECT b FROM Book b

This query returns all Book data objects, where Book is the value of the name argument to the @Entity annotation on the data class (which happens to also be named Book). The class name is followed by an identifier (b); stating that identifier after the word SELECT tells JPA to return objects of that class as results.

To perform a keys-only query, give the name of the key field instead of the class identifier. The methods that return results return values of the type used for the @Id field in the data class:

Query query = em.createQuery("SELECT isbn FROM Book");

@SuppressWarnings("unchecked")

List<String> results = (List<String>) query.getResultList();

The App Engine implementation of JPQL supports queries for specific fields, although perhaps not in the way you’d expect. For a query for specific fields, the datastore returns the complete data for each entity to the application, and the interface implementation selects the requested fields and assembles the results. This is only true if one of the requested fields is a datastore property, and is not true if the only field is a key field (@Id).

If the query is for one field, each result is a value of the type of that field. If the query is for multiple fields, each result is an Object[] whose elements are the field values in the order specified in the query:

Query query = em.createQuery("SELECT isbn, title, author FROM Book");

// Fetch complete Book objects, then

// produce result objects from 3 fields

// of each result

@SuppressWarnings("unchecked")

List<Object[]> results = (List<Object[]>) query.getResultList();

for (Object[] result : results) {

String isbn = (String) result[0];

String title = (String) result[1];

String author = (String) result[2];

// ...

}

You specify filters on fields by using a WHERE clause and one or more conditions:

SELECT b FROM Book b WHERE author = "John Steinbeck"

AND copyrightYear >= 1940

To filter on the entity key, refer to the field that represents the key in the data class (the @Id field):

SELECT b FROM Book b WHERE author = "John Steinbeck"

AND isbn > :firstKeyToFetch

You can perform an ancestor filter by establishing a parent key field (as we did in the previous section) and referring to that field in the query:

SELECT br FROM BookReview br WHERE bookKey = :pk

As with find(), you can use any of the four key types with parameterized queries, but the most portable way is to use the type used in the class.

You specify sort orders by using an ORDER BY clause. Multiple sort orders are comma-delimited. Each sort order can have a direction of ASC (the default) or DESC.

SELECT b FROM Book b ORDER BY rating DESC title

The App Engine implementation of JPQL includes a couple of additional tricks that the datastore can support natively. One such trick is the string prefix trick:

SELECT b FROM Book b WHERE title LIKE 'The Grape%'

The implementation translates this to WHERE title >= 'The Grape', which does the same thing: it returns all books with a title that begins with the string The Grape, including "The Grape", "The Grapefruit", and "The Grapes of Wrath".

This trick only supports a single wildcard at the end of a string. It does not support a wildcard at the beginning of the string.

Another trick App Engine’s JPQL implementation knows how to do is to translate queries on key fields into batch gets. For example:

SELECT b FROM Book b WHERE isbn IN (:i1, :i2, :i3)

This becomes a batch get of three keys, and does not perform a query at all.

In addition to these SELECT queries, App Engine’s JPA implementation supports deleting entities that meet criteria with JPQL. A delete query can include filters on keys and properties to specify the entities to delete:

DELETE FROM Book b WHERE isbn >= "TEST_000" AND isbn <= "TEST_999"

To execute a DELETE query, call the Query object’s executeUpdate() method. This method returns no results.

As with other mechanisms for modifying data, if you perform a delete query outside of a transaction, it is possible for a delete of one entity to fail while the others succeed. If you perform it inside a transaction, it’ll be all or nothing, but all entities must be in the same entity group, and the delete query must use an ancestor filter.

NOTE

The JPA specification supports many features of queries that are common to SQL databases, but are not supported natively in the App Engine datastore. With a SQL database, using one of these features calls the database directly, with all the performance implications (good and bad) of the datastore’s implementation.

When an app uses a feature of JPQL that the underlying database does not support, DataNucleus Access Platform tries to make up the difference using its own in-memory query evaluator. It attempts to load all the information it needs to perform the query into memory, execute the nonnative operations itself, then return the result.

Because such features are potential scalability hazards—an AVG() query would require fetching every entity of the kind, for example—the App Engine implementation disables the Access Platform in-memory query evaluator.

Relationships

Most useful data models involve relationships between classes of data objects. Players are members of guilds, book reviews are about books, messages are posted to message boards, customers place orders, and orders have multiple line items. For logical reasons or architectural reasons, two concepts may be modeled as separate but related classes. Those relationships are as much a part of the data model as the data fields of the objects.

In the App Engine datastore (and most databases), one easy way to model a relationship between two objects is to store the entity key of one object as a property of the other, and (if needed) vice versa. The datastore supports Key values as a native property value type, and also provides a way to encode key values as strings. You don’t need any help from JPA to model relationships this way.

But relationships are so important to data modeling that JPA has a family of features to support them. In JPA, you can define owned relationships in the data model that enforce constraints by managing changes. With owned relationships, you can say that a book has zero or more book reviews, and JPA ensures that you can’t have a book review without a book. If you delete a Book object, JPA knows to also delete all its BookReview objects. In the Java code, the relationship is represented by a field whose type is of the related data class, ensuring that only the appropriate classes are used on either side of the relationship.

The App Engine implementation of JPA supports one-to-one and one-to-many relationships. It does not yet support JPA’s notion of many-to-many relationships.

An unowned relationship is a relationship without these constraints. App Engine supports unowned relationships through the storing of literal key values, but does not yet support them through JPA. You can use multivalued properties of Key values to model unowned one-to-one, one-to-many, and many-to-many relationships.

To completely support the semantics of JPA owned relationships, App Engine stores objects with owned relationships in the same entity group. It’s easy to see why this has to be the case. If one object is deleted within a transaction, the relationship says the related object must also be deleted. But to do that in the same transaction requires that both objects be in the same entity group. If one object is deleted outside of a transaction, then the other object must be deleted in a separate operation, and if one delete or the other fails, an object remains that doesn’t meet the relationship requirement.

While the use of entity groups may sound constraining, it’s also a powerful feature. You can use JPA owned relationships to perform transactions on related entities, and the JPA implementation will manage entity groups for you automatically.

You specify an owned one-to-one relationship by creating a field whose type is of the related class, and giving the field an @OneToOne annotation. For example, you could associate each book with a cover image, like so:

import javax.persistence.Entity;

import javax.persistence.OneToOne;

import bookstore.BookCoverImage;

@Entity(name = "Book")

public class Book {

// ...

@OneToOne(cascade=CascadeType.ALL)

private BookCoverImage bookCoverImage;

}

This annotation declares a one-to-one relationship between the Book and BookCoverImage classes.

In every relationship, one class “owns” the relationship. The owner of a relationship is responsible for propagating changes to related objects. In this example, the Book class is the “owner” of the relationship.

The cascade=CascadeType.ALL argument annotation says that all kinds of changes should propagate to related objects (including PERSIST, REFRESH, REMOVE, and MERGE). For example:

// EntityManager em;

// ...

Book book = new Book();

book.setBookCoverImage(new BookCoverImage();

book.setTitle("The Grapes of Wrath");

book.bookCoverImage.setType("image/jpg");

EntityTransaction txn = em.getTransaction();

txn.begin();

try {

em.persist(book);

txn.commit();

} finally {

if (txn.isActive() {

txn.rollback();

}

}

em.close();

This code creates a Book and a related BookCoverImage. When it makes the Book persistent, the BookCoverImage is made persistent automatically (the PERSIST action cascades). Similarly, if we were to delete the Book, the BookCoverImage would also be deleted (the DELETE action cascades). Cascading actions follow all ownership paths from “owner” to “owned,” and do the right thing if the objects they find have changed since they were loaded from the datastore.

You can have JPA populate a field on the “owned” class that points back to the owner automatically, like so:

import javax.persistence.Entity;

import javax.persistence.OneToOne;

import bookstore.Book;

@Entity(name = "BookCoverImage")

public class BookCoverImage {

// ...

@OneToOne(mappedBy="bookCoverImage")

private Book book;

}

The mappedBy argument tells JPA that the book field refers to the Book object that is related to this object. This is managed from the “owner” side of the relationship: when the BookCoverImage is assigned to the Book’s field, JPA knows that the back-reference refers to the Book object.

To specify a one-to-many relationship, you use a field type that is a List or Set of the related class, and use the @OneToMany annotation on the “one” class, with a mappedBy argument that refers to the property on the entities of the “many” class:

import java.util.List;

import javax.persistence.CascadeType;

import javax.persistence.Entity;

import javax.persistence.OneToMany;

import bookstore.BookReview;

@Entity(name = "Book")

public class Book {

// ...

@OneToMany(cascade=CascadeType.ALL, mappedBy="book")

private List<BookReview> bookReviews = null;

}

To create a back-reference from the “many” class to the “one” class, you use a @ManyToOne annotation, with no arguments:

// BookReview.java

@Entity(name = "BookReview")

public class BookReview {

// ...

@ManyToOne()

private Book book;

}

// Book.java

@Entity(name = "Book")

public class Book {

// ...

@OneToMany(cascade=CascadeType.ALL,

mappedBy="book")

private List<BookReview> bookReviews;

}

In a one-to-many relationship, the “one” is always the owner class, and the “many” is the owned class. In a one-to-one relationship, JPA knows which is the “owned” class by the absence of a back-reference field, or a back-reference field mentioned by a mappedBy annotation argument: the side with the mappedBy is the owned class.

When you fetch a data object that has a relationship field, JPA does not fetch the related objects right away. Instead, it waits until you access the field to fetch the object (or objects). This is called “lazy” fetching, and it saves your app from unnecessary datastore operations. The App Engine implementation of JPA only supports lazy fetching (FetchType.LAZY), and does not yet support its opposite, “eager” fetching (FetchType.EAGER). Note that you must access related objects prior to closing the EntityManager, so they are fetched into memory:

// Fetch a Book, but not its BookCoverImage.

Book book = em.find(Book.class, "9780596156732");

// ...

// The BookCoverImage is fetched when it is first accessed.

resp.setContentType(book.bookCoverImage.type);

In the datastore, the relationship is represented using ancestors. The owner object is the parent, and all owned objects are children. When you access the relationship field on the owner object, the interface uses an ancestor query to get the owned objects. When you access a back-reference to the owner from the owned object, the interface parses the owned object’s key to get the parent.

Related objects are created in the same entity group, so they can all be updated within the same transaction if necessary. The owner’s entity is created first (if necessary) and becomes the parent of the owned objects’ entities. If you declare a back-reference by using mappedBy, no property is stored on the owned object’s entity. Instead, when the field is dereferenced, the implementation uses the owned object’s key path to determine the owner’s key and fetches it.

The App Engine implementation does not support many-to-one relationships where the “many” is the owner. That is, it does not support a one-to-many relationship where actions cascade from the many to the one.

Creating new relationships between existing data classes can be tricky, because the entity group requirements must be met in the migrated data. Adding a relationship to the owner class is like adding a field: the entities that represent instances of the owner class must be updated to have appropriate key properties. The “owned” side is trickier: since an owned object’s entity must have the owner as its parent, if the owned object already exists in the datastore, it must be deleted and re-created with the new parent. You can’t change an entity’s parent after the entity has been created.

This use of datastore ancestors means you cannot reassign an owned object to another owner after it has been saved to the datastore. This also means that one object cannot be on the “owned” side of more than one relationship, since the entity can have only one parent.

Relationships and cascading actions imply that an operation on a data object can translate to multiple datastore operations on multiple entities, all in the same entity group. If you want these operations to occur in a single transaction, you must perform the initial operation (such asem.merge(...) within an explicit transaction.

If you perform a cascading action outside of an explicit transaction, each of the datastore operations performed by JPA occurs in a separate operation. Some of these operations may fail while others succeed. As such, it’s a best practice to perform all JPA updates within explicit transactions, so there is no confusion as to what succeeded and what failed.

You can perform queries on relationship fields in JPQL by using key values. For a query on the owner class, the query is a simple key property query:

SELECT FROM Book b WHERE bookCoverImage = :bci AND publishDate > :pdate

For a query on an owned class, a query on the back-reference field becomes an ancestor filter:

SELECT FROM BookCoverImage bci WHERE book = :b

You cannot refer to properties of the related entity in the query filter. App Engine does not support join queries.

For More Information

The JDO and JPA interfaces have many useful features that work with the App Engine implementation. One excellent source of documentation on these interfaces is the DataNucleus Access Platform website:

http://www.datanucleus.org/products/accessplatform/

The App Engine implementation is an open source project hosted on Google Code. The source includes many unit tests that exercise and demonstrate many features of JDO and JPA. You can browse the source code at the project page:

http://code.google.com/p/datanucleus-appengine/

To read the unit tests, click the Source tab, then click Browse in the navigation bar. The path is svn/trunk/tests/org/datanucleus/store/appengine/.