程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Cayenne,開源ORM盛宴中的另道佳肴,第1部分 - Apache Cayenne基本功能介紹

Cayenne,開源ORM盛宴中的另道佳肴,第1部分 - Apache Cayenne基本功能介紹

編輯:關於JAVA

何為 ORM

在正式介紹 Apache Cayenne 的功能之前,首先讓我們先來看一下 ORM 是什麼,我們為什麼需要 ORM。大家知道,持久化(persistence)的目的是為了將內存中的數據或對象保存到存儲設備上(如磁盤),其中主要的應用就是保存到關系型數據庫,或其他類型的數據庫中。而在一些大型的應用程序中,軟件設計者都會設計一個持久化層來專門做這樣的工作,包括持久化和反持久化(將磁盤上或者數據庫中的數據反持久化到內存中)。而 ORM,即對象關系映射,就是數據持久化層的一項重要技術。有了它,軟件設計和開發人員就可以更加關注業務邏輯,它會幫助將業務邏輯與數據庫操作邏輯分離,實現系統的松耦合關系,從而使得開發人員從繁雜的與有關數據庫操作的工作中解脫出來。可以說,一個健壯、優秀的 ORM 框架能夠在為我們節省開發時間的同時,還能夠提供高效,可靠的數據持久層代碼。

為何 Apache Cayenne

首先,Cayenne 是一個完全開源的基於 Apache License 的數據持久化框架。除了具有一般 ORM 工具所具有的特性外,它還具有很多非常好的特性,比如可以將一個或多個模式數據庫和 Java 對象綁定,可以對提交和回滾操作做原子化管理,可以對數據庫表做逆向工程並生成 Java 代碼,支持遠程對象持久化等。 不僅如此,對於 DBA 所關心的性能問題,Cayenne 同樣也有很好的支持,如 Cache 等等。而這些特性都可以通過它的一個非常易用的可視化工具 CayenneModeler 來配置完成。可以說,這降低了學習者的學習曲線,節省了開發時間。因此,我們將非常願意通過本篇 Apache Cayenne 基礎功能介紹和另一篇文章 Apache Cayenne 高級特性 向您介紹 Cayenne,如何使用 Cayenne,並希望在你學習過這兩篇文章後,Cayenne 能夠成為您開發企業應用數據庫持久層框架中的一個選擇。

配置環境和建模

工具下載

可以從 Apache Cayenne 網站上下載穩定版的 Cayenne2.0 安裝包。開發工具,我們選擇 Eclipse 3.4。因為 Cayenne 是一個數據持久層框架,所以我們還需要一個數據庫。這裡,我們選擇同樣開源的且輕量級的 Derby 數據庫。

搭建環境

首先,在 Eclipse 中創建一個 Java 工程。然後,將運行 Cayenne 和 Derby 所需要的庫文件放到 build path 中。

圖 1. 導入 Cayenne 和 Derby 所需的 lib 文件

圖 2. 將 lib 文件放到 build path 中

完成後,我們再回到 Cayenne 的解壓目錄下,在 bin 目錄中打開 CayenneModeler。

圖 3. 打開 CayenneModeler

我們對於 Cayenne 的了解將從這裡開始。打開後,我們新建一個工程,

圖 4. CayenneModeler 主界面

進入到 Cayenne Modeler 的主界面。在導航條上有三個主要的按鈕,分別是 Create DataDomain, Create DataNode, Create DataMap。其中,DataDomain, DataNode 和 DataMap 是一個 Cayenne 工程中三個重要的概念。

圖 5. 導航條上的 DataDomain, DataNode 和 DataMap

圖 6 說明了這三個概念間的關系

圖 6. DataDomain, DataNode 和 DataMap 的關系

