Cayenne,开源 ORM 盛宴中的另道佳肴,第 2 部分: 再品小辣椒 - Apache Cayenne 的高级特

14年前

使用 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 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 数据对象定义面板
CayenneModeler 数据对象定义面板

选中后 Cayenne 默认会在标准数据库映射 Java 对象包的子目录"client"中生成 Client 端对象持久化 Java 类。之后在 CayenneModeler 的程序主菜单中选择 Tools-->Generate Classes, 并按照图 3 所示 , 在 Code Generation 面板中选择 Client Persistent Objects作为类型来生成数据库映射 Java 对象即可。


图 3. CayenneModeler Code Generation 面板
CayenneModeler Code Generation 面板

Remote Object Persistence 的客户端编程

在一个客户端程序中使用 Remote Object Persistence 来访问远程数据库和使用 Cayenne 访问本地数据库的方法十分相似 , 只有以下 3 点需要注意 :

  1. ROP 客户端程序中 Cayenne 必须使用 Client端对象持久化 Java 类来执行远程数据库操作。
  2. ROP 客户端程序中使用 cayenne-client-nodeps.jar作为 Cayenne 类库 , 除此之外还必须将 hessian-3.0.13.jarcommons-collections,commons-lang以及 commons-logging几个类库加入类路径中。
  3. 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 功能的客户端和服务器端程序。请按照以下的步骤运行示例程序。

  1. 在 CayenneCWSServer 工程中有一个嵌入式的 Derby 数据库和一个 Jetty Servlet 服务器。将该项目导入 eclipse 开发环境后 , 运行类 CWSServerLauncher 即可启动 Servlet 服务器并在地址 http://{hostname}:8090/CayenneCWS/cayenne 上启动 CWS 服务。清单 4 是服务器端程序正常启动后的 console 信息。
  2. 当 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 中的数据库性能优化配置

Cayenne 是一个已经被成功应用于商业生产环境(例如美国冰球联盟网站 NHL.com,日均访问量超过 500 万次)的成熟 Java ORM 框架,它具有很多可以定制的高级数据库性能优化特性,如连接池配置,数据对象缓存,数据分页查询和数据预读等。下面将简要介绍如何在程序开发中使用这些特性。

Cayenne 中的数据库连接池管理

在 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 ConnectionsMax 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 作为数据库连接池
配置 DBCP 作为数据库连接池

 

Cayenne 中的数据对象缓存机制

在 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 Modeler 中配置缓存

 

 

Cayenne 中的数据分页查询和数据预读

数据分页查询和数据预读是数据库应用程序系统性能优化的两种常用技术。当使用数据预读技术时,应用程序能够在一次查询操作中获取多个相关联的数据项,从而能够减少数据库访问次数。当使用数据分页查询技术时,应用程序能够分批次(分页)获取一个包含大量数据的结果集中的少量数据,从而可以减少单次数据库查询所需的时间。当查询结果是一个大型的结果集,但是只有其中的一小部分是所需数据时,使用数据分页查询可以极大的提高应用程序的数据库访问性能。 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 框架的功能灵活 , 性能强大的数据库应用程序。