db4o (database for objects) 是純 Java/.NET 的原生物件資料庫,無需 ORM 映射,直接持久化物件圖。核心優勢:零配置、ACID 事務、原生查詢 (Native Query) 提供 100% 型別安全與重構友善。本文涵蓋安裝、啟動模式、三大查詢 API 實戰與效能考量。
| 模式 | 啟動方式 | 適用場景 |
| 嵌入式 (單用戶) | ObjectContainer db = Db4oEmbedded.openFile(config, "data.yap"); | 桌面應用、嵌入式裝置、單元測試 |
| 客戶端/伺服器 | Server: ObjectServer server = Db4oClientServer.openServer(config, "data.yap", 8080); server.grantAccess("user","pwd"); Client: ObjectContainer db = Db4oClientServer.openClient(config, "host", 8080, "user", "pwd"); | 多行程/多機器共享資料、Java EE 環境 |
注意:ObjectContainer 非執行緒安全,多執行緒環境每執行緒一實例或使用連接池。
範例領域模型
package model;
import java.util.*;
public class Person {
private int id;
private String name;
private String city;
private List cars = new ArrayList<>();
// getter/setter/addCar 略
}
public class Car {
private int id;
private String licensePlate;
private Person owner; // 反向引用
// getter/setter 略
}
儲存物件圖
ObjectContainer db = Db4oEmbedded.openFile("cars.yap");
try {
Person p = new Person(); p.setId(1); p.setName("張三"); p.setCity("成都");
Car c = new Car(); c.setId(1); c.setLicensePlate("川A00001");
c.setOwner(p); p.addCar(c);
db.store(p); // 遞迴持久化關聯物件
db.commit();
} finally { db.close(); }
三種查詢方式實戰對比
1. QBE (Query By Example) — 最直觀、功能最受限
// 以原型物件為範本,非預設值欄位以 AND 組合
Car template = new Car();
template.setLicensePlate("川A00001");
List result = db.queryByExample(template); // 返回 List
for (Car car : result) {
System.out.println("車主: " + car.getOwner().getName());
}
- 優點:零學習曲線、程式碼極短。
- 缺點:無法表達 OR/NOT/範圍/排序;基本型別預設值 (0、false、null) 無法作為查詢條件;依賴反射效能較差。
2. SODA (Simple Object Database Access) — 低階、動態組裝
Query q = db.query();
q.constrain(Car.class);
q.descend("licensePlate").constrain("川A00001");
q.descend("owner").descend("city").constrain("成都"); // 關聯查詢
q.orderDescending("id"); // 排序
List result = q.execute();
- 優點:支援完整查詢運算子 (and/or/not/比較/排序/分頁);可動態組裝查詢 (如依使用者勾選條件);為 NQ 優化器的目標中介表示。
- 缺點:字串欄位名稱無編譯期檢查、重構易斷;API 冗長。
3. NQ (Native Query) — 現代首選、100% 型別安全
List result = db.query(new Predicate() {
@Override public boolean match(Car car) {
return "川A00001".equals(car.getLicensePlate())
&& "成都".equals(car.getOwner().getCity());
}
});
// 或 JDK 8 Lambda
List result = db.query(car -> "川A00001".equals(car.getLicensePlate())
&& "成都".equals(car.getOwner().getCity()));
- 核心優勢:
- 純 Java 語法,IDE 完整支援自動完成、重構、靜態分析。
- 編譯期即發現拼字錯誤、型別不符。
- 內部優化器嘗試轉換為 SODA 利用索引,若無法優化則退回實例化掃描 (需注意效能)。
- 分頁實作:
// 取得所有符合條件的 ID (不實例化完整物件)
List all = db.query(car -> car.getOwner().getCity().equals("成都"));
int pageSize = 20;
for (int page = 0; page < (all.size() + pageSize - 1) / pageSize; page++) {
List pageList = all.subList(page * pageSize, Math.min((page+1)*pageSize, all.size()));
for (Car car : pageList) {
System.out.println(car.getLicensePlate()); // 此時才觸發物件實例化
}
}
db4o 僅在 query() 返回列表時載入內部 ID,實際物件在首次存取時才反序列化,避免記憶體爆炸。
效能優化關鍵
- 索引:
ObjectContainer.ext().configure().objectClass(Car.class).objectField("licensePlate").indexed(true); 建立 B-Tree 索引,NQ/SODA 自動利用。
- 透明激活深度:
activationDepth 控制關聯物件自動載入層級,避免一次載入整張物件圖。
- 查詢優化器:db4o 8.0+ 內建 NQ→SODA 優化器,複雜邏輯 (如集合操作、算術運算) 可能無法優化,建議簡化 Predicate 或改用 SODA。
- 物件身分與 UUID:內部 ID (長整數) 適合內部引用;跨資料庫同步需使用
UUID 欄位。
常見陷阱與對策
| 問題 | 原因 | 解決 |
| 修改物件後查詢不生效 | 未呼叫 db.store(obj) 或 db.commit() | 寫入後必須 store+commit;或啟用 configuration.updateDepth |
| 刪除後關聯物件殘留 | db4o 不自動級聯刪除 | 手動遞迴刪除或使用 cascadeOnDelete 配置 |
| 多執行緒存取衝突 | ObjectContainer 非執行緒安全 | ThreadLocal 容器或連接池 (每執行緒一容器) |
| NQ 效能差 | 優化器無法轉換,退回全表掃描 | 加索引、簡化 Predicate、改用 SODA 指定索引路徑 |
結語
db4o 以「物件即資料」哲學消除阻抗失配。實務建議:預設使用 NQ (Lambda) 享受型別安全與重構友善;動態查詢組裝用 SODA;原型快速驗證用 QBE。配合索引策略與激活深度控制,可在嵌入式/客戶端-伺服器雙模式下支撐中小型專案的高效開發。
- 上一篇文章: 以 Flash Remoting 強化 J2EE 表示層:富客戶端架構新選擇
- 下一篇文章: WebLogic Workshop 開發模式:Java Control 封裝資料存取實現三層架構
Copyright ©
程式碼之家 All Rights Reserved