如何实现通用分页功能

20
我不是在寻找Hibernate/JPA/JDBC的实现,而是要找一个通用的设计模式。
通过谷歌搜索"pagination"得到了很多信息,很多有趣的文章解释了如何在UI上实现分页以及几乎做同样事情的各种实现方式。
因为我使用的是Spring 3.0.5,我偶然发现了这篇好的参考文章How to implement pagination in 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);
}

但这似乎也不自然,我接着想到了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());
         }

这似乎有些脆弱,我一直在想桥接模式可能会有所帮助,但仍然不太合适。

你有什么想法如何处理这个问题吗?

2个回答

24
如果我是你,我会返回一个封装了结果检索的东西,而不是结果(Set)本身。一些结果构建器。看看这个例子:
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);

这样,您可以从查找家族方法中分离出与业务无关的参数。 如果您不想让客户指定这些参数,则不要强制他。

只是为了明确:

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());
    }
}

+1:但是实现中的结果方法仍然需要回调到Hibernate,就像我的策略建议一样。 - Don Roby
在编译时,我们只有 ResultBuilder<T> 通用接口。因此,我们不关心它下面的内容。实现的实际来源是 PersonDAO。在这种情况下,PersonDAOResultBuilder 的一种工厂。 - user381105
1
有趣的想法,不过您是在建议我们检索所有数据,然后将筛选/限制数据的任务委托给Java端,而不是数据库吗? - sachink
@sachnik -- 不,我的解决方案不强制要求这样做(但如果您愿意,可以这样做)。请参见更新。 - user381105
@sachink 你可以直接将 ResultBuilder 返回给调用代码,例如控制器。 - jonathancardoso
显示剩余2条评论

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类中,并共享更多代码。


但这将使他与Criteria(Hibernate)绑定。此外,现在客户端依赖于Criteria。这正是人们试图避免引入DAO的原因。 - user381105
@pavelrappo - 是的,这可以通过另一层间接解决。 - Don Roby
那么上一层就变得无用了 :) 当我们在同一层次上看到某些抽象的内部时,这是不正确的。 - user381105
谢谢,这个解决方案与向每个DAO方法发送请求对象以传递公共参数非常相似,但看起来更好(没有Hibernate特定的依赖)。 - sachink

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接