JPA 與 App Engine 搭配使用

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

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

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

如要進一步瞭解 JPA,請參閱 Access Platform 3.0 說明文件,尤其請參閱 JPA 說明文件JPA API

警告:App Engine 適用的 2.x 版 DataNucleus 外掛程式使用 DataNucleus v3.x。2.x 外掛程式與之前的 1.x 外掛程式無法完全回溯相容。如果您升級至新版本,請務必更新及測試您的應用程式。

建構支援 JPA 2.x 和 3.0 的工具

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

  • 針對 Ant 使用者:SDK 含有會執行強化步驟的 Ant 工作。此外,您必須在設定專案時複製 JAR 並建立設定檔。
  • 針對 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-api-jdo</artifactId>
                            <version>3.1.3</version>
                        </dependency>
                    </dependencies>
                </plugin>

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

本節操作說明將介紹如何升級您的應用程式,以便使用 App Engine 適用的 2.x 版 DataNucleus 外掛程式,該外掛程式與 DataNucleus Access Platform 3.0 和 JPA 2.0 相對應。2.x 外掛程式版本與 1.x 版本無法完全回溯相容,並且可能會在無預警的情況下進行變更。如果您要執行升級作業,請務必更新及測試應用程式程式碼。

新的預設行為

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

  • JPA 的「持續性提供者」現在是 org.datanucleus.api.jpa.PersistenceProviderImpl
  • 「層級 2 快取」預設為啟用。如要取得之前的預設行為,請將持續性屬性 datanucleus.cache.level2.type 設為「none」。(此外,也可以在類別路徑中加入 datanucleus-cache 外掛程式,並將持續性屬性 datanucleus.cache.level2.type 設為「javax.cache」以使用 Memcache 進行 L2 快取。
  • 資料儲存庫 IdentifierFactory 現在預設為「datanucleus2」。如要取得之前的行為,請將持續性屬性 datanucleus.identifierFactory 設為「datanucleus1」
  • EntityManager.persist()EntityManager.merge()EntityManager.remove() 的非交易呼叫現在會自動執行。(在先前版本中,這些呼叫會在下一次交易或出現 EntityManager.close() 時執行。)
  • JPA 已啟用 retainValues,這表示載入欄位的值會在修訂後保留於物件中。
  • javax.persistence.query.chunkSize 現已不再使用。請改用 datanucleus.query.fetchSize
  • 在新版本中,重複的 EMF 分配不會再發生例外狀況。如果您將持續性屬性 datanucleus.singletonEMFForName 設為「true」,則會傳回目前為該名稱分配的單例 EMF。
  • 現在支援非從屬關係。
  • 現在支援 Datastore Identity。

如需新功能的完整清單,請參閱版本資訊

設定檔的變更

如要將應用程式升級為使用 App Engine 適用的 2.0 版 DataNucleus 外掛程式,您必須在 build.xmlpersistence.xml 中變更一些配置設定。如果您要設定新的應用程式,並且想要使用最新版的 DataNucleus 外掛程式,請繼續設定 JPA 2.0

警告! 更新設定後,您必須測試應用程式的程式碼以確保回溯相容性。

在 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>

在 persistence.xml 中

<provider> 目標已變更。請將下列區段:

        <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>

更新為:

        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>

設定 JPA 2.0

如要使用 JPA 來存取資料儲存庫,App Engine 應用程式必須符合以下要求:

  • JPA 與資料儲存庫 JAR 必須位於應用程式的 war/WEB-INF/lib/ 目錄中。
  • 名為 persistence.xml 的設定檔必須位於應用程式的 war/WEB-INF/classes/META-INF/ 目錄中,其中包含指示 JPA 使用 App Engine 資料儲存庫的設定。
  • 專案的建構程序必須對已編譯的資料類別執行後編譯「強化」步驟,才能將這些資料類別與 JPA 實作建立關聯。

複製 JAR

JPA 與資料儲存庫 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 來存取資料儲存庫。

建立 persistence.xml 檔案

JPA 介面需要使用應用程式 war/WEB-INF/classes/META-INF/ 目錄中一個名為 persistence.xml 的設定檔。您可以直接在上述指定位置中建立該檔案,也可透過建構程序從來源目錄複製該檔案。

建立含有以下內容的檔案:

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
            <property name="datanucleus.singletonEMFForName" value="true"/>
        </properties>

    </persistence-unit>

</persistence>

Datastore 讀取政策和呼叫期限

Datastore 查詢頁面所述,您可以針對 persistence.xml 檔案中的 EntityManagerFactory 設定讀取政策 (同步一致性與最終一致性) 與資料儲存庫呼叫期限。這些設定位在 <persistence-unit> 元素中。透過指定 EntityManager 執行個體發出的所有呼叫均會使用 EntityManagerFactory 建立管理員時選取的設定。您也可以覆寫個別 Query 的這些選項 (如下所述)。

如要設定讀取政策,請加入名為 datanucleus.appengine.datastoreReadConsistency 的屬性。該屬性的可能值為 EVENTUAL (適用於最終一致性讀取) 與 STRONG (適用於同步一致性讀取)。如未指定屬性值,則預設值為 STRONG

            <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />

您可以為讀取和寫入作業設定個別的資料儲存庫呼叫期限。對於讀取作業,請使用 JPA 標準屬性 javax.persistence.query.timeout。對於寫入作業,請使用 datanucleus.datastoreWriteTimeout。這兩個屬性的值為時間長度 (毫秒)。

            <property name="javax.persistence.query.timeout" value="5000" />
            <property name="datanucleus.datastoreWriteTimeout" value="10000" />

如要使用跨群組 (XG) 交易,請新增下列屬性:

            <property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />

同一個 persistence.xml 檔案中可以有多個 <persistence-unit> 元素,並使用不同的 name 屬性,這樣就能在相同的應用程式中使用具有不同設定的 EntityManager 執行個體。例如,下列 persistence.xml 檔案會建立兩組設定,一組命名為 "transactions-optional",另一組命名為 "eventual-reads-short-deadlines"

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
        </properties>
    </persistence-unit>

    <persistence-unit name="eventual-reads-short-deadlines">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>

            <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
            <property name="javax.persistence.query.timeout" value="5000" />
            <property name="datanucleus.datastoreWriteTimeout" value="10000" />
            <property name="datanucleus.singletonEMFForName" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

如要瞭解如何使用已命名的設定集來建立 EntityManager,請參閱下方的取得 EntityManager 執行個體

您可以覆寫個別 Query 物件的讀取政策和呼叫期限。如要覆寫 Query 的讀取政策,請呼叫 setHint() 方法,如下所示:

        Query q = em.createQuery("select from " + Book.class.getName());
        q.setHint("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");

如上所述,可能值為 "EVENTUAL""STRONG"

如要覆寫讀取逾時,請呼叫 setHint(),如下所示:

        q.setHint("javax.persistence.query.timeout", 3000);

如果您是透過金鑰來擷取實體,則無法覆寫這些選項的設定。

強化資料類別

JPA 的 DataNucleus 實作可使用建構程序中的後編譯「強化」步驟,將資料類別與 JPA 實作建立關聯。

您可以在指令列使用下列命令,對已編譯的類別執行強化步驟:

java -cp classpath org.datanucleus.enhancer.DataNucleusEnhancer
class-files

「classpath」必須包含 appengine-java-sdk/lib/tools/ 目錄中的 JAR datanucleus-core-*.jardatanucleus-jpa-*datanucleus-enhancer-*.jarasm-*.jargeronimo-jpa-*.jar (其中 * 是每個 JAR 的適當版本編號),以及所有資料類別。

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

取得 EntityManager 執行個體

應用程式必須透過 EntityManager 類別的執行個體與 JPA 互動。如要取得此執行個體,您可以對 EntityManagerFactory 類別的執行個體執行實例化作業並呼叫方法。Factory 會使用 JPA 設定 (由名稱 "transactions-optional" 識別) 來建立 EntityManager 執行個體。

由於 EntityManagerFactory 執行個體需要時間來執行初始化,因此請儘可能重複使用單一執行個體。有一個簡單的方式可以重複使用單一執行個體:使用靜態執行個體來建立單例包裝函式類別,如下所示:

EMF.java

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class EMF {
    private static final EntityManagerFactory emfInstance =
        Persistence.createEntityManagerFactory("transactions-optional");

    private EMF() {}

    public static EntityManagerFactory get() {
        return emfInstance;
    }
}

提示:"transactions-optional" 是指 persistence.xml 檔案中設定集的名稱。如果您的應用程式使用多個設定集,則您必須擴充這個程式碼,以便視需要呼叫 Persistence.createEntityManagerFactory()。您的程式碼應快取每個 EntityManagerFactory 的單例執行個體。

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

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import EMF;

// ...
    EntityManager em = EMF.get().createEntityManager();

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

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

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

類別和欄位註解

每個透過 JPA 儲存的物件都成為 App Engine 資料儲存庫中的實體,而實體的種類衍生自類別的簡單名稱 (不含套件名稱)。類別的每個持續性欄位代表實體的屬性,而屬性名稱即為欄位名稱 (保留大小寫)。

如要宣告 Java 類別能透過 JPA 在資料儲存庫中儲存和擷取,請為類別提供 @Entity 註解。例如:

import javax.persistence.Entity;

@Entity
public class Employee {
    // ...
}

儲存在資料儲存庫中的資料類別欄位,所屬類型必須為預設的持續性類型或明確宣告為持續性。如需詳細說明 JPA 預設持續性行為的圖表,請造訪 DataNucleus 網站。如要將欄位明確宣告為持續性,請提供 @Basic 註解:

import java.util.Date;
import javax.persistence.Enumerated;

import com.google.appengine.api.datastore.ShortBlob;

// ...
    @Basic
    private ShortBlob data;

欄位類型可以是下列任一種:

  • 資料儲存庫支援的其中一種核心類型
  • 核心資料儲存庫類型值的集合 (例如,java.util.List<...>)
  • @Entity 類別的執行個體或執行個體集合
  • 儲存為實體屬性的嵌入類別

資料類別必須具備一個公開或受保護的預設建構函式,以及一個專門儲存對應資料儲存庫實體主鍵的欄位。您可以從四種不同種類的金鑰欄位中選擇,每一種金鑰欄位使用不同的值類型和註解。(詳情請參閱建立資料:金鑰。) 最簡單的金鑰欄位類型是長整數值,當您第一次將這類物件儲存至資料儲存庫時,JPA 會自動為這類物件填入一個獨特的長整數值,該物件類別的其他所有執行個體均不會使用該值。長整數金鑰會使用 @Id 註解,以及 @GeneratedValue(strategy = GenerationType.IDENTITY) 註解:

import com.google.appengine.api.datastore.Key;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

// ...
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;

資料類別的範例如下:

import com.google.appengine.api.datastore.Key;

import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;

    private String firstName;

    private String lastName;

    private Date hireDate;

    // Accessors for the fields. JPA doesn't use these, but your application
    does.

    public Key getKey() {
        return key;
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Date getHireDate() {
        return hireDate;
    }
    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }
}

