關係型資料庫需透過連接表建模一對多、多對多,而在 db4o 這類原生對象數據庫中,直接以 Java 集合 (List/Set) 或陣列 作為物件欄位即可自然表達多樣性關係,無需額外映射配置。本文以「家庭成員」領域模型為例,示範陣列與 ArrayList 的存儲、查詢、激活深度控制、級聯更新與刪除完整流程。
public class Address { // 簡單值物件
String street, city, state, zip;
// getter/setter、equals/hashCode/toString
}
public class Person {
String firstName, lastName;
Gender gender; int age; Mood mood;
Person spouse; // 一對一
Address[] addresses = new Address[3]; // 固定陣列:家庭/工作/度假
List<Person> children = new ArrayList<>(); // 一對多集合
Person mother, father; // 反向引用形成循環圖
// 業務方法:haveBaby() 同時維護雙向關係
public Person haveBaby(String name, Gender gender) {
if (this.gender == Gender.MALE) throw new UnsupportedOperationException("Biological impossibility!");
if (spouse == null) throw new UnsupportedOperationException("Ethical impossibility!");
Person child = new Person(name, this.lastName, gender, 0, Mood.CRANKY);
child.father = this.spouse; child.mother = this;
this.children.add(child); this.spouse.children.add(child);
return child;
}
// getter/setter、equals/hashCode/toString
}
ObjectContainer 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);
// 設定地址、生小孩...
db.store(bruce); // 僅存儲 bruce,可達物件 (maggie、children、addresses) 自動遞歸存儲
db.commit();
db4o 透過「可達性」判斷持久化邊界:從根物件走訪所有非 transient 字段,自動處理循環引用與共享引用。
List<Person> males = db.query(new Predicate<Person>() {
public boolean match(Person p) {
return "Tate".equals(p.getLastName()) && p.getGender() == Gender.MALE;
}
});
Person bruce = males.get(0);
assert bruce.getHomeAddress().equals(new Address("5 Maple Drive", "Austin", "TX", "12345"));
int n = 0;
for (Person child : bruce.getChildren()) { // 返回 Iterator
if (n==0) assert "Kayla".equals(child.getFirstName());
if (n==1) assert "Julia".equals(child.getFirstName());
n++;
}
List<Person> girls = db.query(new Predicate<Person>() {
public boolean match(Person p) { return p.getGender() == Gender.FEMALE; }
});
// girls 將包含 maggie、kayla、julia —— 集合內 Person 為一級實體,不僅屬於父親
Person maggie = db.query(...).next(); // 查詢母親
Person julia = db.query(...).next(); // 查詢女兒
assert julia.getMother() == maggie; // 同一物件實例,雙向引用完好
激活深度:若查詢配置的激活深度不足以到達mother欄位 (需再跳一層),則getMother()返回null。需根據導航路徑設定足夠深度。
Person maggie = db.query(...).next();
Person kayla = db.query(...).next();
kayla.setAge(kayla.getAge() + 1); // 修改子物件
db.store(maggie); // 存儲根物件,級聯更新可達變更
db.commit();
kayla 未被激活 (深度不足),修改其欄位不會持久化。確保更新前物件圖已充分激活,或使用 db.ext().activate(obj, depth)。db.delete(kayla); // 級聯刪除:從所有引用處移除
db.commit();
後果:bruce.children、maggie.children 中對應元素變為 null (或被移除,視 db4o 版本配置),導致集合出現「空洞」。
最佳實踐:軟刪除 (標記deleted=true) + 定期清理,或在應用層手動維護雙向集合一致性後再delete根物件。
| 特性 | 陣列 (Address[]) | 集合 (ArrayList<Person>) |
|---|---|---|
| 查詢陣列本身 | 不可直接查詢 | 可作為獨立實體查詢 (但罕用) |
| 元素獨立身份 | 值物件通常無獨立 ID | 實體物件擁有獨立 ID,可被獨立查詢 |
| 長度固定/動態 | 固定長度 | 動態增減 |
| 建模適用場景 | 固定槽位的值物件 (地址、電話) | 動態一對多實體關係 (子女、訂單項目) |
db4o 讓 Java 開發者以純物件思維處理多樣性關係:集合即關係,存儲即圖持久化,查詢即物件導航。核心關鍵在於:
store(root) 足矣。若專案需跨平台、嵌入式、零配置、原生物件圖持久化,db4o 仍是極具吸引力的選擇;若需複雜報表、SQL 生態整合,傳統 ORM (Hibernate/JPA) 更適合。