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

關聯性

您可以使用物件類型的欄位,在持續性物件之間建立關聯性模型。持續性物件之間的關聯性可以描述為從屬或非從屬關聯性。在從屬關聯性中,其中一個物件缺席時,另一個物件將無法繼續存在;而在非從屬關聯性中,兩個物件可以獨立存在。JDO 介面的「應用服務引擎」實作可以建立一對一和一對多的從屬關聯性,無論是單向或雙向。自然語法尚未支援非從屬關聯性,但是您可以直接在欄位中儲存資料存放區金鑰,即可自行管理這些關聯性。「應用服務引擎」會自動在實體群組中建立關聯實體,以支援關聯物件的同時更新,但是應用程式必須知道使用資料存放區交易的時機。

一對一的從屬關聯性

您可以使用類型為關聯類別的欄位,在兩個持續性物件之間建立單向一對一的從屬關聯性。

下列範例會定義 ContactInfo 資料類別和 Employee 資料類別,其中 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;

    // ...
}

Employee.java

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

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

    @Persistent
    private ContactInfo contactInfo;

    ContactInfo getContactInfo() {
        return contactInfo;
    }
    void setContactInfo(ContactInfo contactInfo) {
        this.contactInfo = contactInfo;
    }

    // ...
}

在資料存放區中,持續性物件是由分屬兩個不同種類的兩個相異實體代表之。關聯性是由實體群組關聯性代表之:子系的金鑰則使用父系的金鑰做為實體群組父系。當應用程式使用父系物件的欄位存取子系物件時,JDO 實作會執行實體群組父系查詢以取得子系。

子系類別必須擁有一個金鑰欄位,且該欄位類型必須能夠包含父系金鑰資訊:Key 或是編碼成字串的 Key 值。如需金鑰欄位類型的詳細資訊,請參閱「建立資料:金鑰」。

您可以同時使用兩個類別的欄位來建立雙向的一對一關聯性,並在子系類別的欄位附上註解,以宣告這些欄位代表雙向關聯性。子系類別的欄位必須包括含有 mappedBy = "..." 引數的 @Persistent 註解,當中的值為父系類別的欄位名稱。如果其中一個物件的欄位填入資料,則另一個物件的相應參考欄位也會自動填入資料。

ContactInfo.java

import Employee;

// ...
    @Persistent(mappedBy = "contactInfo")
    private Employee employee;

第一次存取子系物件時,子系物件會從資料存放區載入。如果您從未存取父系物件上的子系物件,子系物件的實體就永遠不會載入。(資料存放區介面不支援子系物件的「立即」載入。資料存放區不支援 join 查詢,因此立即載入的實作無法省略應用程式對資料存放區的呼叫)。

一對多的從屬關聯性

如果要在某個類別的物件和另一個類別的多個物件之間建立一對多關聯性,您可以使用關聯類別的「集合」:

Employee.java

import java.util.List;

// ...
    @Persistent
    private List<ContactInfo> contactInfoSets;

雙向的一對多關聯性與一對一關聯性類似,其父系類別上的欄位會使用 @Persistent(mappedBy = "...") 註解,當中的值為子系類別的欄位名稱:

Employee.java

import java.util.List;

// ...
    @Persistent(mappedBy = "employee")
    private List<ContactInfo> contactInfoSets;

ContactInfo.java

import Employee;

// ...
    @Persistent
    private Employee employee;

定義資料類別:集合」所列出的集合類型支援一對多關聯性,但是陣列「不支援」一對多關聯性。

「應用服務引擎」不支援 join 查詢:您無法使用子系實體的屬性 (attribute) 查詢父系實體。(您可以查詢內嵌類別的屬性,因為內嵌類別會在父系實體上儲存屬性;請參閱「定義資料類別:內嵌類別」)。

排序集合如何維護其順序

排序集合 (例如 List<...>) 會在儲存父系物件時保留物件的順序。JDO 要求資料庫將每個物件的位置儲存為物件屬性,藉以保留物件順序。「應用服務引擎」會將這個順序儲存為相應實體的屬性,並使用與父系欄位名稱相同的屬性名稱加上 _INTEGER_IDX 命名。位置屬性的效率不彰。如果在集合中新增、移除或移動元素,則必須更新集合中所有位於修改處後方的實體。更新的速度可能十分緩慢,而且如果不是透過交易執行,還很容易出錯。

