在进行Web开发时,很多地方要使用到分页这个技术。.NET框架中是直接集成好的控件,可以直接使用了。而Java中没有,需要程序员自己来编写实现过程。虽然分页过程并不复杂,但是要完美的实现是需要时间来考验的。
先说说分页的基本原理,分页显示就是在数据量大时页面上可以只显示所有数据的一部分,然后点击页面连接可以跳到所需的位置继续查看。需求很明确,实现方法大致分为两种:一种是物理分页,也就是真分页,这种方法是在SQL语句上写获取数据量的关键字然后进行数据库检索,只取出那一部分结果,需要查看其他页时再次进行数据库检索。二是全部结果一次提出,然后再浏览器页面上进行分页,这种方式一般借助JS来完成,当前不少前端框架都自行实现了分页,效果很不错,比如YUI,那么我们先看一个YUI的示例。效果图如下:
[img]http://dl.iteye.com/upload/attachment/271400/73292d34-f8b5-38b9-8a3f-67c295031881.jpg[/img]
页面很简单,但是这里省略了YUI类库的引用,页面效果也是YUI的CSS,这里主要是JS的编写,方法为initStatusResultTable();和queryStatus();,页面里很容易看出。
下面来看方法initStatusResultTable():
这里面设计了表头和分页的信息,指定了列表内容的显示位置resultSet。下面来看queryStatus()方法,这里面用到了YUI的Ajax组件,YUI的Ajax组件使用起来也很顺手,值得一看。
这样就实现了JS分页的过程,是完全在客户端实现的,所有数据是一次发送的,那么缺点也是显而易见的:数据量太大时首次相应速度会很慢,甚至溢出。
下面来说说物理分页的实现,这就需要设计分页类了,我结合Hibernate的特点自行设计了一个分页类并和Struts2框架集成,视图层解析使用FreeMarker的宏来实现。首先来看分页类,目前仅仅支持HQL方式,其他的方式正在思考中:
常量值这里给出:
分页类写好了,然后我们先看页面解析的FreeMarker宏:
需要定义的部分都写完了,下面来看怎么使用分页组件,首先需要抽象出一些公共的东西,写在基类Action中,如下:
使用到的QueryUtil工具类为:
分层设计中Action只负责业务逻辑的跳转执行,而真正的逻辑处理是在Service层进行的,那么还需要配置Service基类,其中的ht是HibernateTemplate对象,这是用过Spring进行的依赖注入,剩下的代码就是包装一下PagingListHt中的构造方法:
东西都准备齐全了,下面来看看如何使用吧,在Service中进行查询操作:
在Action中就更简单了:
在页面只要引入了事先写好的分页宏,一条语句就可以了:
要注意的是使用FreeMarker的list指令来遍历PagingListHt对象时,写法和普通的list稍有区别,应该是:
为什么加了.List?看看PagingListHt是怎么设计的吧,其实就是getList()方法,因为这个分页的List是我们自定义的类,而不是Java类库的List,要是不写,那就是遍历PagingList类型,FreeMarker显然不能进行。
一家之言,仅供参考,但此例为作者原创,您可以对其进行适当改造应用于您自己的系统中,如果发现问题,欢迎交流。后续将对SQL分页进行说明,当然还是这种方式的,只是分野类设计的不同罢了。
先说说分页的基本原理,分页显示就是在数据量大时页面上可以只显示所有数据的一部分,然后点击页面连接可以跳到所需的位置继续查看。需求很明确,实现方法大致分为两种:一种是物理分页,也就是真分页,这种方法是在SQL语句上写获取数据量的关键字然后进行数据库检索,只取出那一部分结果,需要查看其他页时再次进行数据库检索。二是全部结果一次提出,然后再浏览器页面上进行分页,这种方式一般借助JS来完成,当前不少前端框架都自行实现了分页,效果很不错,比如YUI,那么我们先看一个YUI的示例。效果图如下:
[img]http://dl.iteye.com/upload/attachment/271400/73292d34-f8b5-38b9-8a3f-67c295031881.jpg[/img]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>State of Vehicle</title>
</head>
<body class="yui-skin-sam" style="background-color:#e6ecf4">
<div id="resultShow" class="sharp color2" style="margin-top:10px;width: 998px;display: none;">
<b class="b1"></b><b class="b2"></b><b class="b3"></b><b class="b4"></b>
<div class="content">
<h3 align="center">State of Vehicle</h3>
<div style="margin:10px;">
<div align="center" id="resultSet"></div>
</div>
</div>
<b class="b5"></b><b class="b6"></b><b class="b7"></b><b class="b8"></b>
</div>
<script>
initStatusResultTable();
queryStatus();
</script>
</body>
</html>
页面很简单,但是这里省略了YUI类库的引用,页面效果也是YUI的CSS,这里主要是JS的编写,方法为initStatusResultTable();和queryStatus();,页面里很容易看出。
下面来看方法initStatusResultTable():
YAHOO.example.Data = {
areacodes : []
};
function initStatusResultTable(){
var myColumn=[
{key:"owner",label:"车主",width:160,resizeable:true,sortable:true},
{key:"plate",label:"车牌号",width:160,resizeable:true,sortable:true},
{key:"title",label:"当前状态",width:190,resizeable:true,sortable:true}
];
var myDataSource=new YAHOO.util.DataSource(YAHOO.example.Data.areacodes);
myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSARRAY;
myDataSource.responseSchema = {
fields: ["owner","plate","title","newTime"]
};
var myConfigs = {
paginator: new YAHOO.widget.Paginator({
rowsPerPage: 10,
template: YAHOO.widget.Paginator.TEMPLATE_ROWS_PER_PAGE,
rowsPerPageOptions: [10,25,50,100],
pageLinks: 5
}),
draggableColumns:true
}
var myDataTable = new YAHOO.widget.DataTable("resultSet", myColumn, myDataSource, myConfigs);
}
这里面设计了表头和分页的信息,指定了列表内容的显示位置resultSet。下面来看queryStatus()方法,这里面用到了YUI的Ajax组件,YUI的Ajax组件使用起来也很顺手,值得一看。
function queryStatus(){
var callback={
success:function(o){
var clist = YAHOO.lang.JSON.parse(o.responseText);
var list = clist.resList;//获取返回的List
var shows = "";
if (list.length > 0) {
for (var i = 0; i < list.length; i++) {
shows += "YAHOO.example.Data.areacodes[YAHOO.example.Data.areacodes.length]={ owner:\""
+ list[i].owner
+ "\","
+ "plate:\""
+ list[i].plate
+ "\","
+ "title:\""
+ list[i].title
+ "\"};";
}
} else {
shows = "YAHOO.example.Data.areacodes.length=0;";
}
eval("YAHOO.example.Data.areacodes.length=0;");
eval(shows.toString() + " " + "initStatusResultTable();");
Dom.get('resultShow').style.display = 'block';
},
failure:function(o){
alert('ajax请求失败,请检查网络!');
}
};
var surl = 'xxx.action';
var postData ='';
YAHOO.util.Connect.asyncRequest('post',surl,callback,postData);
}
这样就实现了JS分页的过程,是完全在客户端实现的,所有数据是一次发送的,那么缺点也是显而易见的:数据量太大时首次相应速度会很慢,甚至溢出。
下面来说说物理分页的实现,这就需要设计分页类了,我结合Hibernate的特点自行设计了一个分页类并和Struts2框架集成,视图层解析使用FreeMarker的宏来实现。首先来看分页类,目前仅仅支持HQL方式,其他的方式正在思考中:
package xxx.core.common;
import java.util.*;
import xxx.core.Constants;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
/**
* Hibernate分页查询辅助类(目前仅支持HQL方式)
*
* @author Sarin
*
*/
public class PagingListHt extends HibernateDaoSupport {
private int rowCount = 0; // 记录总数
private int pageCount = 1; // 分页总数
private int pageSize = Constants.DEFAULT_PAGE_SIZE; // 每页记录数
private int pageNum = 1; // 当前页数
private int startIndex = 1; // 起始记录数
private int endIndex = 1; // 结束记录数
private List list = new ArrayList();// 记录列表
/**
* 获得对象列表
*/
public List getList() {
return list;
}
/* 获得起始记录数 */
public int getStartIndex() {
return startIndex;
}
public Integer getStartIndexInteger() {
return new Integer(startIndex);
}
/* 获得结束记录数 */
public int getEndIndex() {
return endIndex;
}
public Integer getEndIndexInteger() {
return new Integer(endIndex);
}
/* 获得分页其它信息 */
public int getPageCount() {
return pageCount;
}
public int getPageNum() {
return pageNum;
}
public int getPageSize() {
return pageSize;
}
public int getRowCount() {
return rowCount;
}
/**
* 构造方法,HQL无参数,单对象
*
* @param hibernateTemplate ht对象
* @param pageNum 第几页
* @param pageSize 每页显示数目
* @param hql HQL语句
* @param clazz 对象类型
*/
public PagingListHt(HibernateTemplate hibernateTemplate, int pageNum,
int pageSize, String hql, Class clazz) {
preProcessParams(pageNum, pageSize);
execute(hibernateTemplate, pageNum, pageSize, hql, clazz);
}
/**
* 构造方法,HQL无参数,多对象
*
* @param hibernateTemplate
* @param pageNum
* @param pageSize
* @param hql
*/
public PagingListHt(HibernateTemplate hibernateTemplate, int pageNum,
int pageSize, String hql) {
preProcessParams(pageNum, pageSize);
execute(hibernateTemplate, pageNum, pageSize, hql);
}
/**
* 构造方法,HQL有参数,单对象
*
* @param hibernateTemplate ht对象
* @param pageNum 第几页
* @param pageSize 每页显示数目
* @param hql HQL语句
* @param parameters Object[]类型的参数
* @param clazz 对象类型
*/
public PagingListHt(HibernateTemplate hibernateTemplate, int pageNum,
int pageSize, String hql, Object[] parameters, Class clazz) {
preProcessParams(pageNum, pageSize);
execute(hibernateTemplate, pageNum, pageSize, hql, parameters, clazz);
}
/**
* 构造方法,HQL有参数,多对象
*
* @param hibernateTemplate
* @param pageNum
* @param pageSize
* @param hql
* @param parameters
*/
public PagingListHt(HibernateTemplate hibernateTemplate, int pageNum,
int pageSize, String hql, Object[] parameters) {
preProcessParams(pageNum, pageSize);
execute(hibernateTemplate, pageNum, pageSize, hql, parameters);
}
/**
* 预处理页面参数
*/
private void preProcessParams(int pageNum, int pageSize) {
if (pageNum > 0) {
this.pageNum = pageNum;
}
if (pageSize > 0) {
this.pageSize = pageSize;
}
if (pageSize > Constants.MAX_PAGE_SIZE) {
this.pageSize = Constants.MAX_PAGE_SIZE;
}
}
/**
* 根据给定的数据计算相关分页信息,多对象HQL
*
* @param hibernateTemplate
* @param pageNum
* @param pageSize
* @param hql
*/
private void execute(HibernateTemplate hibernateTemplate, int pageNum,
int pageSize, String hql) {
// 获取Hibernate当前的Session
Session session = hibernateTemplate.getSessionFactory()
.getCurrentSession();
// 获取记录总数
this.rowCount = hibernateTemplate.find(hql).size();
// 计算分页数及起止记录
countPage();
// 获取List
list = session.createQuery(hql)
.setFirstResult((pageNum - 1) * pageSize).setMaxResults(
pageSize).list();
}
/**
* 根据给定的数据计算相关分页信息,单对象HQL,且无where限制条件
*
* @param hibernateTemplate
* @param pageNum
* @param pageSize
* @param hql
* @param clazz
*/
private void execute(HibernateTemplate hibernateTemplate, int pageNum,
int pageSize, String hql, Class clazz) {
// 获取Hibernate当前的Session
Session session = hibernateTemplate.getSessionFactory()
.getCurrentSession();
// 获取记录总数
this.rowCount = Integer.parseInt(String.valueOf(session.createQuery(
"select count(*) from " + clazz.getName()).uniqueResult()));
// 计算分页数及起止记录
countPage();
// 获取List
list = session.createQuery(hql)
.setFirstResult((pageNum - 1) * pageSize).setMaxResults(
pageSize).list();
}
/**
* 重载的分页方法,接受预编译HQL语句,HQL单对象且无where限制条件
*
* @param hibernateTemplate
* @param pageNum
* @param pageSize
* @param hql
* @param parameters
* @param clazz
*/
private void execute(HibernateTemplate hibernateTemplate, int pageNum,
int pageSize, String hql, Object[] parameters, Class clazz) {
// 获取Hibernate当前的Session
Session session = hibernateTemplate.getSessionFactory()
.getCurrentSession();
// 获取记录总数
this.rowCount = Integer.parseInt(String.valueOf(session.createQuery(
"select count(*) from " + clazz.getName()).uniqueResult()));
// 计算分页数及起止记录
countPage();
// 预编译HQL语句
Query query = session.createQuery(hql);
// 为预编译HQL语句设置参数
for (int i = 0; i < parameters.length; i++) {
query.setParameter(i, String.valueOf(parameters[i]),
Hibernate.STRING);
}
// 获取List
list = query.setFirstResult((pageNum - 1) * pageSize).setMaxResults(
pageSize).list();
}
/**
* 重载的分页方法,接受预编译HQL语句,HQL多对象
*
* @param hibernateTemplate
* @param pageNum
* @param pageSize
* @param hql
* @param parameters
*/
private void execute(HibernateTemplate hibernateTemplate, int pageNum,
int pageSize, String hql, Object[] parameters) {
// 获取Hibernate当前的Session
Session session = hibernateTemplate.getSessionFactory()
.getCurrentSession();
// 获取记录总数
this.rowCount = hibernateTemplate.find(hql, parameters).size();
// 计算分页数及起止记录
countPage();
// 预编译HQL语句
Query query = session.createQuery(hql);
// 为预编译HQL语句设置参数
for (int i = 0; i < parameters.length; i++) {
query.setParameter(i, String.valueOf(parameters[i]),
Hibernate.STRING);
}
// 获取List
list = query.setFirstResult((pageNum - 1) * pageSize).setMaxResults(
pageSize).list();
}
/**
* 计算分页数及起止记录
*/
private void countPage() {
// 计算分页总数
if ((rowCount % pageSize) == 0) {
pageCount = rowCount / pageSize;
} else {
pageCount = rowCount / pageSize + 1;
}
if (pageCount == 0) {
pageCount = 1;
}
// 判断pageNum是否过界
if (pageNum > pageCount && rowCount != 0) {
pageNum = pageCount;
}
// 计算起止记录
startIndex = (pageNum - 1) * pageSize + 1;
endIndex = (pageNum) * pageSize;
}
}
常量值这里给出:
/* 默认分页尺寸及分页标记 */
public static final int DEFAULT_PAGE_SIZE = 10;
public static final int MAX_PAGE_SIZE = 1000;
public static final String NORMAL_MARK = "?";
public static final String START_MARK = ":_START_INDEX_";
public static final String END_MARK = ":_END_INDEX_";
/* 默认编码方式 */
public static final String ENCODING = "UTF-8";
分页类写好了,然后我们先看页面解析的FreeMarker宏:
<#-- 处理分页参数 -->
<#function getPageUrl pageNum>
<#local pageUrl=base+fullUrlWithoutPageInfo>
<#if pageUrl?ends_with("?")>
<#return pageUrl + "pageSize=" + pageSize + "&pageNum=" + pageNum>
<#else>
<#return pageUrl + "&pageSize=" + pageSize + "&pageNum=" + pageNum>
</#if>
</#function>
<#-- 全部或分页显示 -->
<#function getPageUrlResize size>
<#local pageUrl=base+fullUrlWithoutPageInfo>
<#if pageUrl?ends_with("?")>
<#return pageUrl + "pageNum=1&pageSize=" + size>
<#else>
<#return pageUrl + "&pageNum=1&pageSize=" + size>
</#if>
</#function>
<#-- 分页信息 -->
<#macro paging pagingListHt>
<#local pageCount=pagingListHt.pageCount>
<#local rowCount=pagingListHt.rowCount>
<#local pageNum=pagingListHt.pageNum>
<#local pageSize=pagingListHt.pageSize>
<#if rowCount == 0>
<#if useFlag?exists>
<div style="border:1px solid #666;padding:2 5 2 5;background:#efefef;color:#333">没有相关记录</div>
<#else>
<#assign useFlag = 1>
</#if>
<#else>
<table>
<tr>
<td style="line-height:150%">
共 ${rowCount} 条记录 ${pageCount} 页
<#if pageCount gt 1 && pageSize!=maxPageSize>
<span class="selectedPage" style="padding:2px 3px 0 3px"><a class="page" href="${getPageUrlResize(maxPageSize)}">全部显示</a></span>
<#elseif pageSize==maxPageSize>
<span class="selectedPage" style="padding:2px 3px 0 3px"><a class="page" href="${getPageUrlResize(defaultPageSize)}">分页显示</a></span>
</#if>
<#if (pageCount <= 11)>
<#local startPage = 1>
<#local endPage = pageCount>
<#elseif (pageNum + 5 > pageCount)>
<#local startPage = pageCount - 10>
<#local endPage = pageCount>
<#elseif (pageNum - 5 < 1)>
<#local startPage = 1>
<#local endPage = 11>
<#else>
<#local startPage = pageNum - 5>
<#local endPage = pageNum + 5>
</#if>
<#if (pageCount > 1)>
<#if (pageNum != 1)>
<#if (pageCount > 11)>
<a class="page" href="${getPageUrl(1)}" style="font-family:Webdings" title="首页">9</a>
</#if>
<a class="page" href="${getPageUrl(pageNum-1)}" style="font-family:Webdings" title="上页">3</a>
<#else>
<#if (pageCount > 11)>
<span style="font-family:Webdings;color:#999">9</span>
</#if>
<span style="font-family:Webdings;color:#999">3</span>
</#if>
<#list startPage..endPage as x><#if x=pageNum>
<span class="selectedPage">${x}</span>
<#else>
<span class="noSelectedPage"><a class="page" href="${getPageUrl(x)}">${x}</a></span>
</#if>
</#list>
<#if (pageCount != pageNum)>
<a class="page" href="${getPageUrl(pageNum+1)}" style="font-family:Webdings" title="下页">4</a>
<#if (pageCount > 11)>
<a class="page" href="${getPageUrl(pageCount)}" style="font-family:Webdings" title="尾页">:</a>
</#if>
<#else>
<span style="font-family:Webdings;color:#999">4</span>
<#if (pageCount > 11)>
<span style="font-family:Webdings;color:#999">:</span>
</#if>
</#if>
</#if>
</#if>
</td>
</tr>
</table>
</#macro>
需要定义的部分都写完了,下面来看怎么使用分页组件,首先需要抽象出一些公共的东西,写在基类Action中,如下:
/* 分页信息 */
protected int pageNum = 1;
protected int pageSize = Constants.DEFAULT_PAGE_SIZE;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getMaxPageSize() {
return Constants.MAX_PAGE_SIZE;
}
public int getDefaultPageSize() {
return Constants.DEFAULT_PAGE_SIZE;
}
public String getQueryStringWithoutPageNum() {
Map m = getParameters();
m.remove("pageNum");
return QueryUtil.getQueryString(m);
}
public String getFullUrlWithoutPageNum() {
return getRequest().getServletPath() + "?"
+ getQueryStringWithoutPageNum();
}
public String getQueryStringWithoutPageInfo() {
Map m = getParameters();
m.remove("pageNum");
m.remove("pageSize");
return QueryUtil.getQueryString(m);
}
public String getFullUrlWithoutPageInfo() {
return getRequest().getServletPath() + "?"
+ getQueryStringWithoutPageInfo();
}
使用到的QueryUtil工具类为:
package xxx.core.util;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import xxx.core.Constants;
public class QueryUtil {
/**
* 将请求参数还原为key=value的形式
*
* @param params
* @return
*/
public static String getQueryString(Map<String, Object> params) {
StringBuffer queryString = new StringBuffer(256);
Iterator it = params.keySet().iterator();
int count = 0;
while (it.hasNext()) {
String key = (String) it.next();
String[] param = (String[]) params.get(key);
for (int i = 0; i < param.length; i++) {
if (count == 0) {
count++;
} else {
queryString.append("&");
}
queryString.append(key);
queryString.append("=");
try {
queryString.append(URLEncoder.encode((String) param[i],
Constants.ENCODING));
} catch (UnsupportedEncodingException e) {
}
}
}
return queryString.toString();
}
}
分层设计中Action只负责业务逻辑的跳转执行,而真正的逻辑处理是在Service层进行的,那么还需要配置Service基类,其中的ht是HibernateTemplate对象,这是用过Spring进行的依赖注入,剩下的代码就是包装一下PagingListHt中的构造方法:
/**
* 获取ValueStack
*
* @return
*/
public ValueStack getValueStack() {
return ActionContext.getContext().getValueStack();
}
/**
* 获取分页的List
*
* @param hql
* @return
*/
public PagingListHt getPagingListHt(String hql) {
int pageNum = ((Integer) getValueStack().findValue("pageNum"))
.intValue();
int pageSize = ((Integer) getValueStack().findValue("pageSize"))
.intValue();
return new PagingListHt(ht, pageNum, pageSize, hql);
}
/**
* 获取分页的List
*
* @param hql
* @param clazz
* @return
*/
public PagingListHt getPagingListHt(String hql, Class clazz) {
int pageNum = ((Integer) getValueStack().findValue("pageNum"))
.intValue();
int pageSize = ((Integer) getValueStack().findValue("pageSize"))
.intValue();
return new PagingListHt(ht, pageNum, pageSize, hql, clazz);
}
/**
* 获取分页的List,预编译HQL语句
*
* @param hql
* @param parameters
* @return
*/
public PagingListHt getPagingListHt(String hql, Object[] parameters) {
int pageNum = ((Integer) getValueStack().findValue("pageNum"))
.intValue();
int pageSize = ((Integer) getValueStack().findValue("pageSize"))
.intValue();
return new PagingListHt(ht, pageNum, pageSize, hql, parameters);
}
/**
* 获取分页的List,预编译HQL语句
*
* @param hql
* @param parameters
* @param clazz
* @return
*/
public PagingListHt getPagingListHt(String hql, Object[] parameters,
Class clazz) {
int pageNum = ((Integer) getValueStack().findValue("pageNum"))
.intValue();
int pageSize = ((Integer) getValueStack().findValue("pageSize"))
.intValue();
return new PagingListHt(ht, pageNum, pageSize, hql, parameters, clazz);
}
东西都准备齐全了,下面来看看如何使用吧,在Service中进行查询操作:
// 获取用户列表
private static final String HQL_GET_ALL_USERS = "from User order by u.userId asc";
public PagingListHt getAllUsers() {
return getPagingListHt(HQL_GET_ALL_USERS);
}
在Action中就更简单了:
private PagingListHt userList;
public PagingListHt getUserList() {
return userList;
}
/**
* 用户列表
*/
public String userList() throws Exception {
userList = getServMgr().getUserService().getAllUsers ();
return "userList";
}
在页面只要引入了事先写好的分页宏,一条语句就可以了:
<div align="right"><@p.paging userList /></div>
要注意的是使用FreeMarker的list指令来遍历PagingListHt对象时,写法和普通的list稍有区别,应该是:
<#list userList.list as news>
</#list>
为什么加了.List?看看PagingListHt是怎么设计的吧,其实就是getList()方法,因为这个分页的List是我们自定义的类,而不是Java类库的List,要是不写,那就是遍历PagingList类型,FreeMarker显然不能进行。
一家之言,仅供参考,但此例为作者原创,您可以对其进行适当改造应用于您自己的系统中,如果发现问题,欢迎交流。后续将对SQL分页进行说明,当然还是这种方式的,只是分野类设计的不同罢了。