将 JDO 3.0 与 App Engine 配合使用

Java 数据对象 (JDO) 是访问 Java 数据库的标准接口,提供 Java 类和数据库表之间的映射。将 JDO 与数据存储区搭配使用需要借助一种开源插件,本页面介绍了如何开始使用该插件。

警告:我们认为,对于大多数开发者来说,使用低层级的 Datastore API 或某种专为 Datastore 开发的开源 API(如 Objectify)会获得更好的体验。 JDO 专为与传统关系型数据库搭配使用而设计,因此无法明确表示数据存储区不同于关系型数据库的一些方面,例如实体组和祖先查询。 这可能会导致难以理解和解决的细微问题。

App Engine Java SDK 包含 2.x 版 App Engine DataNucleus 插件。此插件的作用相当于 3.0 版 DataNucleus Access Platform,让您能够通过 JDO 3.0 使用 App Engine Datastore。

如需详细了解 JDO,请参阅 Access Platform 3.0 文档。请特别留意 JDO 映射JDO API 部分。

警告:2.x 版 App Engine DataNucleus 插件使用 DataNucleus v3.x。此新版插件与旧版 (1.x) 插件不完全向后兼容。如果要升级到新版本,请务必更新并测试您的应用。

支持 JDO 2.x 和 3.0 的编译工具

您可以通过 Maven 来使用 App Engine 的 2.x 或 3.0 版 DataNucleus 插件:

  • 对于 Maven 用户:您可以在 pom.xml 文件中使用以下配置来增强类:
    <plugin>
        <groupId>org.datanucleus</groupId>
        <artifactId>maven-datanucleus-plugin</artifactId>
        <version>3.2.0-m1</version>
        <configuration>
            <api>JDO</api>
            <props>${basedir}/datanucleus.properties</props>
            <verbose>true</verbose>
            <enhancerName>ASM</enhancerName>
        </configuration>
        <executions>
            <execution>
                <phase>process-classes</phase>
                <goals>
                    <goal>enhance</goal>
                </goals>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>org.datanucleus</groupId>
                <artifactId>datanucleus-core</artifactId>
                <version>3.1.3</version>
            </dependency>
        </dependencies>
    </plugin>

迁移到 2.x 版 DataNucleus 插件

本部分说明如何将应用升级为使用 2.x 版 App Engine DataNucleus 插件,该插件的作用相当于 DataNucleus Access Platform 3.0 和 JDO 3.0。该插件与 1.x 版插件不完全向后兼容,且可能会发生更改。如果要升级到新版本,请务必更新并测试应用代码。

新版默认行为

2.x 版 App Engine DataNucleus 插件与之前的 1.x 版之间在一些默认值方面有所不同:

  • 现在,以原子方式执行对 PersistenceManager.makePersistent()PersistenceManager.deletePersistent() 的非事务性调用。(以前,这些函数在下一个事务中执行,或者在 PersistenceManager.close() 之后执行。)
  • 现在,重复的 PersistenceManagerFactory (PMF) 分配不会再引发异常。相反,如果您将持久性属性 datanucleus.singletonPMFForName 设置为 truetrue,则该属性会返回当前为该名称分配的单例 PMF。
  • 现在支持无主关系。请参阅无主关系

配置文件变更

如要将应用程序升级到 App Engine DataNucleus 插件的 2.x 版,您需要更改 build.xmljdoconfig.xml 中的配置设置。

警告!更新配置后,您需要测试应用代码,以确保向后兼容性。如果您要设置新的应用并使用最新版插件,请继续设置 JDO 3.0

  1. PersistenceManagerFactoryClass属性已更改。在 jdoconfig.xml 中将此行:

    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>

    更改为:
    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

build.xml

要适应 DataNucleus 2.x,需要更改 copyjars 目标:

  1. copyjars 目标已更改。将此部分:
    <target name="copyjars"
        description="Copies the App Engine JARs to the WAR.">
      <mkdir dir="war/WEB-INF/lib" />
      <copy
          todir="war/WEB-INF/lib"
          flatten="true">
        <fileset dir="${sdk.dir}/lib/user">
          <include name="**/*.jar" />
        </fileset>
      </copy>
    </target>
    更新为:
    <target name="copyjars"
        description="Copies the App Engine JARs to the WAR.">
      <mkdir dir="war/WEB-INF/lib" />
      <copy
          todir="war/WEB-INF/lib"
          flatten="true">
        <fileset dir="${sdk.dir}/lib/user">
            <include name="**/appengine-api-1.0-sdk*.jar" />
        </fileset>
        <fileset dir="${sdk.dir}/lib/opt/user">
          <include name="appengine-api-labs/v1/*.jar" />
          <include name="jsr107/v1/*.jar" />
          <include name="datanucleus/v2/*.jar" />
        </fileset>
      </copy>
    </target>
  2. datanucleusenhance 目标已更改。将此部分:
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
      <enhance_war war="war" />
    </target>
    更新为:
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
        <enhance_war war="war">
                <args>
                <arg value="-enhancerVersion"/>
                <arg value="v2"/>
            </args>
        </enhance_war>
    </target>

设置 JDO 3.0

要使用 JDO 访问数据存储区,App Engine 应用需要满足以下条件:

  • JDO 和 DataNucleus 插件的 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/opt/user/datanucleus/v2/ 目录中找到它们。

将 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.api.jdo.JDOPersistenceManagerFactory"/>
        <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.singletonPMFForName" 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.api.jdo.JDOPersistenceManagerFactory"/>
        <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.api.jdo.JDOPersistenceManagerFactory"/>
        <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" />
        <property name="datanucleus.singletonPMFForName" value="true" />
    </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 实例的一个简便方法是使用静态实例创建单例封装容器类,如下所示:

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 3.0 功能

JDO 接口的以下功能不受 App Engine 实现的支持:

  • 有主的多对多关系。
  • “Join”查询。对父类型执行查询时,您不能在过滤条件中使用子实体的字段。请注意,您可以直接在查询中使用某个键来测试父类型的关系字段。
  • JDOQL 分组和其他聚合查询。
  • 多态查询。您不能通过对某个类执行查询来获取子类的实例。在数据存储区中,每个类都是由一个单独的实体种类表示的。

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