Do you know this problem. You have to show a table with users on a page. You are using the “ice:dataTable” component and because you don’t want to show all users in the DB on one page you are also using the “ice:dataPaginator” component for the paging stuff.
On the first look it looks good. Maybe you have 50 Users in your test database on localhost and the “ice:dataPaginator” shows always just 10 users in your table and the count of all users who are available. By clicking on the right arrow the component shows you the next 10 users. Perfect.
Then you deploy your app on a prod. server. In the prod. database you have maybe 300 000 users. Then you call the page with the user table it takes a long time and then you got a “Out of Memmory Exception”. 🙂
The Problem is that the dataPaginator makes the paging and the right count just for the elements who are in the List/Array in the managedBean. If you have large amount of data this is a problem, because you don’t want to load your whole data into the memory.
There are several solutions for this problem. I will show here my solution. The trick is, to make the dataPaginator believe that all data is in the list, but in truth there are not.
OK. Assume we have a dataTable like this:
<ice:dataTable value="#{userBean.users}" var="user" id="userTable" width="700" rowClasses="iceDatTblRow1,iceDatTblRow2" columnClasses="colCenter, colCenter, colCenter, colCenter, colCenter, colCenter"> .... </ice:dataTable>
and a dataPaginator like this:
<ice:dataPaginator id="usersPaging" for="userTable" rendered="true" rowsCountVar="rowsCount" displayedRowsCountVar="displayedRowsCountVar" firstRowIndexVar="firstRowIndex" lastRowIndexVar="lastRowIndex" pageCountVar="pageCount" pageIndexVar="pageIndex" > <ice:outputFormat value="#{labels['tableHeadMessages']}" > <f:param value="#{rowsCount}" /> <f:param value="#{displayedRowsCountVar}" /> <f:param value="#{firstRowIndex}" /> <f:param value="#{lastRowIndex}" /> <f:param value="#{pageIndex}" /> <f:param value="#{pageCount}" /> <f:param value="#{element}" /> </ice:outputFormat> </ice:dataPaginator>
The dataTable component expects that “users” is a “java.util.List”. The dataPaginator component will call the “getSize()” method on the list and use the result to make the paging. That we really want to have is a list impl. that returns the complete count of elements in the database but just holds the amount of elements in the memory that we want to show on one page. To achieve this we have to implement our own version of the “java.util.List” interface.
I created an Interface like this:
public interface LazyLoadable<T> extends Serializable{ /** * This method update the managed list. * * @param index the startPoint for the lazy loading * @param pageSize the max. number of elements to load * @return a dynamic List */ public List<T> getUpdatedList(int index, int pageSize); /** * This method returns how much elements are in the list (DB). * * @return Long list size */ public Long getSize(); }
This interface we can implement for every special case. We have to implement it for every special case. Like Users, Companys, Events and so on. The implementation for fetching users could look like this:
public class LazyContacts implements LazyLoadable<ContactTableDto>{ private static final long serialVersionUID = 1676780956302516950L; private IUserService userService; public Long getSize() { return userService.getAllUsersCount(); } public List<ContactTableDto> getUpdatedList(int index, int pageSize) { return userService.getUsers(user.getId(), index, pageSize); } // ******* GETTER AND SETTER FOR DI ************* public IUserService getUserService() { return userService; } public void setUserService(IUserService userService) { this.userService = userService; } }
Now we still need a impl. of the “java.util.List” interface. We can use the “AbstractList” impl and extend this one for our needs.
public class LazyLoadingList extends AbstractList implements Serializable { private static final long serialVersionUID = -3229282717958140661L; private static final Log LOG = LogFactory.getLog(LazyLoadingList.class); private LazyLoadable lazyObject; private List viewList; private int currentPage = -1; private int numResults; private int pageSize; private int hashCode = 0; public LazyLoadingList(LazyLoadable lazyObject, int pageSize) { this.lazyObject = lazyObject; this.numResults = lazyObject.getSize().intValue(); this.pageSize = pageSize; } @Override public Object get(int i) { LOG.debug("get: " + i); int page = i / pageSize; if (page != currentPage) { currentPage = page; viewList = lazyObject.getUpdatedList(i, pageSize); } if (viewList == null) return null; return viewList.get(i % pageSize); } @Override public int size() { LOG.debug("size: " + numResults); return numResults; } @Override public int hashCode() { if (hashCode == 0){ double value = Math.random(); String s = String.valueOf(value); s = s.replaceAll("\\.", ""); hashCode = Integer.parseInt(s.substring(0, 6)); } return hashCode; } @Override public boolean equals(Object o) { if (o == null) return false; return hashCode() == o.hashCode(); } @Override public Iterator iterator() { LOG.debug("iterator"); if (viewList == null && numResults > 0){ get(0); } if (viewList != null) return viewList.iterator(); return new EmptyIterator(); } public void setNumResults(int numResults) { this.numResults = numResults; } public void reset(){ viewList = null; currentPage = -1; numResults = 0; numResults = lazyObject.getSize().intValue(); } public void reload(){ reset(); get(0); } public void clear() { reset(); } public LazyLoadable getLazyObject() { return lazyObject; } public void setLazyObject(LazyLoadable lazyObject) { this.lazyObject = lazyObject; } public List getViewList() { return viewList; } public void setViewList(List viewList) { this.viewList = viewList; } }
Now we have to bring the two impl. together. We can do this job very easy with the Spring Framework.
<bean id="lazyFoundContacts" class="org.company.gui.paging.LazyFoundContacts" scope="request" /> <bean id="lllFoundContacts" class="org.company.gui.paging.LazyLoadingList" scope="request"> <constructor-arg index="0" ref="lazyFoundContacts" /> <constructor-arg index="1" value="10" /> </bean>
Now we have to inject this list impl. into the managedBean.
<bean id="userBean" class="org.company.gui.userBean" scope="request"> <property name="users" ref="lllFoundContacts" /> </bean>
That’s it.