每日一得

          不求多得,只求一得 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 閱讀(2683) 評論(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條,這種分頁好恐怖." 看錯了.這個可以控制.呵呵  回復  更多評論
            

          主站蜘蛛池模板: 阜宁县| 潮安县| 马关县| 高唐县| 安阳市| 砀山县| 宜宾市| 金坛市| 元阳县| 清丰县| 黑水县| 鄂温| 巴彦淖尔市| 大同市| 德庆县| 汉阴县| 乌鲁木齐县| 应城市| 宁阳县| 龙山县| 元朗区| 安国市| 达尔| 子洲县| 勐海县| 平远县| 依兰县| 龙井市| 定远县| 库尔勒市| 营山县| 都江堰市| 民勤县| 清丰县| 黎城县| 响水县| 咸丰县| 太白县| 万全县| 庐江县| 阳山县|