每日一得

不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速开发
最近关心的内容:SSH,seam,flex,敏捷,TDD
本站的官方站点是:颠覆软件

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  220 随笔 :: 9 文章 :: 421 评论 :: 0 Trackbacks
keyword:分页 缓存 eXtremeTable oscache
引子:这几天在弄一个关于页面的分页,查了一下网上的资料,大都不合要求,要么就是说怎么在数据库这个层面上如何实现,晕,有了hibernate我用那么费劲翻身么.看到一个用的比较多的方案是做了一个Page工具类,实现诸如getBooks(),getNextPage(),看了一下底层实现居然是"select * from book",吓死偶了,要是有1千万条记录那不是要吐血啊,这哪叫分页啊,这该叫杀人不见血啊. 一气之下在jbuilder下建了一个项目就叫FuckPage  :)

好了,言归正传,本文主要说的是关于在展示层一些常用的方案和实现,目录如下:
  • 手工实现分页
  • 用eXtremeTable标签实现自动分页
  • 用oscache缓存jsp,提高性能
第一.自己实现一个工具类PageBean完成所有分页工作.

本分页实现概览:Struts + hibernate
PageBean负责两部分内容,一是要在页面显示的业务信息,是一个ArrayList;另一个是逻辑控制信息,诸如是否有下一页,上一页等等.
PageBean代码如下:
public class PageBean {
  
int currentPage = 1;//当前页:Action控制
  int totalPages = 0;//总页数 :自己运算
  int pageRecorders = 5//每页记录数,默认为5,可以在初始化的时候修改//总数据数
  int pageStartRow = 0//每页的起始数
  int pageEndRow = 0//每页显示数据的终止数
  boolean hasNextPage = false//是否有下一页:自己运算
  boolean hasPreviousPage = false//是否有前一页 :自己运算
  List objList = new ArrayList();//存放欲展示的对象列表
  int totalRows;//总记录数,由底层service提供

  
//是否有上一页
  public boolean isHasPreviousPage() {
    
return (currentPage > 1?true:false );
  }

  
//共有多少页,service只提供有多少条记录,多少页数由PageBean自己运算
  public int getTotalPages() {
    
return (totalRows/pageRecorders ==0?totalRows/pageRecorders:totalRows/pageRecorders+1);
  }

  
public int getCurrentPage() {
    
return currentPage;
  }

  
public int getPageEndRow() {
    
return pageEndRow;
  }

  
//是否有下一页
  public boolean isHasNextPage() {
    
return (currentPage < this.getTotalPages() ? true:false);
  }

  
public int getTotalRows() {
    
return totalRows;
  }

  
public int getPageStartRow() {
    
return pageStartRow;
  }

  
public int getPageRecorders() {
    
return pageRecorders;
  }

  
public void setObjList(List objList) {
    
this.objList = objList;
  }

  
public void setHasPreviousPage(boolean hasPreviousPage) {
    
this.hasPreviousPage = hasPreviousPage;
  }

  
public void setTotalPages(int totalPages) {
    
this.totalPages = totalPages;
  }

  
public void setCurrentPage(int currentPage) {
    
this.currentPage = currentPage;
  }

  
public void setPageEndRow(int pageEndRow) {
    
this.pageEndRow = pageEndRow;
  }

  
public void setHasNextPage(boolean hasNextPage) {
    
this.hasNextPage = hasNextPage;
  }

  
public void setTotalRows(int totalRows) {
    
this.totalRows = totalRows;
  }

  
public void setPageStartRow(int pageStartRow) {
    
this.pageStartRow = pageStartRow;
  }

  
public void setPageRecorders(int pageRecorders) {
    
this.pageRecorders = pageRecorders;
  }

  
public List getObjList() {
    
return objList;
  }

  
public PageBean() {}


  
public void description() {

    String description 
= "共有数据数:" + this.getTotalRows() +

        
"共有页数: " + this.getTotalPages() +

        
"当前页数为:" + this.getCurrentPage() +

        
" 是否有前一页: " + this.isHasPreviousPage() +

        
" 是否有下一页:" + this.isHasNextPage() +

        
" 开始行数:" + this.getPageStartRow() +

        
" 终止行数:" + this.getPageEndRow();

    System.out.println(description);
  }
}

注意,我没有在PageBean里放具体的业务逻辑,诸如getBooks()等,目的很简单,具有通用性,业务逻辑由另一个业务类实现BookService,BookService获得的业务数据都放在了PageBean的ArrayList里.

