2006-09-21

分享一个通用数据库分页方案

关键字: struts 模式
subversion地址如下:
https://svn.sourceforge.net/svnroot/powerstone/ps_paging
技术架构:
web层可以是任意的MVC框架,目前只实现了SpringMVC和Struts的版本(Struts版已经在weblogic7下跑通,tomcat5还不行);
dao层也没有任何限制,只要按照范例中的模式来做既可;
最终页面上用一个标签来显示翻页按钮等。
btw:谁有兴趣想写个webwork版本的,可以和我联系,或直接发个patch给我
---------------------------------
工作原理:想法很简单,就是应用设计模式里的"模板方法模式",把分页计算的逻辑封装在一个抽象父类(PagingController)里面,留下三个抽象方法:doHandleRequest(处理请求)、getTotalRecordsNumber(计算总记录数)、makeSearchCriteria(构造查询条件,可选)供子类型重写,使得子类型尽量的简单,让我们可以专注于业务逻辑而不需要处理分页逻辑。示例如下:
public class UserPagingController extends PagingController {
	private final Logger logger = Logger.getLogger(this.getClass());
	private UserDAO userDAO;
	protected ModelAndView doHandleRequest(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		Map map = super.getSearchCriteria(request);
		int beginNo = super.computeRecordsBeginNo(request);
		int recordsNumber = super.computeRecordsNumberToRead(request);
		List usersList = userDAO.findUsers(map, beginNo, recordsNumber);
		logger.debug(usersList);
		return new ModelAndView("usersList", "usersList", usersList);
	}
	protected int getTotalRecordsNumber(HttpServletRequest request) {
		int result = userDAO.countUsers(super.getSearchCriteria(request));
		logger.debug("users number================:" + result);
		return result;
	}
	protected Map makeSearchCriteria(HttpServletRequest request) {
		HashMap map = new HashMap();
		String firstName = request.getParameter("firstName");
		if (firstName != null && firstName.trim().length() > 0) {
			map.put("firstName", firstName);
		}
		logger.debug(map);
		return map;
	}
	public void setUserDAO(UserDAO userDAO) {
		this.userDAO = userDAO;
	}
}

Struts版原理相同。
在页面显示翻页链接只需一行代码:
<page:pagebar url="/user_query.html?" bordercolor="#F4F9FF" bgcolor="#F4F9FF"/>
评论
xvta 2007-05-01
建议不要把那么多jar包放到svn上,用mvn会更方便,毕竟不是所有人都有宽带嘛,呵呵
robbin 2007-05-01
ideafrog 写道
真是奇怪,大家都说count的效率非常高,ms级别就出来,但是我现在最大的问题就是这个总数问题了,几十万的记录,count一下基本需要10秒以上,还算是快的。


什么原因呢。


对该表进行ANALYZE TABLE mytablename COMPUTE STATISTICS
之后,count效率可以达到ms级别,但是这是不可能在程序执行过程中去做这些事情的。

还在郁闷中...


也许你的count导致了对全表数据的扫描。你可以试试看select count(PK) from ...,仅对主键进行count。
ideafrog 2007-05-01
真是奇怪,大家都说count的效率非常高,ms级别就出来,但是我现在最大的问题就是这个总数问题了,几十万的记录,count一下基本需要10秒以上,还算是快的。


什么原因呢。


对该表进行ANALYZE TABLE mytablename COMPUTE STATISTICS
之后,count效率可以达到ms级别,但是这是不可能在程序执行过程中去做这些事情的。

还在郁闷中...
suyulin6688 2007-01-12
daquan198163 写道
subversion地址如下:
https://svn.sourceforge.net/svnroot/powerstone/ps_paging
技术架构:
web层可以是任意的MVC框架,目前只实现了SpringMVC和Struts的版本(Struts版已经在weblogic7下跑通,tomcat5还不行);


daquan198163:

现在,你的这种分页方案的Struts版本在tomcat5下可以跑通了吗?
daquan198163 2006-11-01
rkihabara 写道
daquan198163 写道
Ivan Li 写道
楼主说说如何实现数据库间通用,又保证效率?

这个分页方案只是用“模板方法”模式把分页逻辑封装在了前端控制器,因为我发现分页最麻烦的地方就是在action里面算页数、记录数这些东西。
所以说,这个方案与数据库访问方法无关,自燃就在数据库间通用了。
和效率相关的地方是:每显示一页数据都至少访问两次数据库,分别是计算总记录数和查询当前页记录

这里不明白,总记录数为什么也要每次都查询呢,似乎弄一次就可以了~
---
不要总记录数的情况我想也就限于论坛这类并不关心后面数据的应用吧,大多数情况下人们还是喜欢知道我在怎样的一个范围内操作~

