Precise Java Google
Home  | J2SE  | J2EE  | Book  | Free Books  | Contact
Chapter 1  | Chapter 2  | Chapter 3


Recipe 1.1: Googling large results

Problem

Displaying large results with an inefficient design leads to poor application performance.

Background

One of the general requirements in any web application is to display large results to the user in an efficient way. For example, you may need to display product items in a catalog or search results to the user. Search results or product items are in general very large when compared to other data elements. When the application needs to display large results like this and is designed with any of the inefficient techniques as described in the following Table it becomes a major bottlenecks in your application and leads to poor application performance. This Table describes common techniques that a designer might follow when he needs to display large results. It describes performance problem associated with each technique.

Design techniques to display large results
  Technique Description
1 Get the results from the database only once when a user requests, cache them in custom cache and display small subset of results to the user iteratively in multiple pages from the cache. This technique looks like an effective solution to reduce database calls but it increases memory enormously depending on number of concurrent users in your application. For example, if an average user query returns 1000 records and the application gets 10000 concurrent users, the application has to hold ten million records (10000*1000 = 10000000) in the cache (memory).
2 Get the results from the database only once when a user requests, cache them in HttpSession object instead of custom cache and display small subset of results to the user iteratively in multiple pages from the HttpSession object. This technique is similar to above technique but the results are cached in HttpSession object instead of custom cache. In this case, the results are held in the HttpSession objects (held in the memory) till session timeout value expires or till HttpSession objects are invalidated.
3 Get the same results from the database on every page request, truncate them into subset and display that subset to the user. This technique does not cache the results and does not increase the memory but makes a lot of database calls in order to display the results to the user iteratively in multiple pages. For example, if the user query returns 1000 records, you select a subset of 50 records from the results and display them to the user. Whenever user requests the next set of results, you query the database again to retrieve same 1000 records but select the next subset of 50 records. To show all 1000 records to the user, you query the database 20 times (50 * 20 pages).


These techniques are inefficient when the results are huge; they either increase memory or impose database calls.


Considering large memory and database calls problems, how can we display large results to the user in an efficient manner?

Recipe

The solution to the above problems is to get only small set of results from the database, cache them and display subset of the cached results in one page and allow the user to navigate through multiple pages to see all records depending up on the request. The navigation approach is very similar to google.com search results approach where it only displays sub set of records first and allows user googling (a usual term that people use when browsing through google.com) the results when the user only requests them further. Only a subset of results are displayed to the user at first and lazy loads the results depending on user's request. Using this approach the user can navigate through the results back and forth or jump to the required page. The advantage of this approach is that it gives the user a fine granular browsing experience and is fast and efficient by reducing memory and database calls.

Figure 1.1 shows the user interface screen shot of this recipe's example.

(1) Page Navigator; by which you can navigate through the larger results by clicking page number

(2) Body area; where you will find the results as per the user clicked page number

This screen shot is a result of executing PageNavigator.jsp. Listing 1.1 shows PageNavigator.jsp code. Page Navigator contains page numbers as links by which user can jump to a particular page to see the results. The number of pages depends upon total number of results and the number of results you want to display per page. For example, if the total results are 1000 records for a user query and you want to display 25 records per page, then the number of pages is 40 (1000/25 = 40), which you see in the page navigator.


Listing1.1 PageNavigator.jsp code

<%@ taglib uri="pageNavigator" prefix="pager" %>
<% String index = request.getParameter("index");
if(index == null) index = "0";
%> <pager:pageNavigator itemsPerPage="5" index="<%=index %>" page="PageNavigator.jsp" /> #1
(annotation)<#1 Calls PageNavigatorTag class>
 

As per the user requested page number, PageNavigator.jsp passes items per page, page number as index and the page name to the PageNavigatorTag class. For example, if the user clicks on page number 2, this JSP passes items per page as 5, index as 2 and page as its name and displays the results from 11 to 15. PageNavigatorTag class prepares the page navigator and the body content as shown in Figure 1.1. A partial code snippet of PageNavigatorTag class is shown in Listing1.2.


Listing1.2 code snippet from PageNavigatorTag.java

