Apache Cayenne 是 Apache 旗下成熟開源 ORM 框架,擁有可視化建模工具 CayenneModeler、零配置啟動、逆向工程、遠程持久化等特性。本文以「購物網站」為例,手把手演示環境搭建、ER 建模、代碼生成、API 實戰,助你 30 分鐘上手 Cayenne。
cayenne-2.0.zip (含 Modeler)Java Project → Build Path → Add External JARs
選中 cayenne.jar、derby.jar 等所有 lib
cd cayenne/bin
./cayennemodeler.sh (Windows: cayennemodeler.bat)
| 概念 | 角色 | 類比 |
|---|---|---|
| DataDomain | 虛擬數據域,頂層容器 | 「租戶」或「業務域」 |
| DataNode | 物理數據源,含 JDBC 配置 | 「數據庫實例」 |
| DataMap | 對象-關係映射元數據 | 「ORM 映射文件集」 |
關係:DataDomain 1:N DataNode,DataNode 1:1 DataMap。本文演示 1:1:1 簡化模式。
cayenne-storecayenne-storeNode → 選 Embedded Derby → 配置 JDBC URLcayenne-storeMap → Java Package: com.example.cayennecayenne.xml 至 src/ (Classpath 根目錄)CLIENT_TB | client_id (PK), name, pwd, mail | 1:N → ORDER_TB |
| COMMODITY_TB | commodity_id (PK), name, price, qty | 1:N → ORDER_COMMODITY_TB |
| ORDER_TB | order_id (PK), client_id (FK), date | N:1 ← CLIENT_TB, 1:N → ORDER_COMMODITY_TB |
| ORDER_COMMODITY_TB | id (PK), order_id (FK), commodity_id (FK), qty | N:1 ← ORDER_TB, N:1 ← COMMODITY_TB |
Modeler 操作:Create DBEntity → 添加屬性 → Relationships 面板建立 FK 關係 → 設置 Delete Rule (CASCADE: 刪客戶級聯刪訂單)。
每個 DBEntity 面板點擊 「同步 DBEntity 到 ObjEntity」,自動生成 Java 實體類映射,關係轉為對象引用 (toOne/toMany)。
_Auto 基類Eclipse 刷新項目,即見 com.example.cayenne 包下實體類,Derby 數據庫文件生成於項目目錄。
DataContext | 會話入口,CRUD、事務、查詢執行 | DataContext.createDataContext() |
| SelectQuery | 對象查詢,支援 Qualifier、Prefetch、分頁 | new SelectQuery(ClientTB.class) |
| Expression | 條件構建,字符串解析 | Expression.fromString("name = $name") |
| DataObject | 持久化對象,屬性+關係 | context.newObject(ClientTB.class) |
| QueryChain | 批量非查詢操作 (SQLTemplate) | context.performNonSelectingQuery(chain) |
// 刪除客戶與商品,級聯刪除訂單
context.deleteObject(client);
context.deleteObject(commodity);
context.commitChanges();
// 讀取 loadData.sql 腳本批量執行
QueryChain chain = new QueryChain();
BufferedReader in = new BufferedReader(new InputStreamReader(
getClass().getResourceAsStream("/loadData.sql")));
String line;
while ((line = in.readLine()) != null) {
if (line.endsWith(";")) line = line.substring(0, line.length()-1);
chain.addQuery(new SQLTemplate(domain.getMap("cayenne-storeMap"), line));
}
context.performNonSelectingQuery(chain);
List clients = context.performQuery("allClients");
for (ClientTB c : clients) {
System.out.println(c.getClientName() + " " + c.getClientMail());
for (OrderTB o : c.getOrdersOwned()) {
StringBuilder sb = new StringBuilder();
for (OrderCommodityTB oc : o.getCommoditiesContained()) {
sb.append(oc.getCommoditiesBought().getComName()).append(", ");
}
System.out.println(" Order: " + sb + " on " + o.getOrderDate());
}
}
ClientTB buyer = context.performQuery(new SelectQuery(ClientTB.class,
Expression.fromString("clientName = 'YingChu'"))).get(0);
CommodityTB phone = ... get "Nokia Phone n95";
CommodityTB laptop = ... get "ThinkPad T61p";
OrderTB order = context.newObject(OrderTB.class);
order.setOrderDate(new Date());
order.setClientBelonged(buyer); // 關聯買家
OrderCommodityTB item1 = context.newObject(OrderCommodityTB.class);
item1.setComNumber(1);
item1.setBelongedToOrders(order);
item1.setCommoditiesBought(phone);
OrderCommodityTB item2 = context.newObject(OrderCommodityTB.class);
item2.setComNumber(1);
item2.setBelongedToOrders(order);
item2.setCommoditiesBought(laptop);
// 庫存扣減
phone.setCommodityNumber(phone.getCommodityNumber() - 1);
laptop.setCommodityNumber(laptop.getCommodityNumber() - 1);
context.commitChanges(); // 一次性提交事務
項目結構建議:
dao/
├── ClientDao.java
├── CommodityDao.java
├── OrderDao.java
└── CayenneStoreDao.java // 持有 DataContext、commitChanges() 等通用邏輯
所有 DAO 實現注入 CayenneStoreDao 獲取 DataContext,業務層調用 DAO,解耦持久化細節。
src/ 或 resources/,否則 DataContext.createDataContext() 拋 NullPointerExceptionaddPrefetch("orders") 參數必須與 ObjEntity Relationship 名稱完全一致,區分大小寫通過本文,你已掌握 Cayenne 的環境搭建、可視化建模、代碼生成、核心 API 及完整業務流程。下一步可深入學習:
CayenneModeler + 零 XML 配置 + 豐富企業級特性,讓 ORM 回歸「領域建模」本質。開始你的 Cayenne 之旅吧!