Pagination
July 13, 2008
Yojava provides a range of classes and tags to help with pagination and sorting. They have the benefit of being MVC-friendly.
Installing
There are two jars: yojava-common.jar and yojava-common-web.jar (for tags). In future they will be combined into one. Third party dependencies are detailed in the javadoc.
Pagination
First of all, in the controller we might have something like
import org.yojava.common.search.*;
...
Paginator paginator =
new Paginator(maxResultsPerPage, currentPage);
List results = search.find(criteria,
paginator.getIndexOfFirst(), maxResultsPerPage
paginator.setNumResults(search.getTotalNumResults());
/* Add paginator to the model, so it can be used by the view for rendering */
Tags are provided to simplify the view – illustrated below. A strength of the tags is that the results themselves can be displayed any way you want – it doesn’t have to be a table. (For those who need it, an additional tag is provided to automatically display table rows).
<!-- summary tag outputs something like: Showing results 20 to 30 out of 35 -->
<p><results:summary paginator="${paginator}" /></p>
<table class="results">
<c:forEach items="${myResultsList}" var="item">
<tr>table row goes here...</tr>
</c:forEach>
<table>
<!-- paginator tag outputs links something like: Prev | 1 | 2 | 3 | Next -->
<p><results:paginator paginator="${paginator}" /></p>
Sorting
First, in the view, we add a tag to write clickable header (TH) cells.
<table>
<results:easySortableHeader
columnProps="uid,heading,modifiedDate"
columnLabels="ID,Title,Date" />
<c:forEach ... > etc.
Then, in the controller, we introduce the SearchParams class.
SearchParams searchParams = SearchParamsUtil.initSearchParams(
request);
Paginator paginator = new Paginator(maxResultsPerPage,
searchParams.getCurrentPage());
List results = search.find(criteria, paginator.getIndexOfFirst(),
maxResultsPerPage, searchParams.getOrder());
paginator.setNumResults(search.getTotalNumResults());
Comparison with displaytag
Displaytag is useful for simple cases but can get messy when things get complicated. To customise links, for example, requires specifying a custom decorator class containing embedded HTML. It doesn’t provide any assistance for querying the relevant data in the first place.
Yojava doesn’t provide print-friendly or Excel functionality but these can be handled separately with SiteMesh and Spring’s AbstractExelView respectively. Additional support for Excel is planned. Print-friendly functionality isn’t specific to search results.
AJAX considerations
If you are considering AJAX to page/sort results, be aware that it doesn’t play well with SEO or tracking page/ad impressions. This is generally unimportant for admin applications.
Convenience
The previous examples have illustrated that yojava pagination isn’t limited to simple tables. When a simple table is what you want, you can do this
<results:easyCells object="${myList}"
columnProps="uid,heading,modifiedDate" />
Customisation
Most of the pagination/sorting tags are implemented as tag files which means the source is in the jar – you can use it as a basis for you own scripts. For example, the results:summary tag looks something like this
Results ${paginator.indexOfFirst} to ${paginator.indexOfLast} of ${paginator.numResults}
Basic pagination links can be written like this
<c:forEach begin="1" varStatus="status"
end="${(numPages < 10) ? numPages : 10}">
<a href='<url:replaceParam name="page"
newValue="${status.count}" />' >
${status.count}
</a>
</c:forEach>
To style alternate rows
<tr class="${util:oddEven(status.count, 'odd', 'even')}">...</tr>
GET requests and SEO
It’s good practice to have search results that support GET requests. It enables linking to search results pages, bookmarking and the possibility of browser caching.
For SEO purposes, you may even want to include the query in the path instead of the querystring where possible. For example, “/paris+holidays/results.html” instead of “/results.html?query=paris+holidays”.
When designing for SEO be wary of unwittingly creating a spider trap.
Performance and scalability
There are several strategies for querying consecutive result pages. One approach is storing results in the session to minimise the number of queries to the database (at the cost of using more memory). Depending on your application, caching may also be an option.
Using the Paginator doesn’t restrict your options. It just help keeps the code lean and clean.
Parameterized Domain Classes
July 12, 2008
This post looks at how to make service and persistence layers re-usable by another application – even if the application extends your original domain model. Lets take a simple example…
public class Article {
public String title;
}
public class TravelArticle extends Article {
public String destination;
}
// We CAN'T do
// TravelArticle ta = articleService.findByTitle(myTitle);
public class ArticleService {
public Article findByTitle(String title) {return null;}
}
Making it generic
We can make it generic at the method level like so…
public class ArticleService {
public <T extends ArticleImpl> T findByTitle(String t) {
return null;
}
}
or at the class level
public class ArticleService<T extends ArticleImpl> {
public T findByTitle(String title) {return null;}
}
Taking it further
If we need to instantiate domain classes from within ArticleService we need a bit extra - we can put it in a base class something like the one below. The domain class is infered from the type parameter but it can also be specified explicitly (in case the parameter type is an interface, for example).
public class BaseService<T> {
private Class<T> domainClass;
public Class<T> getDomainClass() {
if (domainClass == null) {
domainClass = (Class<T> ) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
return domainClass;
}
public T newDomainInstance() {
try {
return (T) getDomainClass().newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public void setDomainClass(Class<?> domainClass) {
this.domainClass = (Class<T> ) domainClass;
}
}
// Generified
public class ArticleService<T extends ArticleImpl>
extends BaseService<T> {
public T findByTitle(String title) {
return newDomainInstance();
}
}
Of course, if you have logic specific to the TravelArticle subclass then you have to subclass ArticleService.
Faster Spring initialisation
January 12, 2008
Spring is a great framework but it can take a while to startup (often due to other libraries such as Hibernate or EHCache). This can be minimised using Spring’s lazy initialisation.
Mark beans for lazy initialisation by setting the “lazy-init” attribute to “true”. For example:
<bean id="myBean" class="MyBean" lazy-init="true" />
You can also set default-lazy-init=”true” on the “beans” elements (if appropriate).
For web applications, ensure that the Spring dispatcher servlet (org.springframework.web.servlet.DispatcherServlet) is NOT loaded on startup (it automatically initialises controllers and hence dependent beans). You can do this by removing the “load-on-startup” element in web.xml.
Core beans (such as session factories) with many dependecies should generally avoid lazy-init=”true”.
Production environments
In production environments, eager initialisation may be more appropriate. This can be achieved by having a different WAR file for production with the Spring dispatcher servlet loaded on startup. (Ant’s “replace” task can be used to automatically remove commented out XML for the production build). For example:
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!--@PRODUCTION:UNCOMMENT<load-on-startup>1</load-on-startup>@PRODUCTION:UNCOMMENT-->
</servlet>
Best of both worlds
It is also possible to have a “best of both worlds” approach by loading beans in the background once the application has started up. This means:
- the app starts up as quickly as possible
- for most requests, beans will already be initialised
Code and docs are available at the yojava lab.
Other workarounds
-
Coding to test cases minimise the need for server restarts
-
JavaRebel (and WebLogic 10.3?) can reload classes on the fly
-
Different servlet containers have faster startup times (Jetty? Glassfish?)
Javapolis 2007 Technology Highlights
December 31, 2007
Tech highlights from Javapolis 2007:
-
Glassfish open source application server
-
is now the reference implementation of the Servlet API
-
v3 (currently in beta) is more modular. The demo had a fast startup time.
-
-
Resin (commercial servlet container) claims to serve static content faster than Apache
-
FindBugs will find a lot of potential Java bugs for you!
-
-
provides a combination of modern scripting features and tight Java integration. For example, Groovy classes can implement Java interfaces.
-
v1.5 offers signficant performance gains
-
-
JRuby on Rails = Rails + support for calling Java code
-
JavaRebel is a commercial product that enables auto-reloading of classes without restarting the server