Java Coding Best Practices: Better Search Implementation
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:
01.
public
class
UserSearchAction
extends
Action
02.
{
03.
public
ActionForward execute(...)
04.
{
05.
SearchForm sf = (SearchForm)form;
06.
String searchName = sf.getSearchName();
07.
UserService userService =
new
UserService();
08.
List<User> searchResults = userService.search(searchName);
09.
//put search results in request and dsplay in JSP
10.
}
11.
12.
}
01.
public
class
UserService
02.
{
03.
public
List<User> search(String username)
04.
{
05.
// query the DB and get the results by applying filter on USERNAME column
06.
List<User> users = UserDAO.search(username);
07.
08.
}
09.
}
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.
01.
public
class
UserSearchAction
extends
Action
02.
{
03.
public
ActionForward execute(...)
04.
{
05.
SearchForm sf = (SearchForm)form;
06.
String searchName = sf.getSearchName();
07.
UserService userService =
new
UserService();
08.
Map<String, Object> searchResultsMap = userService.search(searchName, start, pageSize);
10.
Integer count = (Integer)searchResultsMap.get(
"COUNT"
);
11.
//put search results in request and dsplay in JSP
12.
}
13.
14.
}
01.
public
class
UserService
02.
{
03.
public
Map<String, Object> search(String username,
int
start,
int
pageSize)
04.
{
05.
//Get the total number of results for this criteria int count = UserDAO.searchResultsCount(username);
06.
List<User> users = UserDAO.search(username, start, pageSize);
07.
// query the DB and get the start to start+pageSize results by applying filter on USERNAME column
08.
Map<String, Object> RESULTS_MAP =
new
HashMap<String, Object>();
09.
RESULTS_MAP.put(
"DATA"
,users);
10.
RESULTS_MAP.put(
"COUNT"
,count);
11.
return
RESULTS_MAP;
12.
}
13.
}
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.
01.
public
class
UserSearchAction
extends
Action
02.
{
03.
public
ActionForward execute(...)
04.
{
05.
SearchForm sf = (SearchForm)form;
06.
String searchName = sf.getSearchName();
07.
String searchId = sf.getSearchId();
08.
UserService userService =
new
UserService();
09.
Map<String, Object> searchCriteriaMap =
new
HashMap<String, Object>();
10.
//searchCriteriaMap.put("SEARCH_BY","NAME");
11.
searchCriteriaMap.put(
"SEARCH_BY"
,
"ID"
);
12.
searchCriteriaMap.put(
"ID"
,searchId);
13.
searchCriteriaMap.put(
"START"
,start);
14.
searchCriteriaMap.put(
"PAGESIZE"
,pageSize);
15.
16.
Map<String, Object> searchResultsMap = userService.search(searchCriteriaMap);
17.
List<User> users = (List<User>)searchResultsMap.get(
"DATA"
);
18.
Integer count = (Integer)searchResultsMap.get(
"COUNT"
);
19.
//put search results in request and dsplay in JSP
20.
}
21.
22.
}
1.
public
class
UserService
2.
{
3.
public
Map<String, Object> search(Map<String, Object> searchCriteriaMap)
4.
{
5.
return
UserDAO.search(searchCriteriaMap);
6.
}
7.
}
01.
public
class
UserDAO
02.
{
03.
public
Map<String, Object> search(Map<String, Object> searchCriteriaMap)
04.
{
05.
String SEARCH_BY = (String)searchCriteriaMap.get(
"SEARCH_BY"
);
06.
int
start = (Integer)searchCriteriaMap.get(
"START"
);
07.
int
pageSize = (Integer)searchCriteriaMap.get(
"PAGESIZE"
);
08.
if
(
"ID"
.equals(SEARCH_BY))
09.
{
10.
int
id = (Integer)searchCriteriaMap.get(
"ID"
);
11.
//Get the total number of results for this criteria
12.
int
count = UserDAO.searchResultsCount(id);
13.
// query the DB and get the start to start+pageSize results by applying filter on USER_ID column
14.
List<User> users = search(id, start, pageSize);
15.
16.
}
17.
else
18.
{
19.
String username = (String)searchCriteriaMap.get(
"USERNAME"
);
20.
//Get the total number of results for this criteria
21.
int
count = UserDAO.searchResultsCount(username);
22.
// query the DB and get the start to start+pageSize results by applying filter on USERNAME column
23.
List<User> users = search(username, start, pageSize);
24.
25.
}
26.
Map<String, Object> RESULTS_MAP =
new
HashMap<String, Object>();
27.
RESULTS_MAP.put(
"DATA"
,users);
28.
RESULTS_MAP.put(
"COUNT"
,count);
29.
return
RESULTS_MAP;
30.
}
31.
32.
}
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.
01.
package
com.sivalabs.javabp;
02.
public
abstract
class
SearchCriteria
03.
{
04.
private
boolean
pagination =
false
;
05.
private
int
pageSize =
25
;
06.
private
String sortOrder =
"ASC"
;
07.
08.
public
boolean
isPagination()
09.
{
10.
return
pagination;
11.
}
12.
public
void
setPagination(
boolean
pagination)
13.
{
14.
this
.pagination = pagination;
15.
}
16.
public
String getSortOrder()
17.
{
18.
return
sortOrder;
19.
}
20.
public
void
setSortOrder(String sortOrder)
21.
{
22.
this
.sortOrder = sortOrder;
23.
}
24.
public
int
getPageSize()
25.
{
26.
return
pageSize;
27.
}
28.
public
void
setPageSize(
int
pageSize)
29.
{
30.
this
.pageSize = pageSize;
31.
}
32.
33.
}
A generic SearchResults object which holds the actual results and other detials like total available results count, page wise results provider etc.
01.
package
com.sivalabs.javabp;
02.
03.
import
java.util.ArrayList;
04.
import
java.util.List;
05.
06.
public
abstract
class
SearchResults<T>
07.
{
08.
private
int
totalResults =
0
;
09.
private
int
pageSize =
25
;
10.
private
List<T> results =
null
;
11.
12.
public
int
getPageSize()
13.
{
14.
return
pageSize;
15.
}
16.
public
void
setPageSize(
int
pageSize)
17.
{
18.
this
.pageSize = pageSize;
19.
}
20.
public
int
getTotalResults()
21.
{
22.
return
totalResults;
23.
}
24.
private
void
setTotalResults(
int
totalResults)
25.
{
26.
this
.totalResults = totalResults;
27.
}
28.
29.
public
List<T> getResults()
30.
{
31.
return
results;
32.
}
33.
public
List<T> getResults(
int
page)
34.
{
35.
if
(page <=
0
|| page >
this
.getNumberOfPages())
36.
{
37.
throw
new
RuntimeException(
"Page number is zero or there are no that many page results."
);
38.
}
39.
List<T> subList =
new
ArrayList<T>();
40.
int
start = (page -
1
)*
this
.getPageSize();
41.
int
end = start +
this
.getPageSize();
42.
if
(end >
this
.results.size())
43.
{
44.
end =
this
.results.size();
45.
}
46.
for
(
int
i = start; i < end; i++)
47.
{
48.
subList.add(
this
.results.get(i));
49.
}
50.
return
subList;
51.
}
52.
53.
public
int
getNumberOfPages()
54.
{
55.
if
(
this
.results ==
null
||
this
.results.size() ==
0
)
56.
{
57.
return
0
;
58.
}
59.
return
(
this
.totalResults/
this
.pageSize)+(
this
.totalResults%
this
.pageSize >
0
?
1
:
0
);
60.
}
61.
public
void
setResults(List<T> aRresults)
62.
{
63.
if
(aRresults ==
null
)
64.
{
65.
aRresults =
new
ArrayList<T>();
66.
}
67.
this
.results = aRresults;
68.
this
.setTotalResults(
this
.results.size());
69.
}
70.
71.
}
A SearchCriteria class specific to User Search.
01.
package
com.sivalabs.javabp;
02.
03.
public
class
UserSearchCriteria
extends
SearchCriteria
04.
{
05.
public
enum
UserSearchType
06.
{
07.
BY_ID, BY_NAME
08.
};
09.
10.
private
UserSearchType searchType = UserSearchType.BY_NAME;
11.
private
int
id;
12.
private
String username;
13.
14.
public
UserSearchType getSearchType()
15.
{
16.
return
searchType;
17.
}
18.
public
void
setSearchType(UserSearchType searchType)
19.
{
20.
this
.searchType = searchType;
21.
}
22.
23.
public
int
getId()
24.
{
25.
return
id;
26.
}
27.
public
void
setId(
int
id)
28.
{
29.
this
.id = id;
30.
}
31.
public
String getUsername()
32.
{
33.
return
username;
34.
}
35.
public
void
setUsername(String username)
36.
{
37.
this
.username = username;
38.
}
39.
}
A SearchResults class specific to User Search.
01.
package
com.sivalabs.javabp;
02.
import
java.text.MessageFormat;
03.
04.
public
class
UserSearchResults<T>
extends
SearchResults<User>
05.
{
06.
public
static
String getDataGridMessage(
int
start,
int
end,
int
total)
07.
{
08.
return
MessageFormat.format(
"Displaying {0} to {1} Users of {2}"
, start, end, total);
09.
}
10.
11.
}
UserService takes the SearchCriteria, invokes the DAO and get the results, prepares the UserSearchResults and return it back.
01.
package
com.sivalabs.javabp;
02.
03.
import
java.util.ArrayList;
04.
import
java.util.List;
05.
06.
import
com.sivalabs.javabp.UserSearchCriteria.UserSearchType;
07.
public
class
UserService
08.
{
09.
public
SearchResults<User> search(UserSearchCriteria searchCriteria)
10.
{
11.
UserSearchType searchType = searchCriteria.getSearchType();
12.
String sortOrder = searchCriteria.getSortOrder();
13.
System.out.println(searchType+
":"
+sortOrder);
14.
List<User> results =
null
;
15.
if
(searchType == UserSearchType.BY_NAME)
16.
{
17.
//Use hibernate Criteria API to get and sort results based on USERNAME field in sortOrder
18.
results = userDAO.searchUsers(...);
19.
}
20.
else
if
(searchType == UserSearchType.BY_ID)
21.
{
22.
//Use hibernate Criteria API to get and sort results based on USER_ID field in sortOrder
23.
results = userDAO.searchUsers(...);
24.
}
25.
26.
UserSearchResults<User> searchResults =
new
UserSearchResults<User>();
27.
searchResults.setPageSize(searchCriteria.getPageSize());
28.
searchResults.setResults(results);
29.
return
searchResults;
30.
}
31.
32.
}
01.
package
com.sivalabs.javabp;
02.
import
com.sivalabs.javabp.UserSearchCriteria.UserSearchType;
03.
04.
public
class
TestClient
05.
{
06.
public
static
void
main(String[] args)
07.
{
08.
UserSearchCriteria criteria =
new
UserSearchCriteria();
09.
criteria.setPageSize(
3
);
10.
//criteria.setSearchType(UserSearchType.BY_ID);
11.
//criteria.setId(2);
12.
13.
criteria.setSearchType(UserSearchType.BY_NAME);
14.
criteria.setUsername(
"s"
);
15.
16.
UserService userService =
new
UserService();
17.
SearchResults<User> searchResults = userService.search(criteria);
18.
19.
System.out.println(searchResults.getTotalResults());
20.
System.out.println(searchResults.getResults().size()+
":"
+searchResults.getResults());
21.
System.out.println(searchResults.getResults(
1
).size()+
":"
+searchResults.getResults(
1
));
22.
}
23.
}
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.ht