在一個 Cayenne 應用中可以定義多個 Data Domain,同時,作為虛擬數據源的 Domains 也可以包含多個實體數據源(Data Node)。而不同的數據源又可以對應不同的數據庫 Schema 和 Java 對象的映射關系,即 Data Map。因此,Cayenne 可以方便靈活的實現應用程序調用不同的數據庫操作,而具體細節 Cayenne 都幫我們進行了封裝和處理。本文中,為了便於讀者理解,我們僅先考慮一個 Data Domain 包含一個 Data Node,以及處理一個 Data Map 的情況。

在 DataDomain Configuration 視圖中輸入一個名字,如 cayenne-store。接下來,再創建一個 DataNode. 選中根節點 cayenne-store,點擊創建 DataNode 按鈕。所謂 DataNode Configuration 就是對你所要用的一個實體數據庫信息的配置或數據源的配置。這裡,我們選擇 Embedded Derby。

圖 7. 配置 DataDomain 和 DataNode

在配置完 JDBC Driver 信息之後,然後先將我們所做的配置信息保存到我們開始建立的一個 Eclipse 工程 Cayenne-store 下面。這裡,需要注意的是,Cayenne 的映射文件 cayenne.xml 必須放到程序運行時的 CLASSPATH 中,所以,我們可以將它保存放到所建立的 Java 工程 src 目錄下面。

圖 8. 保存 Cayenne 主配置文件

這時,回到 Eclipse 工程裡面,就會看到這個應用的主配置文件 cayenne.xml 以及相應的 JDBC Driver 配置文件了。

圖 9. 生成 Cayenne.xml

到此,開發環境的搭建就算完成了。下面,我們將通過一個簡單的例子帶您逐步的了解 Cayenne,看看它如何幫助我們產生數據持久層的 Java 代碼以及數據庫表。

建模

這個例子很簡單,我們以一個購物網站為背景進行數據建模。可以想象,這裡我們需要如下四張表,客戶信息表 ClientTB,商品信息表 CommodityTB,客戶購買商品的訂單信息表 OrderTB,還有一個保存訂單和訂單所包含商品的訂單商品表 OrderCommodityTB。它們之間的關系可用如下 E-R 圖表示,

圖 9. E-R 圖表

為了在 Cayenne Modeler 中創建 DBEntity 模型,需要先建立一個 DataMap。在 cayenne-storeNode DataNode 下創建 DataMap。如圖 10。在 Java Package 輸入框中,你可以定義將要存儲持久化代碼的包名。其余的,可以暫時留空。

圖 10. 建立 DataMap

接下來,基於這張 E-R 圖,我們就可以在 Cayenne Modeler 中建立 DBEntity 模型了。建立 DBEntity 模型的過程與在一個數據庫管理客戶端建立表的過程類似。即,需要指定表的名字、字段及字段大小等信息。對表 ClientDB 和 CommodityTB 等表的建模結果如下圖所示,

圖 11. 建立 ClientTB 等 DBEntity

這裡,需要注意的一點是表間關系的建立。 以 clientTB 和 orderTB 為例,因為 clientTB 和 orderTB 是一對多的關系,所以在 Cayenne Modeler 中需要在 Relationships 面板上建立表間聯合關系。

圖 12. 建立 ClientTB 和 OrderTB 的表間關系

同時,還需要建立從 orderTB 到 clientTB 的反轉關系,即多對一的關系。

圖 13. 建立 OrderTB 到 ClientTB 的反轉關系

Cayenne modeler 會自動在 OrderTB 的 Relationships 面板中產生一個表間關系

圖 14. OrderTB 的 Relationships 面板

Delete Rule

在 Cayenne 中,有四種 Delete Rule。‘ No Action ’,即一個 DataObject 被刪除後,與它有關的其它 Object 不會有變化。‘ Nullify Rule ’ , 即一旦 Source Object 被刪除,所有指向它的 Target Object 將會被置成 Null。‘ Cascade Rule ’ , 即一旦 Source Object 被刪除,所有指向它的 Target Object 將會被刪除。‘ Deny Rule ’ , 即當嘗試刪除 Source Object 而這時 Target Object 至少有一個實例存在,則會拋出 RunTime Exception。