因为数据库时刻都在变化呀,符合条件的结果也跟着变化
rkihabara 2006-11-01
daquan198163 写道
Ivan Li 写道
楼主说说如何实现数据库间通用,又保证效率?

这个分页方案只是用“模板方法”模式把分页逻辑封装在了前端控制器,因为我发现分页最麻烦的地方就是在action里面算页数、记录数这些东西。
所以说,这个方案与数据库访问方法无关,自燃就在数据库间通用了。
和效率相关的地方是:每显示一页数据都至少访问两次数据库,分别是计算总记录数和查询当前页记录


这里不明白,总记录数为什么也要每次都查询呢,似乎弄一次就可以了~

---

不要总记录数的情况我想也就限于论坛这类并不关心后面数据的应用吧,大多数情况下人们还是喜欢知道我在怎样的一个范围内操作~
applefan 2006-10-27
我也做过一个分页类,传一些参数,获得一个包含分页所需元素的HashMap.
----------------------------------------------
/**
* 获得只显示10条分页标记所需的元素,并打包成HashMap
* @param queryPage 当前页号 页码从1开始
* @param totalRcdNum 总记录数
* @param currSize 当前页获得的记录数
* @param maxRow 一页要显示的行数
*/
public static HashMap paginationPer10HashMap(int queryPage,int totalRcdNum,int currSize,int maxRow){
int totalPageNum = totalRcdNum/maxRow+1; //总页数
if (totalRcdNum %maxRow == 0 )
totalPageNum = totalPageNum -1;
int startNum = (queryPage-1)*maxRow +1; //当前页起始记录数
int endNum = startNum - 1 + currSize; //当前页结束记录数
boolean hasPre = false;
boolean hasNext = false;
if(startNum>maxRow)
hasPre = true;
if(endNum<totalRcdNum)
hasNext = true;
int startP = 1;//页码条开始页码
int endP = 1;//页码条结束页码
if(totalPageNum>1){

if(totalPageNum<=10)
{
startP = 1;
endP = totalPageNum;
}else
{
if(queryPage<=5)
{
startP = 1;
endP = startP+9;
}else
{
if(totalPageNum>=queryPage+5)
{
startP = queryPage-4;
endP = startP+9;
}else
{
startP = totalPageNum-9;
endP = totalPageNum;
}
}
}
}
/* System.out.println("<paginationElement>\r\n" +
"<startRcdNum>"+startNum+"</startRcdNum>\r\n" +
"<endRcdNum>"+endNum+"</endRcdNum>\r\n" +
"<totalRcdNum>"+totalRcdNum+"</totalRcdNum>\r\n" +
"<totalPageNum>"+totalPageNum+"</totalPageNum>\r\n" +
"<currPageNum>"+queryPage+"</currPageNum>\r\n" +
"<startPageNum>"+startP+"</startPageNum>\r\n"+
"<endPageNum>"+endP+"</endPageNum>\r\n" +
"<hasPre>"+hasPre+"</hasPre>\r\n" +
"<hasNext>"+hasNext+"</hasNext>\r\n" +
"</paginationElement>\r\n");*/

HashMap hashMap=new HashMap();
hashMap.put("startRcdNum",startNum);//当前页起始记录数
hashMap.put("endRcdNum",endNum);//当前页结束记录数
hashMap.put("totalRcdNum",totalRcdNum);//总记录数
hashMap.put("totalPageNum",totalPageNum);//总页数
hashMap.put("currPageNum",queryPage);//当前页号
hashMap.put("startPageNum",startP);//页码条开始页码
hashMap.put("endPageNum",endP);//页码条结束页码
hashMap.put("hasPre",hasPre);//是否有上一页
hashMap.put("hasNext",hasNext);//是否有下一页

return hashMap;
}
---------------------------------------------
补充一下:我这个最多只显示10个页面码,如 《 2 3 4 5 6 7 8 9 10 11 》,样式可以自己调了
ddandyy 2006-10-26
感觉这东西想法很不错 就是不知道效能怎么样
前段时间也想过类似的东西
不过想来想去 感觉效能上还是要差一点
ddandyy 2006-10-26
好像还没听说统计里不需要总数 只需要前几百的

您的客户真的太好了

为客户着想才应该把总数弄出来 否则只出来300 说是后面还有 那么到底有多少? 400? 400000?

感觉你更是为自己着想 少写东西
lixigua 2006-10-26
count一次真得要花那么多时间吗?

难不成大家的数据库如此脆弱到要考虑到是否count这样的细节了?
ecsun 2006-10-26
Action中的一个基础调用:


