Cayenne,开源ORM盛宴中的另道佳肴

何为 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

505x331

我们对于 Cayenne 的了解将从这里开始。打开后,我们新建一个工程,

图 4. CayenneModeler 主界面

482x353

进入到 Cayenne Modeler 的主界面。在导航条上有三个主要的按钮,分别是 Create DataDomain, Create DataNode, Create DataMap。其中,DataDomain, DataNode 和 DataMap 是一个 Cayenne 工程中三个重要的概念。

图 5. 导航条上的 DataDomain, DataNode 和 DataMap

476x83

图 6 说明了这三个概念间的关系

图 6. DataDomain, DataNode 和 DataMap 的关系

366x177

在一个 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

507x359

在配置完 JDBC Driver 信息之后,然后先将我们所做的配置信息保存到我们开始建立的一个 Eclipse 工程 Cayenne-store 下面。这里,需要注意的是,Cayenne 的映射文件 cayenne.xml 必须放到程序运行时的 CLASSPATH 中,所以,我们可以将它保存放到所建立的 Java 工程 src 目录下面。

图 8. 保存 Cayenne 主配置文件

464x321

这时,回到 Eclipse 工程里面,就会看到这个应用的主配置文件 cayenne.xml 以及相应的 JDBC Driver 配置文件了。

图 9. 生成 Cayenne.xml

271x122

到此,开发环境的搭建就算完成了。下面,我们将通过一个简单的例子带您逐步的了解 Cayenne,看看它如何帮助我们产生数据持久层的 Java 代码以及数据库表。

建模

这个例子很简单,我们以一个购物网站为背景进行数据建模。可以想象,这里我们需要如下四张表,客户信息表 ClientTB,商品信息表 CommodityTB,客户购买商品的订单信息表 OrderTB,还有一个保存订单和订单所包含商品的订单商品表 OrderCommodityTB。它们之间的关系可用如下 E-R 图表示,

图 9. E-R 图表

484x240

为了在 Cayenne Modeler 中创建 DBEntity 模型,需要先建立一个 DataMap。在 cayenne-storeNode DataNode 下创建 DataMap。如图 10。在 Java Package 输入框中,你可以定义将要存储持久化代码的包名。其余的,可以暂时留空。

图 10. 建立 DataMap

接下来,基于这张 E-R 图,我们就可以在 Cayenne Modeler 中建立 DBEntity 模型了。建立 DBEntity 模型的过程与在一个数据库管理客户端建立表的过程类似。即,需要指定表的名字、字段及字段大小等信息。对表 ClientDB 和 CommodityTB 等表的建模结果如下图所示,

图 11. 建立 ClientTB 等 DBEntity

548x184

这里,需要注意的一点是表间关系的建立。 以 clientTB 和 orderTB 为例,因为 clientTB 和 orderTB 是一对多的关系,所以在 Cayenne Modeler 中需要在 Relationships 面板上建立表间联合关系。

图 12. 建立 ClientTB 和 OrderTB 的表间关系

635x179

同时,还需要建立从 orderTB 到 clientTB 的反转关系,即多对一的关系。

图 13. 建立 OrderTB 到 ClientTB 的反转关系

500x312

Cayenne modeler 会自动在 OrderTB 的 Relationships 面板中产生一个表间关系

图 14. OrderTB 的 Relationships 面板

630x185

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

550x110

到这,建立模型的过程就告一段落了。接下来,我们让 Modeler 来产生 Database Schema 和 Java Classes. 这可以通过在 Tools Menu 下的 Generate Classes 和 Generate Database Schema 两个按钮来完成。

图 16. 生成 Java 代码和 Database Schema

533x216

完成之后,你就会发现在所建的 cayenne-store 工程下面生成了 Java 代码。

图 17. Eclipse 中生成的 Java 代码

以及 cayenneStore 数据库文件目录。

图 18. 文件系统中生成的 Derby 数据库

386x274

到此,您可能已经品尝到了 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

525x315

然后在代码中调用这个 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  clients = context.performQuery(“allClients”,true);
// 修改 DataObject 的属性 
ClientTB clientTB = clients.get(0);
clientTB.setClientMail(“cayennedemo@hotmail.com”);
// 提交修改
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 clients = clientDao.getClients();
for (ClientTB c : clients){
   csd.getDataContext().deleteObject(c);
}
SelectQuery queryCom = new SelectQuery(CommodityTB.class);
List 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 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 orders = ctt.getOrdersOwned();
if (orders.size()==0){
   System.out.println("The client "+clientName+" doesn't sign any order");
}else{
   for (OrderTB order: orders){
     List 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 远程访问技术和数据库查询优化等主题。




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 的原理结构图

570x365

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 配置。

清单 2. CWS Web 应用程序 web.xml 最小配置


  
  cayenne
 
  org.apache.cayenne.remote.hessian.service.HessianServlet
 
  
  
  cayenne
  /cayenne
  
 

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

570x417

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

图 3. CayenneModeler Code Generation 面板

499x257

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 中的数据库性能优化配置

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 Connections和 Max Connections两个参数即可。修改后的参数值会如清单 7 所示保存在配置文件 DataNode.driver.xml中。当程序重新启动后新的参数值即会生效。

图 4. 数据库连接池参数配置面板

570x403

清单 7. Cayenne 的数据库连接池参数


 
 
 
 

除了使用内置的连接池外,Cayenne 还可以使用 Apache Commons DBCP 来作为数据库连接池从而提供功能更加丰富的连接池支持。如图 5 所示,当选择 org.apache.cayenne.conf.DBCPDriverDataSourceFactory作为数据源工厂类后,Cayenne 会自动使用 Apache DBCP Configuration中指定的 DBCP 配置文件中的内容的来实现 DBCP 数据库连接池。

图 5. 配置 DBCP 作为数据库连接池

570x382

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

570x345

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 框架的功能灵活,性能强大的数据库应用程序。

文章来源:

http://www.ibm.com/developerworks/cn/java/j-lo-cayenne2/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值