這裡,由於篇幅關系,就不將建立其他表和表間關系的步驟列出來了。讀者可以依照上述例子將其他 DBEntity 及關系建立起來或者參考附錄中的源代碼示例。然後,需要我們做的就是產生 ObjEntity,即數據庫表到 Java 對象之間的映射。也很簡單,在每個數據庫表的 Entity 面板上點擊“同步 DBEntity 到 ObjEntity”按鈕即可。只是,要注意在 Relationships 中的 Delete Rule。例如,一旦某個 Client 從表中被刪除後,他的相關 Order 記錄也需要被級聯刪除。

圖 15. Modeler 產生的 ObjectEnties

到這,建立模型的過程就告一段落了。接下來,我們讓 Modeler 來產生 Database Schema 和 Java Classes. 這可以通過在 Tools Menu 下的 Generate Classes 和 Generate Database Schema 兩個按鈕來完成。

圖 16. 生成 Java 代碼和 Database Schema

完成之後,你就會發現在所建的 cayenne-store 工程下面生成了 Java 代碼。

圖 17. Eclipse 中生成的 Java 代碼

以及 cayenneStore 數據庫文件目錄。

圖 18. 文件系統中生成的 Derby 數據庫

到此,您可能已經品嘗到了 Cayenne 所帶來的快捷,比如功能豐富的建模工具 Cayenne Modeler,自動生成 Java 代碼等。當然,Cayenne Modeler 還有許多功能和特性。由於篇幅關系,我們就不一一介紹了。

Cayenne API

那麼,在擁有了 Cayenne 為我們自動產生的代碼之後,我們還需要做哪些工作呢?如何應用這些產生的代碼呢?接下來,讓我們看看 Cayenne 所提供的主要的 API。

DataContext

DataContext 類是一個取得 Cayenne 所提供功能的一個用戶入口 , 它搭建了一個用戶與數據庫之間的會話通道,用戶通過它來實現與數據庫相關的各種操作(如 CRUD)。不同的用戶可以建立自己的 DataContext。這裡,值得注意的是,DataContext 會主動隔離處在不同 context 中的對象(除非是共享的)。所以,在一個會話中要盡量保持一個 DataContext 實例來操作 DataObjects。

清單 1. 創建一個 DataContext 示例

import org.apache.cayenne.access.DataContext;
...
DataContext context = DataContext.createDataContext();

Query

用戶對數據庫表的不同操作都可以看成是查詢。這裡,查詢可以分為選擇性和非選擇性。前者可以看成是對數據庫表的讀操作,而後者則可看出是對數據庫表的插入、刪除、更新操作。在 Cayenne 中,有多種 Query 對象可被用戶調用。如最常用的 SelectQuery, SQLTemplate Query, ProcedureQuery。 而構建一個 Query 也比較簡單。

清單 2. 創建 Query 示例

import org.apache.cayenne.query.SelectQuery;
...
// this is a valid Cayenne query that would allow to fetch
// all records from the ClientTB table as ClientTB objects
SelectQuery query = new SelectQuery(ClientTB.class);