繼承

JPA 支援建立使用繼承的資料類別。在討論 JPA 繼承在 App Engine 中的運作方式之前,我們建議您先閱讀 DataNucleus 說明文件中與此主題有關的內容,然後再往下閱讀。已經看完了?好的。JPA 繼承在 App Engine 中的運作原理和 DataNucleus 文件中說明的原理一樣,但還包括一些額外的限制。我們將說明這些限制並提供一些具體範例。

「JOINED」繼承策略可讓您跨多個「資料表」分割單一資料物件的資料,但由於 App Engine 資料儲存庫不支援彙整,因此使用此繼承策略對資料物件執行操作時,需要針對每個繼承層級執行遠端程序呼叫。這項操作的效率可能非常低,因此資料類別不支援「JOINED」繼承策略。

其次,「SINGLE_TABLE」繼承策略可讓您將資料物件的資料儲存在單一「資料表」中,且該表格與您繼承階層根目錄中的持續性類別相關聯。雖然這項策略並無任何繼承效率問題,但我們目前暫不支援這項策略。我們會在日後的版本中重新討論這個項目。

現在有個好消息:「TABLE_PER_CLASS」與「MAPPED_SUPERCLASS」策略的運作方式均與 DataNucleus 文件中的說明相同。以下方這段程式碼為例:

