將 JDO 2.3 與 App Engine 搭配使用

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

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

App Engine Java SDK 包含 App Engine Datastore 的 JDO 2.3 實作。這是一項以 DataNucleus Access Platform 1.0 版為基礎,且適用於 JDO 2.3 的開發原始碼參照實作。

附註:此頁面提供的操作說明適用於 JDO 2.3 版,這個版本的 JDO 使用 App Engine 適用的 1.0 版 DataNucleus 外掛程式。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

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

您可以在相同的 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.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」classpath必須包含 appengine-java-sdk/lib/ 目錄中的 JAR appengine-tools-api.jar,以及您所有的資料類別。

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

取得 PersistenceManager 執行個體

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

應用程式會使用 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 2.3 功能

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

  • 非從屬關聯性。您可以使用明確的金鑰值來實作非從屬關聯性。不過,日後的版本可能會支援適用於非從屬關聯性的 JDO 語法。
  • 多對多從屬關聯性。
  • 「彙整」查詢。當您對父項種類執行查詢時,不可在篩選器中使用子項實體的欄位。提醒您,您可以使用金鑰直接在查詢中測試父項的關聯性欄位。
  • JDOQL 分組和其他匯總查詢。
  • 多型態查詢。您無法透過類別查詢取得子類別的執行個體。每個類別都是由資料儲存庫中的個別實體種類代表。
  • IdentityType.DATASTORE 適用於 @PersistenceCapable 註解。系統僅支援 IdentityType.APPLICATION
  • 目前已知有一個錯誤會造成無法建立同類別父項和子項的一對多從屬關聯性,因而難以建立樹狀結構的模型。我們將在日後的新版本中修正這個錯誤。目前您暫時可透過為父項或子項儲存明確金鑰值來避開這個問題。

停用交易和移轉現有的 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 會擲回例外狀況。如要避免這個例外狀況,您不必在可能非常龐大的程式碼基底中逐一尋找交易管理程式碼,然後將這些程式碼全數移除,只要停用資料儲存庫交易功能即可。雖然這種做法沒有處理程式碼的假設:修訂多個記錄時,不可只完成一部分;但是這種做法能讓您的應用程式正常運作,如此一來,您就可專注在交易程式碼的重構作業並可視情況以漸進方式處理程式碼,不必一次重構所有的交易程式碼。