// create a qualifier with one parameter: "clientID"
Expression qual = Expression.fromString("clientID = $clientID);

// build a query prototype of a query - simply another select query 
SelectQuery proto = new SelectQuery(ClientTB.class, qual);

Context.performQuery(proto);

當然,也可以在 Cayenne Modeler 中構建 Query。

圖 19. 在 Modeler 中創建 Query

然後在代碼中調用這個 Query

清單 3. 調用並執行在 Modeler 中創建的 Query

Map param = new HashMap();
param.put("manuName","Nokia Beijing");
// 執行“getCommodities” Query 並不刷新 Cache List
context.performQuery(“getCommodities”, param, false);

DataObject

一個 ORM 框架 , 它的一端連著 Database, 一端連著 Java 對象。因此,這裡的 DataObject 可以理解成一個 DB Record 在內存中的對象的映射。它由屬性和對象關系組成。在 ORM 中,對數據庫表中一行記錄的修改轉變為對一個 DataObject 屬性或關系的修改。其余的操作,包括數據檢查,生成 SQL 語句、事務控制、回滾等交由 ORM 框架來完成。

DataObject 可以通過 Query 執行產生,也可以通過用戶自己創建產生。

清單 4. 執行 Query 返回 DataObjects 並修改其屬性

List <ClientTB> clients = context.performQuery(“allClients”,true);
// 修改 DataObject 的屬性 
ClientTB clientTB = clients.get(0);
clientTB.setClientMail(“[email protected]”);
// 提交修改
context.commitChanges();

一個 Demo

為了讓讀者更好的體會 ORM 以及 Cayenne API, 我們來看一個 Demo。在這個 Demo 中,首先會裝載一個 SQL 腳本並初始化數據庫表中的基礎數據。然後查詢所有 Client 信息。最後,模擬一個 client 購買兩個商品並生成 Order 的過程。另外,考慮到代碼的實用性和可讀性,我們創建了一些 DAO 類將 ObjEntity 以及數據庫操作做進一步的封裝。代碼的主要文件結構如圖 19 所示。讀者也可以在源代碼中仔細查看它們的類間關系。

圖 19. 工程文件結構

清空和裝載數據

由於在生成 ObjEntity 時設置了對象間的 Delete Rule, 所以我們可以直接刪除 ClientTB 和 CommodityTB 中的數據即可。OrderTB 和 OrderCommodityTB 中的數據將會被級聯刪除。

清單 4. 清空數據庫表的代碼

ClientDao clientDao = (ClientDao)DaoManager.getInstance().getDao(ClientDao.class);
CommodityDao comDao = (CommodityDao) DaoManager.getInstance().getDao(CommodityDao.class);
CayenneStoreDao csd = (CayenneStoreDao)DaoManager.getInstance()
     .getDao(CayenneStoreDao.class);
List<ClientTB> clients = clientDao.getClients();
for (ClientTB c : clients){
   csd.getDataContext().deleteObject(c);
}
SelectQuery queryCom = new SelectQuery(CommodityTB.class);
List<CommodityTB> coms = csd.getDataContext().performQuery(queryCom);
for (CommodityTB cd : coms){
   csd.getDataContext().deleteObject(cd);
}
csd.commitChanges();

裝載原始數據時,我們會通過讀取一個 SQL 腳本並生成一個 QueryChain。然後調用 DataContext 執行。

清單 5. 裝載數據

QueryChain qc = new QueryChain();
BufferedReader in = new BufferedReader(new 
   InputStreamReaderDBDataHelper.class.getResourceAsStream("/loadData.sql")));
String line ;
try {
while ((line = in.readLine())!=null){
   if (line.endsWith(";")){
     line = line.substring(0,line.length()-1);
   }
   qc.addQuery(new SQLTemplate(getDataDomain().getMap("cayenne-storeMap"), line));
}
} catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
}
CayenneStoreDao csd = (CayenneStoreDao)DaoManager.getInstance()
     .getDao(CayenneStoreDao.class);
csd.getDataContext().performNonSelectingQuery(qc);

查詢 Client 信息

查詢客戶信息的例子中,我們首先查詢出所有 Client 的信息,包括 ClientID,ClientName,ClientMail。然後查詢指定 ClientName 的 Client 其所擁有的訂單信息。

清單 6. 查詢客戶信息

