Supposons que vous ayez écrit une classe de DAO assez générique avec une méthode de requête paginée.

class GenericDaoHibernate<T, PK extends Serializable>
    implements GenericDao<T, PK> {
 
    /**
      * La classe persistante sur laquelle nous allons faire nos requêtes
      */
    private Class<T> persistentClass;
 
    /**
     * Constructeur initialisant la classe persistante à manipuler
     * Ceci nous permettra de développer des classes de DAO fortement typée.
     * 
     * @param persistentClass the class type you'd like to persist
     */
    public GenericDaoHibernate(final Class<T> persistentClass)
    {
        this.persistentClass = persistentClass;
    }
 
    public List<T> getAll(final SearchCriteria searchCriteria)
    {
        //récupérons la session et créons un critère
        final Session session = getSessionFactory().getCurrentSession();
        final Criteria crit = session.createCriteria(persistentClass);
 
        // gestion de l'ordre des résultats
        if (searchCriteria.isDesc()) {
            crit.addOrder(Order.desc(searchCriteria.getActiveOrder()));
        }
        else {
            crit.addOrder(Order.asc(searchCriteria.getActiveOrder()));
        }
 
        // Les sous classes de GenericDaoHibernate
        // devront redéfinir cette méthode pour ajouter des 
        // critères supplémentaires
        addExtraCriteria(crit, searchCriteria);
 
        // Procédons à un comptage du résultat total de la requête
        // mais sans charger la collection d'objets.
        // D'une certaine manière on peut dire que le 
        // critère bascule en mode "comptage".
        crit.setProjection(Projections.countDistinct("id"));
        final Long totalResult = new Long((Integer) crit.uniqueResult());
        searchCriteria.setTotalResult(totalResult);
 
        // On rebascule en mode "liste d'objets"
        // si l'on est amené a faire des jointures assurons nous que la liste 
        //qui nous est retournée est réellement unique       
        crit.setProjection(null);
        crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
 
        // puis retournons la liste dans les limites de la pagination
        return crit
                .setMaxResults(searchCriteria.getNumberOfResultPerPage())
                .setFirstResult( (int) (searchCriteria.getActivePage() - 1)  * searchCriteria.getNumberOfResultPerPage())
                .list();
    }
 
    /**
     * Les sous classes doivent surdéfinir cette méthode pour ajouter plus de contraintes à la recherche.
     * 
     * @param crit  le critere  hibernate que l'on veut modifier.
     * @param searchCriteria Le critère de recherche fourni par la couche service.
     */
    public void addExtraCriteria(final Criteria crit,
            final SearchCriteria searchCriteria)
    {
        // rien : c'est la sous classe qui redéfinira le corps de la méthode
    }
 
}

Si vous lisez attentivement ce code, vous constatez qu'il couvre des besoins très génériques :

  1. Récupération d'une collection d'objets
  2. Pagination des résultats
  3. Gestion systématique d'un ordonnancement

Et bien entendu, on souhaiterait ne pas récrire ça à chaque requête ... Par exemple ProductHibernateDao devrait pouvoir utiliser ce travail en restreignant le résultat à une catégorie ou à la comparaison d'une chaîne dans le libellé des contenus localisés du produit.

Heureusement, pour ce faire, ProductHibernateDao n'a qu' à redéfinir la méthode addExtraCriteria(final Criteria crit, final SearchCriteria searchCriteria).

Voici un exemple d'implémentation de ProductHibernateDao :

public class ProductDaoHibernate
    extends GenericDaoHibernate<Product, Long>
    implements ProductDao
{
 
   public ProductDaoHibernate()
    {
        super(Product.class);
    }    
 
 
    /**
     * {@inheritDoc}
     * 
     * @see com.netapsys.shop.dao.hibernate.GenericDaoHibernate#addExtraCriteria(org.hibernate.Criteria,
     * com.netapsys.shop.flow.SearchCriteria)
     */
    @Override
    public void addExtraCriteria(final Criteria crit,
            final SearchCriteria searchCriteria)
    {
        ProductCriteria productCriteria = (ProductCriteria) searchCriteria;
        // On restreint la recherche a une catégorie si elle est définie.
        if (productCriteria.getCategory() != null
                && productCriteria.getCategory().getId() != null) {
            crit.add(Expression.eq("category.id", productCriteria.getCategory()
                    .getId()));
        }
 
        if (productCriteria.getSearchOn() != null
                && !productCriteria.getSearchOn().equals("none")) {
            // On recherche une chaîne de caractères "sword" dans la référence produit
            if (productCriteria.getSearchOn().equals("ref")) {
                crit.add(Expression.ilike("ref", productCriteria.getSword(),
                        MatchMode.ANYWHERE));
            }
            // Remarquez qu'ici j'utilise une fonction puissante de 
            // l'API Criteria qui me permet de rechercher 
            // dans une sous collection de Product.
            if (productCriteria.getSearchOn().equals("title")) {
                crit.createCriteria("localizedProductContents").add(
                        Expression.ilike("publishInfo.title", productCriteria
                                .getSword(), MatchMode.ANYWHERE));
            }
        }
    }
 
}

Ainsi, tout le processus de requêtage et de pagination a pu être mutualisé dans GenericHibernateDao alors que le requêtage particulier a été déplacé dans les classes dérivées, ici par exemple ProductHibernateDao.

On a d'une certaine manière décoré la requête en modifiant l'objet Criteria par la classe fille. Il ne s'agit pas néanmoins de la véritable mise en oeuvre d'un pattern décorateur mais la chose pourrait tout à fait être envisagée avec l'utilisation des critères détachés.