Cayenne,開源 ORM 盛宴中的另道佳肴,第 2 部分: 再品小辣椒 - Apache Cayenne 的高級特
使用 Cayenne Remote Object Persistence(Cayenne ROP)
ROP(Remote Object Persistence)遠程對象持久化是 Cayenne 的一個獨有特性,它通過使用 Web Service 技術提供了一種輕量級的遠程對象持久化和查詢功能。通過使用這一功能,客戶端程序可以使用與訪問本地數據庫類似的 Cayenne API 來訪問一個遠程的數據庫(遠程數據庫可以是任意的能夠通過網絡連接訪問的數據庫)。圖 1 是 Cayenne Remote Object Persistence 的原理結構圖。當一個客戶端程序執行對遠程數據對象的訪問操作時,這個客戶端程序中的 Cayenne Connection 對象會自動調用運行在遠程服務器中的 Cayenne web service(CWS),CWS 會使用真正連接數據庫的 Cayenne DataContext 執行相應的數據庫操作,并將操作結果通過 Web Service 返回給客戶端程序。
圖 1. Cayenne Remote Object Persistence 的原理結構圖

Cayenne ROP 的詳細概念和使用方法介紹,請參考 參考資源中的"Cayenne Remote Object Persistence(ROP) 使用指南"。
Cayenne Web Service(CWS) 的概念和配置
CWS 是 Cayenne Remote Object Persistence 的核心組件,它是一個使用 Hessian 作為 Web Service 引擎的常規 Java Web 應用程序。在這個應用程序中包含了 Cayenne 映射文件和持久化對象使用的 java 類。 當將這個程序部署在支持 servlet 功能的 JavaEE 應用服務器中后 , 即可通過 Hessian 提供的 Web Service 支持使用遠程對象持久化功能。
CWS 的配置非常簡單 , 只需將 Cayenne 映射文件和持久化對象的 java 類放置在 CWS Web 程序的 java classPath 上 , 并且在 web.xml 中配置一個 HessianServlet 即可。清單 1 是一個 CWS Web 應用程序的典型文件結構。
清單 1. CWS Web 應用程序典型文件結構
CayenneCWSServer/ WEB-INF/ web.xml lib/ cayenne.jar hessian-3.0.13.jar databaseb-driver.jar otherJars...... classes/ cayenne.xml DataMap.map.xml DataNode.driver.xml // 服務器端持久化對象 com/cn/ibm/PersistentObject.class com/cn/ibm/auto/_PersistentObject.class ...... // 客戶端遠程訪問持久化對象 com/cn/ibm/client/PersistentObject.class com/cn/ibm/client/auto/_PersistentObject.class ...... |
清單 2 是 CWS Web 應用程序所需的最小 web.xml 配置。關于 Hessian 的更多詳細信息 , 請參閱 參考資源 中的 "Hessian 二進制對象 Web Service 的介紹"。
清單 2. CWS Web 應用程序 web.xml 最小配置
<web-app> <servlet> <servlet-name>cayenne</servlet-name> <servlet-class> org.apache.cayenne.remote.hessian.service.HessianServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>cayenne</servlet-name> <url-pattern>/cayenne</url-pattern> </servlet-mapping> </web-app> |
Remote Object Persistence 的服務器端設置
為了使用 ROP 功能 , 需要在真正使用 cayenne 連接數據庫的服務器中啟動 Cayenne Web Service 程序 , 在這個程序可以訪問到的 java classPath 中 , 需要放置 Cayenne 的配置文件 (cayenne.xml), 對象 - 數據庫映射文件 (DataMap.map.xml) 和數據庫連接配置文件 (DataNode.driver.xml)。此外還必須將供 Server 端和 Client 端使用的對象持久化 Java 類放置在類路徑中。 Server 端對象持久化 Java 類即是本系列文章第一部分中介紹過的標準 Cayenne 數據庫映射 Java 對象 , 而 Client 端對象持久化 Java 類是一種特殊的專門供 ROP 客戶端使用的 Cayenne 數據庫映射 Java 對象。它的生成方法和標準的 Cayenne 數據庫映射 Java 對象類似。首先如圖 2 所示 , 需要在 Cayenne Modeler 中數據對象的定義面板的 Client Class Defaults區域選中 Allow Client Entities復選框。
圖 2. Cayenne Modeler 數據對象定義面板

