2 Replies Latest reply on Apr 15, 2007 12:16 PM by chubinator

    Large Data Tables in Seam - feedback welcome

      This is my take at the way to manage large datasets in @DataModel, without loading them all in memory. Hopefully, this code will be of use to others and/or generate useful improvement suggestions. Anyway, here goes:

      LazyList.java - implements List interface, allowing its use in @DataModel. Only holds one page (size can be configured) worth of data, delegating the actual retrieval to the fetcher interface. Knows the total amount of data, again by calling the appropriate method in fetcher.

      public class LazyList<T> implements List<T> {
       public static final int DEFAULT_PAGE_SIZE = 20;
       private int currentPage;
       private List<T> dataset;
       private int datasetSize;
       private IFetcher<T> fetcher;
       private int pageSize = DEFAULT_PAGE_SIZE;
      
       public LazyList(IFetcher<T> fetcher) {
       this.fetcher = fetcher;
       datasetSize = fetcher.getDatasetSize();
       currentPage = 0;
       }
       public LazyList(IFetcher<T> fetcher, int pageSize) {
       this(fetcher);
       this.pageSize = pageSize;
       }
       public T get(int index) {
       int requestedPage = index / pageSize;
       if (index < 0 || index > datasetSize) {
       throw new IndexOutOfBoundsException();
       }
       if (dataset == null || currentPage != requestedPage) {
       currentPage = requestedPage;
       dataset = fetcher.getDatasetPage(currentPage * pageSize, pageSize);
       }
       return dataset.get(index % pageSize);
       }
       public boolean isEmpty() {
       return datasetSize == 0;
       }
       public int size() {
       return datasetSize;
       }
       // the rest of the interface methods {
       throw new UnsupportedOperationException("Not implemented");
       }
      }
      


      IFetcher.java:
      public interface IFetcher<T> {
       /** Fetch a page of data */
       List<T> getDatasetPage(int start, int pageSize);
       /** Fetch total dataset size */
       int getDatasetSize();
      }
      


      CriteriaFetcher.java - implemets fetcher interface via Hibernate criteria calls. Hibernate Session is injected via Seam, the criteria properties neeed to be specified during fetcher construction (cannot be placed in constructor as Seam needs a no-arg one). Criterias are used because sorting and filtering can easily be added to them.
      @Name ("fetcher")
      @Scope (ScopeType.CONVERSATION)
      public class CriteriaFetcher<T> implements IFetcher<T>, Serializable {
       @Logger
       private static Log log;
       private DetachedCriteria countCriteria;
       private DetachedCriteria queryCriteria;
       @In
       private Session session;
      
       @SuppressWarnings ("unchecked")
       public List<T> getDatasetPage(int start, int pageSize) {
       log.trace("Fetching #0 records starting from #1", pageSize, start);
       return queryCriteria.getExecutableCriteria(session).setFirstResult(start).setMaxResults(pageSize).list();
       }
       @SuppressWarnings ("unchecked")
       public int getDatasetSize() {
       log.trace("Fetching dataset size");
       return (Integer) countCriteria.getExecutableCriteria(session).uniqueResult();
       }
       public void setCountCriteria(DetachedCriteria countCriteria) {
       this.countCriteria = countCriteria.setProjection(Projections.rowCount());
       }
       public void setQueryCriteria(DetachedCriteria queryCriteria) {
       this.queryCriteria = queryCriteria;
       }
      }
      


      Now any bean would use the paged lists in the following way:
      @In (create = true)
       private CriteriaFetcher<T> fetcher;
       @DataModel
       private LazyList<T> records;
      
       protected DetachedCriteria buildCriteria() {
       return DetachedCriteria.forClass(T.class);
       }
       @Factory ("records")
       public void createRecords() {
       fetcher.setCountCriteria(buildCriteria());
       fetcher.setQueryCriteria(buildCriteria());
       records = new LazyList<T>(fetcher, 10);
       }
      


      If sorting/filtering is needed, simply recreate the records variable with a different fetcher that has the necessary sorts/filters built in. Until then, the constructed LazyList stays in its scope (CONVERSATION is recommended), fetching data pages as necessary.

      Comments/suggestions are welcome - though the code is working already, so I'm not looking for others to fix it for me :)

      Alex

        • 1. Re: Large Data Tables in Seam - feedback welcome
          chubinator

          This looks cool.

          I'm new to seam and have been mucking around with paging data models as well.

          Question: you said you use conversational scope. Do you create a long running conversation or do you just use the temporary one created for you?

          If long running, how do you end it when the user just jumps to another page that may itself require a long running conversation?

          If temporary, do you have any links that invoke methods like "editRow(row)" and if so do you get null for row? I have and have used long running conversations to deal with it, but then struggle with the above.

          Again, these are probably silly questions, but I'm new to all this. Any help/suggestions is greatly appreciated.

          • 2. Re: Large Data Tables in Seam - feedback welcome
            chubinator

            Well, I "think" I solved my own problem. I was able to use the temporary conversation after all. My problem was that I was using s:link for my row level commands instead commandLink. I read in another post that s:link does submit the form, etc.

            None the less, I am curious as to how you went about it.

            Thanks