My favorites | 中文(繁體) | Sign in
英文版或許有比此中譯版新的內容

定義資料類別

您可以在資料存放區中使用 JDO 來儲存純 Java 的資料物件 (有時稱為 Plain Old Java Object 或 POJO)。經由 PersistenceManager 設定持續性的每個物件都會成為資料存放區中的實體。您可以使用註解來指示 JDO 如何儲存和重新建立資料類別的實例。

注意:舊版 JDO 使用的是 .jdo 的 XML 檔,而不是 Java 註解。JDO 2.3 仍然可以使用這些 XML 檔,但是本文僅說明如何搭配使用 Java 註解和資料類別。

類別和欄位註解

由 JDO 儲存的每個物件都會成為「應用服務引擎」資料存放區中的實體。實體的種類衍生自類別的簡稱 (內部類別使用不含套件名稱的 $ 路徑)。類別的每個持續性欄位表示實體的屬性,而屬性名稱等於欄位名稱 (大小寫相同)。

如要宣告 Java 類別能夠透過 JDO 儲存至資料存放區以及從資料存放區擷取,請為類別設定 @PersistenceCapable 註解。例如:

import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    // ...
}

如果要將資料類別儲存至資料存放區,您必須宣告資料類別欄位為持續性欄位。如要宣告欄位為持續性,請為欄位設定 @Persistent 註解:

import java.util.Date;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    private Date hireDate;

如要宣告欄位為非持續性 (欄位不會儲存至資料存放區,也不會在擷取物件時還原),請為欄位設定 @NotPersistent 註解。

提示:如果沒有指定 @Persistent@NotPersistent 註解,JDO 會按照預設將特定類型的欄位指定為持續性,並按照預設將其他類型的欄位指定為非持續性。如需此預設行為的完整說明,請參閱 DataNucleus 文件。並非所有「應用服務引擎」資料存放區的核心值類型都會按照 JDO 規格預設為持續性,因此建議您將欄位明確註解為 @Persistent@NotPersistent 以便釐清。

欄位可以是下列任何一種類型。這些類型的詳述如下。

  • 資料存放區支援的核心類型之一
  • 核心資料存放區類型的值集合 (例如 java.util.List<...>) 或值陣列
  • @PersistenceCapable 類別的實例或實例集合
  • 可序列化類別的實例或實例集合
  • 儲存為實體屬性的內嵌類別

資料類別必須具備一個欄位,專門儲存相應資料存放區實體的主要金鑰。您可以從四種不同的金鑰欄位種類選擇一種,每個種類使用的值類型和註解都不同 (如需詳細資訊,請參閱「建立資料:金鑰」)。最簡單的金鑰欄位是長整數值,JDO 會在物件首次儲存至資料存放區時,自動填入該類別所有實例中的唯一值。長整數金鑰使用 @PrimaryKey 註解,以及 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) 註解:

提示:將所有的持續性欄位設定為 privateprotected (或 package protected),並且只提供透過 accessor 方法的公開存取。從另一個類別直接存取持續性欄位可能會略過 JDO 類別強化。或者,您可以將其他類別設定為 @PersistenceAware。如需詳細資訊,請參閱 DataNucleus 文件

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

下列為資料類別的範例:

import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private String firstName;

    @Persistent
    private String lastName;

    @Persistent
    private Date hireDate;

    public Employee(String firstName, String lastName, Date hireDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.hireDate = hireDate;
    }

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

    public Long getId() {
        return id;
    }

    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 String getHireDate() {
        return hireDate;
    } 
    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    } 
}

核心值類型

資料存放區支援下列核心值類型:

類型 Java 類別 排序順序 附註
小於 500 位元組的短文字字串 java.lang.String Unicode 大於 500 位元組的值會擲回 JDOFatalUserException。
小於 500 位元組的短位元組字串 com.google.appengine.api.datastore.ShortBlob 位元組順序 大於 500 位元組的值會擲回 JDOFatalUserException。
布林值 booleanjava.lang.Boolean false < true
整數 shortjava.lang.Shortintjava.lang.Integerlongjava.lang.Long 數字 儲存為長整數,然後轉換成欄位類型。超出範圍的值會發生溢位。
浮點數 floatjava.lang.Floatdoublejava.lang.Double 數字 儲存為兩倍寬度浮點數,然後轉換成欄位類型。超出範圍的值會發生溢位。
日期時間 java.util.Date 依時間順序
Google 帳戶 com.google.appengine.api.users.User 依電子郵件地址 (Unicode)
長文字字串 com.google.appengine.api.datastore.Text (無法排序) 不會編製索引。
長位元組字串 com.google.appengine.api.datastore.Blob (無法排序) 不會編製索引。
實體金鑰 com.google.appengine.api.datastore.Key,或參照物件 (做為子系) 依路徑元素 (種類、ID 或名稱、種類、ID 或名稱...)
URL com.google.appengine.api.datastore.Link Unicode

