Hibernate - Utilisation efficace de l'API Criteria
Par Michael Courcy le mercredi 2 avril 2008, 17:06 - Java - Lien permanent
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 :
- Récupération d'une collection d'objets
- Pagination des résultats
- 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.









Il y a aucun commentaire