選中后 Cayenne 默認會在標準數據庫映射 Java 對象包的子目錄"client"中生成 Client 端對象持久化 Java 類。之后在 CayenneModeler 的程序主菜單中選擇 Tools-->Generate Classes, 并按照圖 3 所示 , 在 Code Generation 面板中選擇 Client Persistent Objects作為類型來生成數據庫映射 Java 對象即可。
圖 3. CayenneModeler Code Generation 面板

Remote Object Persistence 的客戶端編程
在一個客戶端程序中使用 Remote Object Persistence 來訪問遠程數據庫和使用 Cayenne 訪問本地數據庫的方法十分相似 , 只有以下 3 點需要注意 :
- ROP 客戶端程序中 Cayenne 必須使用 Client端對象持久化 Java 類來執行遠程數據庫操作。
- ROP 客戶端程序中使用 cayenne-client-nodeps.jar作為 Cayenne 類庫 , 除此之外還必須將 hessian-3.0.13.jar和 commons-collections,commons-lang以及 commons-logging幾個類庫加入類路徑中。
- ROP 客戶端程序中需要使用 org.apache.cayenne.ObjectContext 來作為數據操作的上下文對象
清單 3 是一段 ROP 客戶端程序使用 ROP 訪問遠程數據庫的代碼 , 其中的"http://localhost:8090/CayenneCWS/cayenne"為 CWS Web 服務運行的監聽地址。更多的詳細配置請參考本文 下載部分中的 CayenneCWSClient.zip eclipse 樣例工程。
清單 3. 使用 ROP 訪問遠程數據庫
org.apache.cayenne.remote.ClientConnection connection
= new HessianConnection("http://localhost:8090/CayenneCWS/cayenne");
org.apache.cayenne.DataChannel channel = new ClientChannel(connection);
org.apache.cayenne.ObjectContext context = new CayenneContext(channel);
org.apache.cayenne.query.SelectQuery objectSelectQuery
= new SelectQuery(com.cn.ibm.client.PersistentObject.class);
java.util.List objectDataList = context.performQuery(objectSelectQuery);
System.out.println(objectDataList.get(0));
context.commitChanges();
|
本文 下載部分中的兩個樣例 eclipse 工程 CayenneCWSClient 和 CayenneCWSServer 分別是演示 Cayenne ROP 功能的客戶端和服務器端程序。請按照以下的步驟運行示例程序。
- 在 CayenneCWSServer 工程中有一個嵌入式的 Derby 數據庫和一個 Jetty Servlet 服務器。將該項目導入 eclipse 開發環境后 , 運行類 CWSServerLauncher 即可啟動 Servlet 服務器并在地址 http://{hostname}:8090/CayenneCWS/cayenne 上啟動 CWS 服務。清單 4 是服務器端程序正常啟動后的 console 信息。
- 當 CayenneCWSServer 中的 CWS 程序啟動后 , 就可以通過 Cayenne ROP 客戶端程序遠程訪問服務器端程序中內置的 Derby 數據庫。當客戶端程序與服務器端程序運行在同一臺主機中時 , 客戶端程序可以使用地址 http://localhost:8090/CayenneCWS/cayenne 或 http://127.0.0.1:8090/CayenneCWS/cayenne 訪問 CWS。當客戶端與服務器端程序運行在不同的主機中時 , 請將訪問地址中的 hostname 改為服務器端程序所在機器的 IP 地址。例如清單 5 中的代碼將會建立一個指向運行在 IP 為 192.168.0.2的主機中的服務器端程序的 CWS 連接。
清單 4. 服務器端程序正常啟動 console 信息
2009-08-13 11:50:27.366::INFO: Logging to STDERR via org.mortbay.log.StdErrLog 2009-08-13 11:50:27.460::INFO: jetty-6.1.9 2009-08-13 11:50:27.647::INFO: NO JSP Support for /CayenneCWS, did not find org.apache.jasper.servlet.JspServlet 2009-08-13 11:50:31.286::INFO: Started SelectChannelConnector@0.0.0.0:8090 |
清單 5. 建立 CWS 連接
org.apache.cayenne.remote.ClientConnection connection
= new HessianConnection("http://192.168.0.2:8090/CayenneCWS/cayenne");
|
![]() ![]() |
Cayenne 是一個已經被成功應用于商業生產環境(例如美國冰球聯盟網站 NHL.com,日均訪問量超過 500 萬次)的成熟 Java ORM 框架,它具有很多可以定制的高級數據庫性能優化特性,如連接池配置,數據對象緩存,數據分頁查詢和數據預讀等。下面將簡要介紹如何在程序開發中使用這些特性。
在 Cayenne 中內置有一個數據庫連接池機制,當系統第一次連接數據庫時 Cayenne 會根據配置文件中的參數設置來初始化連接池以備后續操作使用。后續的所有數據庫操作都會從這個連接池中獲得數據庫連接,從而可以極大的提高性能。清單 6 是 Cayenne 數據庫連接池初始化的日志信息,該日志顯示已經創建了一個數據庫連接池,這個池中最少會存在 5 個數據庫連接,最多容納 10 個連接。
清單 6. Cayenne 的數據庫連接池初始化信息
INFO QueryLogger: Created connection pool:jdbc:derby:DBContainer Driver class: org.apache.derby.jdbc.EmbeddedDriver Min. connections in the pool: 5 Max. connections in the pool: 10 INFO QueryLogger: --- will run 1 query. INFO QueryLogger: Opening connection: jdbc:derby:DBContainer Login: null Password: ******* INFO QueryLogger: +++ Connecting: SUCCESS. INFO QueryLogger: --- transaction started. |
Cayenne 數據庫連接池參數的配置非常簡便,如圖 6 所示只需在 CayenneModeler 中選中 DataNode 數據節點,然后在右側面板中的 JDBC Configuration區域修改 Min Connections和 Max Connections兩個參數即可。修改后的參數值會如清單 7 所示保存在配置文件 DataNode.driver.xml中。當程序重新啟動后新的參數值即會生效。
圖 4. 數據庫連接池參數配置面板
清單 7. Cayenne 的數據庫連接池參數
<?xml version="1.0" encoding="utf-8"?> <driver project-version="2.0" class="org.apache.derby.jdbc.EmbeddedDriver"> <url value="jdbc:derby:DBContainer"/> <connectionPool min="5" max="10" /> </driver> |
除了使用內置的連接池外,Cayenne 還可以使用 Apache Commons DBCP 來作為數據庫連接池從而提供功能更加豐富的連接池支持。如圖 5 所示,當選擇 org.apache.cayenne.conf.DBCPDriverDataSourceFactory作為數據源工廠類后,Cayenne 會自動使用 Apache DBCP Configuration中指定的 DBCP 配置文件中的內容的來實現 DBCP 數據庫連接池。在 Cayenne 中使用 DBCP 作為數據庫連接池的更多信息請閱讀 參考資源中的“在 Cayenne 中配置 DBCP 數據庫連接池”一文。
圖 5. 配置 DBCP 作為數據庫連接池

