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

12 篇文章 0 订阅
 现在有这样一个情况:
点击查询按钮,会查询出N万条数据。
后台java方法会把查询出的这N万条数据,以JSON格式传送给前台。
代码如:
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

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定义为:

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定义为:

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毫秒
	}


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

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


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

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() 里面的代码写在某个组件的

listeners : {			
	'afterrender' : function() {
		/*here*/
	}.createDelegate(this)
}

的afterrender里就可以了。

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

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

		if (page != null) 
		{
			vehicleInfo = (List<VehicleInfo>) 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层:

public Page queryDetailData(int startIndex, int pageSize,String pInfo,long total){
....
return QueryDAO.queryDetailData(queryCountSql.toString(), queryResultSql.toString(),startIndex, pageSize,total);
}

DAO层:

@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;
    }
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值