Hibernate.orgCommunity Documentation

Chapitre 17. Requêtes par critères

17.1. Créer une instance de Criteria
17.2. Restriction du résultat
17.3. Trier les résultats
17.4. Associations
17.5. Peuplement d'associations de manière dynamique
17.6. Requêtes par l'exemple
17.7. Projections, agrégation et regroupement
17.8. Requêtes et sous-requêtes détachées
17.9. Requêtes par identifiant naturel

Hibernate offre une API d'interrogation par critères intuitive et extensible.

L'interface net.sf.hibernate.Criteria représente une requête sur une classe persistante donnée. La Session fournit les instances de Criteria.

Criteria crit = sess.createCriteria(Cat.class);

crit.setMaxResults(50);
List cats = crit.list();

Un critère de recherche (criterion) individuel est une instance de l'interface org.hibernate.criterion.Criterion. La classe org.hibernate.criterion.Restrictions définit des méthodes de fabrique pour obtenir des types de Criterion intégrés.

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.like("name", "Fritz%") )
    .add( Restrictions.between("weight", minWeight, maxWeight) )
    .list();

Les restrictions peuvent être groupées de manière logique.

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.like("name", "Fritz%") )
    .add( Restrictions.or(
        Restrictions.eq( "age", new Integer(0) ),
        Restrictions.isNull("age")
    ) )
    .list();
List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
    .add( Restrictions.disjunction()
        .add( Restrictions.isNull("age") )
        .add( Restrictions.eq("age", new Integer(0) ) )
        .add( Restrictions.eq("age", new Integer(1) ) )
        .add( Restrictions.eq("age", new Integer(2) ) )
    ) )
    .list();

Il y a un grand choix de types de critères intégrés (sous classes de Restriction), dont un est particulièrement utile puisqu'il vous permet de spécifier directement SQL.

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
    .list();

La zone {alias} sera remplacée par l'alias de colonne de l'entité que l'on souhaite interroger.

Une autre approche pour obtenir un critère est de le récupérer d'une instance de Property. Vous pouvez créer une Property en appelant Property.forName().



Property age = Property.forName("age");
List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.disjunction()
        .add( age.isNull() )
        .add( age.eq( new Integer(0) ) )
        .add( age.eq( new Integer(1) ) )
        .add( age.eq( new Integer(2) ) )
    ) )
    .add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
    .list();

Vous pouvez trier les résultats en utilisant org.hibernate.criterion.Order.

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.like("name", "F%")
    .addOrder( Order.asc("name") )
    .addOrder( Order.desc("age") )
    .setMaxResults(50)
    .list();
List cats = sess.createCriteria(Cat.class)

    .add( Property.forName("name").like("F%") )
    .addOrder( Property.forName("name").asc() )
    .addOrder( Property.forName("age").desc() )
    .setMaxResults(50)
    .list();

En naviguant les associations qui utilisent createCriteria(), vous pouvez spécifier des contraintes associées à des entités :

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.like("name", "F%") )
    .createCriteria("kittens")
        .add( Restrictions.like("name", "F%") )
    .list();

Notez que la seconde createCriteria() retourne une nouvelle instance de Criteria, qui se rapporte aux éléments de la collection kittens.

La forme alternative suivante est utile dans certains cas :

List cats = sess.createCriteria(Cat.class)

    .createAlias("kittens", "kt")
    .createAlias("mate", "mt")
    .add( Restrictions.eqProperty("kt.name", "mt.name") )
    .list();

(createAlias() ne crée pas de nouvelle instance de Criteria.)

Notez que les collections kittens contenues dans les instances de Cat retournées par les deux précédentes requêtes ne sont pas pré-filtrées par les critères ! Si vous souhaitez récupérer uniquement les kittens correspondant aux critères, vous devez utiliser ResultTransformer.

List cats = sess.createCriteria(Cat.class)

    .createCriteria("kittens", "kt")
        .add( Restrictions.eq("name", "F%") )
    .setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
    .list();
