How to do Lazy Pagination using JSF-Richfaces-SEAM?

Let me take you to, how to do dynamic pagination using  rich:dataTable, rich:datascroller, org.ajax4jsf.model.SerializableDataModel.  We will get into a step by step approach on implementing Lazy Pagination using JSF-Richfaces-SEAM.

Lazy Pagination using JSF-Richfaces-SEAM

Please Note: For Richfaces 4 usage, after downloading DynamicDataModel.java. Please do following two bullet points change to java class file. SerializableDataModel was removed in release of Richfaces 4.

  • Replace extends class from  org.ajax4jsf.model.SerializableDataModel to  org.ajax4jsf.model.ExtendedDataModel
  • Remove following method from class file


How to do Step by Step approach!

Step 1:

Create a Class DynamicDataModel extending org.ajax4jsf.model.SerializableDataModel; with all overridden methods.

Step 2:

Declare below given local variables to manipulate the pagination in the class.

Step 3:

And now we have to implement following methods to achieve dynamic loading of limited data required to display on the page.

Step 4:

Ignore following methods since they are never invoked by framework. However you can make use of it on need basis.

Step 5: 

Now I’m gonna explain the method walk(), vital part of implementation.

Step 6: 

We have completed Java part; let’s move on to JSF code snippet to invoke the Java class

That’s all we have to do above steps to achieve dynamic data model pagination through Lazy Pagination using JSF-Richfaces-SEAM. This approach could minimize the weight on presentation layer loading the required data for the page rather loading all the data; that are yet to be displayed in forthcoming pages assuming the data extraction calls are not so expensive.


Downloads

Code files used in the article.  Now all the Code Snippets and related files will be shared from GitHub Repo.

