Just had an interesting discussion on ejb3-feedback@sun.com, started by David Cherryhomes, which saw me stupidly insistingthat something can't be done when in fact, now that I think about it, /I realize I've actually done it before/, and that even the Hibernate AdminApp example uses this pattern!
So, just so I don't forget this pattern again, I'm going to write it down, and also write a reuseable class implementing it.
The basic problem is pagination. I want to display next
and previous
buttons to the user, but disable them if there are
no more, or no previous query results. But I don't want to retrieve all the query results in each request, or execute a
separate query to count them. So, here's the correct approach:
public class Page {
private List results;
private int pageSize;
private int page;
public Page(Query query, int page, int pageSize) {
this.page = page;
this.pageSize = pageSize;
results = query.setFirstResult(page * pageSize)
.setMaxResults(pageSize+1)
.list();
}
public boolean isNextPage() {
return results.size() > pageSize;
}
public boolean isPreviousPage() {
return page > 0;
}
public List getList() {
return isNextPage() ?
results.subList(0, pageSize-1) :
results;
}
}
You can return this object to your JSP, and use it in Struts, WebWork or JSTL tags. Getting a page in your persistence logic is as simple as:
public Page getPosts(int page) {
return new Page(
session.createQuery("from Posts p order by p.date desc")
page,
40
);
}
The Page class works in both Hibernate and EJB 3.0.
Gavin thanks for the good blog. We are doing the same thing in our swing based application but we want to show the user the total count of resultset. So kindly can you modify this code to show the best way of doing so maybe once in the queries lifecycle.
Regards,
Shoaib Akhtar
Using this modified class, you can display something like, "Showing records 1 to 25 of 47." It requires that your underlying database (and driver) support a scrollable result set (to get the total number of results that match the query).
I am using this class with SQL Server 2000 and jTDS 0.8.1 and it works very well.
Enjoy!
/*
* Created on Oct 27, 2004
*/
package com.ugs.it.salescentre.navigation;
import java.util.List;
import org.apache.log4j.Logger;
import com.ugs.it.salescentre.logger.SalesCentreLogger;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Query;
import net.sf.hibernate.ScrollableResults;
/**
* This class provides pagination for displaying results from a large result set
* over a number of pages (i.e. with a given number of results per page).
*
* Taken from http://blog.hibernate.org/cgi-bin/blosxom.cgi/2004/08/14#fn.html.
*
* @author Gavin King
* @author Eric Broyles
*/
public class Page
{
private List results;
private int pageSize;
private int page;
private ScrollableResults scrollableResults;
private int totalResults = 0;
/**
* Construct a new Page. Page numbers are zero-based, so the
* first page is page 0.
*
* @param query
* the Hibernate Query
* @param page
* the page number (zero-based)
* @param pageSize
* the number of results to display on the page
*/
public Page(Query query, int page, int pageSize)
{
this.page = page;
this.pageSize = pageSize;
try
{
scrollableResults = query.scroll();
/*
* We set the max results to one more than the specfied pageSize to
* determine if any more results exist (i.e. if there is a next page
* to display). The result set is trimmed down to just the pageSize
* before being displayed later (in getList()).
*/
results = query.setFirstResult(page * pageSize).setMaxResults(
pageSize + 1).list();
}
catch (HibernateException e)
{
getLogger().error(
"Failed to get paginated results: " + e.getMessage());
}
}
public boolean isFirstPage()
{
return page == 0;
}
public boolean isLastPage()
{
return page >= getLastPageNumber();
}
public boolean hasNextPage()
{
return results.size() > pageSize;
}
public boolean hasPreviousPage()
{
return page > 0;
}
public int getLastPageNumber()
{
/*
* We use the Math.floor() method because page numbers are zero-based
* (i.e. the first page is page 0).
*/
double totalResults = new Integer(getTotalResults()).doubleValue();
return new Double(Math.floor(totalResults / pageSize)).intValue();
}
public List getList()
{
/*
* Since we retrieved one more than the specified pageSize when the
* class was constructed, we now trim it down to the pageSize if a next
* page exists.
*/
return hasNextPage() ? results.subList(0, pageSize) : results;
}
public Logger getLogger()
{
return SalesCentreLogger.getStaticLogger(this);
}
public int getTotalResults()
{
try
{
getScrollableResults().last();
totalResults = getScrollableResults().getRowNumber();
}
catch (HibernateException e)
{
getLogger().error(
"Failed to get last row number from scollable results: "
+ e.getMessage());
}
return totalResults;
}
public int getFirstResultNumber()
{
return page * pageSize + 1;
}
public int getLastResultNumber()
{
int fullPage = getFirstResultNumber() + pageSize - 1;
return getTotalResults() < fullPage ? getTotalResults() : fullPage;
}
public int getNextPageNumber()
{
return page + 1;
}
public int getPreviousPageNumber()
{
return page - 1;
}
protected ScrollableResults getScrollableResults()
{
return scrollableResults;
}
}
Click HELP for text formatting instructions. Then edit this text and check the preview.
Excellent logic.Works very well.
I'm using Hibernate in my project, i wanna implement pagination. how can i implement using Hibernate?? plz guide me. is it any options there to implement.
Using the example, there would be 41 rows per page instead of 40.
Anybody else have this problem?
I tried using an iterator instead of calling list. However, it causes many selects to be run really slowing things down.
thoughts ?
just wanted to say thanks. I ran across this example a little bit ago and just ran across a situation where I need it.
http://www.hibernate.org/243.html
As Shoaib Akhtar mentioned, I did not find a good way to get total out of my criterias.
Supposedly in JDBC, I could do the following tricks:
BufferString selectStatement;
BufferString fromStatement;
BufferString whereStatement;
BufferString orderStatement;
.......
then I dynamically populate all the statements basing on my requirements;
..........
then when I need solid result lists, I could put in page number /page size.
WHEN I NEED THE TOTAL COUNTS, I COULD REPLACE selectStatement with my "select count(*)" and run another query.
Criteria/Query API in Hibernate shall able to do similar things. But it is a very valid requirement to know the numbers of results as well as the list of results.
Thank you very much,
Melvin
http://www.ginkgosoft.com
One problem I encountered with the "subList" method (JDK1.4) is that the returned List object is not Serializable causing problem when the result is returned from the EJB tier. The implementation class is a "RandomAccessSubList" or something like that.
I needed to wrap it with new ArrayList(result.subList(...)) to get it to work.
http://blog.hibernate.org/Bloggers/Everyone/2004/08/14
So I have a method return a list call getSearchResult
public List getSearchResult(int page, int pageSize) { SQLQuery query
http://blog.hibernate.org/Bloggers/Everyone/2004/08/14
So I have a method return a list call getSearchResult
public List getSearchResult(int page, int pageSize) { SQLQuery query
So I have a method return a list call getSearchResult
public List getSearchResult(int page, int pageSize) { SQLQuery query
Wouldn't using a scrollable result set keep the connection open all the time ? Correct me if i m missing something.