发新话题
打印

解决MVC下分页显示的问题

解决MVC下分页显示的问题

  前几天做一个系统,用到的是Tomcat struts Mysql的MVC框架。由于很多模块都需要分页,想写一个分页的方法。常见的方法每次翻页都查询一次数据库,从ResultSet中只取出一页数据(使用rs.last();rs.getRow()获得总计录条数,使用rs.absolute()定位到本页起始记录)。这种方式在某些数据库(如oracle)的JDBC实现中差不多也是需要遍历所有记录,实验证明在记录数很大时速度非常慢。
  至于缓存结果集ResultSet的方法则完全是一种错误的做法。因为ResultSet在Statement或Connection关闭时也会被关闭,如果要使ResultSet有效势必长时间占用数据库连接。
  因此比较好的分页做法应该是每次翻页的时候只从数据库里检索页面大小的块区的数据。这样虽然每次翻页都需要查询数据库,但查询出的记录数很少,网络传输数据量不大,如果使用连接池更可以略过最耗时的建立数据库连接过程。而在数据库端有各种成熟的优化技术用于提高查询速度,比在应用服务器层做缓存有效多了。

  所以我用到了从数据库中查询当前页面记录的办法。由于设计到公司核心代码的缘故,我就简单的用一个图书的例子介绍一下是我的思路:

开发思路:
  我是用Struts的,当然就要使用到MVC这个模式,分页的时候也是这样的。
  首先要有一个和数据库链接的bean,我们暂时叫DBUtil吧,这里面封装了很多和数据库有关的东西,比如有查询,修改,插入等方法,这是一个基本的类,这里我们用到的是查询,这个方法返回的数据类型是Object[][]。这里大家要注意一下,你也可以返回别的类型,但是一定要注意把后面对应的程序也修改一下。一下是这个类的部分代码:

package com.model;

import com.attribute.Constants;

import java.sql.*;
import java.util.*;

