How to implement general pagination
I am not looking for a Hibernate/JPA/JDBC implementation, but for a general design pattern.
Googling "pagination" gives me loads of information, lot of interesting articles that explain how to implement pagination on the UI and various implementations which more or less do the same.
Since I am using Spring 3.0.5, and I stumbled this good reference article How to implement pagination in Spring MVC 3.
Simple bean:
public class Person{
private String personName;
private int age;
// ...
}
A simple DAO interface:
public interface PersonDAO{
Set<Person> getAllPersons(int start, int limit,String orderBy);
Set<Person> findPersonsByName(String name, int start, int limit,String orderBy);
}
And the hibernate implementation
@Repository
public class PersonDAOImpl implements PersonDAO {
@Autowired(required = true)
private SessionFactory sessionFactory;
public Set<Person> getAllPersons(int start, int limit, String orderBy){
Criteria crit = sessionFactory.getCurrentSession().createCriteria(Person.class);
crit.setFirstResult(start);
crit.setMaxResults(limit);
crit.addOrder(Order.asc("personName"));
return new LinkedHashSet<Person>(crit.list());
}
public Set<Person> findPersonsByName(String name, int start, int limit, String orderBy){
Criteria crit = sessionFactory.getCurrentSession().createCriteria(Person.class);
crit.add(Restrictions.eq("name", name));
crit.setFirstResult(start);
crit.setMaxResults(limit);
crit.addOrder(Order.asc(orderBy));
return new LinkedHashSet<Person>(crit.list());
}
Now, I am thinking if I have to include similar parameters across all the interface then there is something really wrong here. Either I can wrap the request in a request bean object and pass this bean to the methods, something like this
public class PersonRequest{
private int start;
private int limit;
private String orderBy;
private String name;
// ...
}
And subsequently
public interface PersonDAO{
Set<Person> getAllPersons(PersonRequest request);
Set<Person> findPersonsByName(PersonRequest request);
}
But this too seems unnatural, for some reason. Then I am thinking of varargs in Java
public interface PersonDAO{
Set<Person> getAllPersons(Object... params);
Set<Person> findPersonsByName(String name,Object... params);
}
@Repository
public class PersonDAOImpl implements PersonDAO {
@Autowired(required = true)
private SessionFactory sessionFactory;
public Set<Person> getAllPersons(Object... params){
Criteria crit = sessionFactory.getCurrentSession().createCriteria(Person.class);
crit.setFirstResult((Integer)params[0]);
crit.setMaxResults((Integer)params[1]);
crit.addOrder(Order.asc("personName"));
return new LinkedHashSet<Person>(crit.list());
}
public Set<Person> findPersonsByName(String name, Object... params){
Criteria crit = sessionFactory.getCurrentSession().createCriteria(Person.class);
crit.add(Restrictions.eq("name", name));
crit.setFirstResult((Integer)params[0]);
crit.setMaxResults((Integer)params[1]);
crit.addOrder(Order.asc((String)params[2]));
return new LinkedHashSet<Person>(crit.list());
}
This too seems bit flimsy, for some reason I keep thinking bridge pattern could be helpful but still is distant unfit.
开发者_开发百科Any idea how you would deal with this?
If I were you I would return not the result (Set
) itself but something that encapsulates retrieval of the result. Some sort of ResultBuilder. Look:
public interface ResultBuilder<T> {
ResultBuilder<T> withOffset(int offset);
ResultBuilder<T> withLimit(int limit);
ResultBuilder<T> orderedBy(String property);
List<T> result();
}
and then change DAO method signature:
ResultBuilder<Person> findPersonsByName(String name);
This way you can factor out business-irrelevant arguments from find-family methods. If you don't want to make client specify this params then don't make him.
Just to be clear:
public final class HibernateGenericResultBuilder<T> implements ResultBuilder<T> {
private final Criteria criteria;
public HibernateGenericResultBuilder(Criteria criteria) {
this.criteria = criteria;
}
@Override public ResultBuilder<T> withOffset(int offset) {
criteria.setFirstResult(offset);
return this;
}
@Override public ResultBuilder<T> withLimit(int limit) {
criteria.setMaxResults(limit);
return this;
}
@Override public ResultBuilder<T> orderedBy(String property) {
criteria.addOrder(Order.asc(property));
return this;
}
@Override public List<T> result() {
return new LinkedHashSet<T>(criteria.list());
}
}
I would consider applying the Strategy Pattern here.
Basically, instead of supplying the start and limit as parameters or wrapping them in a varargs, make a real object, put them there, and move the responsibility of setting the paging on the criteria to this object.
Roughly speaking (I'm not compiling...):
public interface PagingSpecification {
void apply(Criteria criteria);
}
public class ConcretePagingSpecification implements PagingSpecification {
private int start;
private int limit;
public ConcretePagingSpecification(int start, int limit) {
this.start = start;
this.limit = limit;
}
public void apply(Criteria crit) {
crit.setFirstResult(start);
crit.setMaxResults(limit);
}
}
And then of course pass this into your finders and call it in the obvious places.
One advantage of this is that you can make a NullPagingSpecification
implementation which does nothing, so that you can use the same code when you don't actually want paging.
Another is that you can move things like the next()
and previous()
methods you're likely to need (to allow actual paging) into the PagingSpecification
classes as well, and share yet more code.
精彩评论