If you have any queries please leave a comment.

  • Om Behera

    Gud one Jeeva.. keep it up.

  • harish

    Currently I am trying to follow your approch of lazy loading but am unable to do so.in my case I am having a resultset of 1000 rows in my datatable which is getting populated by a select query written inside my DAO class which is getting called from Managebean class for richfaces4.0.I was trying to implement walk metthod to do a page specific lazy loading but i am unable to get through.Please help me in this regard.

    • Jeevanandam Madanagopal

      Harish –

      Sure, I will provide guidance.
      Download DynamicDataModel.java from article, place it in your project(i believe you might have already did). As per your comment, you have to focus on below items-
      * Within walk method call your service method to populate sampleDataList
      * carry firstRow & numberOfRows to DAO class query data from table according to these values
      * mapping DynamicDataModel class in rich:dataTable

      If it’s still unclear to you, no issues. Kindly share below information from your end. I will assist you.
      * code snippet of walk method, service method and DAO method

      Cheers,
      Jeeva

  • harish

    Thanks for the reply.

    Do I need to use DynamicDataModel ..What I was thinking is that I will write a plain Richfaces Managebean class which will extend SerializableDataModel(In 4.0 its ExtendedDataModel) after that I will implement the methods of the ExtendedDataModel.

    • Jeevanandam Madanagopal

      Harish – Apologies for delay response. You can straight away use DynamicDataModel.java in project for richfaces 3.x

      for richfaces 4 you can ahead with ExtendedDataModel, however I would suggest you to go thru DynamicDataModel class for understanding.

      Cheers,
      Jeeva

  • Aladin

    Hi,

    I have problem with method walk. It’s called twice in INVOKE_APPLICATION phase. Any suggestion?

    Thanks,

    Aladin

    • Jeevanandam Madanagopal

      Aladin – hmm actually it depends, let me describe. lets say page has ‘one data scroller with datatable’ so walk called twice from the framework. one for datascroller total record count and next one for actual data fetch of datatable.
      Just to describe in another way is ‘two datascroller with datatable (one scroller above datatable & next scroller below datatable)’ so walk method will be called three times.

      Cheers,
      Jeeva

      • Aladin

        Hi Jeevanandam,
        Thank you very much. :D
        Cheers,
        Aladin

  • Shruti

    Hi Jeeva,
    Thank you for the nice article you have posted on dynamic pagination..I m trying to implement dynamic pagination in my project but unable to achieve it…I am using ehcache to store the data which is retrieved from the database…My walk method is being called 5 times..I don’t know if it is behaving correctly..Can you please help

    • jeevatkm

      Thanks Shruti, sure I will help you. Please share your JSF code snippet.

       

      Cheers,

      Jeeva

    • Shruti

      Hi Jeeva,
       
      Thanks for your quick reply , I’m really stuck into this problem…I am posting the code for my walk method..Can you please look into it and suggest where am I going wrong.
      Also I wanted to confirm 2 things
      1)How is the wrappedKeys being used internally by the model?
      2)How many times is the walk method called on each click ?
      3)Is it necessary that each row should have a unique primary key, what I am doing is that
      I get 5 rows from the DB give them  ids 0,1,2,3,4 put these keys in the wrapped keys and create a Map with these keys and DTO’s and put it in wrappedData and then iterate over the keySet of   the Map and call the visitor.process method.Then I put the Map in ehcache with a id (id is the page no I have visited) and on the next call I get theold data from the Cache and the new data from the DB.
       
      The problem is that it works ok , but in between I get blank rows in the datatable  also when I debug I see that the current key is not proper, sometimes it is null and sometimes it is not correct.
       

      • Shruti

        Hi Jeeva,
        This is my walk method 
        public void walk(FacesContext context, DataVisitor visitor, Range range,
                          Object argument) throws IOException {
         int firstRow = ((SequenceRange) range).getFirstRow();
                    int numberOfRows = ((SequenceRange) range).getRows();
        int numberOfRows = ((SequenceRange) range).getRows();
        String cachingWrappedKeys = firstRow + “wrappedKeys”;
        String cachingWrappedData = firstRow + “wrappedData”;     
        wrappedKeys = new ArrayList<Integer>();
        wrappedData = new LinkedHashMap<Integer, ContainerDocumentDTO>();
        if (CacheManager.getInstance().getCache(“ContainerDocumentSearchCache”)
                                .get(cachingWrappedKeys) != null) {
                          Element elt = CacheManager.getInstance()
                                      .getCache(“ContainerDocumentSearchCache”)
                                      .get(cachingWrappedKeys);
                          if (elt != null)
                          wrappedKeys=(List<Integer>) elt.getValue();
                          wrappedData=((Map) CacheManager.getInstance()
                                      .getCache(“ContainerDocumentSearchCache”)
                                      .get(cachingWrappedData).getValue());
                         for (Integer key : wrappedData.keySet()) {
                         visitor.process(context, key, argument);
                          }
                    }

        • Shruti

          This is the second part of walk method
           
           else {
                         List<ContainerDocumentDTO> list=new LinkedList<ContainerDocumentDTO>();
                         list=containerDocumentDao
                                        .searchContainerDocument(new Integer(firstRow),
                                                    numberOfRows, queryBuilder, this.sortField,
                                                    this.ascending);
          for (ContainerDocumentDTO containerDocumentDTO :list ) {
                                wrappedKeys.add(containerDocumentDTO.getPrimaryKey());
                                wrappedData.put(containerDocumentDTO.getPrimaryKey(),
                                containerDocumentDTO);
                                                          searchResults.add(containerDocumentDTO);
                                  visitor.process(context, containerDocumentDTO.getPrimaryKey(),
                                              argument);
                            }
          if (!wrappedKeys.isEmpty() && !wrappedData.isEmpty()) {
                                  if (CacheManager.getInstance().getCache(“ContainerDocumentSearchCache”)
                                              .get(cachingWrappedKeys) == null){
                                  CacheManager.getInstance()
                                              .getCache(“ContainerDocumentSearchCache”)
                                              .put(new Element(cachingWrappedKeys, wrappedKeys));
                                  }
                                  if (CacheManager.getInstance().getCache(“ContainerDocumentSearchCache”)
                                              .get(cachingWrappedData) == null){
                                  CacheManager.getInstance()
                                              .getCache(“ContainerDocumentSearchCache”)
                                              .put(new Element(cachingWrappedData, wrappedData));
                                
                                 }
                            }

        • jeevatkm

          Hello Shruti –
           
          I went through your code snippet, it seems you’re taking long route and ContainerDocumentDTO object data gets conflicted in your code.
           
          My observation from your code snippet:
          —————————————————-
          – numberOfRows variable is present twice (i believe it may be during a copy/paste in the comment)
          – cachingWrappedKeys & cachingWrappedData cache keys has high possibility of duplicate key value; will lead to overwrite or dataloss
          – Don’t persist wrappedKeys, you already have that in the List of objects. So wrapped keys will be populated before calling visitor.process in the foreach loop
          – wrappedKeys must be unique; I believe attribute ‘containerDocumentDTO.getPrimaryKey()’ is unique value at your end
           
          Try below approach and let me know, how it goes:
          ——————————————————————
          Create a class DataProvider.java
          create a method ‘getSearchContainerDocument’ inside
          PS: correct the data type of parameters, I’m placing with my guess
           
          public List<ContainerDocumentDTO> getSearchContainerDocuments(int firstRow, int numberOfRows, String sortField, boolean ascending) {        
          // keep your query builder reference in this class level         List<ContainerDocumentDTO> dataList = containerDocumentDao.searchContainerDocument(new Integer(firstRow), numberOfRows, queryBuilder, sortField, ascending);
           
          // persist only this dataList and apply your cache logic    
          // retrieval of cache value and storing a new dataList, etc..    
          // lets say your CACHE_KEY value is DATA_TABLE_VALUES_<PAGENO>         List<ContainerDocumentDTO> mergedList = CacheManager.getInstance().getCache(“ContainerDocumentSearchCache”)                               .get(CACHE_KEY);    
          if (null == mergedList || mergeList.isEmpty) {        
           
          // apply your cache population logic here    
           
          } else {        
           
          // apply your merge or manipulation logic here                
          //just for sample        
          mergedList.addAll(dataList);    
          }        
           
          return mergedList;
          }
           
          Data Model Class:
          ————————
          Initialize the wrappedData variable at class level Map<Integer, ContainerDocumentDTO> wrappedData = new HashMap<Integer, ContainerDocumentDTO>();
           
          DataProvider dataProvider; // Initialize it as per your need
           
          In the walk method just do following-
          int firstRow = ((SequenceRange) range).getFirstRow();
          int numberOfRows = ((SequenceRange) range).getRows();
          wrappedKeys = new ArrayList<Integer>();
           
          List<ContainerDocumentDTO> dataList = dataProvider.getSearchContainerDocuments(firstRow, numberOfRows, sortField, ascending);
           
          if (!dataList.isEmpty()) {    
          for (ContainerDocumentDTO containerDocumentDTO : dataList) {         wrappedKeys.add(containerDocumentDTO.getPrimaryKey());         wrappedData.put(containerDocumentDTO.getPrimaryKey(), containerDocumentDTO);        
          visitor.process(context, containerDocumentDTO.getPrimaryKey(), argument);    
          }
          }
           
          Cheers,
          Jeeva

      • jeevatkm

        Hello Shruti –
         
        For point #1: wrappedKeys list used to preserve actual order of items
        For point #2: If only datatable is used walk method will get called once. Lets you have datatable and datascroller above & below the datatable then it will get called three times
        For point #3: yes, basically will be you used to retrieve row data while click/select/rendering/sorting
         
        Give a try for below mentioned approach, your problem may get resolved!
         
        Cheers,
        Jeeva

        • Shruti

          [email protected] Jeeva,
          Thank you so much..I removed caching logic from the walk method and implemented it at the Dao level. Thanks a lot for your help ….its working now!!!
          However I have observed one behaviour that on each click my walk method is called 5 times. I just have one datatable and one scroller.

        • jeevatkm

          Hello Shruti – Good to hear :)
           
          Just share your JSF code snippet from your page (datable & datascroller), Let me have a look.
           
          Cheers, Jeeva

  • Bipin

    I am always getting 0 as first row and -1 as numberOfRows from below code snippet.

    this.firstRow = ((SequenceRange) range).getFirstRow();

    this.numberOfRows = ((SequenceRange) range).getRows();

    However I have 39 records in DB.

    Please suggest me how to get actual value of firstRow and numberOfRows

    • Hello Bipin – To sort out your issue, download DynamicDataModel.java from article and focus on line # 100. Once you implement totalRows logic, mentioned issue will get resolved. All the best and let me know.

      Cheers,
      Jeeva