如果您不需要保留集合中的任意順序,但是需要使用排序集合類型,您可以使用註解 (DataNucleus 所提供的 JDO 擴充套件),並根據元素屬性指定排序:

import java.util.List;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.Order;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    @Order(extensions = @Extension(vendorName="datanucleus", key="list-ordering", value="state asc, city asc"))
    private List<ContactInfo> contactInfoSets = new List<ContactInfo>();

@Order 註解 (使用 list-ordering 擴充套件) 可將集合元素的偏好順序指定為 JDOQL 排序子句。排序會使用元素的屬性值。和查詢一樣,集合的所有元素均需擁有子句所使用的屬性值。

存取集合時會執行查詢。如果欄位的排序子句使用超過一個排序順序,查詢就需要資料存放區索引。如需索引的詳細資訊,請參閱「查詢和索引」。

為增加效率,在排序集合類型的一對多關聯性中,請盡可能使用明確的排序子句。

非從屬關聯性

除了從屬關聯性之外,JDO API 也提供非從屬關聯性的管理機能。「應用服務引擎」的 JDO 實作尚未支援這項機能,但是別擔心,您可以使用 Key 值取代模型物件的實例 (或實例集合),藉此管理這些關聯性。您可以將 Key 物件的儲存想成是在兩個物件之間建立任意「外部金鑰」模型。資料存放區不保證這些 Key 參照能夠提供完整參考,但是 Key 的使用可讓您輕鬆地在兩個物件之間建立 (然後擷取) 任何的關聯性模型。不過,如果您要採取這種方式,您必須留意幾件事。首先,應用程式必須確定 Key 的種類是適當的,因為 JDO 和編譯器不會為您執行任何類型的檢查。其次,所有物件必須屬於相同的實體群組,以便在關聯性的兩端執行物件的單一性更新。

提示:在特定情況下,您可能必須為從屬關聯性建立非從屬模型。這是因為所有涉及從屬關聯性的物件都會自動放在相同的實體群組中,但是一個實體群組僅支援每秒 1 至 10 次寫入。因此,假設父系物件每秒接收 .75 次寫入,而子系物件也是每秒接收 .75 次寫入,那麼或許我們可以為這個關聯性建立非從屬模型,讓父系和子系各自存放在獨立的實體群組。

一對一的非從屬關聯性

假設我們想要在個人和食物之間建立關聯性模型,其中每個人只能選擇一種喜好的食物,但是每種食物不需專屬於某個人,因為可能有無數的人喜歡這種食物:

Person.java

// ... imports ...

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

    @Persistent
    private Key favoriteFood;

    // ...
}

Food.java

import Person;
// ... imports ...

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

    // ...
}

在這個範例中,我們並未賦予 Person 一個 Food 類型的成員來代表個人的喜好食物;反之,我們改為賦予 Person 一個 Key 類型的成員,其中 KeyFood 物件的唯一識別碼。請注意,除非 Person.favoriteFood 所參考的 Person 實例和 Food 實例位於相同的實體群組,否則要在單一交易中更新個人和個人的喜好食物是不可能的。

一對多的非從屬關聯性

現在,假設我們想要讓個人選擇多種喜好食物。同樣地,每種喜好食物不需專屬於某個人,因為可能有無數的人喜歡這種食物:

Person.java

// ... imports ...

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

    @Persistent
    private Set<Key> favoriteFoods;

    // ...
}

在這個範例中,我們並未賦予 Person 一個 Set<Food> 類型的成員來代表個人的喜好食物;反之,我們改為賦予 Person 一個 Set<Key> 類型的成員,其中包含 Food 物件的唯一識別碼。請注意,除非 Person.favoriteFoods 所包含的 Person 實例和 Food 實例位於相同的實體群組,否則要在單一交易中更新個人和個人的喜好食物是不可能的。

多對多關聯性

我們可以在關聯性的兩端維護金鑰集合,以建立多對多關聯性模型。讓我們調整一下上述範例,讓 Food 追蹤將該食物視為喜好食物的人們:

Person.java

import java.util.Set;
import com.google.appengine.api.datastore.Key;

