即使包括所有這些代碼,清單 2 提供的家庭關系模型還是過於簡單。在這個層次結構中的某些地方,必須處理那些 null 值。但是,在 db4o 中,那個問題更應該在對象建模中解決,而不是在對象操作中解決。所以現在我可以放心地忽略它。
填充和測試對象模型
對於清單 2 中的 Person 類,需要重點注意的是,如果以關系的方式,使用父與子之間分層的、循環的引用來建模,那肯定會比較笨拙。通過一個實例化的對象模型可以更清楚地看到我所談到的復雜性,所以我將編寫一個探察測試來實例化 Person 類。注意,清單 3 中省略了 JUnit 支架(scaffolding);我假設您可以從其他地方,包括本系列之前的文章學習 JUnit 4 API。通過閱讀本文的源代碼,還可以學到更多東西。
清單 3. 幸福家庭測試
@Test public void testTheModel()
{
Person bruce = new Person("Bruce", "Tate",
Gender.MALE, 29, Mood.HAPPY);
Person maggie = new Person("MaggIE", "Tate",
Gender.FEMALE, 29, Mood.HAPPY);
bruce.setSpouse(maggIE);
Person kayla = maggIE.haveBaby("Kayla", Gender.FEMALE);
Person julia = maggIE.haveBaby("Julia", Gender.FEMALE);
assertTrue(julia.getFather() == bruce);
assertTrue(kayla.getFather() == bruce);
assertTrue(julia.getMother() == maggIE);
assertTrue(kayla.getMother() == maggIE);
int n = 0;
for (Iterator<Person> kids = bruce.getChildren(); kids.hasNext(); )
{
Person child = kids.next();
if (n == 0) assertTrue(child == kayla);
if (n == 1) assertTrue(child == julia);
n++;
}
}
目前一切尚好。所有方面都能通過測試,包括小孩 ArrayList 的使用中的長嗣身份。但是,當我增加 @Before 和 @After 條件,以便用我的測試數據填充 db4o 數據庫時,事情開始變得更有趣。
清單 4. 將孩子發送到數據庫
@Before public void prepareDatabase()
{
db = Db4o.openFile("persons.data");
Person bruce = new Person("Bruce", "Tate", Gender.MALE, 29, Mood.HAPPY);
Person maggie = new Person("MaggIE", "Tate", Gender.FEMALE, 29, Mood.HAPPY);
bruce.setSpouse(maggIE);
bruce.setHomeAddress(new Address("5 Maple Drive", "Austin", "TX", "12345"));
bruce.setWorkAddress( new Address("5 Maple Drive", "Austin", "TX", "12345"));
bruce.setVacationAddress(new Address("10 Wanahokalugi Way", "Oahu", "HA", "11223"));
Person kayla = maggIE.haveBaby("Kayla", Gender.FEMALE);
kayla.setAge(8);
Person julia = maggIE.haveBaby("Julia", Gender.FEMALE);
julia.setAge(6);
db.set(bruce);
db.commit();
}
注意,存儲整個家庭所做的工作仍然不比存儲單個 Person 對象所做的工作多。您可能還記得,在上一篇文章中,由於存儲的對象具有遞歸的性質,當把 bruce 引用傳遞給 db.set() 調用時,從 bruce 可達的所有對象都被存儲。不過眼見為實,讓我們看看當運行我那個簡單的探察測試時,實際上會出現什麼情況。首先,我將測試當調用隨 Person 存儲的各種 Address 時,是否可以找到它們。然後,我將測試是否孩子們也被存儲。
清單 5. 搜索住房和家庭
@Test public void testTheStorageOfAddresses()
{
List<Person> maleTates = db.query(new Predicate<Person>() {
public boolean match(Person candidate) {
return candidate.getLastName().equals("Tate") &&
candidate.getGender().equals(Gender.MALE);
}
});
Person bruce = maleTates.get(0);
Address homeAndWork = new Address("5 Maple Drive", "Austin", "TX", "12345");
Address vacation = new Address("10 Wanahokalugi Way", "Oahu", "HA", "11223");
assertTrue(bruce.getHomeAddress().equals(homeAndWork));
assertTrue(bruce.getWorkAddress().equals(homeAndWork));
assertTrue(bruce.getVacationAddress().equals(vacation));
}
@Test public void testTheStorageOfChildren()
{
List<Person> maleTates = db.query(new Predicate<Person>() {
public boolean match(Person candidate) {
return candidate.getLastName().equals("Tate") &&
candidate.getGender().equals(Gender.MALE);
}
});
Person bruce = maleTates.get(0);
int n = 0;
for (Iterator<Person> children = bruce.getChildren();
children.hasNext();
)
{
Person child = children.next();
System.out.println(child);
if (n==0) assertTrue(child.getFirstName().equals("Kayla"));
if (n==1) assertTrue(child.getFirstName().equals("Julia"));
n++;
}
}
理解關系
您可能會感到奇怪,清單 5 中顯示的基於 Collection 的類型(ArrayList)沒有被存儲為 Person 類型的 “dependents”,而是被存儲為一個成熟的對象。這還說得過去,但是當對對象數據庫中的 ArrayList 運行一個查詢時,它可能,有時候也確實會導致返回奇怪的結果。由於目前數據庫中只有一個 ArrayList,所以還不值得運行一個探察測試,看看當對它運行一個查詢時會出現什麼情況。我把這作為留給您的練習。
自然地,存儲在一個集合中的 Person 也被當作數據庫中的一級實體,所以在查詢符合某個特定標准(例如所有女性 Person)的所有 Person 時,也會返回 ArrayList 實例中引用到的那些 Person,如清單 6 所示。
清單 6. 什麼是 Julia?
@Test public void findTheGirls()
{
List<Person> girls = db.query(new Predicate<Person>() {
public boolean match(Person candidate) {
return candidate.getGender().equals(Gender.FEMALE);
}
});
boolean maggIEFound = false;
boolean kaylaFound = false;
boolean juliaFound = false;
for (Person p : girls)
{
if (p.getFirstName().equals("MaggIE"))
maggIEFound = true;
if (p.getFirstName().equals("Kayla"))
kaylaFound = true;
if (p.getFirstName().equals("Julia"))
juliaFound = true;
}
assertTrue(maggIEFound);
assertTrue(kaylaFound);
assertTrue(juliaFound);
}
注意,對象數據庫將盡量地使引用 “correct” — 至少在知道引用的情況下如此。例如,分別在兩個不同的查詢中檢索一個 Person(也許是母親)和檢索另一個 Person(假設是女兒),仍然認為她們之間存在一個雙向關系,如清單 7 所示。
清單 7. 保持關系的真實性
@Test public void findJuliaAndHerMommy()
{
Person maggIE = (Person) db.get(
new Person("MaggIE", "Tate", Gender.FEMALE, 0, null)).next();
Person julia = (Person) db.get(
new Person("Julia", "Tate", Gender.FEMALE, 0, null)).next();
assertTrue(julia.getMother() == maggIE);
}
當然,您正是希望對象數據庫具有這樣的行為。還應注意,如果返回女兒對象的查詢的激活深度被設置得足夠低,那麼對 getMother() 的調用將返回 null,而不是實際的對象。這是因為 Person 中的 mother 字段是相對於被檢索的原本對象的另一個 “跳躍(hop)”。(請參閱 前一篇文章,了解更多關於激活深度的信息。)
更新和刪除
至此,您已經看到了 db4o 如何存儲和取出多個對象,但是對象數據庫如何處理更新和刪除呢?就像結構化對象一樣,多對象更新或刪除期間的很多工作都與管理更新深度有關,或者與級聯刪除有關。現在您可能已經注意到,結構化對象與集合之間有很多相似之處,所以其中某一種實體的特性也適用於另一種實體。如果將 ArrayList 看作 “另一種結構化對象”,而不是一個集合,就很好理解了。
所以,根據到目前為止您學到的東西,我應該可以更新數據庫中的某一個女孩。而且,為了更新這個對象,只需將她父母中的一個重新存儲到數據庫中,如清單 8 所示。
清單 8. 生日快樂,Kayla!
@Test public void kaylaHasABirthday()
{
Person maggIE = (Person) db.get(
new Person("MaggIE", "Tate", Gender.FEMALE, 0, null)).next();
Person kayla = (Person) db.get(
new Person("Kayla", "Tate", Gender.FEMALE, 0, null)).next();
kayla.setAge(kayla.getAge() + 1);
int kaylasNewAge = kayla.getAge();
db.set(maggIE);
db.close();
db = Db4o.openFile("persons.data");
kayla = (Person) db.get(new Person("Kayla", "Tate", Gender.FEMALE, 0, null)).next();
assert(kayla.getAge() == kaylasNewAge);
}
還記得嗎,在 前一篇文章 中,我必須顯式地關閉到數據庫的連接,以避免被誤診為重取已經位於工作內存中的對象。
對於多樣性關系中的對象,其刪除工作非常類似於上一篇文章介紹索的結構化對象的刪除工作。只需注意級聯刪除,因為它對這兩種對象可能都有影響。當執行級聯刪除時,將會從引用對象的每個地方徹底刪除對象。如果執行一個級聯刪除來從數據庫中刪除一個 Person,則那個 Person 的母親和父親在其 children 集合中突然有一個 null 引用,而不是有效的對象引用。
結束語
在很多方面,將數組和集合存儲到對象數據庫中並不總與存儲常規的結構化對象不同,只是要注意數組不能被直接查詢,而集合則可以。不管出於何種目的,這都意味著可以在建模時使用集合和數組,而不必等到持久引擎需要使用集合或數組時才使用它們。