将 JDO 2.3 与 App Engine 搭配使用

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 实现关联。

如果您使用 Apache Ant 构建项目,则可以使用在 SDK 中添加 Ant 任务来执行上述增强步骤。设置项目时,您必须复制 JAR 并创建配置文件。

复制 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 的属性。可能值为 EVENTUALSTRONG:如果未指定值,则默认值为 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 关联。

如果您使用的是 Apache Ant,则 SDK 中包含用来执行此步骤的 Ant 任务。

您可以从命令行使用以下命令对已编译的类执行增强步骤:

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

类路径必须包含来自 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 会抛出异常。您只需停用数据存储区事务,而无需检查(可能很大的)代码库并移除所有事务管理代码。尽管这样做并不能解决您的代码对多记录修改的原子性所作的假设,但可让您的应用正常运行,从而使您够专注于以递增方式重构事务性代码,并且是根据需要进行重构,而不是一次全部完成。