在 Cayenne 中內置有一套數據對象緩存機制 , 在該機制啟動后 Cayenne 會將已經獲得的查詢結果緩存在系統中 , 從而可以避免不必要的數據庫重復查詢。對需要頻繁重復執行的查詢啟動緩存支持 , 可以極大的提高數據庫系統的性能。如表 1 所示 ,Cayenne 中共有 5 種數據緩存策略 , 每一種策略都具有不同的作用域或行為。
表 1. Cayenne 數據緩存策略
緩存策略 | 緩存作用域 | 緩存行為 |
---|---|---|
QueryMetadata.NO_CACHE ( 默認策略 ) | 無 | 不使用緩存機制 |
QueryMetadata.LOCAL_CACHE | 在本地的 DataContext 中緩存數據 | 如果緩存中存在所需數據則直接使用該緩存數據 , 否則從數據庫中讀取該數據并將它放入緩存共后續查詢使用 |
QueryMetadata.LOCAL_CACHE_REFRESH | 在本地的 DataContext 中緩存數據 | 不使用緩存中的數據 , 總是從數據庫中讀取該數據并將它放入緩存共后續查詢使用 |
QueryMetadata.SHARED_CACHE | 在 DataDomain 中緩存數據 ( 同一 JVM 中的所有 context 可共享該緩存數據 ) | 如果緩存中存在所需數據則直接使用該緩存數據 , 否則從數據庫中讀取該數據并將它放入緩存共后續查詢使用 |
QueryMetadata.SHARED_CACHE_REFRESH | 在 DataDomain 中緩存數據 ( 同一 JVM 中的所有 context 可共享該緩存數據 ) | 不使用緩存中的數據 , 總是從數據庫中讀取該數據并將它放入緩存共后續查詢使用 |
Cayenne 中可以使用 API 或 Cayenne Modeler 來啟動緩存機制。使用 API 啟動查詢緩存只需要為 SelectQuery 指定一個名稱 ( 作為緩存的唯一鍵值 ) 并設定緩存策略即可 , 清單 8 是一段通過 API 來使用緩存的示例程序。
清單 8. 通過 API 使用 Cache 機制
DataContext context = DataContext.createDataContext(); SelectQuery persistentObjectSelectQuery = new SelectQuery(PersistentObject.class); // 為查詢指定名稱 persistentObjectSelectQuery.setName("MyQueryForCache"); // 設定查詢緩存策略 persistentObjectSelectQuery.setCachePolicy(QueryMetadata.LOCAL_CACHE); // 第一次查詢 , 訪問數據庫并將查詢結果放入緩存 List persistentObjectDataList = context.performQuery(persistentObjectSelectQuery); // 直接使用緩存 , 不訪問數據庫 List persistentObjectDataList1 = context.performQuery(persistentObjectSelectQuery); // 重新設定緩存策略 persistentObjectSelectQuery.setCachePolicy(QueryMetadata.LOCAL_CACHE_REFRESH); // 不使用緩存數據 , 訪問數據庫并更新緩存 List persistentObjectDataList2 = context.performQuery(persistentObjectSelectQuery); |
Cayenne 緩存也可以通過在 Cayenne Modeler 中創建 Query 并啟動 Result Caching 來開啟。如圖 6 所示 , 在查詢配置的 Result Cacheing選項中選擇 DataContext Cache 或 Shared Cache 即可啟動緩存支持。在程序代碼中通過執行 context.performQuery("MyQueryForCache",false)即可以從緩存中獲得數據 , 執行 context.performQuery("MyQueryForCache",true)則可以執行緩存刷新操作。
圖 6. 在 Cayenne Modeler 中配置緩存

