星期四, 4月 29, 2004

Pageable DisplayTag with Hibernate

啊~~~~~~ 昨、今兩天都跟 DisplayTag 大戰。搞了好久總算搞定了。DisplayTag 這個 taglib 寫的真很不錯,介面很好看,而且 pageable, sort, group 使用上都很簡單。最後還有無限變化的 decorator,設計的人真的非常有經驗!然而它還是有一些地方還不夠完美。使用它的 page 功能,比方說120筆分12頁,每頁10筆好了,它沒有機制可以讓你一次只讀10筆。一定要一次讀完 120 筆才行。這樣分頁的用意就少了一大半了!這兩天就都花在這玩意上,浪費了好多時間。最後的答案還算差強人意。

HibernatePageList page = new HibernatePageList(pageSize, "from Foo f where f.id >20" ) ;
page.loadPage(2) ;

HibernatePageList 是一個帶有 iterator() method 的 list (不全是,沒有 implement List) 。這個物件裡面會有空的 list,裡面全部裝滿 null,整個 list 的大小就是 上面 "from... >20" query 出來的筆數。當執行 loadPage(2) 時,內部會去抽第二頁所需的資料,然後 HibernatePageList這個物件的內部的List 的第二頁會被填滿(其餘保持 null) 。如此當使用者點到分頁的第二頁時,在 servlet/action 裡執行 loadPage(2),就可以讀取需要顯示的值。

displayTag 本身並沒有提供讀取目前是第幾個分頁的功能。我們只好從它的 request parameter 下手;當 displayTag 分頁時,比如說第三頁,它會在 url 後面接上 "&d-12345-p=3" 這樣的 parameter 。12345 是它產生的隨機碼,有時是四個數字。抓取這個數字可用 regular expression : params.match("d-[0-9]+-p") 這個 pattern 來找,找到後便可傳給 loadPage. 所以最後可簡化為:

page.loadPage(request) ; // get current pageNo from request

完整的程式碼如下:

public class HibernatePageList {

private List cachedList;
private int pageNo;
private int pageSize;
private int totalSize;
private String queryString;

public HibernatePageList(int pageSize, int totalSize, String queryString) {

this.queryString = queryString;
this.pageSize = pageSize;
this.totalSize = totalSize;

// initialize full list with null;
cachedList = new ArrayList(totalSize);
for (int i = 0; i < totalSize; i++) {
cachedList.add(null);
}
}
public HibernatePageList(int pageSize, String fromBasedQueryString)
throws HibernateException {
this(
pageSize,
getTotalSize(fromBasedQueryString),
fromBasedQueryString);
}
private static int getTotalSize(String fromBasedQueryString)
throws HibernateException {

return (
(Integer) HibernateUtil
.currentSession()
.iterate("select count(*) " + fromBasedQueryString)
.next())
.intValue();
}
public void loadPage(final int pageNo) throws HibernateException {
//check cache has data:
if (pageIsCached(pageNo)) {
return;
}
Query query = HibernateUtil.currentSession().createQuery(queryString);
query.setMaxResults(pageSize);
query.setFirstResult(getPageStartRow(pageNo));

final Iterator iter = query.iterate();
for (int i = getPageStartRow(pageNo); iter.hasNext(); i++) {
cachedList.set(i, iter.next());
}
}

public void loadPage(HttpServletRequest request)
throws HibernateException {
loadPage(getDisplayTagPageNo(request));
}

/**
* extract page no from request of display tag
*
* @param request
* @return
* @throws NumberFormatException
*/
public int getDisplayTagPageNo(HttpServletRequest request)
throws NumberFormatException {
Enumeration names = request.getParameterNames();
int pageNo = 1;
while (names.hasMoreElements()) {
String parameter = (String) names.nextElement();
if (parameter.matches("d-[0-9]+-p")) {
pageNo = Integer.parseInt(request.getParameter(parameter));
break;
}
}
return pageNo;
}
private int getPageEndRow(int pageNo) {
return Math.min(pageNo * pageSize, totalSize);
}
private int getPageStartRow(int pageNo) {
return (pageNo - 1) * pageSize;
}

private boolean pageIsCached(final int pageNo) {
boolean pageIsCached = true;
for (int i = getPageStartRow(pageNo);
i < getPageEndRow(pageNo) && pageIsCached;
i++) {
if (cachedList.get(i) == null) {
pageIsCached = false;
}
}
return pageIsCached;
}
public Object get(int index) {
return cachedList.get(index);
}
public boolean isEmpty() {
return cachedList.isEmpty();
}
public Iterator iterator() {
return cachedList.iterator();
}
public int size() {
return cachedList.size();
}
public int getPageSize() {
return pageSize;
}
public String getQueryString() {
return queryString;
}
}