Как реализовать общую разбивку на страницы
Я не ищу реализацию Hibernate/JPA/JDBC, но для общей схемы проектирования.
Поисковая разметка "pagination" дает мне массу информации, множество интересных статей, которые объясняют, как реализовать разбиение на страницы на пользовательский интерфейс и различные реализации, которые более или менее делают то же самое.
Так как я использую Spring 3.0.5, и я наткнулся на эту хорошую справочную статью Как реализовать разбиение на страницы в Spring MVC 3.
Простой bean:
public class Person{
private String personName;
private int age;
// ...
}
Простой интерфейс DAO:
public interface PersonDAO{
Set<Person> getAllPersons(int start, int limit,String orderBy);
Set<Person> findPersonsByName(String name, int start, int limit,String orderBy);
}
И реализация hibernate
@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());
}
Теперь я думаю, что если мне нужно включить подобные параметры во весь интерфейс, то здесь есть что-то действительно неправильное. Либо я могу обернуть запрос в объект запроса bean и передать этот bean методам, что-то вроде этого
public class PersonRequest{
private int start;
private int limit;
private String orderBy;
private String name;
// ...
}
И впоследствии
public interface PersonDAO{
Set<Person> getAllPersons(PersonRequest request);
Set<Person> findPersonsByName(PersonRequest request);
}
Но это тоже кажется неестественным, почему-то. Тогда я думаю о varargs в 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());
}
Это тоже кажется немного хлипким, по какой-то причине я продолжаю думать, что шаблон моста может быть полезным, но все еще далек от непригодности.
Любая идея, как вы справитесь с этим?
Ответы
Ответ 1
Если бы я был вами, я бы вернул не результат (Set
), а то, что инкапсулирует извлечение результата. Какой-то ResultBuilder. Посмотрите:
public interface ResultBuilder<T> {
ResultBuilder<T> withOffset(int offset);
ResultBuilder<T> withLimit(int limit);
ResultBuilder<T> orderedBy(String property);
List<T> result();
}
а затем изменить подпись метода DAO:
ResultBuilder<Person> findPersonsByName(String name);
Таким образом, вы можете разделить аргументы, не относящиеся к бизнесу, из методов find-family.
Если вы не хотите, чтобы клиент указывал эти параметры, не делайте его.
Просто, чтобы быть ясным:
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());
}
}
Ответ 2
Я бы рассмотрел применение шаблона стратегии здесь.
В принципе, вместо того, чтобы предоставлять начальные и конечные значения в качестве параметров или обертывать их в varargs, создайте реальный объект, поместите их туда и переместите ответственность за настройку поискового вызова по критериям для этого объекта.
Грубо говоря (я не компилирую...):
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);
}
}
И тогда, конечно, передайте это в свои искатели и назовите его в очевидных местах.
Одно из преимуществ этого заключается в том, что вы можете сделать реализацию NullPagingSpecification
, которая ничего не делает, чтобы вы могли использовать тот же код, когда вам действительно не нужен пейджинг.
Другим является то, что вы можете перемещать объекты, подобные методам next()
и previous()
, которые вам могут понадобиться (чтобы разрешить реальный пейджинг) в классах PagingSpecification
, а также поделиться еще большим количеством кода.