Iterator iter = cats.iterator();
while ( iter.hasNext() ) {
    Map map = (Map) iter.next();
    Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS);
    Cat kitten = (Cat) map.get("kt");
}

Additionally you may manipulate the result set using a left outer join:

                List cats = session.createCriteria( Cat.class )
                       .createAlias("mate", "mt", Criteria.LEFT_JOIN, Restrictions.like("mt.name", "good%") )
                       .addOrder(Order.asc("mt.age"))
                       .list();
        
        

This will return all of the Cats with a mate whose name starts with "good" ordered by their mate's age, and all cats who do not have a mate. This is useful when there is a need to order or limit in the database prior to returning complex/large result sets, and removes many instances where multiple queries would have to be performed and the results unioned by java in memory.

Without this feature, first all of the cats without a mate would need to be loaded in one query.

A second query would need to retreive the cats with mates who's name started with "good" sorted by the mates age.

Thirdly, in memory; the lists would need to be joined manually.

Vous pouvez spécifier, au moment de l'exécution, le peuplement d'une association en utilisant setFetchMode().

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.like("name", "Fritz%") )
    .setFetchMode("mate", FetchMode.EAGER)
    .setFetchMode("kittens", FetchMode.EAGER)
    .list();

This query will fetch both mate and kittens by outer join. See Section 21.1, « Stratégies de chargement » for more information.

La classe org.hibernate.criterion.Example vous permet de construire un critère de requête à partir d'une instance d'objet donnée.

Cat cat = new Cat();

cat.setSex('F');
cat.setColor(Color.BLACK);
List results = session.createCriteria(Cat.class)
    .add( Example.create(cat) )
    .list();

Les propriétés de type version, identifiant et association sont ignorées. Par défaut, les valeurs null sont exclues.

Vous pouvez ajuster la stratégie d'utilisation de valeurs de l'Exemple.

Example example = Example.create(cat)

    .excludeZeroes()           //exclude zero valued properties
    .excludeProperty("color")  //exclude the property named "color"
    .ignoreCase()              //perform case insensitive string comparisons
    .enableLike();             //use like for string comparisons
List results = session.createCriteria(Cat.class)
    .add(example)
    .list();

Vous pouvez utiliser les "exemples" pour des critères sur des objets associés.

List results = session.createCriteria(Cat.class)

    .add( Example.create(cat) )
    .createCriteria("mate")
        .add( Example.create( cat.getMate() ) )
    .list();

La classe org.hibernate.criterion.Projections est une fabrique d'instances de Projection. Nous appliquons une projection sur une requête en appelant setProjection().

List results = session.createCriteria(Cat.class)

    .setProjection( Projections.rowCount() )
    .add( Restrictions.eq("color", Color.BLACK) )
    .list();
List results = session.createCriteria(Cat.class)

    .setProjection( Projections.projectionList()
        .add( Projections.rowCount() )
        .add( Projections.avg("weight") )
        .add( Projections.max("weight") )
        .add( Projections.groupProperty("color") )
    )
    .list();

Il n'y a pas besoin de "group by" explicite dans une requête par critère. Certains types de projection sont définis pour être des projections de regroupement, qui apparaissent aussi dans la clause SQL group by.

Un alias peut optionnellement être assigné à une projection, ainsi la valeur projetée peut être référencée dans des restrictions ou des tris. À cet effet, voici deux procédés différents :

List results = session.createCriteria(Cat.class)

    .setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) )
    .addOrder( Order.asc("colr") )
    .list();
List results = session.createCriteria(Cat.class)

    .setProjection( Projections.groupProperty("color").as("colr") )
    .addOrder( Order.asc("colr") )
    .list();

Les méthodes alias() et as() enveloppent simplement une instance de projection dans une autre instance (aliasée) de Projection. Pour un raccourci, vous pouvez assigner un alias lorsque vous ajoutez la projection à une liste de projections :

List results = session.createCriteria(Cat.class)

    .setProjection( Projections.projectionList()
        .add( Projections.rowCount(), "catCountByColor" )
        .add( Projections.avg("weight"), "avgWeight" )
        .add( Projections.max("weight"), "maxWeight" )
        .add( Projections.groupProperty("color"), "color" )
    )
    .addOrder( Order.desc("catCountByColor") )
    .addOrder( Order.desc("avgWeight") )
    .list();
List results = session.createCriteria(Domestic.class, "cat")

    .createAlias("kittens", "kit")
    .setProjection( Projections.projectionList()
        .add( Projections.property("cat.name"), "catName" )
        .add( Projections.property("kit.name"), "kitName" )
    )
    .addOrder( Order.asc("catName") )
    .addOrder( Order.asc("kitName") )
    .list();

Vous pouvez aussi utiliser Property.forName() pour formuler des projections :

List results = session.createCriteria(Cat.class)

    .setProjection( Property.forName("name") )
    .add( Property.forName("color").eq(Color.BLACK) )
    .list();
List results = session.createCriteria(Cat.class)

    .setProjection( Projections.projectionList()
        .add( Projections.rowCount().as("catCountByColor") )
        .add( Property.forName("weight").avg().as("avgWeight") )
        .add( Property.forName("weight").max().as("maxWeight") )
        .add( Property.forName("color").group().as("color" )
    )
    .addOrder( Order.desc("catCountByColor") )
    .addOrder( Order.desc("avgWeight") )
    .list();

La classe DetachedCriteria vous laisse créer une requête en dehors de la portée de la session, et puis l'exécuter plus tard en utilisant une Session arbitraire.

DetachedCriteria query = DetachedCriteria.forClass(Cat.class)

    .add( Property.forName("sex").eq('F') );
    
Session session = ....;
Transaction txn = session.beginTransaction();
List results = query.getExecutableCriteria(session).setMaxResults(100).list();
txn.commit();
session.close();

Les DetachedCriteria peuvent aussi être utilisés pour exprimer une sous-requête. Des instances de critère impliquant des sous-requêtes peuvent être obtenues via Subqueries ou Property.

DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)

    .setProjection( Property.forName("weight").avg() );
session.createCriteria(Cat.class)
    .add( Property.forName("weight").gt(avgWeight) )
    .list();
DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)

    .setProjection( Property.forName("weight") );
session.createCriteria(Cat.class)
    .add( Subqueries.geAll("weight", weights) )
    .list();

Des sous-requêtes corrélées sont également possibles :

DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")

    .setProjection( Property.forName("weight").avg() )
    .add( Property.forName("cat2.sex").eqProperty("cat.sex") );
session.createCriteria(Cat.class, "cat")
    .add( Property.forName("weight").gt(avgWeightForSex) )
    .list();

Pour la plupart des requêtes, incluant les requêtes par critère, le cache de requêtes n'est pas très efficace, parce que l'invalidation du cache de requêtes arrive trop souvent. Cependant, il y existe une requête spéciale où l'on peut optimiser l'algorithme d'invalidation du cache : les recherches par une clef naturelle constante. Dans certaines applications, ce genre de requête se produit fréquemment. L'API des critères fournit une disposition spéciale pour ce cas d'utilisation.

D'abord, vous devrez mapper la clé naturelle de votre entité en utilisant <natural-id>, et activer l'utilisation du cache de second niveau.


<class name="User">
    <cache usage="read-write"/>
    <id name="id">
        <generator class="increment"/>
    </id>
    <natural-id>
        <property name="name"/>
        <property name="org"/>
    </natural-id>
    <property name="password"/>
</class
>

Cette fonctionnalité n'est pas prévue pour l'utilisation avec des entités avec des clés naturelles mutables.

Une fois que vous aurez activé le cache de requête d'Hibernate, Restrictions.naturalId() vous permettra de rendre l'utilisation de l'algorithme de cache plus efficace.

session.createCriteria(User.class)

    .add( Restrictions.naturalId()
        .set("name", "gavin")
        .set("org", "hb") 
    ).setCacheable(true)
    .uniqueResult();