接触Richfaces时,我正在学习Dorado(上海锐道软件公司的Web表现层组件)控件,两者相比,Dorado是轻量级的,Richfaces是重量级的,比起Dorado,Richfaces更丰富、更强大、更灵活、更高知名度、更高质量、更具生命力、资源更多,而且还免费。
我毅然决定学习Richfaces,它的表现也让我肯定自己的正确选择。当我在实际项目中使用Richfaces时,发现我的项目运行起来很慢,比起PHP,简直是差远了,同样的记录数,如果使用PHP,页面响应速度是毫秒级的,使用Richfaces的响应速度却是秒级的。我不由得开始怀疑Richfaces的效率,在网络上搜寻相关问题时,很多人都有同样的感受,都觉得Richfaces比较慢,究其原因,有一种普遍的认识是Richfaces为了实现Ajax效果会下载很多js脚本本文件,会影响速度。的确,当做一个简单的Richfaces页面时,真正的数据可能才几十K,但是浏览器却下载了近1M的数据。在使用Firebug跟踪请求页面时,注意到Ajax脚本文件下载一次后再次使用时不会从服务器下载,而是直接来自Firefox缓存,这样的话页面响应速度并不会收到影响,那么到底是哪里出现了问题呢?
一次偶然,我用rich:dataTable标记时,在创建数据列表recordsList时,在循环中放置了System.out.println(),当进行页面操作时,控制台有大量输出,显示recordsList被重复创建,这些页面操作包括页面切换、通过a4j:commandLink或a4j:commandLink、或刷新a4j区域,也就是说,每当进行这些页面操作时,recordsList都会不停的创建,即每次都对数据库进行了读取,更让人惊讶的时,每次都将表中全部的记录读取出来,即便页面只显示了几条数据。看来问题就出在这里,至于Richfaces的效率问题不会是我们程序效率的关键因素。
下面我们做个实验,使用MySQL数据库,创建数据表Org,设置ID_、orgName、orgDesc三个字段,填充30万条记录,每页显示10条记录,共30012页我们分别使用三种创建数据表的方法对读取这张表的记录。
1 内存分页,managed-bean-scope=request
页面效果
将Org的managed-bena-scope的属性设置成request, 通过Firebug记录每次切换页面所耗时的时间。可以看到,每一次切换页面都耗时了4秒的时间。
2 内存分页,managed-bean-scope=session
将Org的managed-bena-scope的属性设置成session,注意到除了第一次请求耗时4.55秒外,切换页面时平均耗时仅50毫秒。但是,实际项目中我们不能在session中存放过多数据,尤其是这样30万条记录,几乎是灾难。
3 数据库分页方式,即每次仅读取需要的10条数据,managed-bean-scope=request
页面效果
Firebug记录如下,平均耗时400毫秒。
在实际项目中,我们如果不知道Richfaces的这种特性,往往选择服务器内存分页方式,当记录数非常小的时候,不会感觉到对速度的影响,但数据量大,访问量也大的时候,即便我们已经充分考虑减少对页面的刷新,但是你还是会发现速度很慢。于是我们开始选择数据库分页的方式,也只能这么选择。
选择了数据库分页,我们还应该考虑如果减少对recordsList的创建,以便减少对数据库的查询,如果加以处理,每当点击a4j:commandButton或a4j:commandLink时,或是对a4j区域进行了刷新时,recordsList都会被创建,虽然此时仅仅读取了10条记录,但是在页面上并没有显示变化,所以连这10条记录的读取都是多余的。那么我们如何控制呢?我们可以在a4j:commandButton或a4j:commandLink设置一行代码<f:param name="reload" value="false"/>,即设置reload参数,然后在mRecordsList()中判断这个参数,如果reload等于false,就退出函数。
if(!reload){
return;
}
思路是对的,但是你会注意到a4j:commandButton或a4j:commandLink的action没有执行,这是怎么回事呢?实践说明recordsList总是先于action执行,我猜测是如果recordsList没有正确返回,action就不会被执行。于是,修改代码如下,创建一个recordsList,记录条数一样即可,便于节省时间,只循环添加相当于pageSize的recordsList,运行代码,奇迹出现了,在不更新recordsList的情况下action也得到了执行。while(i<10),不会耗时1毫秒,这样做能显著节约时间。
if(!reload){
int size = 0;
while(size < me.getPageSize()){
recordsList.add(new Org(size));
size++;
}
return ;
}
你也可能会想,为什么不是while(i<1),只创建一条记录,这样不是更好?在实践中,如果只创建一条记录,那么只有第一条记录中的a4j:commandButton或a4j:commandLink有效。
关于速度问题,还有以下因素
数据库执行SQL效率
我们还应该注意SQL语句的写法,例如排序,按照ID_排序比按照orgName快的不是一秒两秒的问题,这一点你可以在MySQL中测试一下,尤其是使用limit和offset关键字控制分页时。这里还不考虑对字段的搜索。筛选条件或语句写法都会影响页面影响速度。
浏览器影响
实践证明在使用Richfaces标记时,Firefox比IE要快的多,在IE6中尤其慢。