随笔 - 170  文章 - 536  trackbacks - 0
<2005年12月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

常用链接

我参与的团队

随笔分类(103)

搜索

  •  

积分与排名

  • 积分 - 412357
  • 排名 - 135

最新评论

阅读排行榜

     前面一篇直接使用了Myfaces中的两个Component完成了一个简单的分页,这里将会介绍一种On-demand loading的方法来进行分页,仅仅在需要数据的时候加载。

     先来说一些题外话,为了实现这种方式的分页,公司里大约5-6个人做了半个多月的工作,扩展了dataTable,修改了dataScrollor,以及各种其他的方法,但是都不是很优雅。在上个月底的时候,在MyfacesMail List中也针对这个问题展开了一系列的讨论,最后有人总结了讨论中提出的比较好的方法,提出了以下的分页方法,也是目前实现的最为优雅的方法,也就是不对dataTabledataScrollor做任何修改,仅仅通过扩展DataModel来实现分页。

     DataModel 是一个抽象类,用于封装各种类型的数据源和数据对象的访问,JSFdataTable中绑定的数据实际上被包装成了一个DataModel,以消除各种不同数据源和数据类型的复杂性,在前面一篇中我们访问数据库并拿到了一个List,交给dataTable,这时候,JSF会将这个List包装成 ListDataModel dataTable访问数据都是通过这个DataModel进行的,而不是直接使用List

     接下来我们要将需要的页的数据封装到一个DataPage中去,这个类表示了我们需要的一页的数据,里面包含有三个元素:datasetSizestartRow,和一个用于表示具体数据的ListdatasetSize表示了这个记录集的总条数,查询数据的时候,使用同样的条件取count即可,startRow表示该页的起始行在数据库中所有记录集中的位置。

/**
 * A simple class that represents a "page" of data out of a longer set, ie a
 * list of objects together with info to indicate the starting row and the full
 * size of the dataset. EJBs can return instances of this type when returning
 * subsets of available data.
 
*/

public   class  DataPage
{
    
private   int  datasetSize;
    
private   int  startRow;
    
private  List data;

    
/**
     * Create an object representing a sublist of a dataset.
     * 
     * 
@param  datasetSize
     *            is the total number of matching rows available.
     * 
     * 
@param  startRow
     *            is the index within the complete dataset of the first element
     *            in the data list.
     * 
     * 
@param  data
     *            is a list of consecutive objects from the dataset.
     
*/

    
public  DataPage( int  datasetSize,  int  startRow, List data)
    
{
        
this .datasetSize  =  datasetSize;
        
this .startRow  =  startRow;
        
this .data  =  data;
    }


    
/**
     * Return the number of items in the full dataset.
     
*/

    
public   int  getDatasetSize()
    
{
        
return  datasetSize;
    }


    
/**
     * Return the offset within the full dataset of the first element in the
     * list held by this object.
     
*/

    
public   int  getStartRow()
    
{
        
return  startRow;
    }


    
/**
     * Return the list of objects held by this object, which is a continuous
     * subset of the full dataset.
     
*/

    
public  List getData()
    
{
        
return  data;
    }

}

     接下来,我们要对DataModel进行封装,达到我们分页的要求。该DataModel仅仅持有了一页的数据DataPage,并在适当的时候加载数据,读取我们需要页的数据。


/**
 * A special type of JSF DataModel to allow a datatable and datascroller to page
 * through a large set of data without having to hold the entire set of data in
 * memory at once.
 * <p>
 * Any time a managed bean wants to avoid holding an entire dataset, the managed
 * bean should declare an inner class which extends this class and implements
 * the fetchData method. This method is called as needed when the table requires
 * data that isn't available in the current data page held by this object.
 * <p>
 * This does require the managed bean (and in general the business method that
 * the managed bean uses) to provide the data wrapped in a DataPage object that
 * provides info on the full size of the dataset.
 
*/

