私はHibernate/JPA/JDBCの実装を探しているのではなく、一般的なデザインパターンを探しています。
「ページネーション」をグーグルで検索すると、たくさんの情報、UIにページネーションを実装する方法を説明する興味深い記事、そして多かれ少なかれ同じことを行うさまざまな実装が得られます。
私はSpring3.0.5を使用しているので、この優れた参考記事に出くわしました 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);
}
そして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());
}
これも少し薄っぺらなようです。何らかの理由で、ブリッジパターンが役立つ可能性があると考え続けていますが、それでもまだ不適当です。
これにどのように対処するか考えていますか?
もし私があなたなら、結果(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());
}
}
ここで戦略パターンを適用することを検討します。
基本的に、開始と制限をパラメーターとして指定したり、可変個引数でラップしたりする代わりに、実際のオブジェクトを作成してそこに配置し、基準のページングを設定する責任をこのオブジェクトに移します。
大まかに言えば(私はコンパイルしていません...):
_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);
}
}
_
そしてもちろん、これをファインダーに渡して、明白な場所で呼び出します。
これの利点の1つは、何もしないNullPagingSpecification
実装を作成できることです。これにより、実際にページングが必要ない場合でも同じコードを使用できます。
もう1つは、(実際のページングを可能にするために)必要になる可能性のあるnext()
メソッドやprevious()
メソッドなどをPagingSpecification
クラスに移動できることです。さらに多くのコードを共有します。