Hibernate – Utilisation efficace de l’API Criteria

Utiliser l'API criteria plutôt que le langage de requête HQL présente de sérieux avantages. Parmi l'un deux, on peut en considérer un particulièrement interessant : la décoration de requête.

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.

2 commentaires

  1. Très bonne astuce ! Ça m’a beaucoup servi.
    J’ai fait une petite modification en passant une liste de Criterion à la place de la surdefinition de la fonction addExtraCriteria.
    En effet, au lieu d’appeler la fonction addExtraCriteria dans getAll je fais une boucle qui ajoute les critères supplémentaires qui sont passés en paramètre à la fonction getAll (sous forme de liste de Criterion).

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.