BookService代码如下:
public class BookService {
  
private static Logger log = Logger.getLogger(BookService.class.getName());
  
public BookService() {
  }

  
/**
   * 获得book列表
   * 
@param pageBean PageBean:返回的对象存在pageBean里
   
*/
  
public static void getBooks(PageBean pageBean) {
    String infoSql 
= "from Book";//获得业务信息
    String countSql = "select count(*) from Book";//获得控制信息
    Session session = null;
    
try {
      session 
= DBUtil.currentSession();
      Query query 
= session.createQuery(infoSql);
      query.setFirstResult((pageBean.getCurrentPage()
-1)* pageBean.getPageRecorders());//起始页
      query.setMaxResults(pageBean.getPageRecorders());//每页记录数
      pageBean.getObjList().clear();
      
for (Iterator it = query.iterate(); it.hasNext(); ) {
        Book po 
= (Book)it.next();
        BookVo vo 
= new BookVo();
        BeanUtils.copyProperties(vo,po);
        pageBean.getObjList().add(vo);
      }
      session 
= DBUtil.currentSession();
      query 
= session.createQuery(countSql);
      
int totalRecords = ((Integer)query.list().get(0)).intValue();
      pageBean.setTotalRows(totalRecords);
    }
    
catch (Exception e) {
      e.printStackTrace();
      System.out.println(
"数据库异常" + e.toString());
    }
    
finally {
      
try {
        
if (null != session) {
          session.close();
        }
      }
      
catch (HibernateException ex) {
        ex.printStackTrace();
      }
    }

  }


}

在Struts的Action中调用service,返回一个PageBean给展示页面
Action代码如下:
 1 public class PageListAction extends Action {
 2 
 3   public PageListAction() {}
 4 
 5   ArrayList arrayList = new ArrayList();
 6 
 7   public ActionForward execute(ActionMapping mapping,ActionForm form,HttpServletRequest request,HttpServletResponse response) throws Exception {
 8     String action;
 9     PageBean pageBean = null;
10     action = request.getParameter("action");
11     if(action == null || action.equals("null")) { //第一次读取数据
12       pageBean = new PageBean();
13     } else {//用户选择上一页或者下一页
14       if(action == "nextPage" || action.equals("nextPage")) {
15         pageBean = (PageBean)request.getSession().getAttribute("pageBean");
16         pageBean.setCurrentPage(pageBean.getCurrentPage()+1);
17       }else if(action == "previousPage" || action.equals("previousPage")) {
18         pageBean = (PageBean)request.getSession().getAttribute("pageBean");
19         pageBean.setCurrentPage(pageBean.getCurrentPage()-1);
20       }else if(action == "targetPage" || action.equals("targetPage")){//指定页
21         pageBean = (PageBean)request.getSession().getAttribute("pageBean");
22         System.out.println("targetPage=" + request.getParameter("targetPage"));
23         //这里根据需要可以对填写的目标页进行判断,不要大于最大页数[此处省略]
24         pageBean.setCurrentPage(Integer.parseInt(request.getParameter("targetPage")));
25       }
26     }
27     if(null == pageBean) throw new Exception("获得PageBean异常");
28     BookService service = new BookService();
29     service.getBooks(pageBean);
30     pageBean.description();
31     request.getSession().setAttribute("pageBean",pageBean);
32     request.setAttribute("result",pageBean.getObjList());
33     return(mapping.findForward("success"));
34   }
35 }

在本Action中判断了可能出现的三种情况:
  1. 用户选择了"上一页"
  2. 用户选择了"下一页"
  3. 用户手工输入了指定的某一页
这里有点感觉不爽的是必须hard coding,但是不这么做感觉暂时也想不出什么好的办法来,毕竟一个PageBean不可能封装所有的细节,如果你有更好的方式请指点哦 :)

好了,到了我们呼之欲出的展示页面了 :)
show.jsp代码如下
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ page contentType="text/html; charset=gb2312" language="java"%>

<html:html locale="true">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
  
<script language="javaScript">
    
function go(){
      
try{
      
var targetValue = document.getElementById("targetPage").value;
      parseInt(targetValue);
      alert(targetValue);
      }
catch(e){
        alert(
"请正确填写目标页");
        
return;
      }
      
if(targetValue == null || targetValue == ''){
        alert(
"请填写目标页");
        
return;
      }
      window.location 
= "/fuck/pageList.do?action=targetPage&targetPage="+targetValue;
    }
  
</script>
</head>
<body>
<logic:present name="pageBean">
  共有数据总数
<bean:write name="pageBean" property="totalRows"/>;
共分
<bean:write name="pageBean" property="totalPages"/>页,
当前是第
<bean:write name="pageBean" property="currentPage"/>
</logic:present>
<table border="1">
<tr><th>书名</th><th>作者</th><th>价格</th></tr>
    
<logic:present name="result">
        
<logic:iterate id="book" name="result">
        
<logic:present name="book">
        
<tr>
           
