搭配使用 JPA 與 App Engine

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

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

1.x 版的外掛程式包含在 App Engine Java SDK 中,其實作了 JPA 1.0 版,且這項實作以 DataNucleus Access Platform 1.1 版為基礎。

附註:此頁面提供的操作說明適用於 JPA 1 版,這個版本的 JPA 使用 App Engine 適用的 1.x 版 DataNucleus 外掛程式。您也可以使用 2.x 版的 DataNucleus 外掛程式,其可讓您使用 JPA 2.0。2.x 外掛程式提供一些新的 API 與功能;但是,升級與 1.x 版並非完全回溯相容。如果您使用 JPA 2.0 重新建構應用程式,則必須更新及重新測試程式碼。如要進一步瞭解新版本,請參閱將 JPA 2.0 與 App Engine 搭配使用一文。

設定 JPA

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

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

如果您使使用 Apache Ant 建置專案,可透過 SDK 內含的 Ant 工作來執行強化步驟;此外,您必須在設定專案時複製 JAR 並建立設定檔。

複製 JAR

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

建立 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.store.appengine.jpa.DatastorePersistenceProvider</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
        </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.store.appengine.jpa.DatastorePersistenceProvider</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.store.appengine.jpa.DatastorePersistenceProvider</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" />
        </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 實作建立關聯。

如果您使用的是 Apache Ant,SDK 內含的 Ant 工作會執行這個步驟,

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

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 資料儲存庫中的實體,而實體的種類衍生自類別的簡稱 (不含套件名稱)。類別的每個持續性欄位都代表了實體的屬性,而屬性名稱即為欄位名稱 (保留大小寫)。

如要宣告可透過 JPA 儲存及擷取資料儲存庫中的某個 Java 類別,請為該類別提供 @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 1.0 功能

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

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

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

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