[开发技巧] Dataset分页处理技术

hotbarsmu 2008-09-20


前言


在BS开发上不可避免地要实现分页处理,分页处理目前来说基本是两种处理方式:数据库分页和内存分页。
数据库分页
数据库分页:利用数据库提供的分页查询功能实现,如在MySql3中的查询代码:
select * from employee where dept_id='D11' limit 10,20
这儿利用limit实现数据库分页查询。这样做的好处是显而易见的,当employee表格的数据量很大时可以获得一个效率非常好的查询。
但是这样做也存在一个缺点,就是我们无法获知部门D11下员工的总记录数信息,为了得到这个信息,我们就只能再通过一个select count(*) from (select * from employee where dept_id='D11')这种sql语句再执行一次查询,得到记录总数并计算总页数信息。从而得到如下图翻页导航栏。
 
内存分页


内存分页:目前不少web框架提供的标签分页功能都是通过内存分页实现的。内存分页就是你把所有的数据统统交给标签(或则标签的DataProvider),由标签根据自身的需要从这些数据中检索出需要的数据传递到客户端并加以展示。很显然,这种做法对于记录总数和总页数信息的统计非常直接。很容易就可以计算出来。
但是我们也要看到这种处理方式应付数据量较小的数据库问题不大,遇上数据量大的就一定有较严重的性能问题。
笔者曾经遇到过一家软件开发公司使用了SiteMesh开发框架实现分页处理,初期很顺利,可以到系统上线的时候系统响应就极为缓慢。为此还特别论证了数日,最终不得不在持久层实现数据库分页技术再整合到SiteMesh标签中加以解决。工作量较大,会引起应用框架(BO,DAO等等)的大调整。
以上两种实现方式中除了性能上的差异,我想最大的差异会体现在软件架构的设计上。一个实现内存分页的软件架构在设计上只要在View层将数据过滤一下就可以。这样在BO这一层以及DAO调用层就不需要考虑分页问题,例如一个员工查询我们可以这么设计一个接口:
List getEmployees();//获取员工
List getEmployees(Object queryParameters);//根据参数查询员工
//但是如果我们要在数据库层实现分页技术,则我们在调用DAO或BO的时候的接口就得怎么写:
List getEmployees(int pageIndex, int pageSize);//获取员工
List getEmployees(Object queryParameters, int pageIndex, intPageSize);//根据参数查询员工
很显然,这种接口对于UML设计模式来说实在是令人太难接受了,所有的接口都得这么写,几乎找不到任何OO理念了。

Dataset默认支持数据库分页技术
DBDataset
Dataset默认支持数据库分页技术,DBDataset(子类有SqlDataset, AutoSqlDataset)都是直接在数据库中分页的,例如我们在SqlDataset中设定了sql为select * from employee属性,并设置pageSize属性为5之后,系统运行时SqlDataset会自动的在sql语句之后添加分页处理代码。运行时sql为select * from employee limit 5,当我们查看第二页数据时,则sql语句变为select * from employee limit 6,10
在dorado中,DBDataset支持各种类型数据库的开发,不同的数据库采用不同的方言实现(Dialect).dialect在DataSource.xml中配置。dorado在这些方言中针对不同的数据库提供了各种不同的分页查询技术。
datasource.xml中配置代码如下:
<datasource name="doradosample" type="JDBC">
<minEvictableIdleTimeMillis>30000</minEvictableIdleTimeMillis>
<timeBetweenEvictionRunsMillis>30000</timeBetweenEvictionRunsMillis>
<driver>org.hsqldb.jdbcDriver</driver>
<url>jdbc:hsqldb:file:D:/dorado5/sample/data/hsqldb/doradosample</url>
<dialect>com.bstek.dorado.data.db.dialect.HSQLDBDialect</dialect>
<user>sa</user>
<minIdle>0</minIdle>
<maxIdle>0</maxIdle>
<maxActive>0</maxActive>
<loginTimeout>0</loginTimeout>
<maxWait>0</maxWait>
</datasource>
对于未提供方言实现的数据库,我们可以直接继承com.bstek.dorado.data.db.dialect.DefaultDialect,或则想更改方言默认分页实现机制的我们可以继承默认的方言实现类,实现扩展类的getPagingSql方法,方法申明如下:
public String getPagingSql(String sql, int pageSize, int pageIndex) ;

参考范例(MySql3):
public String getPagingSql(String sql, int pageSize, int pageIndex) {
int offset = (pageSize * (pageIndex - 1));
boolean hasOffset = (pageIndex > 1);

StringBuffer pageSql = new StringBuffer();
pageSql.append(sql);
pageSql.append(hasOffset ?
" limit " + offset + ", " + (offset + pageSize) :
" limit " + pageSize);
return pageSql.toString();
}

CustomDataset
CustomDataset,MarmotDataset等都是被动的接受外部分页之后的数据展示到客户端,并且还要告诉dataset的总页数(pageCount)信息,我们看如下的代码:
Session session = HibernateUtils.openSession();
try{
int pageSize = dataset.getPageSize();//获取dataset的pageSize信息
int pageIndex = dataset.getPageIndex();//获取dataset当前请求的页索引

int rowCount = ((Integer)session.createQuery("select count(*) from Employee").list().iterator().next()).intValue();
PagingHelper helper = new PagingHelper(pageSize, pageIndex, rowCount);

List employees = session.createQuery("from Employee").setFirstResult(helper.getFromIndex()).setMaxResults(pageSize).list();
dataset.fromDO(employees);//将list中的数据导入到dataset

dataset.setPageCount(helper.getPageCount());//设置dataset的pageCount属性
}
finally{
session.close();
}

注意以上的代码中,在dorado的dataset发出分页数居请求时,我们都可以通过dataset的getPageIndex()获得当前请求的页信息,以及通过getPageSize()获取dataset请求的每页记录数信息。后台的逻辑层代码可以通过这两个参数查询出相关的数据并保存到dataset中,另外一个很重要的工作不要忘,要设置dataset的pageCount属性,通知dataset一共有多少页的查询结果。dataset的fromDO方法参考:VO开发with dorado dataset的思考http://www.bstek.com/bbs/posts/list/1547.page

由于笔者所在公司的客户所开发的项目一般都为大数据量的项目,因此在框架设计中就已经实现了分页处理,并且还有更多的优化措施,例如缓存处理机制等等。就不一一而言了。当然有时候也会遇上对分页处理没有概念的客户,需要引导。也会遇到没有分页处理功能的框架,则可以做一定的重构加以解决。
(完)

Global site tag (gtag.js) - Google Analytics