public class PageNavigatorTag extends TagSupport{
  private int index = 0;
  private int itemsPerPage = 5;
  private String page = null;
  private int size = 0;
  private static int navigatorSize = 10;
  public int doStartTag() throws JspException {
  try {
   String navigator = getNavigator();
   pageContext.getOut().print(navigator);
   pageContext.getOut().print("<br><br>");
   pageContext.getOut().print(getItems());
   pageContext.getOut().print("<br><br>");
   pageContext.getOut().print(navigator);
  }
  catch (IOException ioe) {
   throw new JspException(ioe.getMessage());
  }
   return SKIP_PAGE;
  }
  public int doEndTag() throws JspException {
   return EVAL_PAGE;
  }
  public String getNavigator() { # 1
   ItemsHelper helper = new ItemsHelper();
   size = helper.getItemsSize(); #2
   int maxIndex = getMaxIndex();
   StringBuffer navigator = new StringBuffer();
  if (index > 0) {
   navigator.append(getAnchor(index - 1, "Previous"));
   navigator.append("  ");
  }
  if (maxIndex != 0) {
   int fromIndex = index - (navigatorSize/2);
   if(fromIndex < 0) fromIndex = 0;
  if(fromIndex > (maxIndex-navigatorSize)) fromIndex = maxIndex-navigatorSize;
   int toIndex = index + (navigatorSize/2);
   if(toIndex < navigatorSize) toIndex = navigatorSize;
   if(toIndex > maxIndex) toIndex = maxIndex;

   for (int i = fromIndex; i <= toIndex; i++) {
   if (i == index) {
   navigator.append(i);
   }
   else {
   navigator.append(getAnchor(i, Integer.toString(i)));
   }    navigator.append(" ");
   }
  }

  public String getItems() { #3
   ItemsHelper helper = new ItemsHelper();
   int fromIndex = index * itemsPerPage;
   int toIndex = fromIndex + itemsPerPage;
   if (size < toIndex) toIndex = size;
   List list = helper.getItems(fromIndex, toIndex); #4
   StringBuffer body = new StringBuffer();

   if (list != null && list.size() > 0) {
   ListIterator iter = list.listIterator();
   body.append(" <TABLE border=\"1\"><TBODY>" );

   while (iter.hasNext()) {
   Item item = (Item) iter.next();
   body.append(" <TR>" );
   body.append(" <TD>" );
   body.append(item.getName());
   body.append(" </TD>" );
   body.append(" <TD>" );
   body.append(item.getDesc());
   body.append(" </TD>" );
   body.append(" </TR>" );
   }
   body.append(" </TBODY></TABLE>" );
   }
   return body.toString();
   }

   }

   (annotation)<#1 Prepares page navigator>
   (annotation)<#2 Gets total number of items>
   (annotation)<#3 Prepares body content>
   (annotation)<#4 Gets only subset of items from the data teir>

 


Discussion

There are two main areas in the user interface; one is the Page Navigator where you will find number of pages (containing results) as links (see Figure 1.1 screen shot) and the other is body content where you will find user query results. In this example, the body contains user requested books.

PageNavigator.jsp and PageNavigatorTag class are main classes in web tier to display Page Navigator and the page content. PageNavigator.jsp passes the user-clicked index to the PageNavigatorTag tag class, which in turn responsible to delegate the request to the data tier. PageNavigatorTag class passes from-index and to-index to the data tier through ItemsHelper class, for example 20 - 30 when user clicks on 2nd link, to get the results between those indexes. You can limit the number of links to be displayed in the page navigator and items per page by specifying them in PageNavigator.jsp. This approach limits the amount of data that we get them from the database for every request. Caching should be done in data tier to improve performance and to enable other clients to access the cache. When you cache the results, you should keep in mind that caching all the results for each user can increase the memory enormously and opposes this recipe's advantage, instead, cache few pages of results for example 150 records out of 1000 records because ideally most users browse through only first few pages.

Feed back

We appreciate and welcome your comments on this section. Email commentsZZZ@precisejavaZZZ.com (remove ZZZ which is placed to prevent spam). Please note that we may not be able to reply to all the emails due to huge number of emails that we receive but we appreciate your comments and feedback.

 





Copyright © 2001-2005, Ravi Kalidindi and Rohini Kalidindi. All rights reserved.