<td><bean:write name="book" property="name" /></td>
           
<td> <bean:write name="book" property="author" /></td>
           
<td><bean:write name="book" property="price" /></td>
        
/tr>
        
</logic:present>
        
</logic:iterate>
    
</logic:present>
</table>
<logic:present name="pageBean">
<logic:equal name="pageBean" property="hasNextPage" value="true">
    
<html:link page="/pageList.do?action=nextPage">nextPage</html:link>
</logic:equal>
<logic:equal name="pageBean" property="hasPreviousPage" value="true">
    
<html:link page="/pageList.do?action=previousPage">PreviousPage</html:link>
</logic:equal>
<input type="text" name="targetPage" id="targetPage"/>
<input type="button" value="go!" size="2" onclick="go();"/>
</logic:present>
</body>
</html:html>

是否有上一页或者下一页,全部根据PageBean里的逻辑值动态判断.
这个页面没什么可说的,你可以根据自己的情况调整就OK了


第二. eXtremeTable标签实现自动分

上面的方案大家已经看出来了,实际上是每一次用户点击一个页面都会查询数据库,这可以算是既是优点也是缺点,优点是数据库不用一次查询出所有的数据,在高数据量的情况下尤其如此,缺点就是和数据库的交互次数有点多了,不过这个完全看你的业务策略了,如果用户大多数情况下就是看没几条的记录,你又何必把全部数据给他取出来呢? 当然,在这里我们就说说一次取出全部数据,然后让标签帮助我们自动分页,终于可以偷懒了,你所要做的仅仅是取出所需要的业务数据而已,其他的就交给eXtremeTable标签来完成就OK.
eXtremeTable
标签的下载,安装和文档请参看官方网站

public static List getBooks() {
    log.debug(
"execute getBooks method!");
    String infoSql 
= "from Book";//获得业务信息
    Session session = null;
    List rtnList 
= new ArrayList();
    
try {
      session 
= DBUtil.currentSession();
      Query query 
= session.createQuery(infoSql);
      
for (Iterator it = query.iterate(); it.hasNext(); ) {
        Book po 
= (Book) it.next();
        BookVo vo 
= new BookVo();
        BeanUtils.copyProperties(vo,po);
        log.debug(
"vo = [" + vo + "]");
        rtnList.add(vo);
      }
    }
    
catch (Exception e) {
      e.printStackTrace();
      System.out.println(
"数据库异常" + e.toString());
    }
    
finally {
      
try {
        
if (null != session) {
          session.close();
        }
      }
      
catch (HibernateException ex) {
        ex.printStackTrace();
      }
    }
    
return rtnList;
  }
  • Action
代码如下:
public class PageListWithTagAction extends Action {
  
public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
    BookService service 
= new BookService();
   
    //我这里把数据放到session中,相当于做了缓存,根据你的业务策略也可以不用这么做
    
//如果session中没有,则从数据库中查询
    if(null == httpServletRequest.getSession().getAttribute("result")){
    List result 
= service.getBooks();
    httpServletRequest.getSession().setAttribute(
"result",result);
  }
    
return (actionMapping.findForward("success"));
  }
}

  • jsp[show.jsp代码如下,留心里面的标签使用方法]
<body bgcolor="#ffffff">
<ec:table tableId="fuck"
        items
="result"
        action
="${pageContext.request.contextPath}/pageListWithTag.do"
        imagePath
="${pageContext.request.contextPath}/jsp/images/table/*.gif"
        title
="Books"
        width
="60%"
        rowsDisplayed
="5"
        locale
="zh_CN"
        cellpadding
="1"
        cellspacing
="1"
        border
="1"
        method
="post"
        showPagination
="false"
        filterable
="false"
        
>
       
<ec:exportXls fileName="Book.xls" tooltip="导出Excel">
       
</ec:exportXls>
       
<ec:exportPdf fileName="Book.pdf" tooltip="导出pdf" headerColor="blue" headerBackgroundColor="red" headerTitle="Book"></ec:exportPdf>
        
<ec:row highlightRow="true">
            
<ec:column property="name" title="书名">
                          
<href="${pageContext.request.contextPath}/bookDetail.do?bookID=${fuck.id}&amp;bookName=${fuck.name}">${fuck.name}
                          
</a>
                        
</ec:column>
            
<ec:column property="author" title="作者">
                          
<!--here can't use 'result.author'-->
                          ${fuck.author}
                        
</ec:column>

            
<ec:column property="price" title="价格" cell="currency" format="$###,###,##0.00"/>
                        
<ec:column property="date" title="日期" cell="date" format="yyyy年MM月dd日">
                        
</ec:column>
        
</ec:row>
    
</ec:table>
</body>

