I am Siva, a passionate java developer, open source enthusiast, blogger. I like to cover Java, Struts, Spring, Hibernate, Ajax Tutorials, How-To's and Best Practices. Sivaprasadreddy is a DZone MVB and is not an employee of DZone and has posted 38 posts at DZone. You can read more from them at their website. View Full User Profile

Java Coding Best Practices: Better Search Implementation

02.09.2011
| 19262 views |
  • submit to reddit

In web applications searching for information based on the selected criteria and displaying the results is a very common requirement. Suppose we need to search users based on their name.  The end user will enter the username in the textbox and hit the search button and the user results will be fetched from database and display in a grid.

At first this looks simple and we can start to implement it as follows:
 

public class UserSearchAction extends Action
{
public ActionForward execute(...)
{
SearchForm sf = (SearchForm)form;
String searchName = sf.getSearchName();
UserService userService = new UserService();
List<User> searchResults = userService.search(searchName);
//put search results in request and dsplay in JSP
}

}

public class UserService
{
public List<User> search(String username)
{
// query the DB and get the results by applying filter on USERNAME column
List<User> users = UserDAO.search(username);

}
}
The above implementation works fine for the current requirement.

Later client wants to display only 10 rows per page and display a message like "Displaying 1-10 of 35 Users".

Now the code need to be changed for the change request.
 
public class UserSearchAction extends Action
{
public ActionForward execute(...)
{
SearchForm sf = (SearchForm)form;
String searchName = sf.getSearchName();
UserService userService = new UserService();
Map<String, Object> searchResultsMap = userService.search(searchName, start, pageSize);
List<User> users = (List<User>)searchResultsMap.get("DATA");
Integer count = (Integer)searchResultsMap.get("COUNT");
//put search results in request and dsplay in JSP
}

}

 

public class UserService
{
public Map<String, Object> search(String username, int start, int pageSize)
{
//Get the total number of results for this criteria int count = UserDAO.searchResultsCount(username);
List<User> users = UserDAO.search(username, start, pageSize);
// query the DB and get the start to start+pageSize results by applying filter on USERNAME column
Map<String, Object> RESULTS_MAP = new HashMap<String, Object>();
RESULTS_MAP.put("DATA",users);
RESULTS_MAP.put("COUNT",count);
return RESULTS_MAP;
}
}


Later the client wants to give an option to the end user to choose the search type either by UserID or by Username and show the paginated results.
Now again the code needs to be changed for the change request.
 

public class UserSearchAction extends Action
{
public ActionForward execute(...)
{
SearchForm sf = (SearchForm)form;
String searchName = sf.getSearchName();
String searchId = sf.getSearchId();
UserService userService = new UserService();
Map<String, Object> searchCriteriaMap = new HashMap<String, Object>();
//searchCriteriaMap.put("SEARCH_BY","NAME");
searchCriteriaMap.put("SEARCH_BY","ID");
searchCriteriaMap.put("ID",searchId);
searchCriteriaMap.put("START",start);
searchCriteriaMap.put("PAGESIZE",pageSize);

Map<String, Object> searchResultsMap = userService.search(searchCriteriaMap);
List<User> users = (List<User>)searchResultsMap.get("DATA");
Integer count = (Integer)searchResultsMap.get("COUNT");
//put search results in request and dsplay in JSP
}

}

 

public class UserService
{
public Map<String, Object> search(Map<String, Object> searchCriteriaMap)
{
return UserDAO.search(searchCriteriaMap);
}
}

 

public class UserDAO
{
public Map<String, Object> search(Map<String, Object> searchCriteriaMap)
{
String SEARCH_BY = (String)searchCriteriaMap.get("SEARCH_BY");
int start = (Integer)searchCriteriaMap.get("START");
int pageSize = (Integer)searchCriteriaMap.get("PAGESIZE");
if("ID".equals(SEARCH_BY))
{
int id = (Integer)searchCriteriaMap.get("ID");
//Get the total number of results for this criteria
int count = UserDAO.searchResultsCount(id);
// query the DB and get the start to start+pageSize results by applying filter on USER_ID column
List<User> users = search(id, start, pageSize);

}
else
{
String username = (String)searchCriteriaMap.get("USERNAME");
//Get the total number of results for this criteria
int count = UserDAO.searchResultsCount(username);
// query the DB and get the start to start+pageSize results by applying filter on USERNAME column
List<User> users = search(username, start, pageSize);

}
Map<String, Object> RESULTS_MAP = new HashMap<String, Object>();
RESULTS_MAP.put("DATA",users);
RESULTS_MAP.put("COUNT",count);
return RESULTS_MAP;
}

}


