在進行Web開發時,很多地方要使用到分頁這個技術。.NET架構中是直接內建好的控件,可以直接使用了。而Java中沒有,需要程式員自己來編寫實作過程。雖然分頁過程并不複雜,但是要完美的實作是需要時間來考驗的。
先說說分頁的基本原理,分頁顯示就是在資料量大時頁面上可以隻顯示所有資料的一部分,然後點選頁面連接配接可以跳到所需的位置繼續檢視。需求很明确,實作方法大緻分為兩種:一種是實體分頁,也就是真分頁,這種方法是在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方式,其他的方式正在思考中:
常量值這裡給出:
/* 預設分頁尺寸及分頁标記 */
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)}" target="_blank" rel="external nofollow" >全部顯示</a></span>
<#elseif pageSize==maxPageSize>
<span class="selectedPage" style="padding:2px 3px 0 3px"><a class="page" href="${getPageUrlResize(defaultPageSize)}" target="_blank" rel="external nofollow" >分頁顯示</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)}" target="_blank" rel="external nofollow" style="font-family:Webdings" title="首頁">9</a>
</#if>
<a class="page" href="${getPageUrl(pageNum-1)}" target="_blank" rel="external nofollow" 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)}" target="_blank" rel="external nofollow" >${x}</a></span>
</#if>
</#list>
<#if (pageCount != pageNum)>
<a class="page" href="${getPageUrl(pageNum+1)}" target="_blank" rel="external nofollow" style="font-family:Webdings" title="下頁">4</a>
<#if (pageCount > 11)>
<a class="page" href="${getPageUrl(pageCount)}" target="_blank" rel="external nofollow" 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分頁進行說明,當然還是這種方式的,隻是分野類設計的不同罷了。