天天看點

基于Struts2的Hibernate分頁實作(FreeMarker進行視圖解析)

在進行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分頁進行說明,當然還是這種方式的,隻是分野類設計的不同罷了。

繼續閱讀