Finally the code becomes a big mess and completely violates object oriented principles. There are lot of problems with the above code.
1. For each change request the method signatures are changing
2. Code needs to be changed for each enhancement such as adding more search criteria

We can design a better object model for this kind of search functionality which is Object Oriented and scalable as follows.

A generic SearchCriteria which holds common search criteria like pagination, sorting details.
 

package com.sivalabs.javabp;
public abstract class SearchCriteria
{
private boolean pagination = false;
private int pageSize = 25;
private String sortOrder = "ASC";

public boolean isPagination()
{
return pagination;
}
public void setPagination(boolean pagination)
{
this.pagination = pagination;
}
public String getSortOrder()
{
return sortOrder;
}
public void setSortOrder(String sortOrder)
{
this.sortOrder = sortOrder;
}
public int getPageSize()
{
return pageSize;
}
public void setPageSize(int pageSize)
{
this.pageSize = pageSize;
}

}


A generic SearchResults object which holds the actual results and other detials like total available results count, page wise results provider etc.
 

package com.sivalabs.javabp;

import java.util.ArrayList;
import java.util.List;

public abstract class SearchResults<T>
{
private int totalResults = 0;
private int pageSize = 25;
private List<T> results = null;

public int getPageSize()
{
return pageSize;
}
public void setPageSize(int pageSize)
{
this.pageSize = pageSize;
}
public int getTotalResults()
{
return totalResults;
}
private void setTotalResults(int totalResults)
{
this.totalResults = totalResults;
}

public List<T> getResults()
{
return results;
}
public List<T> getResults(int page)
{
if(page <= 0 || page > this.getNumberOfPages())
{
throw new RuntimeException("Page number is zero or there are no that many page results.");
}
List<T> subList = new ArrayList<T>();
int start = (page -1)*this.getPageSize();
int end = start + this.getPageSize();
if(end > this.results.size())
{
end = this.results.size();
}
for (int i = start; i < end; i++)
{
subList.add(this.results.get(i));
}
return subList;
}

public int getNumberOfPages()
{
if(this.results == null || this.results.size() == 0)
{
return 0;
}
return (this.totalResults/this.pageSize)+(this.totalResults%this.pageSize > 0 ? 1: 0);
}
public void setResults(List<T> aRresults)
{
if(aRresults == null)
{
aRresults = new ArrayList<T>();
}
this.results = aRresults;
this.setTotalResults(this.results.size());
}

}



A SearchCriteria class specific to User Search.
 

package com.sivalabs.javabp;

public class UserSearchCriteria extends SearchCriteria
{
public enum UserSearchType
{
BY_ID, BY_NAME
};

private UserSearchType searchType = UserSearchType.BY_NAME;
private int id;
private String username;

public UserSearchType getSearchType()
{
return searchType;
}
public void setSearchType(UserSearchType searchType)
{
this.searchType = searchType;
}

public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
}


A SearchResults class specific to User Search.
 

package com.sivalabs.javabp;
import java.text.MessageFormat;

public class UserSearchResults<T> extends SearchResults<User>
{
public static String getDataGridMessage(int start, int end, int total)
{
return MessageFormat.format("Displaying {0} to {1} Users of {2}", start, end, total);
}

}


UserService takes the SearchCriteria, invokes the DAO and get the results, prepares the UserSearchResults and return it back.
 

package com.sivalabs.javabp;

import java.util.ArrayList;
import java.util.List;