public   abstract   class  PagedListDataModel  extends  DataModel
{
    
int  pageSize;
    
int  rowIndex;
    DataPage page;

    
/**
     * Create a datamodel that pages through the data showing the specified
     * number of rows on each page.
     
*/

    
public  PagedListDataModel( int  pageSize)
    
{
        
super ();
        
this .pageSize  =  pageSize;
        
this .rowIndex  =   - 1 ;
        
this .page  =   null ;
    }


    
/**
     * Not used in this class; data is fetched via a callback to the fetchData
     * method rather than by explicitly assigning a list.
     
*/


    
public   void  setWrappedData(Object o)
    
{
        
if (o  instanceof  DataPage)
        
{
            
this .page  =  (DataPage) o;
        }

        
else
        
{
            
throw   new  UnsupportedOperationException( " setWrappedData " );
        }

    }


    
public   int  getRowIndex()
    
{
        
return  rowIndex;
    }


    
/**
     * Specify what the "current row" within the dataset is. Note that the
     * UIData component will repeatedly call this method followed by getRowData
     * to obtain the objects to render in the table.
     
*/


    
public   void  setRowIndex( int  index)
    
{
        rowIndex 
=  index;
    }


    
/**
     * Return the total number of rows of data available (not just the number of
     * rows in the current page!).
     
*/


    
public   int  getRowCount()
    
{
        
return  getPage().getDatasetSize();
    }


    
/**
     * Return a DataPage object; if one is not currently available then fetch
     * one. Note that this doesn't ensure that the datapage returned includes
     * the current rowIndex row; see getRowData.
     
*/

    
private  DataPage getPage()
    
{
        
if  (page  !=   null )
        
{
            
return  page;
        }


        
int  rowIndex  =  getRowIndex();
        
int  startRow  =  rowIndex;
        
if  (rowIndex  ==   - 1 )
        
{
            
//  even when no row is selected, we still need a page
            
//  object so that we know the amount of data available.
            startRow  =   0 ;
        }


        
//  invoke method on enclosing class
        page  =  fetchPage(startRow, pageSize);
        
return  page;
    }


    
/**
     * Return the object corresponding to the current rowIndex. If the DataPage
     * object currently cached doesn't include that index then fetchPage is
     * called to retrieve the appropriate page.
     
*/


    
public  Object getRowData()
    
{
        
if  (rowIndex  <   0 )
        
{
            
throw   new  IllegalArgumentException(
                    
" Invalid rowIndex for PagedListDataModel; not within page " );
        }


        
//  ensure page exists; if rowIndex is beyond dataset size, then
        
//  we should still get back a DataPage object with the dataset size
        
//  in it
         if  (page  ==   null )
        
{
            page 
=  fetchPage(rowIndex, pageSize);
        }


        
int  datasetSize  =  page.getDatasetSize();
        
int  startRow  =  page.getStartRow();
        
int  nRows  =  page.getData().size();
        
int  endRow  =  startRow  +  nRows;

        
if  (rowIndex  >=  datasetSize)
        
{
            
throw   new  IllegalArgumentException( " Invalid rowIndex " );
        }


        
if  (rowIndex  <  startRow)
        
{
            page 
=  fetchPage(rowIndex, pageSize);
            startRow 
=  page.getStartRow();
        }

        
else   if  (rowIndex  >=  endRow)
        
{
            page 
=  fetchPage(rowIndex, pageSize);
            startRow 
=  page.getStartRow();
        }

        
return  page.getData().get(rowIndex  -  startRow);
    }


    
public  Object getWrappedData()
    
{
        
return  page.getData();
    }


    
/**
     * Return true if the rowIndex value is currently set to a value that
     * matches some element in the dataset. Note that it may match a row that is
     * not in the currently cached DataPage; if so then when getRowData is
     * called the required DataPage will be fetched by calling fetchData.
     
*/


    
public   boolean  isRowAvailable()
    
{
        DataPage page 
=  getPage();
        
if  (page  ==   null )
        
{
            
return   false ;
        }


        
int  rowIndex  =  getRowIndex();
        
if  (rowIndex  <   0 )
        
{
            
return   false ;
        }

        
else   if  (rowIndex  >=  page.getDatasetSize())
        
{
            
return   false ;
        }

        
else
        
{
            
return   true ;
        }

    }


    
/**
     * Method which must be implemented in cooperation with the managed bean
     * class to fetch data on demand.
     
*/

    
public   abstract  DataPage fetchPage( int  startRow,  int  pageSize);
    
}

     最后,我们需要在Backing Bean中加一些东西,调用业务逻辑,并将数据交给PagedListDataModel,来帮我们完成最后的分页工作。

   public  SomeManagedBean  {
    .


    
private  DataPage getDataPage( int  startRow,  int  pageSize)  {
      
//  access database here, or call EJB to do so
    }


    
public  DataModel getDataModel()  {
        
if  (dataModel  ==   null {
            dataModel 
=   new  LocalDataModel(20);
        }


        
return  dataModel;
    }


    
private   class  LocalDataModel  extends  PagedListDataModel  {
        
public  LocalDataModel( int  pageSize)  {
            
super (pageSize);
        }

        
        
public  DataPage fetchPage( int  startRow,  int  pageSize)  {
            
//  call enclosing managed bean method to fetch the data
             return  getDataPage(startRow, pageSize);
        }

}


这里面有一个getDataPage的方法,只需要把所有业务逻辑的调用放在这里就可以了,最后业务逻辑调用的结果返回一个List,总条数返回一个int型的count放到DataPage中去就可以了。

为了实现复用,把上面第三段的代码中的LocalDataModel类和getDataPage方法抽到BasePagedBackingBean中,把getDataPage方法改成:

protected abstract DataPage getDataPage(int startRow, int pageSize);

这样我们把所有需要分页的Backing Bean继承自这个抽象类,并实现getDataPage方法即可很容易的实现分页。

 

   在具体应用中可以这么写:

     protected  DataPage getDataPage( int  startRow,  int  pageSize)
    
{
        List scheduleList 
=  scheduleService.getSchedulesByDate(scheduleDate, startRow, pageSize);
        
int  dataSetSize  =  scheduleService.getSchedulesCountByDate(scheduleDate);
        
return   new  DataPage(dataSetSize, startRow, scheduleList);
    }


在数据访问中,我们只需要取出我们需要行数的记录就可以了,这在hibernate中非常容易实现。

如果使用Criteria查询的话,只要加上:

     criteria.setFirstResult(startRow);

     criteria.setMaxResults(pageSize);

使用Query查询的话,只要加上

     query.setFirstResult(startRow);

     query.setMaxResults(pageSize);

并把两个参数传入即可。

我们还需要另外写一个CountDAO,取出相同查询条件的记录条数即可。

还要修改一下Backing Bean中与dataTable绑定的property,将返回类型由List改成DataModel,而第一篇中用到的页面不需要做任何修改就可以满足新的需求了。

里面最重要的是 PagedListDataModel fetchPage 这个方法,当满足取数据的条件时,都会调用它取数据,因为业务逻辑不同,不便于将业务逻辑的调用放在里面实现,于是将其作为抽象方法,将具体的实现放到具体的Backing Bean中进行,在BaseBackingBean中,实现了这个方法,调用了getDataPage(startRow, pageSize)这个方法,而在BaseBackingBean中,这个方法又推迟到更具体的页面中实现,这样,我们在具体的页面中只需要实现一个getDataPage(startRow, pageSize)这个方法访问业务逻辑。

大功告成,这个实现把前面遇到的两个问题都解决了, On-demand loading 是没有问题了,因为只有在首次读取和换页的时候DataModel才会向数据库请求数据,虽然在JSF的生命周期中多次调用与dataTable绑定的方法,但是因为每次业务逻辑请求以后,数据都会存放在DataPage中,如果里面的数据满足需求的话,就不再请求访问数据库,这样多次访问数据库的问题也解决了。

虽然这样的话,dataScrollorTag使用起来还是很复杂,通常在同一个项目中,我们只会使用一种样式的分页导航,不过没关系,我们只需要修改以下DataScrollorRender Kit,把一些可以定义的值固定下来,再定义一个TLD文件,就可以在项目中使用简化版的Tag了。

这个方法一开始发布在MyfacesWiki中,http://wiki.apache.org/myfaces/WorkingWithLargeTables,那里很少有人关注到,大家有兴趣可以看看原文,本文只是对这种方法做一些简单的介绍,并非自创,希望大家能够多多关注开源社区,因为那里有最新最好的东西。

Nightly Build服务器中拿到的12.27Myfaces包,发现里面扩充了很多新的Component,只是并没有正式发布,大家有兴趣的话可以研究研究。

posted on 2005-12-30 10:30 steady 阅读(14997) 评论(21)  编辑  收藏 所属分类: JSF & Myfaces

FeedBack:
# re: 在JSF中实现分页(二) 2006-08-29 14:30 你好
你好,我看了你写的这个分页.我觉得不错.可以发一份源代码给我吗?  回复  更多评论
  
# re: 在JSF中实现分页(二) 2006-10-25 13:48 figo
写的乱七八糟 还不如自己用TAG和存储过程来写  回复  更多评论
  
# re: 在JSF中实现分页(二) 2006-10-25 15:34 figo
我估计你自己都不会 乱把人家东西拿来  回复  更多评论
  
# re: 在JSF中实现分页(二) 2006-10-25 15:40 steady
该方法被很多人应用过了,非常简单,稍微懂一点的人都可以正常的调试出来。  回复  更多评论
  
# re: 在JSF中实现分页(二) 2006-10-25 15:53 figo
呵呵多有得罪 才学JSF 你加我QQ352292867 我给你看看我的分页 何必用JSF来写呢  回复  更多评论
  
# re: 在JSF中实现分页(二) 2006-10-25 15:54 figo
兄弟别介意 我这个人脾气不好向来看东西很急 加我QQ 352292867 一起研究研究 我也有一个很不错的分页   回复  更多评论
  
# re: 在JSF中实现分页(二) 2006-12-17 00:10 flmn
http://wiki.apache.org/myfaces/WorkingWithLargeTables给的代码可能比你的新,有了些新的东西,我看getRowData方法里多了这段
// Check if rowIndex is equal to startRow,
// useful for dynamic sorting on pages

if (rowIndex == page.getStartRow()){
page = fetchPage(rowIndex, pageSize);
}
不太懂他的意思,而且看sql,取一页调用了三次,而你的代码只调一次,我不太明白他这样做的意图。  回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-01-07 11:49 鱼肠剑
有几个地方不明白:
public Object getRowData(){

}
这个方法应该是取一行数据吧?就是一个model的实例?

fetchPage(rowIndex, pageSize)这个方法应该是取得一页的数据吧?

那在这个方法里的下面这段代码是什么意思:
if (rowIndex < startRow)
{
page = fetchPage(rowIndex, pageSize);
startRow = page.getStartRow();
}
else if (rowIndex >= endRow)
{
page = fetchPage(rowIndex, pageSize);
startRow = page.getStartRow();
}
return page.getData().get(rowIndex - startRow);
  回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-01-16 06:17 harderwu
潜水很久,看了您不少文章,很是崇拜,这两天我试图做一个分页组件,但对您文章中"我们只需要修改以下DataScrollor的Render Kit,把一些可以定义的值固定下来,再定义一个TLD文件,就可以在项目中使用简化版的Tag了"感觉不从下手,“DataScrollor的Render Kit”在什么地方,我怎么找不到,可能是自己太弱原故(小人学jsf不久),能详细介绍一下如何实现制作一个组件标签及在项目里使用吗?如果有好的例子能否共享?我的邮箱harder-wu@163.com  回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-04-25 18:00 waryist
页面怎么调用?唉。就是把人家的东西抄过来说明一下就算了,又没有说详细  回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-05-19 00:43 嘻哈标明
很高兴看到这篇文章,我按这个操作已经两天了都没有完成,报rowIndex出错,作者能提供<t:dataTable>的代码吗?  回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-05-19 23:29 原版
谢谢。
扩展DataModel才是正解,你们五六人半月时间浪费了啊!呵呵。  回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-09-07 12:48 小偉
你好!!
现在应用这个成功实现分页!
一问:不过好像在渲染时会调用两次,与myfaces原先的分页实现一样!可能是生命周期的问题。

二问:应用了这个分页,在我的backing bean里面偶不知要如何获取我所显现出来的dataModel中的数据。
偶现在需要做一个多项删除,在bean中添加一个boolean属性,以判别是否要删除,虽然可以用getRequestParameterMap来获取其相应的selectBooleanCheck信息,但最好还是通过datamodel中的Bean对象来判断该值是否为true,现在就是不知要如何获得datamodel的bean对象信息。

请解惑?谢谢

  回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-11-09 16:19 各个
我用的是workshop for weblogic 的IDE ,用myfaces的标签 就是不能翻页 是怎么回事 就是点击下一页的时候 没有什么反映 有能人帮助一下  回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-12-12 13:30 cenjun
做分页二遇到的问题:
在点上页和下页的时候没有规律跳的问题,其他地方没有改
bean中代码
private DataModel pagedDataModel;

private TableListBean listBean;

/**
*
*/
public PagedTableListBean() {

}

/**
* @return the itemList
*/
public DataModel getItemList() {
if (pagedDataModel == null) {
pagedDataModel = new LocalDataModel(15);
}

return pagedDataModel;
}

/**
* @return the pagedDataModel
*/
public DataModel getPagedDataModel() {
return pagedDataModel;
}

/**
* @param pagedDataModel
* the pagedDataModel to set
*/
public void setPagedDataModel(DataModel pagedDataModel) {
this.pagedDataModel = pagedDataModel;
}

/**
* @return the listBean
*/
public TableListBean getListBean() {
return listBean;
}

/**
* @param listBean
* the listBean to set
*/
public void setListBean(TableListBean listBean) {
this.listBean = listBean;
}

// local class
private class LocalDataModel extends PagedListDataModel {
public LocalDataModel(int pageSize) {
super(pageSize);
}

public DataPage fetchPage(int startRow, int pageSize) {
// call enclosing managed bean method to fetch the data
return getDataPage(startRow, pageSize);
}
}

// get datePage from DAO
private DataPage getDataPage(int startRow, int pageSize) {
// access database here, or call EJB to do so
TableListBean listBean = new TableListBean();
List subList = listBean.getTableList(startRow, pageSize);
DataPage dataPage = new DataPage(104, startRow, subList);
return dataPage;
}   回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-12-12 13:34 cenjun
listBean.getTableList(startRow, pageSize)
中sql语句拼出来的代码
select * from (select rownum num, a.* from (select * from tb_wo_page_item) a) where num >=startRow and num <startRow+pageSize


jsp中代码:

<rich:dataTable id="dataTableId" value="#{pagedTableListBean.itemList}" var="dT" cellpadding="5px" border="1" rows="15">
<f:facet name="header">

<rich:columnGroup>
<h:column>
</h:column>
<h:column>
<h:outputText value="表 名" />
</h:column>
<h:column>
<h:outputText value="数 量" />
</h:column>
</rich:columnGroup>
</f:facet>


<h:column>
<h:outputText value="#{dT.tbCode}" />
</h:column>
<h:column>
<h:outputText value="#{dT.num}" />
</h:column>
<f:facet name="footer">
<rich:datascroller id="aa" for="dataTableId" />
</f:facet>

</rich:dataTable>

请问这是怎么回事啊???   回复  更多评论
  
# re: 在JSF中实现分页(二) 2007-12-12 13:37 cenjun
更正jsp中代码
<rich:datascroller id="aa" fastControls ="hidden" for="dataTableId" >
<f:facet name="previous">

<h:outputText value="上页"/>

</f:facet>
<f:facet name="pages">
<h:outputText value=""></h:outputText>
</f:facet>
<f:facet name="next">

<h:outputText value="下页"/>

</f:facet>
</rich:datascroller>  回复  更多评论
  
# re: 在JSF中实现分页(二) 2009-03-27 16:44 jsfbegainner
@小偉

你现在这个问题解决了么?  回复  更多评论
  
# re: 在JSF中实现分页(二) 2009-06-15 09:40 Gerry
放在BackBeanBase中导致一个页面上只能有一个分页,如果我想有两个呢?  回复  更多评论
  
# re: 在JSF中实现分页(二) 2010-05-24 21:29 mikeyuan
单纯的分页能做,单纯的按字段排序也能做,
我花费了很长时间,想做一个既能排序又能分页的DataTable,就是不行,郁闷.
又:<JSF in Action>的例子讲了这个问题,但是有Bug
我把<JSF核心编程>的分页和排序的两个例子合在一起,总有问题.

大侠解决一下  回复  更多评论
  
# re: 在JSF中实现分页(二) 2010-11-17 09:21 Wind Wood
你好,最新我初学了一点JSF,准备来做一个项目。但是在项目中遇到一个基础的问题:后台支撑bean里有List的成员属性,其通过dataTable显示后,我点击Form里的Action按钮进入重建视图应用请求值的时候,原先的List属性并没有带过来,导致第二个页面就为空了。我把List改为DataModel也还是一样,传不了值回来,这个问题已经卡了几天了,通宵了两晚都搞不出来。不知道你们是如何处理这样的问题的,如果可以,希望能尽快收到您的回信。万分感谢!!!  回复  更多评论
  

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


网站导航: