How to implement general pagination

31,741

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.

Share:
31,741
sachink
Author by

sachink

Updated on June 08, 2020

Comments

  • sachink
    sachink about 4 years

    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?

  • Admin
    Admin over 13 years
    But that locks him with Criterias (Hibernate). Moreover now a client depends on Criteria. That is exactly what people are trying to avoid introducing DAOs.
  • Don Roby
    Don Roby over 13 years
    @pavelrappo - Yes, and that could be solved by another level of indirection.
  • Admin
    Admin over 13 years
    then previous layer is useless :) It is not right when we see internals of some abstraction at the same level as abstraction itself
  • sachink
    sachink over 13 years
    Thanks, this solution is very similar to sending a request object to each and every DAO method to pass the common parameter but looks lot better (without the hibernate specific dependency).