import com.sivalabs.javabp.UserSearchCriteria.UserSearchType;
public class UserService
{
public SearchResults<User> search(UserSearchCriteria searchCriteria)
{
UserSearchType searchType = searchCriteria.getSearchType();
String sortOrder = searchCriteria.getSortOrder();
System.out.println(searchType+":"+sortOrder);
List<User> results = null;
if(searchType == UserSearchType.BY_NAME)
{
//Use hibernate Criteria API to get and sort results based on USERNAME field in sortOrder
results = userDAO.searchUsers(...);
}
else if(searchType == UserSearchType.BY_ID)
{
//Use hibernate Criteria API to get and sort results based on USER_ID field in sortOrder
results = userDAO.searchUsers(...);
}

UserSearchResults<User> searchResults = new UserSearchResults<User>();
searchResults.setPageSize(searchCriteria.getPageSize());
searchResults.setResults(results);
return searchResults;
}

}

 

package com.sivalabs.javabp;
import com.sivalabs.javabp.UserSearchCriteria.UserSearchType;

public class TestClient
{
public static void main(String[] args)
{
UserSearchCriteria criteria = new UserSearchCriteria();
criteria.setPageSize(3);
//criteria.setSearchType(UserSearchType.BY_ID);
//criteria.setId(2);

criteria.setSearchType(UserSearchType.BY_NAME);
criteria.setUsername("s");

UserService userService = new UserService();
SearchResults<User> searchResults = userService.search(criteria);

System.out.println(searchResults.getTotalResults());
System.out.println(searchResults.getResults().size()+":"+searchResults.getResults());
System.out.println(searchResults.getResults(1).size()+":"+searchResults.getResults(1));
}
}


With this approach if we want to add a new criteria like search by EMAIL we can do it as follows:
1. Add BY_EMAIL criteria type to UserSearchType enum
2. Add new property "email" to UserSearchCriteria
3. criteria.setSearchType(UserSearchType.BY_EMAIL);
   criteria.setEmail("gmail");
4. In UserService prepare the HibernateCriteria with email filter.

Thats it :-)

From : http://sivalabs.blogspot.com/2011/02/java-coding-best-practices-better.html

Published at DZone with permission of Sivaprasadreddy Katamreddy, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Gervais Blaise replied on Wed, 2011/02/09 - 3:20am

Hello, Thanks for your interesting article. I'm just looking for a generic Search Api like Hibernate Criteria but who isn't linked with the database (because we have some apps wo use nosql). Your idea is great but can be improved to be more self descriptive. Ps: You have a mistake into the 7 code sample (UserDAO) at line 5, you are casting the map object as Integer instead of String.

Andrea Mattioli replied on Wed, 2011/02/09 - 6:20am in response to: Gervais Blaise

You can give a look at JavATE. The DominATE module contains the Specification APIs that is exactly what you are looking for. At this moment there is only an implementation atop Hibernate but I'm working to a MongoDb implementation. If you are interested let me know on the project forum

Sivaprasadreddy... replied on Wed, 2011/02/09 - 7:18am in response to: Gervais Blaise

Hi,

Thanks that you like this article. This idea can be improved to be more generic and support others features also including pagination(client/server), filtering etc.

That Map is of type <String, Object> and the count is of Integer type. So I cast it to Integer.

Gervais Blaise replied on Wed, 2011/02/09 - 8:07am in response to: Sivaprasadreddy Katamreddy

You cast the "SEARCH_BY" to Integer but you assign it to a String.

String SEARCH_BY = (Integer)searchCriteriaMap.get("SEARCH_BY");

Sivaprasadreddy... replied on Wed, 2011/02/09 - 8:30am in response to: Gervais Blaise

Ooops... corrected... Thank you Gervais Blaise :-)

Suhasini Ch replied on Thu, 2011/02/10 - 4:21am

nice article,how do i navigate to the next page if i set pagesize 10 and i have results more than 10..

Do i need to use dispaly tag?

Sivaprasadreddy... replied on Thu, 2011/02/10 - 4:29am in response to: Suhasini Ch

To get all the results we can call:  searchResults.getResults();
If we want to get page wise results pass the page number as: searchResults.getResults(2);


This logic can be integrated with DisplayTag.

Thomas Kern replied on Thu, 2012/09/06 - 10:48am

Nice article. Code review is single best development practice I recommend since code spent 90% time in maintenance and only 10% in initial development , a code which has comments and readable is easy to maintain and understand and saves a lot of time to understand and less error prone while making any changes but at the same time there should be guidelines for maintainers also because I have seen code quality getting degraded with every version.

http://www.java-tips.org 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.