數據分頁查詢和數據預讀是數據庫應用程序系統性能優化的兩種常用技術。當使用數據預讀技術時,應用程序能夠在一次查詢操作中獲取多個相關聯的數據項,從而能夠減少數據庫訪問次數。當使用數據分頁查詢技術時,應用程序能夠分批次(分頁)獲取一個包含大量數據的結果集中的少量數據,從而可以減少單次數據庫查詢所需的時間。當查詢結果是一個大型的結果集,但是只有其中的一小部分是所需數據時,使用數據分頁查詢可以極大的提高應用程序的數據庫訪問性能。 Cayenne 對這兩種技術均提供了良好的支持 , 并且使用這些技術的代碼也非常的簡潔。本文的最后部分簡要介紹這兩種技術在 Cayenne 中的應用。
數據預讀 (Prefetching):Cayenne 可以在一個 SelectQuery 上設定附加的 Prefetch 參數,該參數可以將待查詢的數據對象中的關系屬性傳遞到 SelectQuery 中,從而可以在單次 performQuery 操作中查詢到多個數據對象關系鏈中的數據對象,使用數據預讀可以減少大量的 SQL 查詢操作。清單 9 是一個使用數據預讀的示例代碼片斷,該示例中的數據對象 ClientTB 中具有一個名為 orders的 to many 類型的關系。SelectQuery 對象的 addPrefetch("orders") 方法設定了針對關系 orders 的數據預讀。當 context.performQuery 方法執行時,Cayenne 執行數據預讀操作,在同一個查詢中執行針對 ClientTB 和 orders 關系中關聯對象的查詢 , 并將 orders 關系中關聯對象儲存在 ClientTB 的 orders 屬性中 ( 可以通過 getOrders()) 方法訪問。
清單 9. 使用數據預讀
DataContext context = DataContext.createDataContext(); SelectQuery clientTBSelectQuery = new SelectQuery(ClientTB.class); // 設定針對關系 orders 的數據預讀 clientTBSelectQuery.addPrefetch("orders"); List clientTBDataList = context.performQuery(clientTBSelectQuery); Iterator it = clientTBDataList.iterator(); while (it.hasNext()) { ClientTB a = (ClientTB) it.next(); // 數據預讀后查詢到的 orders 對象被放置在 ClientTB 的 orders 屬性中 , 通過 getOrders() 方法訪問 System.out.println("orders: " + a.getOrders().size()); } |
數據分頁查詢 (Paginated Queries):在 Cayanne 中當啟動了數據分頁查詢功能后,在代碼中需要為 SelectQuery 設定一個 Page Size值,每次數據庫訪問時 Cayanne 只會獲取 Page Size 指定行數的結果集數據,同時只讀取其余數據行的主鍵值。當查詢一個沒有被獲取過的數據項時,包含該數據項的整個 page 的所有數據項都會立刻被獲取。這些數據庫訪問操作 Cayenne 會在后臺自動透明的執行,不需要用戶代碼參與。清單 10 是一個使用數據分頁查詢的示例代碼片斷,假設該查詢結果集中包含超過 200 條的數據項,Page Size 設定為 50, 當 context.performQuery 運行時,第一頁的 第 1到第 50條數據被從數據庫中獲取,運行 clientTBRows.get(3) 時,使用已經得到的 page 中的第 3 條數據,不再訪問后端數據庫。當執行 clientTBRows.get(153) 操作時,第四頁的第 151到第 200條數據被從數據庫中獲取。
清單 10. 使用數據分頁查詢
DataContext context = DataContext.createDataContext(); SelectQuery clientTBSelectQuery = new SelectQuery(ClientTB.class); // 設定 Page Size 為 50 clientTBSelectQuery.setPageSize(50); // 運行查詢,獲得數據庫中的第 1-50 條數據 List clientTBRows = context.performQuery(clientTBSelectQuery); // 不訪問數據庫,直接從已獲得的 page 中返回第 3 條數據 ClientTB clientTB1 = (ClientTB)clientTBRows.get(3); // 查詢數據庫,獲得第 4 頁中的第 151-200 條數據,返回第 153 條數據 ClientTB clientTB2 = (ClientTB)clientTBRows.get(153); |
![]() ![]() |
本文介紹了 Cayenne 中的 Remote Object Persistence 遠程訪問技術和數據庫連接池 , 數據對象緩存以及數據分頁查詢和數據預讀等數據庫應用程序優化技術。通過結合使用這些技術 , 可以簡便快捷的構建出一個基于 Cayenne ORM 框架的功能靈活 , 性能強大的數據庫應用程序。