注意:資料存放區支援數種額外的核心值類型,這些類型尚未在 Java Datastore API (Java 資料存放區 API) 中實作,不過已經在 Python API 中實作。如果屬性擁有任何一個上述類型的值,且實體已經載入具備該屬性欄位的物件,則 JDO 將忽略該屬性:如果欄位類型不接受空值,JDO 會將欄位設為 null 或擲回 NullPointerException。 不支援的類型為:Category、Email、IM、PhoneNumber、PostalAddress、Rating、GeoPt。

如要代表包含單一核心類型值的屬性,請宣告 Java 類型欄位,並使用 @Persistent 註解:

import java.util.Date;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    private Date hireDate;

可序列化物件

欄位值可以包含 Serializable 類別的實例,將序列化的實例值儲存在 Blob 類型的單一屬性值中。如要告知 JDO 將值序列化,請在欄位使用註解 @Persistent(serialized=true)。Blob 值不會編製索引,也無法用於查詢篩選器和排序順序。

下方的簡易 Serializable 類別範例代表一個檔案,包括檔案內容、檔名和 MIME 類型。這不是 JDO 資料類別,因此沒有持續性註解。

import java.io.Serializable;

public class DownloadableFile implements Serializable {
    private byte[] content;
    private String filename;
    private String mimeType;

    // ... accessors ...
}

如要將 Serializable 類別的實例儲存為屬性的 Blob 值,請宣告類型為此類別的欄位,並使用 @Persistent(serialized = "true") 註解:

import javax.jdo.annotations.Persistent;
import DownloadableFile;

// ...
    @Persistent(serialized = "true")
    private DownloadableFile file;

子系物件和關聯性

如果欄位值是 @PersistenceCapable 類別的實例,則會在兩個物件之間建立一對一的從屬關聯性。如果欄位是該等參照的集合,則會建立一對多的從屬關聯性。

重要事項:從屬關聯性隱含交易、實體群組和階層式刪除。如需詳細資訊,請參閱「交易」和「關聯性」。

下方的簡單範例將說明 Employee 物件和 ContactInfo 物件之間的一對一從屬關聯性:

ContactInfo.java

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

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class ContactInfo {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String streetAddress;

    @Persistent
    private String city;

    @Persistent
    private String stateOrProvince;

    @Persistent
    private String zipCode;

    // ... accessors ...
}

Employee.java

import ContactInfo;
// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private ContactInfo myContactInfo;

    // ... accessors ...
}

在這個範例中,如果應用程式建立 Employee 實例,並在實例的 myContactInfo 欄位填入新的 ContactInfo 實例,然後使用 pm.makePersistent(...) 儲存 Employee 實例,則資料存放區會建立兩個實體。其中一個實例的種類為 "ContactInfo",代表 ContactInfo 實例。另一個實例的種類為 "Employee"ContactInfo 實體的金鑰會將 Employee 實體的金鑰當做其實體群組父系。

內嵌類別

透過內嵌類別,您不需要建立新的資料存放區實體與設定關聯性,即可使用類別建立欄位值的模型。物件值的欄位會直接儲存在包含物件的資料存放區實體中。

任何一個 @PersistenceCapable 資料類別均可做為另一個資料類別的內嵌物件。該類別的 @Persistent 欄位會內嵌於物件中。如果您將類別設為內嵌 @EmbeddedOnly 註解,則該類別只能做為內嵌類別。因為內嵌類別不會被儲存為個別實體,所以不需要主要金鑰欄位。

下方為內嵌類別的範例。在這個範例中,資料類別將使用的內嵌類別設定為其內部類別;這個做法十分實用,但是在設定內嵌類別時不一定要這麼做。

import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.EmbeddedOnly;
// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class EmployeeContacts {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Long id;
    
    @PersistenceCapable
    @EmbeddedOnly
    public static class ContactInfo {
        @Persistent
        private String streetAddress;

        @Persistent
        private String city;

        @Persistent
        private String stateOrProvince;

        @Persistent
        private String zipCode;

        // ... accessors ...
    }

    @Persistent
    @Embedded
    private ContactInfo homeContactInfo;
}