Worker.java

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@Entity
@MappedSuperclass
public abstract class Worker {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;

    private String department;
}

Employee.java

// ... imports ...

@Entity
public class Employee extends Worker {
    private int salary;
}

Intern.java

import java.util.Date;
// ... imports ...

@Entity
public class Intern extends Worker {
    private Date internshipEndDate;
}

在此範例中,我們已將 @MappedSuperclass 註解新增至 Worker 類別宣告。此操作可指示 JPA 將 Worker 的所有持續性欄位都儲存在其子類別的資料儲存庫實體中。透過 Employee 執行個體呼叫 persist() 而建立的資料儲存庫實體,將具有名為「department」及「salary」的兩個屬性。透過 Intern 執行個體呼叫 persist() 而建立的資料儲存庫實體,將具有名為「department」及「inernshipEndDate」的兩個屬性。資料儲存庫中不會有「Worker」種類的任何實體。

現在,我們再將上方範例做一些更有意思的變化。假設除了擁有 EmployeeIntern 以外,我們還想有一個專門的 Employee 來描述離開公司的員工:

FormerEmployee.java

import java.util.Date;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
// ... imports ...

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class FormerEmployee extends Employee {
    private Date lastDay;
}

在本例中,我們已將 @Inheritance 註解新增至 FormerEmployee 類別宣告,並將其 strategy 屬性設為 InheritanceType.TABLE_PER_CLASS。此操作讓 JPA 將 FormerEmployee 的所有持續性欄位及其父類別儲存在 FormerEmployee 執行個體對應的資料儲存庫實體中。透過 FormerEmployee 執行個體呼叫 persist() 而建立的資料儲存庫實體,將具有名為「department」、「salary」及「lastDay」的三個屬性。絕不會有「Employee」種類的實體與 FormerEmployee 對應,但如果您使用執行階段類型為 Employee 的物件來呼叫 persist(),則會建立「Employee」種類的實體。

只要關係欄位的宣告類型與指派給這些欄位的物件的執行階段類型相符,即可運用關係和繼承的結合。詳情請參閱多型態關係一節。儘管其中提供的是 JDO 範例,不過該章節中說明的概念和限制均適用於 JPA。

不支援的 JPA 功能

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

  • 多對多從屬關係。
  • 「彙整」查詢。當您對父項種類執行查詢時,不可在篩選器中使用子項實體的欄位。提醒您,您可以使用金鑰直接在查詢中測試父項的關係欄位。
  • 匯總查詢 (「group by」、「having」、「sum」、「avg」、「max」、「min」)
  • 多型態查詢。您無法透過查詢類別來取得子類別的執行個體。每個類別均由資料儲存庫中的個別實體種類來代表。
本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Java 適用的 App Engine 標準環境