Java 数据对象 (JDO) 是访问 Java 数据库的标准接口,提供 Java 类和数据库表之间的映射。将 JDO 与数据存储区搭配使用需要借助一种开源插件,本页面介绍了如何开始使用该插件。
警告:我们认为,对于大多数开发者来说,使用低层级的 Datastore API 或某种专为 Datastore 开发的开源 API(如 Objectify)会获得更好的体验。 JDO 专为与传统关系型数据库搭配使用而设计,因此无法明确表示数据存储区不同于关系型数据库的一些方面,例如实体组和祖先查询。 这可能会导致难以理解和解决的细微问题。
App Engine Java SDK 包含 App Engine Datastore 的 JDO 2.3 实现。该实现基于 DataNucleus Access Platform 1.0 版,后者是 JDO 2.3 的开源参考实现。
注意:本页面上的说明适用于 JDO 2.3 版,该版本采用适用于 App Engine 的 DataNucleus 1.0 版插件。App Engine 现在提供 DataNucleus 2.x 版插件,该插件可运行 JDO 3.0。新插件支持无主关系,并提供了许多新的 API 和功能。此升级并不能向后完全兼容前一版本。要使用 JDO 3.0 重新构建应用,您需要更新并重新测试代码。如需详细了解新版本,请参阅 JDO 3.0。如需详细了解如何升级,请参阅将 JDO 3.0 与 App Engine 搭配使用。
设置 JDO 2.3
要使用 JDO 访问数据存储区,App Engine 应用需要满足以下条件:
- JDO 和 DataNucleus App Engine 插件 JAR 都必须位于应用程序的
war/WEB-INF/lib/
目录中。 - 名为
jdoconfig.xml
的配置文件必须位于应用的war/WEB-INF/classes/META-INF/
目录中,该文件包含告诉 JDO 使用 App Engine 数据存储区的配置。 - 项目的构建过程必须对已编译的数据类执行编译后“增强”步骤,以将这些其与 JDO 实现关联。
复制 JAR
JDO 和数据存储区 JAR 都包含在 App Engine Java SDK 中。您可以在 appengine-java-sdk/lib/user/orm/
目录中找到它们。
将 JAR 复制到您的应用的 war/WEB-INF/lib/
目录中。
确保 appengine-api.jar
也在 war/WEB-INF/lib/
目录中。(创建项目时您可能已经复制了此文件。)App Engine DataNucleus 插件使用此 JAR 来访问数据存储区。
创建 jdoconfig.xml 文件
JDO 接口需要应用的 war/WEB-INF/classes/META-INF/
目录中名为 jdoconfig.xml
的配置文件。您可以直接在此位置中创建该文件,也可以让构建进程从源目录中复制该文件。
请使用以下内容创建该文件:
<?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>
设置 Datastore 读取政策和最后调用期限
如 Datastore 查询页面所述,您可以通过设置读取政策(高度一致性与最终一致性)和调用时限自定义 Datastore 的行为。在 JDO 中,您可以通过在 jdoconfig.xml
文件的 <persistence-manager-factory>
元素中指定所需的值来实现此目的。借助给定的 PersistenceManager
实例执行的所有调用都将使用 PersistenceManagerFactory
创建管理器时生效的配置值。您还可以为单个 Query
对象替换这些设置。
要为 PersistenceManagerFactory
设置读取策略,请包含一个名为 datanucleus.appengine.datastoreReadConsistency
的属性。可能值为 EVENTUAL
和 STRONG
:如果未指定值,则默认值为 STRONG
。请注意,这些设置仅适用于给定实体组内的祖先查询。无论主要读取政策是哪一种,非祖先查询和跨组查询始终具备最终一致性。
<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
您可以为读取和写入分别设置数据存储区调用期限。对于读取,请使用 JDO 标准属性 javax.jdo.option.DatastoreReadTimeoutMillis
。对于写入,请使用 javax.jdo.option.DatastoreWriteTimeoutMillis
。该值为时间量,以毫秒为单位。
<property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" /> <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />
如果您要使用跨组 (XG) 事务,请添加以下属性:
<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />
借助不同的 name
特性,您可以在同一个 jdoconfig.xml
文件中拥有多个 <persistence-manager-factory>
元素,从而可在同一个应用中使用具有不同配置的 PersistenceManager
实例。例如,以下 jdoconfig.xml
文件建立了两组配置,一组名为 "transactions-optional"
,另一组名为 "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>
如需了解如何使用指定配置集创建 PersistenceManager
,请参阅下面的获取 PersistenceManager 实例。
增强数据类
JDO 在构建过程中使用编译后“增强”步骤,以便将数据类与 JDO 关联。
您可以从命令行使用以下命令对已编译的类执行增强步骤:
java -cp classpath com.google.appengine.tools.enhancer.Enhance class-files
classpath类路径必须包含来自 appengine-java-sdk/lib/
目录的 JAR appengine-tools-api.jar
,以及您的所有数据类。
如需详细了解 DataNucleus 字节码增强器,请参阅 DataNucleus 文档。
获取 PersistenceManager 实例
应用使用 PersistenceManager 类的实例与 JDO 进行交互。 通过在 PersistenceManagerFactory 类的实例上实例化并调用一个方法,您可以获取此实例。 该工厂使用 JDO 配置来创建 PersistenceManager 实例。
因为对 PersistenceManagerFactory 实例进行初始化需要时间,所以应用应该重用单个实例。在执行此操作时,如果应用实例化一个以上的 PersistenceManagerFactory(具有相同配置名称),则将抛出异常。管理 PersistenceManagerFactory 实例的一个简便方法是使用静态实例创建单例包装函数类,如下所示:
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; } }
提示:"transactions-optional"
指的是 jdoconfig.xml
文件中的配置集的名称。如果您的应用使用多个配置集,则必须根据需要扩展此代码来调用 JDOHelper.getPersistenceManagerFactory()
。您的代码应该缓存每个 PersistenceManagerFactory
的单例实例。
该应用使用工厂实例为访问数据存储区的每个请求创建一个 PersistenceManager 实例。
import javax.jdo.JDOHelper; import javax.jdo.PersistenceManager; import javax.jdo.PersistenceManagerFactory; import PMF; // ... PersistenceManager pm = PMF.get().getPersistenceManager();
您可以使用 PersistenceManager 来存储、更新和删除数据对象,并执行数据存储区查询。
使用 PersistenceManager 实例之后,必须调用该实例的 close()
方法。在调用 PersistenceManager 实例的 close()
方法之后使用该实例是一种错误做法。
try { // ... do stuff with pm ... } finally { pm.close(); }
不受支持的 JDO 2.3 功能
JDO 接口的 App Engine 实现不支持该接口的以下功能:
- 无主关系。您可以使用明确的键值实现无主关系。未来版本可能支持 JDO 的无主关系语法。
- 有主多对多关系。
- “Join”查询。对父类型执行查询时,您不能在过滤条件中使用子实体的字段。请注意,您可以直接在查询中使用某个键来测试父类型的关系字段。
- JDOQL 分组和其他聚合查询。
- 多态查询。您不能通过对某个类执行查询来获取子类的实例。在 Datastore 中,每个类都由一个单独的实体种类表示。
- 适用于
@PersistenceCapable
批注的IdentityType.DATASTORE
。只有IdentityType.APPLICATION
是受支持的。 - 目前存在一个 Bug,会在父类和子类属于同个类时阻止实现有主一对多关系,这使得为树型结构建模变得非常困难。这一问题可能在未来版本中加以解决。您可以通过为父类或子类存储明确的键值解决这个问题。
停用事务和移植现有 JDO 应用
我们推荐使用的 JDO 配置将名为 datanucleus.appengine.autoCreateDatastoreTxns
的属性设置为 true
。该属性为 App Engine 专属属性,可指示 JDO 实现将数据存储区事务与应用代码中托管的 JDO 事务关联。 如果您要从头构建新应用,则可能希望实现这一操作。不过,如果您有一个基于 JDO 的现有应用,并且您想在 App Engine 上运行它,那么您可以使用将此属性的值设置为 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>
为了理解这样做的好处,请记住,您只能对属于某事务中的同一实体组的对象进行操作。 使用传统数据库构建的应用通常会假定全局事务具备可用性,以便您更新某事务中的任意记录集。 由于 App Engine 数据存储区不支持全局事务,因此,如果您的代码假定全局事务具备可用性,则 App Engine 会抛出异常。您只需停用数据存储区事务,而无需检查(可能很大的)代码库并移除所有事务管理代码。尽管这样做并不能解决您的代码对多记录修改的原子性所作的假设,但可让您的应用正常运行,从而使您够专注于以递增方式重构事务性代码,并且是根据需要进行重构,而不是一次全部完成。