ClientDao clientDao = (ClientDao)DaoManager.getInstance().getDao(ClientDao.class);
List<ClientTB> clients = clientDao.getClients();
for (ClientTB ct: clients){
   System.out.println("client : "+ct.getObjectId()+
     " "+ct.getClientName()+" "+ct.getClientPWD()+" "+ct.getClientMail());
}
String clientName = "YingChu";
ClientTB ctt = clientDao.getClient(clientName);
System.out.println("client YingChu 's id , pwd, mail "+ctt.getObjectId()+
     " "+ctt.getClientPWD()+" "+ctt.getClientMail());
List<OrderTB> orders = ctt.getOrdersOwned();
if (orders.size()==0){
   System.out.println("The client "+clientName+" doesn't sign any order");
}else{
   for (OrderTB order: orders){
     List<OrderCommodityTB> orderedComs = order.getCommoditiesContained();
     StringBuffer sb = new StringBuffer();
     for (OrderCommodityTB com: orderedComs){
       sb.append(com.getCommoditiesBought().getComName());
       sb.append(" , ");
     }
     if (sb.length()>0) sb.delete(sb.length()-3, sb.length());
     System.out.println("The client "+clientName+
       " has ordered the following commodities : "+
       sb.toString()+" on "+order.getOrderDate().toString());
   }
}

模擬客戶購買商品生成訂單的過程

在這個例子中,首先指定一個 Client。然後指定購買的商品。之後,就是在 OrderTB 和 OrderCommodityTB 中創建記錄的過程。

清單 7. 模擬客戶購買商品生成訂單的代碼

//Assume current client is YingChu 
ClientDao clientDao = (ClientDao)DaoManager.getInstance().getDao(ClientDao.class);
String clientName = "YingChu";
ClientTB ctt = clientDao.getClient(clientName);

//Assume two commodities are chosen, two is Nokia Phone n95, one is Thinkpad t61p
CommodityDao comDao = (CommodityDao)DaoManager.getInstance().getDao(CommodityDao.class);
CommodityTB nokiacom = comDao.getCommodity("Nokia Phone n95");
CommodityTB thinkPad = comDao.getCommodity("ThinkPad T61p");

//Generate an order record
CayenneStoreDao csd=(CayenneStoreDao)DaoManager.getInstance()
   .getDao(CayenneStoreDao.class);
OrderTB order = (OrderTB)csd.getDataContext().createAndRegisterNewObject(OrderTB.class);
Date dd = Calendar.getInstance().getTime();
order.setOrderDate(dd);

//Add the generated order into the buying list of current client
order.setClientBelonged(ctt);

//Generate two records in OrderCommodityTB
OrderCommodityTB OCNokia = (OrderCommodityTB)csd.getDataContext()
   .createAndRegisterNewObject(OrderCommodityTB.class);
OCNokia.setComNumber(1);
OrderCommodityTB OCThinkpad = (OrderCommodityTB)csd.getDataContext()
   .createAndRegisterNewObject(OrderCommodityTB.class);
OCThinkpad.setComNumber(1);

//Create the relationship between OrderTB and OrderCommodityTB
OCNokia.setBelongedToOrders(order);
OCThinkpad.setBelongedToOrders(order);

//Create the relationship between CommodityTB and OrderCommodityTB
OCNokia.setCommoditiesBought(nokiacom);
OCThinkpad.setCommoditiesBought(thinkPad);

//Update the quantity number in CommodityTB
nokiacom.updateCommodityNumber(1);
thinkPad.updateCommodityNumber(1);

//Commit changes
csd.commitChanges();

//Check the result of purchase
searchClients ();

結束語

本文主要介紹了 Apache Cayenne 的一些基礎特性與應用,包括如何搭建環境、建立模型以及生成 Database Schema 和 Java 代碼。最後,本文還通過一個 Demo 介紹了如何使用 Cayenne API 來完成一個簡單的數據庫操作應用。在第二部分中,我們將主要介紹與 Cayenne 有關的高級特性。包括 Remote Object Persistence 遠程訪問技術和數據庫查詢優化等主題。

本文配套源碼

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