程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 面向Java開發人員db4o指南:數組和集合

面向Java開發人員db4o指南:數組和集合

編輯:關於JAVA

處理多樣性關系

舒適的家庭生活會導致一個或更多 “小人兒” 降臨到這個家庭。但是,在增加小孩到家庭中之前,先確保 Person 真正有地方可住。給他們一個工作場所,或者還有一個很好的夏日度假屋。一個 Address 類型應該可以解決所有這三個地方。

清單 1. 添加一個 Address 類型到 Person 類中

package com.tedneward.model;
public class Address
{
public Address()
{
}
public Address(String street, String city, String state, String zip)
{
this.street = street; this.city = city;
this.state = state; this.zip = zip;
}
public String toString()
{
return "[Address: " +
"street=" + street + " " +
"city=" + city + " " +
"state=" + state + " " +
"zip=" + zip + "]";
}
public int hashCode()
{
return street.hashCode() & city.hashCode() &
state.hashCode() & zip.hashCode();
}
public boolean equals(Object obj)
{
if (obj == this)
return this;
if (obj instanceof Address)
{
Address rhs = (Address)obj;
return (this.street.equals(rhs.street) &&
this.city.equals(rhs.city) &&
this.state.equals(rhs.state) &&
this.zip.equals(rhs.zip));
}
else
return false;
}
public String getStreet() { return this.street; }
public void setStreet(String value) { this.street = value; }
public String getCity() { return this.city; }
public void setCity(String value) { this.city = value; }
public String getState() { return this.state; }
public void setState(String value) { this.state = value; }
public String getZip() { return this.zip; }
public void setZip(String value) { this.zip = value; }
private String street;
private String city;
private String state;
private String zip;
}

可以看到,Address 只是一個簡單的數據對象。將它添加到 Person 類中意味著 Person 將有一個名為 addresses 的 Address 數組作為字段。第一個地址是家庭住址,第二個是工作地址,第三個(如果不為 null 的話)是度假屋地址。當然,這些都被設置為 protected,以便將來通過方法來封裝。

完成這些設置後,現在可以增強 Person 類,使之支持小孩,所以為 Person 定義一個新字段:一個 Person ArrayList,它同樣也有一些相關的方法,以便進行適當的封裝。

接下來,由於大多數小孩都有父母,還將添加兩個字段來表示母親和父親,並增加適當的 accessor/mutator 方法。將為 Person 類增加一個新的方法,使之可以創建一個新的 Person,這個方法有一個貼切的名稱,即 haveBaby。此外還增加一些業務規則,以支持生小孩的生物學需求,並將這個新的小 Person 添加到為母親和父親字段創建的 children ArrayList 中。做完這些之後,再將這個嬰兒返回給調用者。

清單 2 顯示,新定義的 Person 類可以處理這種多樣性關系。

清單 2. 定義為多樣性關系的家庭生活

package com.tedneward.model;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Person
{
public Person()
{ }
public Person(String firstName, String lastName, Gender gender, int age, Mood mood)
{
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.age = age;
this.mood = mood;
}
public String getFirstName() { return firstName; }
public void setFirstName(String value) { firstName = value; }
public String getLastName() { return lastName; }
public void setLastName(String value) { lastName = value; }
public Gender getGender() { return gender; }
public int getAge() { return age; }
public void setAge(int value) { age = value; }
public Mood getMood() { return mood; }
public void setMood(Mood value) { mood = value; }
public Person getSpouse() { return spouse; }
public void setSpouse(Person value) {
// A few business rules
if (spouse != null)
throw new IllegalArgumentException("Already married!");
if (value.getSpouse() != null && value.getSpouse() != this)
throw new IllegalArgumentException("Already married!");
spouse = value;
// Highly sexist business rule
if (gender == Gender.FEMALE)
this.setLastName(value.getLastName());
// Make marriage reflexive, if it's not already set that way
if (value.getSpouse() != this)
value.setSpouse(this);
}
public Address getHomeAddress() { return addresses[0]; }
public void setHomeAddress(Address value) { addresses[0] = value; }
public Address getWorkAddress() { return addresses[1]; }
public void setWorkAddress(Address value) { addresses[1] = value; }
public Address getVacationAddress() { return addresses[2]; }
public void setVacationAddress(Address value) { addresses[2] = value; }
public Iterator<Person> getChildren() { return children.iterator(); }
public Person haveBaby(String name, Gender gender) {
// Business rule
if (this.gender.equals(Gender.MALE))
throw new UnsupportedOperationException("Biological impossibility!");
// Another highly objectionable business rule
if (getSpouse() == null)
throw new UnsupportedOperationException("Ethical impossibility!");
// Welcome to the world, little one!
Person child = new Person(name, this.lastName, gender, 0, Mood.CRANKY);
// Well, wouldn't YOU be cranky if you'd just been pushed out of
// a nice warm place?!?
// These are your parents...
child.father = this.getSpouse();
child.mother = this;
// ... and you're their new baby.
// (Everybody say "Awwww....")
children.add(child);
this.getSpouse().children.add(child);
return child;
}
public String toString()
{
return
"[Person: " +
"firstName = " + firstName + " " +
"lastName = " + lastName + " " +
"gender = " + gender + " " +
"age = " + age + " " +
"mood = " + mood + " " +
(spouse != null ? "spouse = " + spouse.getFirstName() + " " : "") +
"]";
}
public boolean equals(Object rhs)
{
if (rhs == this)
return true;
if (!(rhs instanceof Person))
return false;
Person other = (Person)rhs;
return (this.firstName.equals(other.firstName) &&
this.lastName.equals(other.lastName) &&
this.gender.equals(other.gender) &&
this.age == other.age);
}
private String firstName;
private String lastName;
private Gender gender;
private int age;
private Mood mood;
private Person spouse;
private Address[] addresses = new Address[3];
private List<Person> children = new ArrayList<Person>();
private Person mother;
private Person father;
}

即使包括所有這些代碼,清單 2 提供的家庭關系模型還是過於簡單。在這個層次結構中的某些地方,必須處理那些 null 值。但是,在 db4o 中,那個問題更應該在對象建模中解決,而不是在對象操作中解決。所以現在我可以放心地忽略它。

填充和測試對象模型

對於清單 2 中的 Person 類,需要重點注意的是,如果以關系的方式,使用父與子之間分層的、循環的引用來建模,那肯定會比較笨拙。通過一個實例化的對象模型可以更清楚地看到我所談到的復雜性,所以我將編寫一個探察測試來實例化 Person 類。注意,清單 3 中省略了 JUnit 支架(scaffolding)。

清單 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 引用,而不是有效的對象引用。

結束語

在很多方面,將數組和集合存儲到對象數據庫中並不總與存儲常規的結構化對象不同,只是要注意數組不能被直接查詢,而集合則可以。不管出於何種目的,這都意味著可以在建模時使用集合和數組,而不必等到持久引擎需要使用集合或數組時才使用它們。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved