Глава 17. Запросы Criteria

Оглавление
  1. 17.1. Создание экземпляра Criteria
  2. 17.2. Сужение набора результатов
  3. 17.3. Упорядочивание результатов
  4. 17.4. Ассоциации
  5. 17.5. Динамическая ассоциативная выборка
  6. 17.6. Компоненты
  7. 17.7. Коллекции
  8. 17.8. Запросы Example
  9. 17.9. Проекции, агрегация и группировка
  10. 17.10. Отсоединённые запросы и подзапросы
  11. 17.11. Запросы по естественному идентификатору

Hibernate имеет интуитивно понятный, расширяемый API запросов Criteria.

17.1. Создание экземпляра Criteria

Интерфейс org.hibernate.Criteria представляет запрос к определённому постоянному классу. Session является фабрикой для экземпляров Criteria.

Criteria crit = sess.createCriteria(Cat.class);
crit.setMaxResults(50);
List cats = crit.list();

17.2. Сужение набора результатов

Индивидуальный критерий запроса является экземпляром интерфейса org.hibernate.criterion.Criterion. Класс org.hibernate.criterion.Restrictions определяет фабричные методы для получения определённых встроенных типов Criterion.

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .add( Restrictions.between("weight", minWeight, maxWeight) )
    .list();

Ограничения могут быть сгруппированы логически.

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();

Существует ряд встроенных типов критериев (подклассы Restrictions). Один из самых полезных позволяет вам напрямую указывать SQL.

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
    .list();

Заполнитель {alias} будет заменён псевдонимом записи запрашиваемой сущности.

Вы также можете получить критерий из экземпляра Property. Вы можете создать Property, вызвав 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();

17.3. Упорядочивание результатов

Вы можете упорядочить результаты, используя 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();

17.4. Ассоциации

Путём навигации с использованием createCriteria() вы можете указать ограничения для связанных сущностей:

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "F%") )
    .createCriteria("kittens")
        .add( Restrictions.like("name", "F%") )
    .list();

Второй createCriteria() возвращает новый экземпляр Criteria, который относится к элементам коллекции kittens.

Существует также альтернативная форма, которая полезна при определённых обстоятельствах:

List cats = sess.createCriteria(Cat.class)
    .createAlias("kittens", "kt")
    .createAlias("mate", "mt")
    .add( Restrictions.eqProperty("kt.name", "mt.name") )
    .list();

(createAlias() не создает новый экземпляр Criteria.)

Коллекции kittens, хранящие экземпляры Cat, возвращённые предыдущими двумя запросами, не подвергаются предварительной фильтрации по критериям. Если вы хотите получить только котят, которые соответствуют критериям, вы должны использовать 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");
}

Кроме того, вы можете манипулировать результирующим набором, используя 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();

Это вернёт всех Cat с mate, чьё имя начинается с «good», упорядоченному по возрасту их mate, и всех Cat, у которых нет mate. Это полезно, когда необходимо упорядочить или ограничивать в базе данных до возвращения сложных/больших наборов результатов и удаляет множество экземпляров, в которых необходимо выполнить несколько запросов, и результаты объединяются java в памяти.

Без этой функции сначала все Cat без mate, должны быть загружены в одном запросе.

Второй запрос должен будет получить Cat с mate, имя которых начинается с «good», отсортированного по возрасту mate.

В-третьих, в памяти; списки должны быть объединены вручную.

17.5. Динамическая ассоциативная выборка

Вы можете указать семантику ассоциации выборки во время выполнения с помощью setFetchMode().

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .setFetchMode("mate", FetchMode.EAGER)
    .setFetchMode("kittens", FetchMode.EAGER)
    .list();

Этот запрос будет извлекать обоих mate и kittens через outer join. Дополнительную информацию см. в разделе 20.1 «Стратегии выборки».

17.6. Компоненты

Чтобы добавить ограничение на свойство встроенного компонента, имя свойства компонента должно быть добавлено перед именем свойства при создании Restriction. Объект Criteria должен быть создан на собственной сущности и не может быть создан на самом компоненте. Например, предположим, что у Cat есть свойство компонента fullName с под-свойствами firstName и lastName:

List cats = session.createCriteria(Cat.class)
    .add(Restrictions.eq("fullName.lastName", "Cattington"))
    .list();
	

Примечание: это не применяется при запросе коллекций компонентов, см. ниже раздел «Коллекции».

