將 JDO 3.0 與 App Engine 搭配使用

Java 資料物件 (JDO) 是一種可以在 Java 中存取資料庫的標準介面,可讓您對應 Java 類別與資料庫表格。透過開放原始碼外掛程式,即可將 JDO 與 Datastore 搭配使用。此頁面提供如何開始使用此外掛程式的相關資訊。

警告:我們認為,大多數的開發人員在使用低階的 Datastore API,或是專為 Datastore 開發的某種開放原始碼 API (例如 Objectify) 時,會有比較好的體驗。JDO 專為與傳統關聯資料庫搭配使用而設計,因此無法明確指出 Datastore 與關聯資料庫不同的部分,例如實體群組與祖系查詢。這可能會導致難以瞭解及修正的小問題發生。

App Engine Java SDK 包含 App Engine 適用的 2.x 版 DataNucleus 外掛程式。這個外掛程式與 3.0 版 DataNucleus Access Platform 對應,可讓您透過 JDO 3.0 使用 App Engine Datastore。

如要進一步瞭解 JDO,請參閱 Access Platform 3.0 說明文件。特別是 JDO 對應JDO API

警告:App Engine 適用的 DataNucleus 外掛程式 2.x 使用 DataNucleus v3.x。這個新的外掛程式與之前的 (1.x) 外掛程式並非完全回溯相容。如果您升級至新版本,請確保更新及測試您的應用程式。

建構支援 JDO 2.x 與 3.0 的工具

您可以使用 Maven 使用 App Engine 適用的 DataNucleus 外掛程式 2.x 版或 3.0 版:

  • 針對 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>

遷移至 DataNucleus 外掛程式 2.x 版

這部分提供的操作說明,可將您的應用程式升級為使用與 DataNucleus Access Platform 3.0 和 JDO 3.0 對應的 App Engine 適用 2.x 版本 DataNucleus 外掛程式。這個外掛程式與 1.x 外掛程式並非完全回溯相容,且可能會發生變更。如果您升級,請確保更新及測試您的應用程式程式碼。

新預設行為

2.x 版本的 App Engine DataNucleus 外掛程式與之前的版本相比,有一些不同的預設值:

  • 針對 PersistenceManager.makePersistent()PersistenceManager.deletePersistent() 的非交易呼叫,現在會自動執行 (這些函式先前是在下一個交易或出現 PersistenceManager.close() 時執行)。
  • 現在,重複的 PersistenceManagerFactory (PMF) 分配不會再發生例外狀況。如果您將持續性屬性 datanucleus.singletonPMFForName 設為「true」,則會傳回目前為該名稱分配的單例 PMF。
  • 現在支援非從屬關聯性。請參閱「非從屬關聯」。

設定檔的變更

如要將應用程式升級至 App Engine DataNucleus 外掛程式的 2.x 版,您必須變更 build.xmljdoconfig.xml 中的設定。

請注意!更新設定之後,您需要測試應用程式的程式碼,以確保回溯相容。如果您要設定新的應用程式,且想要使用最新版的 JDO 外掛程式,請前往「設定 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

copyjars 目標必須變更,才能容納 DataNucleus 2.x:

  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

App Engine Java SDK 包括 JDO 和資料儲存庫 JAR,您可以在 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" />

您可以在相同的 jdoconfig.xml 檔案中使用多個 <persistence-manager-factory> 元素,且分別有不同的 name 屬性,以便在相同的應用程式中使用有不同設定的 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」classpath必須包含 appengine-java-sdk/lib/ 目錄中的 JAR appengine-tools-api.jar,以及您所有的資料類別。

如要進一步瞭解 DataNucleus 位元組程式碼強化工具,請參閱 DataNucleus 說明文件

取得 PersistenceManager 執行個體

應用程式必須透過 PersistenceManager 類別的例項與 JDO 互動。如要取得此例項,您可以對 PersistenceManagerFactory 類別的例項執行實例化作業並呼叫方法。Factory 會使用 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 的單例執行個體。

應用程式會使用 Factory 執行個體,為每個存取資料儲存庫的要求建立一個 PersistenceManager 執行個體。

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

import PMF;

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

您可以使用 PersistenceManager 來儲存、更新和刪除資料物件,以及執行資料儲存庫查詢。

完成 PersistenceManager 執行個體的作業後,您必須呼叫該執行個體的 close() 方法。在呼叫 close() 方法後使用 PersistenceManager 執行個體會發生錯誤。

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

不支援的 JDO 3.0 功能

App Engine 實作不支援以下的 JDO 介面功能:

  • 多對多從屬關聯性。
  • 「彙整」查詢。當您對父項種類執行查詢時,不可在篩選器中使用子項實體的欄位。提醒您,您可以使用金鑰直接在查詢中測試父項的關聯性欄位。
  • 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 會擲回例外狀況。如要避免這個例外狀況,您不必在可能非常龐大的程式碼基底中逐一尋找交易管理程式碼,然後將這些程式碼全數移除,只要停用資料儲存庫交易功能即可。雖然這種做法沒有處理程式碼的假設:修訂多個記錄時,不可只完成一部分;但是這種做法能讓您的應用程式正常運作,如此一來,您就可專注在交易程式碼的重構作業並可視情況以漸進方式處理程式碼,不必一次重構所有的交易程式碼。