English

Using JDO with App Engine

Java Data Objects (JDO) is a standard interface for storing objects containing data into a database. The standard defines interfaces for annotating Java objects, retrieving objects with queries, and interacting with a database using transactions. An application that uses the JDO interface can work with different kinds of databases without using any database-specific code, including relational databases, hierarchical databases and object databases. As with other interface standards, JDO makes your application easy to port between different storage solutions.

The App Engine Java SDK includes an implementation of JDO 2.3 for the App Engine datastore. The implementation is based on DataNucleus Access Platform, the open source reference implementation for JDO 2.3.

See the Access Platform 1.1 documentation for more information about JDO. In particular, see "JDO Mapping" and "JDO API".

Setting Up JDO

To use JDO to access the datastore, an App Engine app needs the following:

  • The JDO and DataNucleus App Engine plugin JARs must be in the app's war/WEB-INF/lib/ directory.
  • A configuration file named jdoconfig.xml must be in the app's war/WEB-INF/classes/META-INF/ directory, with configuration that tells JDO to use the App Engine datastore.
  • The project's build process must perform a post-compilation "enhancement" step on the compiled data classes to associate them with the JDO implementation.

If you are using the Google Plugin for Eclipse, these three things are taken care of for you. The new project wizard puts the JDO and DataNucleus App Engine plugin JARs in the correct location, and creates the jdoconfig.xml file. The build process performs the "enhancement" step automatically.

If you are using Apache Ant to build your project, you can use an Ant task included with the SDK to perform the enhancement step. You must copy the JARs and create the configuration file when you set up your project. See Using Apache Ant for more information about the Ant task.

Copying the JARs

The JDO and datastore JARs are included with the App Engine Java SDK. You can find them in the appengine-java-sdk/lib/user/orm/ directory.

Copy the JARs to your application's war/WEB-INF/lib/ directory.

Make sure the appengine-api.jar is also in the war/WEB-INF/lib/ directory. (You may have already copied this when creating your project.) The App Engine DataNucleus plugin uses this JAR to access the datastore.

Creating the jdoconfig.xml File

The JDO interface needs a configuration file named jdoconfig.xml in the application's war/WEB-INF/classes/META-INF/ directory. You can create this file in this location directly, or have your build process copy this file from a source directory.

Create the file with the following contents:

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
    </persistence-manager-factory>
</jdoconfig>

Enhancing Data Classes

JDO uses a post-compilation "enhancement" step in the build process to associate data classes with the JDO implementation.

If you are using Eclipse, the Google Plugin for Eclipse does this step automatically when building.

If you are using Apache Ant, the SDK includes an Ant task to perform this step. See Using Apache Ant for more information on using the Ant task.

You can perform the enhancement step on compiled classes from the command line with the following command:

java -cp classpath com.google.appengine.tools.enhancer.Enhance class-files

The classpath must contain the JAR appengine-tools-api.jar from the appengine-java-sdk/lib/ directory, as well as all of your data classes.

For more information on the DataNucleus bytecode enhancer, see the DataNucleus documentation.

Getting a PersistenceManager Instance

An app interacts with JDO using an instance of the PersistenceManager class. You get this instance by instantiating and calling a method on an instance of the PersistenceManagerFactory class. The factory uses the JDO configuration to create PersistenceManager instances.

Because a PersistenceManagerFactory instance takes time to initialize, an app should reuse a single instance. To enforce this, an exception is thrown if the app instantiates more than one PersistenceManagerFactory (with the same configuration name). An easy way to manage the PersistenceManagerFactory instance is to create a singleton wrapper class with a static instance, as follows:

PMF.java

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

The app uses the factory instance to create one PersistenceManager instance for each request that accesses the datastore.

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import PMF;

// ...
    PersistenceManager pm = PMF.get().getPersistenceManager();

You use the PersistenceManager to store, update and delete data objects, and to perform datastore queries.

When you are done with the PersistenceManager instance, you must call its close() method. It is an error to use the PersistenceManager instance after calling its close() method.

    try {
        // ... do stuff with pm ...
    } finally {
        pm.close();
    }

Unsupported Features of JDO

The following features of the JDO interface are not supported by the App Engine implementation:

  • Unowned relationships. You can implement unowned relationships using explicit Key values. JDO's syntax for unowned relationships may be supported in a future release.
  • Owned many-to-many relationships.
  • contains() syntax for query filters on Collection fields. You can test that a multi-valued property (a Collection field) has a value using an equality filter: collection == "value"
  • "Join" queries. You cannot use a field of a child entity in a filter when performing a query on the parent kind. Note that you can test the parent's relationship field directly in query using a key.
  • JDOQL grouping and other aggregate queries.
  • Polymorphic queries. You cannot perform a query of a class to get instances of a subclass. Each class is represented by a separate entity kind in the datastore.
  • IdentityType.DATASTORE for the @PersistenceCapable annotation. Only IdentityType.APPLICATION is supported.
  • There is currently a bug preventing preventing persistent fields on superclasses from being saved to the datastore. This will be fixed in a future release.