17.7. Коллекции

При использовании Criteria на коллекциях существуют два отдельных случая. Один из них состоит в том, что коллекция содержит сущности (например, <one-to-many /> или <many-to-many />) или компоненты (<composite-element />), а второй — если коллекция содержит скалярные значения (<element />). В первом случае синтаксис приведён выше в разделе «Ассоциации», где мы ограничиваем коллекцию kittens. По существу, мы создаем объект Criteria на свойстве коллекции и ограничиваем свойства сущности или компонента с помощью этого экземпляра.

Для запроса базовых значений мы по-прежнему создаем объект Criteria на коллекции, но для ссылки на значение мы используем специальные свойства «elements». Для индексированного набора мы также можем ссылаться на свойство index, используя специальные свойства «indices».

List cats = session.createCriteria(Cat.class)
	.createCriteria("nickNames")
	.add(Restrictions.eq("elements", "BadBoy"))
	.list();
	

17.8. Запросы Example

Класс org.hibernate.criterion.Example позволяет построить критерий запроса из данного экземпляра.

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

Свойства версии, идентификаторы и ассоциации игнорируются. По умолчанию свойства со значением null исключаются.

Вы можете настроить как Example будет применяться.

Example example = Example.create(cat)
    .excludeZeroes()           // исключить совйства со нулевыми значениями
    .excludeProperty("color")  // исключить совйства с именем «color»
    .ignoreCase()              // выполнить сравнения строк без учета регистра
    .enableLike();             // использовать like для сравнения строк
List results = session.createCriteria(Cat.class)
    .add(example)
    .list();

Вы даже можете использовать примеры для размещения Criteria для связанных объектов.

List results = session.createCriteria(Cat.class)
    .add( Example.create(cat) )
    .createCriteria("mate")
        .add( Example.create( cat.getMate() ) )
    .list();

17.9. Проекции, агрегация и группировка

Класс org.hibernate.criterion.Projections - это фабрика для экземпляров Projection. Вы можете применить проекцию к запросу, вызвав 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();

В запросе criteria нет явной «group by». Определённые типы проекций определены как группирующие проекции, которые также отображаются в секции SQL group by.

Псевдоним (alias) может быть присвоен проекции, так что прогнозируемое значение может быть указано в ограничениях или упорядочивании. Вот два способа сделать это:

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();

Методы alias() и as() просто оборачивают экземпляр проекции в другой, псевдонимный, экземпляр Projection. В качестве ярлыка вы можете назначить псевдоним, когда вы добавляете проекцию в список проекций:

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();

Вы также можете использовать Property.forName() для выражения проекций:

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();

17.10. Отсоединённые запросы и подзапросы

Класс DetachedCriteria позволяет создать запрос за пределами области видимости сессии, а затем выполнить его с помощью произвольного Session.

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();

Функция DetachedCriteria также может использоваться для выражения подзапроса. Экземпляры критериев, содержащие подзапросы, могут быть получены через Subqueries или 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();

Возможны также коррелированные подзапросы:

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();

Пример многостолбцового ограничения на основе подзапроса:

DetachedCriteria sizeQuery = DetachedCriteria.forClass( Man.class )
    .setProjection( Projections.projectionList().add( Projections.property( "weight" ) )
                                                .add( Projections.property( "height" ) ) )
    .add( Restrictions.eq( "name", "John" ) );
session.createCriteria( Woman.class )
    .add( Subqueries.propertiesEq( new String[] { "weight", "height" }, sizeQuery ) )
    .list();

17.11. Запросы по естественному идентификатору

Для большинства запросов, включая запросы критериев, кэш запросов неэффективен, поскольку недействительность (invalidation) кэша запросов происходит слишком часто. Однако существует особый вид запроса, в котором вы можете оптимизировать алгоритм недействительности кэша: поиск по постоянному естественному ключу. В некоторых приложениях такой запрос возникает часто. Criteria API предоставляют специальное положение для этого варианта использования.

Сначала отобразите естественный ключ вашей сущности с помощью <natural-id> и включите использование кэша второго уровня.

<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>

Эта функциональность не предназначена для использования с сущностями с изменяемыми (mutable) естественными ключами.

После того, как вы включили кэш запросов Hibernate, Restrictions.naturalId() позволяет использовать более эффективный алгоритм кэширования.

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