/**
* Created by IntelliJ IDEA.
* User: 7612CE
* Date: 2005-6-2
* Time: 21:41:38
* To change this template use Options | File Templates.
*/
public class DBUtil {

String sDBDriver=Constants.DBDriver;
String url=Constants.DBUrl;
String dbUser=Constants.DBUser;
String dbPassword=Constants.DBPassword;
Connection conn=null;
PreparedStatement stmt=null;
ResultSet rs=null;

public DBUtil()throws ClassNotFoundException{
try{
Class.forName(sDBDriver);
conn=DriverManager.getConnection(url,dbUser,dbPassword);
}catch(Exception e){
System.out.println("DBUtil():" e.getMessage());
}
}
/**
* Search some record in object table
* @param sql sql segment
* @ param map values for match
* @return Collection
*/
public Object[][] doSearch(String sql,Object [] data)throws SQLException{
PreparedStatement stmt = conn.prepareStatement(sql);
for(int i=0;data!=null

TOP

  由于是写分页的,当然也是要有一个page的类,具体代码如下,由于有很多注释,不用一一介绍了:

package com.util;

/**
* Title: 分页对象

* Description: 用于包含数据及分页信息的对象

*Page类实现了用于显示分页信息的基本方法,但未指定所含数据的类型,
*可根据需要实现以特定方式组织数据的子类,

*如RowSetPage以RowSet封装数据,ListPage以List封装数据

* Copyright: Copyright (c) 2002

* @author evan_zhao@hotmail.com

* @version 1.0
*/
public class Page implements java.io.Serializable {
public static final Page EMPTY_PAGE = new Page();
public static final int DEFAULT_PAGE_SIZE = 2;
public static final int MAX_PAGE_SIZE = 2;

** int myPageSize = DEFAULT_PAGE_SIZE;

** int start;
** int avaCount,totalSize;
** Object[][] data=null;

** int currentPageno;
** int totalPageCount;

/**
* 默认构造方法,只构造空页
*/
public Page(){
this.init(0,0,0,DEFAULT_PAGE_SIZE,null);
}
/**
*构造分页对象
*@param crs 包含一页数据的OracleCachedRowSet
*@param start 该页数据在数据库中的起始位置
*@param totalSize 数据库中包含的记录总数
*@param pageSize 本页能容纳的记录数
*/
public Page(Object[][] crs, int start, int totalSize, int pageSize) {
try{
int avaCount=0;
if (crs!=null) {
avaCount = crs.length;
}
data = crs;
this.init(start,avaCount,totalSize,pageSize,data);
}catch(Exception ex){
throw new RuntimeException(ex.toString());
}
}

/**
* 分页数据初始方法
* @param start 本页数据在数据库中的起始位置
* @param avaCount 本页包含的数据条数
* @param totalSize 数据库中总记录条数
* @param pageSize 本页容量
* @param data 本页包含的数据
*/
protected void init(int start, int avaCount, int totalSize, int pageSize, Object[][] data){

this.avaCount =avaCount;
this.myPageSize = pageSize;

this.start = start;
this.totalSize = totalSize;

this.data=data;

//System.out.println("avaCount:" avaCount);
//System.out.println("totalSize:" totalSize);
if (avaCount>totalSize) {
//throw new RuntimeException("记录条数大于总条数?!");
}

this.currentPageno = (start -1)/pageSize  1;
this.totalPageCount = (totalSize   pageSize -1) / pageSize;

if (totalSize==0

TOP

  由于是用mvc这样的框架,所以在c这一层仅仅起到一个转向和收集页面信息的作用,所以这里我有写了一个分页查询的bean,暂时命名是PageCtBean,代码如下:

package com.util;

import com.model.DBUtil;
import java.sql.*;
/**
* Created by IntelliJ IDEA.
* User: 7612ce
* Date: 2005-6-23
* Time: 10:36:57
* To change this template use Options | File Templates.
*/
public class PageCtBean {
public final static int MAX_PAGE_SIZE = Page.MAX_PAGE_SIZE;
protected String countSQL, querySQL;
protected int pageNo,pageSize,startIndex,totalCount;
protected javax.sql.RowSet rowSet;
protected Page setPage;
protected Object[][] objTables=null;
protected Object[][] objCount=null;
protected Object[] obj=null;

public PageCtBean(){

}
/**
* 构造一查询出所有数据的PageStatement
* @param sql query sql
*/
public PageCtBean(String sql){
this(sql,1,MAX_PAGE_SIZE);
}
/**
* 构造一查询出当页数据的PageStatement
* @param sql query sql
* @param pageNo 页码
*/
public PageCtBean(String sql, int pageNo){
this(sql, pageNo, Page.DEFAULT_PAGE_SIZE);
}

/**
* 构造一查询出当页数据的PageStatement,并指定每页显示记录条数
* @param sql query sql
* @param pageNo 页码
* @param pageSize 每页容量
*/
public PageCtBean(String sql, int pageNo, int pageSize){
this.pageNo = pageNo;
this.pageSize = pageSize;
this.startIndex = Page.getStartOfAnyPage(pageNo, pageSize);
this.querySQL = intiQuerySQL(sql, this.startIndex, pageSize);
}

/**
*生成查询一页数据的sql语句
*@param sql 原查询语句
*@ startIndex 开始记录位置
*@ size 需要获取的记录数
*/
protected String intiQuerySQL(String sql, int startIndex, int size){
StringBuffer querySQL = new StringBuffer();
querySQL.append(sql)
.append(" limit ")
.append(startIndex-1 )
.append(",").append(size);
return querySQL.toString();
}

/**
*使用给出的对象设置指定参数的值
*@param obj 包含参数值的对象
*/
public void setObject(Object obj[]) throws SQLException{
this.obj=obj;
}

public void setCountSql(String sql){
this.countSQL=sql;
}
/**
* 执行查询取得一页数据,执行结束后关闭数据库连接
* @return RowSetPage
* @throws SQLException
*/
public Page executeQuery() throws ClassNotFoundException{
System.out.println("executeQueryUsingPreparedStatement");
DBUtil DBean=new DBUtil();
try{
objCount = DBean.doSearch(this.countSQL,obj);
if (!objCount.equals(null)){
System.out.println("the count is =" objCount[0][0].toString());
totalCount =Integer.parseInt(objCount[0][0].toString()) ;
System.out.println("the count is =" totalCount);
} else {
totalCount = 0;
}
if (totalCount < 1 )
return null;
objTables= DBean.doSearch(this.querySQL,obj);
this.setPage = new Page(this.objTables,startIndex,totalCount,pageSize);
return this.setPage;
}catch(SQLException sqle){
//System.out.println("executeQuery SQLException");
sqle.printStackTrace();

}catch(Exception e){
e.printStackTrace();
throw new RuntimeException(e.toString());
}
return null;
}
/**
*取封装成Page的查询结果
*@return Page
*/
public Page getPage() {
return this.setPage;
}
}

TOP

  接下来是Action里面的代码,暂时定义这个Action 是ComputerAction,代码如下:

package com.action;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.form.LoginForm;
import com.util.LoginBean;
import com.util.ComputerBean;
import com.util.BaseView;
import com.util.Page;
import com.model.FunctionManager;
import com.attribute.Constants;
import com.attribute.SQLBook;

import java.sql.ResultSet;

/**
* Created by IntelliJ IDEA.
* User: 7612CE
* Date: 2005-6-14
* Time: 13:31:34
* To change this template use Options | File Templates.
*/
public class ComputerAction extends BaseAction {
** Log log=LogFactory.getLog(this.getClass().getName());

public ActionForward execute(ActionMapping mapping,
ActionForm Form,
HttpServletRequest request,
HttpServletResponse response){
boolean flag=false;
Object[][] obj=null;
Page page=new Page();
Integer id=new Integer(Constants.id);
String sql=SQLBook.Computer_select_SQL;
BaseView view=new BaseView();
String pageNo = request.getParameter("pageNo");
if (pageNo == null || pageNo.equals("null") || pageNo.length() <= 0) {
pageNo = "1";
}
try{
Object[] table={id};
ComputerBean computerBean=new ComputerBean();
computerBean.setBeanDate(sql,table);
computerBean.setPageNo(pageNo);
page=computerBean.getResult();
obj=page.getData();
if(!obj.equals(null)){
flag=true;
view.setObject(obj);
request.setAttribute(Constants.QUERY_RESULT,view);
request.setAttribute("page",page);
}

}catch(Exception ex){
ex.printStackTrace();
}
log.info("system print the flag =" flag);
if(flag){
return(mapping.findForward(Constants.FORWARD_SUCCESS));
}else{
return(mapping.findForward(Constants.FORWARD_FAILURE));
}
}
}

TOP

  由于Action里面用到了查询的SQL语句,所有SQL语句写在一个特定的类中,这个类名定义为SQLBook,代码如下:

public class SQLBook {

public SQLBook(){}
/**
* computer sql
*/

public static final String Computer_select_SQL=
"select a.id,a.bookname,a.bookclass,b.classname,"
"a.author,a.publish,a.bookno,a.content,a.prince,a.amount,"
"a.Leav_number,a.regtime,a.picture from book a,bookclass b"
" where a.Bookclass = b.Id and a.bookclass=? "
" order by a.Id desc ";
public static final String Computer_select_count_sql=
"select count(*) from book a,bookclass b"
" where a.Bookclass = b.Id and a.bookclass=? "
" order by a.Id desc ";

}

  到此为止,基本上分页的代码基本完成,为了使得分页的代码共用,我把他封装成了一个标签,这个自定义的标签命名为PaginatorTag,代码如下:

package com.util;

import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class PaginatorTag extends BodyTagSupport {
protected Log log = LogFactory.getLog(this.getClass());
//以下是一标签中的一些属性,后面有较详细的介绍
int currentPage = 1;//当前页码
String url = "";//转向的地址
int totalSize = 0;//总的记录数
int perPage = 20;//每页显示的记录数目
boolean showTotal = true;//是否显示总数量
boolean showAllPages = false;//是否显示总页码
String strUnit ="";//计数单位

//得到当前页码
public int getCurrentPage() {
return currentPage;
}
//设置当前页码
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
//得到每页显示记录的数目
public int getMaxPerPage() {
return perPage;
}
//设置每页显示的记录数目
public void setMaxPerPage(int perPage) {
this.perPage = perPage;
}
//判断是否显示总的页码数目
public boolean isShowAllPages() {
return showAllPages;
}
//设置是否显示总的页码数目
public void setShowAllPages(boolean showAllPages) {
this.showAllPages = showAllPages;
}
//判断是否显示总的记录数目
public boolean isShowTotal() {
return showTotal;
}
//设置是否显示总的记录数目
public void setShowTotal(boolean showTotal) {
this.showTotal = showTotal;
}
//得到计数单位
public String getStrUnit() {
return strUnit;
}
//设置计数单位
public void setStrUnit(String strUnit) {
this.strUnit = strUnit;
}
//得到总的记录数目
public int getTotalPut() {
return totalSize;
}
//设置总的记录数目
public void setTotalPut(int totalSize) {
this.totalSize = totalSize;
}
//得到转向的链接地址
public String getUrl() {
return url;
}
//设置链接地址
public void setUrl(String url) {
this.url = url;
}
public int doStartTag() throws JspException {
return SKIP_BODY;
}

public int doEndTag() throws JspException {
String out = showPage(currentPage, url, totalSize, perPage, showTotal, showAllPages, strUnit);
try {
pageContext.getOut().print(out);
} catch (IOException e) {
e.printStackTrace();
}
return EVAL_PAGE;
}

/**
* 作 用:显示“上一页 下一页”等信息
*
* @param url
* ----链接地址
* @ param totalSize
* ----总数量
* @ param perPage
* ----每页数量
* @param showTotal
* ----是否显示总数量
* @param showAllPages
* ---是否用下拉列表显示所有页面以供跳转。有某些页面不能使用,否则会出现JS错误。
* @param strUnit
* ----计数单位
* @return .
* @ throws IOException
*/
protected String showPage(int currentPage,String url, int totalSize, int perPage,

boolean showTotal, boolean showAllPages, String strUnit){

int n = 0;

StringBuffer buf = new StringBuffer();

String strUrl;

n = (totalSize   perPage -1) / perPage;

buf.append("<table align='center'><tr><td>");

if (showTotal == true)

buf.append("共 <b>"   totalSize   "</b> "   strUnit

  " ");

strUrl = JoinChar(url);

if (currentPage < 2) {

buf.append("首页 上一页 ");

} else {

buf.append("<a href='"   strUrl   "pageNo=1' title='首页'>首页</a>
");

buf.append("<a href='"   strUrl   "pageNo="   (currentPage
- 1)

  "' title='上一页'>上一页</a> ");

}

if (n - currentPage < 1)

buf.append("下一页 尾页");

else {

buf.append("<a href='"   strUrl   "pageNo="   (currentPage
  1)

  "' title='下一页'>下一页</a> ");

buf.append("<a href='"   strUrl   "pageNo="   n   "'
title='尾页'>尾页</a>");

}

buf.append(" 页次:<strong><font color=red>"   currentPage

  "</font>/"   n   "</strong>页 ");

buf.append(" <b>"   perPage   "</b>"   strUnit
  "/页");

if (showAllPages == true) {

buf

.append(" 转到:<select name='page' size='1' onchange=\"javascript:window.location='"

  strUrl

  "pageNo="

  "' this.options[this.selectedIndex].value;\">");

for (int i = 1; i <= n; i  ) {

buf.append("<option value='"   i   "'");

if(currentPage == i)

buf.append(" selected ");

buf.append(">第"   i   "页</option>");

}

buf.append("</select>");

}

buf.append("</td></tr></table>");

return (buf.toString());

}

/**

* 向地址中加入 ? 或

TOP

  有了自定义标签,当然少不了用于处理标签的tld,我们定义一个swsoft-struts.tld,代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library
1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">

<taglib>

<tlibversion>1.0</tlibversion>

<jspversion>1.1</jspversion>

<shortname>out</shortname>

<uri>http://www.swsoftware.com/</uri>

<info>Tab Library for PaginatorTag</info>

<tag>

<name>paginator</name>

<tagclass>com.swsoftware.tags.PaginatorTag</tagclass>

<bodycontent>JSP</bodycontent>

<info>Returns a paginator</info>

<attribute>

<name>currentPage</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

<attribute>

<name>url</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

<attribute>

<name>totalPut</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

<attribute>

<name>maxPerPage</name>

<required>true</required>

<rtexprvalue>true</rtexprvalue>

</attribute>

<attribute>

<name>showTotal</name>

<required>true</required>

<rtexprvalue>false</rtexprvalue>

</attribute>

<attribute>

<name>showAllPages</name>

<required>true</required>

<rtexprvalue>false</rtexprvalue>

</attribute>

<attribute>

<name>strUnit</name>

<required>true</required>

<rtexprvalue>false</rtexprvalue>

</attribute>

</tag>

</taglib>

TOP

  好了,到现在我们开始来真正看一下jsp中的处理,我们写一个computer.jsp,代码如下:

<%@ page import="com.util.BaseView,

com.attribute.Constants,

com.util.Page"%>

<%@ page contentType="text/html;charset=GB2312" language="java"
errorPage=""%>

<%@ taglib uri="/WEB-INF/swsoft-struts.tld"prefix="swtag"%>

<html>

<head>

<title></title>
</head>

<body bgcolor="#e3edfc">

<div id="Layer1" style="position:absolute; left:1px; top:2px;
width:780px; height:406px; z-index:1; background-color: #e3edfc; layer-background-color:
#e3edfc; border: 1px none #000000;">

<table width="100%" cellpadding="1">

<tr>

<td colspan="6">

<table width="100%" cellpadding="1">

<tr>

<td width="20%" align="center">BOOKNAME</td>

<td width="10%" align="center">AUTHOR</td>

<td width="10%" align="center">TYPE</td>

<td width="30%" align="center">PUBLISH</td>

<td width="10%" align="center">PRINCE</td>

<td colspan="2" align="center">SELECT</td>

</tr>

<%

String contextPath = request.getContextPath();

String url=contextPath "/computer.do";

BaseView view=(BaseView)request.getAttribute(Constants.QUERY_RESULT);

Page setpage=(Page)request.getAttribute("page");

int currentPage=setpage.getCurrentPageNo();

System.out.println("this is currentPage=" currentPage);

int totalPut=setpage.getTotalSize();

System.out.println("this is totalPut=" totalPut);

int maxPerPage=setpage.getPageSize();

System.out.println("this is maxPerPage=" maxPerPage);

if(view.haveRecord()){

String sBgcolor="";

int length=view.getRecordCount();

for(int i=0;i<length;i  ){

String type =view.getValue(i,2);

if(type.equals("1")){

type="computer";

}

if(i%2!=0){

sBgcolor="#A5C6EB";

}

else{

sBgcolor="#B7D7EF";

}

%>

<tr bgcolor=<%=sBgcolor%> height="10">

<td align="center" ><%=view.getValue(i,1)%></td>

<td align="center"><%=view.getValue(i,4)%></td>

<td align="center"><%=type%></td>

<td align="center"><%=view.getValue(i,5)%></td>

<td align="center"><%=view.getValue(i,8)%></td>

<td width="20%" align="center">BUY PARTICULAR</td>
</tr>

<%}}%>

</table>

</td>

</tr>
<tr>

<swtag:paginator url="<%=url%>"

currentPage="<%=currentPage%>"

totalPut="<%=totalPut%>"

maxPerPage="<%=maxPerPage%>"

showTotal="true"

showAllPages="true"

strUnit="页" />

</tr>

</table>

</div>
</body>

</html>

TOP

  到此为止,分页的类基本完成,这样的话可以在别的模块都可以用这个标签,同时在开发别的系统的时候这个标签也可以使用,具有比较好的可移植性。这个数据库是mysql的,要是oracle的,仅仅在PageCtBean类中的intiQuerySQL方法里面改成

protected String intiQuerySQL(String sql, int startIndex, int size){
StringBuffer querySQL = new StringBuffer();
if (size != this.MAX_PAGE_SIZE) {
querySQL.append("select * from (select my_table.*,rownum as my_rownum from(")
.append( sql)
.append(") my_table where rownum<").append(startIndex   size)
.append(") where my_rownum>=").append(startIndex);
} else {
querySQL.append("select * from (select my_table.*,rownum as my_rownum from(")
.append(sql)
.append(") my_table ")
.append(") where my_rownum>=").append(startIndex);
}
return querySQL.toString();
}

  就可以了。

同时在数据库中,返回当前页需要显示的数据,主要有以下方法:
a.使用mysql控制:

  select * from user
  order by Host
  limit m, n

  结果返回的是第m 1行到第n行的数据集。比如:

  select * from user
  order by Host
  limit 1, 5

  返回的是第2行到第5行的数据集

b.使用sqlserver

  SELECT *
  FROM (SELECT TOP m *
  FROM (SELECT TOP n *
  FROM Customers) A
  ORDER BY CustomerID DESC) B
  ORDER BY CustomerID

  获得的结果集数据为第n-m 1行到第n行。

  对整个过程的解释:
  首先按照升序得到前n行的结果集A,然后按照降序从A中得到后m行的结果集B,最后按照升序对B进行重新排序,返回结果集。其中CustomerID为主键,比如:

  SELECT *
  FROM (SELECT TOP 5 *
  FROM (SELECT TOP 10 *
  FROM Customers) A
  ORDER BY CustomerID DESC) B
  ORDER BY CustomerID

  的意思就是返回包含第6行到第10行的数据结果集。

c.使用Oracle:

  select * from (select rownum r ,* from test) tt
  where tt.r > 50 and tt.r <= 100;

希望大家有好方法的话能说一下,大家共同进步。

TOP

发新话题