內嵌類別的欄位會被儲存為實體屬性,並採用每個欄位的名稱和相應屬性的名稱。如果您的物件類型為內嵌類別,且擁有超過一個欄位,您必須重新命名內嵌類別或物件欄位,以免彼此衝突。您可以在 @Embedded 註解使用引數來指定新的欄位名稱。例如:

    @Persistent
    @Embedded
    private ContactInfo homeContactInfo;

    @Persistent
    @Embedded(members = {
        @Persistent(name="streetAddress", columns=@Column(name="workStreetAddress")),
        @Persistent(name="city", columns=@Column(name="workCity")),
        @Persistent(name="stateOrProvince", columns=@Column(name="workStateOrProvince")),
        @Persistent(name="zipCode", columns=@Column(name="workZipCode")),
    })
    private ContactInfo workContactInfo;

同樣地,物件上的欄位也不能使用可能與內嵌類別的欄位衝突的名稱,除非內嵌欄位已經重新命名。

由於內嵌類別的持續性屬性會儲存在與其他欄位相同的實體中,因此您可以在 JDOQL 查詢篩選器和排序順序中,使用內嵌類別的持續性欄位。如要指向內嵌欄位,您可以輸入外部欄位的名稱、句號 (.) 加上內嵌欄位的名稱。無論內嵌欄位的屬性名稱是否已經由 @Column 註解變更,這個方法都行得通。

    select from EmployeeContacts where workContactInfo.zipCode == "98105"

集合

資料存放區屬性可以擁有超過一個的值。在 JDO 中,這種屬性是由 Collection (集合) 類型的單一欄位代表之,而「集合」是一種核心值類型或是 Serializable 類別。支援的「集合」類型如下:

  • java.util.ArrayList<...>
  • java.util.HashSet<...>
  • java.util.LinkedHashSet<...>
  • java.util.LinkedList<...>
  • java.util.List<...>
  • java.util.Set<...>
  • java.util.SortedSet<...>
  • java.util.Stack<...>
  • java.util.TreeSet<...>
  • java.util.Vector<...>

如果欄位宣告為 List,則資料存放區傳回的物件將會具備 ArrayList 值。如果欄位宣告為 Set,則資料存放區會傳回 HashSet。如果欄位宣告為 SortedSet,則資料存放區會傳回 TreeSet。

例如,類型為 List<String> 的欄位會被儲存為零或多個的屬性字串值,一個 List 的值一個字串值。

import java.util.List;
// ... imports ...

// ...
    @Persistent
    List<String> favoriteFoods;

(@PersistenceCapable 類別的) 子系物件的「集合」會建立多個一對多關聯性的實體。請參閱「關聯性」。

多重值的資料存放區屬性擁有特殊的查詢篩選器和排序順序行為。如需詳細資訊,請參閱「查詢和索引:擁有多個值的排序順序和屬性」。

物件欄位和實體屬性

「應用服務引擎」資料存放區會區別沒有指定屬性的實體以及擁有 null 屬性值的實體。JDO 則不支援這個區別:物件的每個欄位都擁有一個值,且可能是 null。如果欄位擁有可接受空值的值類型 (內建類型以外的類型,例如 intboolean),並設為 null,則儲存物件時,產生的實體會將該屬性設為空值。

當資料存放區實體載入物件,卻缺少物件其中一個欄位的屬性時,如果該欄位的類型為可接受空值的單一值類型,則該欄位會設為 null。當物件存回資料存放區時,null 屬性在資料存放區中會設為空值。如果欄位不是可接受空值的值類型,則載入缺少相應屬性的實體會擲回例外狀況。如果使用重新建立實例時所使用的相同 JDO 類別建立實體,上述情況就不會發生,但是如果 JDO 類別變更,或是如果改用低階 API 取代 JDO 建立實體,則上述情況就可能發生。

如果欄位的類型為核心資料類型的「集合」或 Serializable 類別,而且實體上沒有屬性值,則屬性會設為單一空值,代表資料存放區中的空集合。如果欄位的類型為陣列類型,將被指派 0 元素的陣列。如果物件已載入且沒有屬性值,則欄位將被指派適當類型的空集合。在內部,資料存放區能夠區別空集合以及擁有空值的集合。

如果實體某個屬性在物件中沒有相應欄位,則無法從物件存取該屬性。如果物件存回至資料存放區,多餘的屬性將遭到刪除。

如果實體某個屬性的值類型與物件中的相應欄位不同,則 JDO 會嘗試將該值轉化為欄位類型。如果無法將該值轉化為欄位類型,JDO 會擲回 ClassCastException。 如果值為數字 (長整數和兩倍寬度浮點數),JDo 會嘗試轉換而非轉化值。如果數字屬性值大於欄位類型,則轉換會發生溢位,但不會擲回例外狀況。