【问题标题】:How to implement general pagination如何实现通用分页
【发布时间】:2011-03-23 21:49:44
【问题描述】:

我不是在寻找 Hibernate/JPA/JDBC 实现,而是在寻找通用设计模式。

谷歌搜索“分页”给了我很多信息,很多有趣的文章解释了如何在 UI 上实现分页,以及或多或少做同样的各种实现。

由于我使用的是 Spring 3.0.5,我偶然发现了这篇很好的参考文章 How to implement pagination in Spring MVC 3

简单的豆子:

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

还有休眠实现

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

这似乎也有点脆弱,出于某种原因,我一直认为桥接模式可能会有所帮助,但仍然遥不可及。

知道你会如何处理这个问题吗?

【问题讨论】:

    标签: java hibernate spring design-patterns pagination


    【解决方案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());
        }
    }
    

    【讨论】:

    • +1:但是实现中的结果方法仍然需要像我的策略建议一样回调休眠。
    • 在编译期间,我们只有ResultBuilder&lt;T&gt; 通用接口。所以我们不在乎它下面是什么。实际的实现来源是PersonDAO。在这种情况下,PersonDAO 有点像 ResultBuilders 的工厂。
    • 有趣的是,您是否建议我们检索所有数据,然后将任务委托给 java 端而不是 DB 端过滤/限制数据?
    • @sachnik -- 不。我的解决方案不会强制执行此操作(但您可以根据需要执行此操作)。查看更新。
    • 看起来很有希望..谢谢。最后一个问题,由于 DAO 接口没有参数列表(limit、orderby、offset),这个信息是如何传递的?到目前为止,我看到我们已经将 orderby/limit 等参数列表抽象为一个单独的接口(ResultBuilder)。我理解这一点的方式仍然需要了解如何为每个 find-family 方法传达/传递请求对象,以识别限制/订单限制。我错过了什么吗?
    【解决方案2】:

    我会考虑在这里应用策略模式。

    基本上,与其将 start 和 limit 作为参数提供或将它们包装在 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 类中,并共享更多代码。

    【讨论】:

    • 但这用Criterias(休眠)锁定了他。此外,现在一个客户依赖于Criteria。这正是人们试图避免引入 DAO 的原因。
    • @pavelrappo - 是的,这可以通过另一个间接级别来解决。
    • 那么上一层就没用了:) 当我们看到一些抽象的内部与抽象本身处于同一级别时,这是不对的
    • 谢谢,这个解决方案非常类似于向每个 DAO 方法发送请求对象以传递公共参数,但看起来要好得多(没有特定于休眠的依赖项)。
    猜你喜欢
    • 2020-06-19
    • 2017-07-04
    • 1970-01-01
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 2015-11-10
    相关资源
    最近更新 更多