public ActionForward search(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response,
			String queryName, String[] params, Object[] values,
			String forwardName) {
		try {
			// 取得数据索引
			int startIndex = getHttp().getCurrentIndex(request);
			// 查询得到一页数据,按指定方式排序(在HQL中指定)
			PaginationSupport ps = getService().getBaseService().getPage(queryName, params,
					values, startIndex);
			// 将查询结果存储到request 中
			getHttp().setPaginationSupport(ps, request);
			// 按Struts-Config.xml中的配置进行转发。
			return mapping.findForward(forwardName);
		} catch (Exception e) {
			ActionMessages messages = new ActionMessages();
			messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
					"com.myecsun.common.search.failer"));
			super.saveMessages(request, messages);
			return this.throwErrors(mapping, form, request, response);
		}
	}
ecsun 2006-10-26
DAO中的分页处理(基于Hibernate):

/**
   * =========================================================================
   * 取得一页数据
   *
   * =========================================================================
   */

  //---------------------------------------------------------------------
  //无参数取得一页数据
  //--------------------------------------------------------------------
  public PaginationSupport getPage(String queryName) {
     return getPage(queryName,(String []) null,(Object []) null,PaginationSupport.PAGESIZE,0);
   }

   public PaginationSupport getPage(String queryName, int startIndex) {
     return getPage(queryName,(String []) null,(Object []) null,PaginationSupport.PAGESIZE,startIndex);
   }

  public PaginationSupport getPage(String queryName, int pageSize,
                                   int startIndex) {
    return getPage(queryName,(String []) null,(Object []) null,pageSize,startIndex);
  }

  //---------------------------------------------------------------------------
  //使用带一个参数的HQL取得一页数据
  //---------------------------------------------------------------------------
  public PaginationSupport getPage(String queryName, String param, Object value) {
      return getPage(queryName,new String [] {param},new Object [] {value},PaginationSupport.PAGESIZE,0);
    }

    public PaginationSupport getPage(String queryName, String param, Object value,
                                     int startIndex) {
      return getPage(queryName,new String [] {param},new Object [] {value},PaginationSupport.PAGESIZE,startIndex);
  }
  public PaginationSupport getPage(String queryName, String param,
                                   Object value, int pageSize, int startIndex) {

    return getPage(queryName,new String [] {param},new Object [] {value},pageSize,startIndex);
  }

//------------------------------------------------------------------------------
//使用带有一系列参数的HQL取得一页数据
//.------------------------------------------------------------------------------
  public PaginationSupport getPage(String queryName, String[] params,
                                  Object[] values) {
   return getPage(queryName,params,values,PaginationSupport.PAGESIZE,0);
 }

 public PaginationSupport getPage(String queryName, String[] params,
                                  Object[] values, int startIndex) {
   return getPage(queryName,params,values,PaginationSupport.PAGESIZE,startIndex);
 }


  public PaginationSupport getPage(final String queryName, final String[] params,
                                   final Object[] values, final int pageSize,
                                   final int startIndex) {
    if (params != null && values != null && params.length != values.length) {
                        throw new IllegalArgumentException("Length of paramNames array must match length of values array");
                }

    return (PaginationSupport) getHibernateTemplate().execute(
        new HibernateCallback() {
       public Object doInHibernate(Session session) throws HibernateException{
         StringUtil stringUtil=new StringUtil();

         Query queryObject=session.getNamedQuery(queryName);
         if(log.isDebugEnabled()){
           log.debug("Query String In getPage is :"+queryObject.getQueryString());
         }
         if (values != null) {
           for (int i = 0; i < values.length; i++) {
             queryObject.setParameter(params[i],values[i]);
           }
         }
         queryObject.setFirstResult(startIndex).setMaxResults(pageSize);
         String hql=stringUtil.createCountHQL(queryObject.getQueryString());
         Query countQuery=session.createQuery(hql);
         if(values!=null){
             for (int j = 0; j < values.length; j++) {
                 countQuery.setParameter(params[j], values[j]);
             }
         }
        int totalCount= ((Integer) countQuery.list().iterator().next()).intValue();
         List items=queryObject.list();
         PaginationSupport ps=new PaginationSupport(items,totalCount,pageSize,startIndex);
         return ps;
       }
    }
    );
  }
ecsun 2006-10-26
不够通用,其实前一段时间robbin给了一个很不错的实现,我整理了一下,现在用在我的代码中,给大家一块分享:
1.分而工具类
package com.myecsun.util.helper;

import java.util.List;

import org.apache.commons.collections.FastArrayList;

public class PaginationSupport {

  public final static int PAGESIZE = 30;