// ...
    @Persistent
    private Set<Key> favoriteFoods;

Food.java

import java.util.Set;
import com.google.appengine.api.datastore.Key;

// ...
    @Persistent
    private Set<Key> foodFans;

在這個範例中,Person 維護一組可唯一識別 Food 物件的 Key 值,而 Food 則維護一組可唯一識別 Person 物件 (食物喜好者) 的 Key 值。

使用 Key 值建立多對多模型時,請留意應用程式必須負責維護關聯性的兩端:

Album.java


// ...
public void addFavoriteFood(Food food) {
    favoriteFoods.add(food.getKey());
    food.getFoodFans().add(getKey());
}

public void removeFavoriteFood(Food food) {
    favoriteFoods.remove(food.getKey());
    food.getFoodFans().remove(getKey());
}

請注意,除非 Person.favoriteFoods 所包含的 Person 實例和 Food 實例位於相同的實體群組,否則要在單一交易中更新個人和個人的喜好食物是不可能的。如果應用程式無法在相同的實體群組中共置多個物件,可能會導致下列情形:更新個人的喜好食物時,無法相應更新食物的喜好者;或是相反地,更新食物的愛好者時,無法相應更新愛好者的喜好食物。

關聯性、實體群組和交易

當具備從屬關聯性的物件儲存至資料存放區時,所有涉及該關聯性且需要儲存的其他物件 (新建立的物件或在前次載入後曾經修改的物件) 將自動儲存。這對交易和實體群組而言關係重大。

請參考下列範例,這個範例在上述的 EmployeeContactInfo 類別之間使用單向關聯性:

    Employee e = new Employee();
    ContactInfo ci = new ContactInfo();
    e.setContactInfo(ci);

    pm.makePersistent(e);

使用 pm.makePersistent() 方法儲存新的 Employee 物件時,新的 ContactInfo 關聯物件將自動儲存。由於兩個物件都是新建立的,「應用服務引擎」會在相同實體群組中建立兩個新實體,並使用 Employee 實體做為 ContactInfo 實體的父系。同樣地,如果 Employee 物件已儲存,而 ContactInfo 關聯物件是新建立的,則「應用服務引擎」會建立 ContactInfo 實體,並使用現有的 Employee 實體做為父系。

不過請注意,這個範例中的 pm.makePersistent() 呼叫沒有使用交易。沒有明確的交易時,這兩個實體會透過個別的單一性動作建立。在這種情況下,Employee 實體的建立可能會成功,但是 ContactInfo 實體的建立會失敗。如要確認這兩個實體均已成功建立或均未能建立,您必須使用交易:

    Employee e = new Employee();
    ContactInfo ci = new ContactInfo();
    e.setContactInfo(ci);

    try {
        Transaction tx = pm.currentTransaction();
        tx.begin();
        pm.makePersistent(e);
        tx.commit();
    } finally {
        if (tx.isActive()) {
            tx.rollback();
        }
    }

如果在建立關聯性「之前」,兩種物件均已儲存,則「應用服務引擎」無法將現有的 ContactInfo 實體「移動」至 Employee 實體的實體群組中,因為只有在建立實體時才能指派實體群組。「應用服務引擎」可以透過參照建立關聯性,但是關聯實體不會位於相同群組中。在這種情形下,將「無法」在相同的交易中更新或刪除兩個實體。如果試圖在相同交易中更新或刪除不同群組的實體,系統將擲回 JDOFatalUserException。

儲存父系物件時,如果其子系物件已遭修改,將一併儲存子系物件的變更。建議您透過這種方法,讓父系物件維護所有關聯子系物件的持續性,然後在儲存變更時使用交易。

相依子系和階層式刪除

JDO 的「應用服務引擎」實作會使所有的從屬關聯性變成「相依」。如果父系物件遭到刪除,所有子系物件也會遭到刪除。如果透過指派新值給父系的相依欄位來破解從屬關聯性,也會刪除舊的子系。

和物件的建立與更新一樣,如果您需要在單一的單一性動作中執行階層式刪除的每項刪除,您必須使用交易。

注意:刪除相依子系物件的工作是由 JDO 實作執行,「不是」資料存放區。如果您是透過低階 API 或「管理控制台」刪除父系實體,關聯的子系物件將不會遭到刪除。