如何用extjs实现海量数据的快速分页显示

点击查询按钮,会查询出N万条数据。 
后台java方法会把查询出的这N万条数据,以JSON格式传送给前台。 
代码如: 
view plain 
JSONObject object = new JSONObject(); 
object.put("success", true); 
object.put("root", dataList); 
object.put("totalProperty", total); 
//推送给前台 
writeResponse(object.toString()); 
======================================= 
/** 
* ajax方式返回字符串 
* @param str 
* @return 
*/ 
public boolean writeResponse(String str){ 
boolean ret = true; 
try{ 
HttpServletResponse response = ServletActionContext.getResponse(); 
response.setContentType("text/html;charset=utf-8"); 
PrintWriter pw = response.getWriter(); 
pw.print(str); 
pw.close(); 
}catch (Exception e) { 
ret = false; 
e.printStackTrace(); 
return ret; 
======================================= 

项目框架:SSH+ExtJs。 
客户情况:客户等不起grid显示的loading... 
开发人员:不是我的错,是数据太多,慢慢等吧 
DBA意见: 不是我的错,Oracle根本不拥塞 

好吧,我们来看看实现过程,一定有优化的地方,只是我们还没发现或不愿意去发现而已。 

如果一次性查询出的dataList是超过千行甚至万行级的海量数据,若再将dataList以JSON格式传送给前台,势必会影响前台的显示速度。 
如果给grid设置了loadMask,你一定会看到那个loading...一直在loading,直到把你loading到不耐烦。 
实际上,当JSON打包的数据超过千行时,就已经有明显的等待时间了(当然,与数据库的性能也有关)。 

我们知道在EXT中,如果数据量比较大时,通常给Grid配置分页工具栏PageBar,PageBar再配置grid的store,并设置pageSize,来完成数据分页显示的功能。这是最友好的显示方式。其实这也给我们暗示了一种理念,就是gird到底一次需要读取多少条数据。答案是肯定的,那就是pageSize条数据。 

以此为出发点,我们会产生疑问,难道我们只需要读取pageSize条数据?那PageBar的总页数岂不是只有pageSize条数据了,这明显与实际是不符的!比如实际上如果有1W条数据,但由于pageSize设置了10条,那按照以上逻辑,只需要去读取top 10条?若数据库中只有这10条数据,那毫无疑问,PageBar的总页数就是10条了。 

以上怀疑是正确的,没有错误。错误的是我们在实现上,没看清一个关键问题。那就是gird和PageBar共享同一个store。这个store有多少数据,PageBar就会有多少条数据。也就是说,gird和PageBar都傻傻地依赖着同一个store! 

那要是gird和PageBar不是同一个store,且要去完成同一片数据的分页功能,那能实现吗?答案也是肯定的,这也是本文预介绍的内容:用ext实现海量数据的快速分页显示。 

既然知道了gird和PageBar有了这层关系(共享同一个store,这个store总是一次性加载完所有的数据),那我们就借位思考,打破它们俩这种藕断丝连的关系:让gird和PageBar分别有自己的store!让这两个store去共享那片数据区(本来就应该这样,数据是共有的,至于谁读不读,那是他自己的事。只是EXTJS把这两个组件通过store联系在一起)。 

那怎么实现呢? 
原理是:GridPanel的store第一次只装载GRID_PAGE_SIZE条记录即可,PageBar的store第一次只装载总记录数。当需要翻页时,给GridPanel的store传递当前页数和PageBar显示的总条数。 

第一步,让gird和PageBar分别有自己的store 

view plain 
var resultPanel = new top.Ext.grid.GridPanel( { 
// title : '搜索结果', 
ds : store, 
height : 153,// autoHeight:true, 
sm : new top.Ext.grid.RowSelectionModel( { 
singleSelect : true 
}), 
cm : cm, 
viewConfig : { 
forceFit : true 
}, 
// view : new top.Ext.ux.grid.BufferView({ 
// rowHeight : 20, 
// scrollDelay : false 
// }), 
border : false, 
columnLines : false, 
frame : false, 
autoScroll : true, 
trackMouseOver : true, 
loadMask : true, 
id : 'ridGrid', 
stripeRows : true, 
bbar : new top.Ext.ux.DynamicPageBar( { 
pageSize : GRID_PAGE_SIZE, 
store : storeDetailPage, 
cls : 'toolbar_bottom', 
autoload : true, 
displayInfo : true 
}) 
}); 



可以看到grid和PageBar分别有自己的store。而它们俩的store要共享同一部分数据,就要指定同一个action URL,并且传递同样的参数,即指向同一个java方法即可。 

第二步,让gird和PageBar各自的store共享同一片数据区。 

其中,grid的store定义为: 

view plain 
var store = new top.Ext.data.Store( { 
proxy : new top.Ext.data.HttpProxy( { 
url : path + '/QueryAction!queryDetailData.action', 
timeout : EXT_QUERY_TIME_OUT 
}), 
reader : reader, 
listeners : { 
load : function() { 
isDetailReady = true;// 查询置换标志为已查过 

}); 

PageBar的store定义为: 

view plain 
var storeDetailPage = new top.Ext.data.Store( { 
proxy : new top.Ext.data.HttpProxy( { 
url : path + '/QueryAction!queryDetailData.action', 
timeout : EXT_QUERY_TIME_OUT 
}), 
reader : reader, 
listeners : { 
load : function() { 
if (isDetailReady) { 
if (!this.isDetailConfigured) { 
// 只在第一次加载完毕的时候,为Grid重新赋数据源 
resultPanel.reconfigure(storeDetailPage, cm); 
storeDetailPage.baseParams.total = storeDetailPage.getTotalCount(); 
isDetailConfigured = true; 
// 下一页的时候就不用重复赋数据源了 
} else { 
var o = this; // 解决setTimeout的参数中有this的问题 
setTimeout(function() { 
o.load() 
}, VEHICLEPASSTORE_TIMETERVAL);// VEHICLEPASSTORE_TIMETERVAL毫秒 

上面看到了几个变量,定义为全局变量吧: 

view plain 
var VEHICLEPASSTORE_TIMETERVAL = 100;// 查询时,轮训判断VehiclePassStore是否加载完毕的时间间隔,毫秒级别 
var EXT_QUERY_TIME_OUT = 1200000; // EXT查询超时时间 
var isDetailReady = false;// 判断Grid的store是否加载完毕 
var isDetailConfigured = false; 

第三步,让查询干点活--传递适当的参数 

view plain 
var btnSearch = new top.Ext.Button( { 
width : 70, 
height : 23, 
text : '查询', 
listeners : { 
click : function() { 
isDetailReady = false; 
isDetailConfigured = false; 

store.baseParams = { 
operate : "Search", 
/*参数列表区*/, 
limit : 50,//grid第一次查询50条 
total : 50 
}; 
store.load(); 
storeDetailPage.baseParams = { 
operate : "Search", 
/*参数列表区*/, 
limit : 50, 
total : -1 //如果total是-1,即count记录数给PageBar 
}; 
storeDetailPage.load(); 
}); 

好了,除了对应的java方法没给出外,上面的JS已经可以实现我们需要的功能了。 
当然,上面是点击按钮触发的事件。如果是一打开页面就触发的话,我们就要把click : function() 里面的代码写在某个组件的 
view plain 
listeners : { 
'afterrender' : function() { 
/*here*/ 
}.createDelegate(this) 
的afterrender里就可以了。 

更完整的步骤: 
第四步:java实现方法 
Action: 

view plain 
public void queryDetailData() { 
// 调用service,进行查询处理 
Page page = QueryService.queryDetailData(start, limit,pInfo,total); 

if (page != null) 
vehicleInfo = (List) page.getResult(); 

if(vehicleInfo != null) 
。。。 } 
total = page.getTotalCount(); 
JSONObject object = new JSONObject(); 
object.put("success", true); 
object.put("totalProperty", vehicleInfo); 
object.put("total", page.getTotalCount()); 
//控制台打印 
System.out.println(object.toString()); 
//推送给前台 
writeResponse(object.toString()); 

// 记录日志 
this.addOperationLog(...); 
Service层: 

view plain 
public Page queryDetailData(int startIndex, int pageSize,String pInfo,long total){ 
.... 
return QueryDAO.queryDetailData(queryCountSql.toString(), queryResultSql.toString(),startIndex, pageSize,total); 
DAO层: 

view plain 
@SuppressWarnings("unchecked") 
public Page queryDetailData(final String countSql,final String resultSql,final int startIndex, final int pageSize, final long total) 
Page result = (Page)getHibernateTemplate() 
.execute(new HibernateCallback() 
public Object doInHibernate(Session session) throws HibernateException, SQLException 
long totalCount = 0; 

if(total < 0) 
List countlist = session.createSQLQuery(countSql).list(); 

for(int i=0;i<countlist.size();i++) 
 { 
totalCount+= Long.parseLong(countlist.get(i).toString()); 
else 
totalCount = total; 
if (totalCount < 1) 
return new Page(); 
// 实际查询返回分页对象 
Query query = session.createSQLQuery(resultSql) 
.addScalar("字段名1", Hibernate.STRING) 
.addScalar("字段名2", Hibernate.STRING) 
.addScalar("字段名3", Hibernate.STRING) 
.addScalar("字段名4", Hibernate.STRING) 
.addScalar("字段名5", Hibernate.STRING) 
。。。 
.setResultTransformer(Transformers.aliasToBean(VehicleInfo.class)); 

if (pageSize > 0) 
if (startIndex < 0) 
query.setFirstResult(0); 
else if (startIndex >= totalCount) 
return new Page(); 
else 
query.setFirstResult(startIndex); 
if(startIndex+pageSize > totalCount) 
query.setMaxResults((int)totalCount-startIndex); 
else 
query.setMaxResults(pageSize); 
return new Page(startIndex, totalCount, pageSize, query.list()); 
}); 

return result; 
}  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值