  private int pageSize = PAGESIZE;

  private List items;

  private int totalCount;

  private int[] indexes = new int[0];

  private int startIndex = 0;
   
  public PaginationSupport(List items){
	  setItems(items);
	  setPageSize(items.size());
	  setTotalCount(items.size());
	  setStartIndex(0);
  }
  public PaginationSupport(List items, int totalCount) {
    setPageSize(PAGESIZE);
    setTotalCount(totalCount);
    setItems(items);
    setStartIndex(0);
  }

  public PaginationSupport(List items, int totalCount, int startIndex) {
    setPageSize(PAGESIZE);
    setTotalCount(totalCount);
    setItems(items);
    setStartIndex(startIndex);
  }

  public PaginationSupport(List items, int totalCount, int pageSize,
                           int startIndex) {
    setPageSize(pageSize);
    setTotalCount(totalCount);
    setItems(items);
    setStartIndex(startIndex);
  }


  public List getItems() {
    return items;
  }

  public void setItems(List items) {
    this.items = items;
  }

  public int getPageSize() {
    return pageSize;
  }

  public void setPageSize(int pageSize) {
    this.pageSize = pageSize;
  }

  public int getTotalCount() {
    return totalCount;
  }

  public void setTotalCount(int totalCount) {
    if (totalCount > 0) {
      this.totalCount = totalCount;
      int count = totalCount / pageSize;
      if (totalCount % pageSize > 0) {
        count++;
      }
      indexes = new int[count];
      for (int i = 0; i < count; i++) {
        indexes[i] = pageSize * i;
      }
    }
    else {
      this.totalCount = 0;
    }
  }

  public int[] getIndexes() {
    return indexes;
  }

  public void setIndexes(int[] indexes) {
    this.indexes = indexes;
  }

  public int getStartIndex() {
    return startIndex;
  }

  public void setStartIndex(int startIndex) {
    if (totalCount <= 0) {
      this.startIndex = 0;
    }
    else if (startIndex >= totalCount) {
      this.startIndex = indexes[indexes.length - 1];
    }
    else if (startIndex < 0) {
      this.startIndex = 0;
    }
    else {
      this.startIndex = indexes[startIndex / pageSize];
    }
  }

  public int getNextIndex() {
    int nextIndex = getStartIndex() + pageSize;
    if (nextIndex >= totalCount) {
      return getStartIndex();
    }
    else {
      return nextIndex;
    }
  }

  public int getPreviousIndex() {
    int previousIndex = getStartIndex() - pageSize;
    if (previousIndex < 0) {
      return 0;
    }
    else {
      return previousIndex;
    }
  }
  
  public List transItems(){
	  CollectionUtil util=new CollectionUtil();
	  return util.transCollection(this.getItems());
  }
  public List transItems(List items){
	 CollectionUtil util=new CollectionUtil();
	return util.transCollection(items);
  }

}
kabbesy 2006-10-25
javaest 写道
唉,大家都不用CacheRowSet嘛?????性能可能不是最好的.但也不是最坏的.但在代码方面,确是最干净的.我的项目在用,也证明可用.大家好好的封装一下,就可以完全通用的去用了.


关于CachedRowSet的讨论的确非常非常少,我觉得挺好的东西
xmx111 2006-10-12
客户是关心的,如果count数大就不翻,小的话就翻翻
xmx111 2006-10-12
客户是关心的,如果count数大就不翻,小的话就翻翻
抛出异常的爱 2006-10-10
。。。。。。你的客户是作软件出身的。。。。
cnliuxj 2006-10-10
抛出异常的爱 写道
有哪个客户会关心
N种条件后的总记录数?

是的,我的客户关心!
javaest 2006-10-09
唉,大家都不用CacheRowSet嘛?????性能可能不是最好的.但也不是最坏的.但在代码方面,确是最干净的.我的项目在用,也证明可用.大家好好的封装一下,就可以完全通用的去用了.
daquan198163 2006-09-28
谢谢同学们的支持与肯定,本贴已经从一个被投了10票的垃圾贴变成良好贴,如今又成了首页上的“本周社区热点话题”。
在这里,我首先要感谢我的妈妈。。。。。。。。
还有我的姐姐,她10月8号就要嫁人了,我不能回去,愿她婚姻生活美满
还有,唉,谁扔的鸡蛋
daquan198163
  • 浏览: 71837 次
  • 性别: Icon_minigender_1
  • 来自: 吉林->北京->上海
  • 详细资料
搜索本博客
我的相册
F7063e49-a7bb-47bc-8dd9-89c20c8b52d3-thumb
4939384202000irl_0
共 3 张
最近加入圈子
存档
最新评论