Google Code offered in: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
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 simplifies porting your application 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".
Note: The instructions on this page apply to version 1.0 of the App Engine DataNucleus plugin. They also generally apply to version 2.0, which supports DataNucleus v3.x with the differences listed under Using or Migrating to DataNucleus Plugin 2.0.
To use JDO to access the datastore, an App Engine app needs the following:
war/WEB-INF/lib/
directory.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.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.
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.
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>
As described in the overview, you can set the read policy (strong consistency vs. eventual consistency) and the datastore call deadline for a PersistenceManagerFactory
in the jdoconfig.xml
file. These settings go in the <persistence-manager-factory>
element. All calls made with a given PersistenceManager
instance use the configuration selected when the manager was created by the PersistenceManagerFactory
. You can also override these options for a single Query
object.
To set the read policy for a PersistenceManagerFactory
, include a property named datanucleus.appengine.datastoreReadConsistency
. Its possible values are EVENTUAL
(for reads with eventual consistency) and STRONG
(for reads with strong consistency). If not specified, the default is STRONG
.
<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
You can set separate datastore call deadlines for reads and for writes. For reads, use the JDO standard property javax.jdo.option.DatastoreReadTimeoutMillis
. For writes, use javax.jdo.option.DatastoreWriteTimeoutMillis
. The value is an amount of time, in milliseconds.
<property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" /> <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />
You can have multiple <persistence-manager-factory>
elements in the same jdoconfig.xml
file, using different name
attributes, to use PersistenceManager
instances with different configurations in the same app. For example, the following jdoconfig.xml
file establishes two sets of configuration, one named "transactions-optional"
and another named "eventual-reads-short-deadlines"
:
<?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> <persistence-manager-factory name="eventual-reads-short-deadlines"> <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"/> <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" /> <property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" /> <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" /> </persistence-manager-factory> </jdoconfig>
See Getting a PersistenceManager Instance below for information on creating a PersistenceManager
with a named configuration set.
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.
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; } }
Tip: "transactions-optional"
refers to the name of the configuration set in the jdoconfig.xml
file. If your app uses multiple configuration sets, you'll have to extend this code to call JDOHelper.getPersistenceManagerFactory()
as desired. Your code should cache a singleton instance of each PersistenceManagerFactory
.
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(); }
The following features of the JDO interface are not supported by the App Engine implementation:
IdentityType.DATASTORE
for the @PersistenceCapable
annotation. Only IdentityType.APPLICATION
is supported.The JDO configuration we recommend using sets a property named datanucleus.appengine.autoCreateDatastoreTxns
to true
. This is an App Engine-specific property that tells the JDO implementation to associate datastore transactions with the JDO transactions that are managed in application code. If you are building a new app from scratch, this is probably what you want. However, if you have an existing, JDO-based application that you want to get running on App Engine, you may want to use an alternate persistence configuration which sets the value of this property to false
:
<?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="false"/> </persistence-manager-factory> </jdoconfig>
In order to understand why this may be useful, remember that you can only operate on objects that belong to the same entity group within a transaction. Applications built using traditional databases typically assume the availability of global transactions, which allow you to update any set of records inside a transaction. Since the App Engine datastore does not support global transactions, App Engine throws exceptions if your code assumes the availability of global transactions. Instead of going through your (potentially large) codebase and removing all your transaction management code, you can simply disable datastore transactions. This does nothing to address assumptions your code makes about atomicity of multi-record modifications, but it allows you to get your app working so that you can then focus on refactoring your transactional code incrementally and as needed, rather than all at once.
Warning: App Engine DataNucleus plugin 2.0 uses DataNucleus v3.x. This new plugin is not a drop-in replacement for the previous plugin. If you rebuild an application using version 2.0 of the plugin, that may require you to update and retest your code.
DataNucleus plugin 2.0 has the following differences from version 1.0:
PersistenceManagerFactoryClass
is now org.datanucleus.api.jdo.JDOPersistenceManagerFactory
.datanucleus.cache.level2.type
to none. (Alternatively include the datanucleus-cache plugin in the classpath, and set the persistence property datanucleus.cache.level2.type
to javax.cache to use Memcache for L2 caching.IdentifierFactory
now defaults to datanucleus2. To get the previous behavior, set the persistence property datanucleus.identifierFactory
to datanucleus1.PersistenceManager.makePersistent()
, and PersistenceManager.deletePersistent()
are now executed atomically. (Previously, execution occurred at the next transaction or upon PersistenceManager.close()
.datanucleus.singletonPMFForName
set to true then it will return the currently allocated singleton PMF for that name.setResultClass
now works.long
.You can download App Engine DataNucleus plugin 2.0 at the datanucleus-appengine downloads page.