感觉如何?你不用再画你的页面了,不用再画table了,这点我特别喜欢,因为我自己画的东西都比较难看,毕竟我的美工功夫不够  :(   "自从有了eXtremeTable吃嘛嘛香"    :)
具体的标签使用方法请参考官方文档的Manual,说明还是比较详细的.

第三.用oscache缓存你的页面

为了提高页面的速度我们想了很多办法,比如预编译的办法,以及把你常用的数据放到内存里,是的,除了用内存我们还能想到用什么办法呢,恩,我想以后cpu的缓存也特别大的话我们的下一个方案肯定就是把数据全部放到cpu里得了,哈哈,展望一下  :)

言归正传,oscache的广告我就不做了,差不多地球人都知道了,这里仅仅提供了一个想法,我想这确实是一个不错的方案,它提供了2个途径使用,一是通过tag的方式,可能也是用的最多的方式,另一个便是调用API,当然就可以在任何想调用的地方使用了.另外还有一个特别不错的功能就是有策略的刷新数据.这是一个useful的方式,比如你做了增删改操作那么数据库的数据已经发生变化了,你可以通知缓存来更新数据,方式是通过key或者group.
下面是几个摘自FAQ里的几个常用Example
  • Example1
<cache:cache time="600">
        
<%= myBean.getTitle() %>
 
</cache:cache>

  • Example2
<cache:cache key="foobar" scope="session">
        
<%= myBean.getTitle() %>
</cache:cache>

  • Example3
<cache:cache>
        
<% try { %>
            
<%= myBean.getTitle() %>>
        
<% } catch (Exception e) { %>
            
<% application.log("Exception occurred in myBean.getTitle(): " + e); %>
            
<cache:usecached />
        
<% } %>
</cache:cache>

上面的Example3可以实现在数据库当机的情况下从缓存里读取数据展示,显得更加友好

不知你注意到了没有,在oscache官方展示的例子里的jsp都有一个我称之为毛病的东西,或者说是困惑,那就是都用了
<%=.%>
这种方式,感觉有点别扭,毕竟这种使用方式对于别人我不知道,反正对于我来说用的比较少,在以前的使用中我记得只有使用xml数据岛的时候用过这种方式,其他情况下很少用=的方式来打印出一些动态的数据,更常见的可能是如下这样的情况:
<cache:cache key="dispInfo" groups="disInfo" time="1200">
  
<%
  BookService service 
= new BookService();
  List list 
= service.getBooks();
  request.setAttribute(
"list",list);
  
%>
  
</cache:cache>
 
<c:forEach items="${list}" var="item">
  
<c:out value="${item.id}"/> == <c:out value="${item.name}"/><br />
</c:forEach>

不幸的是,这个一厢情愿的做法并不被oscache支持,除了第一次能够显示数据,下一次就显示不了了.也许oscache所谓的缓存jsp代码就是指缓存诸如<%=%>的方式才是jsp代码,其他的java型的就不被支持了?可能是理解不够,继续研究吧,希望有知道的不吝赐教 :)

恩,就是这么多,关于页面的缓存和分页以及标签.欢迎有这方面的更好的想法来交流.
本人不才,可能有很多地方理解不够深入,见笑了  :)




posted on 2006-02-25 21:26 Alex 阅读(2663) 评论(6)  编辑  收藏 所属分类: java

评论

# re: Fuck the page! 关于分页,标签,缓存 2006-02-26 10:45 JAVA梦想
关于eXtremeTable:
我已经得到作者(Jeff)同意并翻译了官方的使用指南(共七篇),发布在我的blog上。欢迎大家参阅,指正。我现在正在翻译它的参考手册。  回复  更多评论
  

# re: Fuck the page! 关于分页,标签,缓存 2006-02-26 13:10 Alex
正在想关于翻译的事情要不要弄一下呢,替大伙谢谢你了  回复  更多评论
  

# re: Fuck the page! 关于分页,标签,缓存 2006-02-28 14:23 胡子鱼
呵,第一种分页,封装不够彻底;第二种分页只能小数据量,所以建议你改进第一种封装,把分页逻辑和具体业务分开,这样,每个业务,对同一个getPageBean就完成分页了。  回复  更多评论
  

# re: Fuck the page! 关于分页,标签,缓存 2006-03-29 10:37 keke
标签太多.不爽.一个字:乱  回复  更多评论
  

# re: Fuck the page! 关于分页,标签,缓存 2006-03-29 12:02 keke
如果resultset中有1000000条,这种分页好恐怖.  回复  更多评论
  

# re: Fuck the page! 关于分页,标签,缓存 2006-03-29 12:04 keke
"如果resultset中有1000000条,这种分页好恐怖." 看错了.这个可以控制.呵呵  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: