36.Что такое Criteria API и для чего он используется?
UNKNOWNНачиная с версии 5.0 собственный Hibernate Criteria API признан устаревшим и не развивается. Вместо него рекомендуется использовать JPA Criteria API.
Начиная с версии 5.2 Hibernate Criteria API объявлен deprecated и не рекомендуется к использованию.
Hibernate Criteria API
Это тоже язык запросов, аналогичный JPQL (Java Persistence query language), однако запросы основаны на методах и объектах. Hibernate Criteria API является более объектно-ориентированным для запросов, которые получают результат из базы данных. Для операций update, delete или других DDL манипуляций использовать Criteria API нельзя. Критерии используются только для выборки из базы данных в более объектно-ориентированном стиле. Используется для динамических запросов. Запросы выглядят так:
session.createCriteria(Person.class)
.setMaxResults(10)
.list()
.forEach(System.out::println);
Запрос выше полностью аналогичен запросу HQL \"from Person\". С Criteria также работают и все те вещи, которые работают и с Query: пейджинг, таймауты и т.д.
Разумеется, в Criteria запросах можно и нужно накладывать условия, по которым объекты будут отбираться:
session.createCriteria(Person.class)
.add(Restrictions.eq(\"lastName\", \"Testoff\"))
.list()
.forEach(System.out::println);
JPA Criteria API
Criteria API - это актуальный API, используемый для определения запросов для сущностей. Это альтернативный способ определения JPQL-запроса. Эти запросы типобезопасны, переносимы и легко меняются путем изменения синтаксиса.
Основные преимущества JPA Criteria API:
- ошибки могут быть обнаружены во время компиляции;
- позволяет динамически формировать запросы на этапе выполнения приложения.
Запросы на основе строк JPQL и запросы на основе критериев JPA одинаковы по производительности и эффективности.
Для простых статических запросов предпочтительнее использовать строковые запросы JPQL (например, в виде именованных запросов). Для динамических запросов, которые создаются во время выполнения - JPA Criteria API может быть предпочтительней. Например, построение динамического запроса на основе полей, которые пользователь заполняет в рантайме в форме, которая содержит много необязательных полей. Ожидается, что построение этого запроса будет более ясным и понятным при использовании JPA Criteria API, поскольку устраняет необходимость в создании запроса с использованием многих операций конкатенации строк.
Пример использования JPA Criteria API:
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> personCriteria = cb.createQuery(Person.class);
Root<Person> personRoot = personCriteria.from(Person.class);
personCriteria.select(personRoot);
em.createQuery(personCriteria)
.getResultList()
.forEach(System.out::println);
Алгоритм:
- создаём EntityManager,
- открываем транзакцию и создаём CriteriaBuilder, который будет строить объекты запросов.
- С помощью CriteriaBuilder создаём CriteriaQuery, который параметризуется типом, который этот запрос возвращает.
- Затем создаём корневой объект, от которого производится обход дерева свойств при накладывании ограничений или указании, что выбирать.
- Последним шагом говорится, что же мы хотим выбрать
- и, наконец, запрос отправляется в EntityManager, где и выполняется как обычно. Построенный выше весьма многословный пример эквивалентен JPQL запросу «from Person».
Все шаги, перечисленные выше, являются обязательными для создания запроса с помощью Criteria API. Важно понимать, что корневой объект указывает JPQL, откуда будут браться данные, а CriteriaQuery указывает тип возвращаемых данных. И типы Root и CriteriaQuery могут отличаться:
CriteriaQuery<Passport> passportCriteria = cb.createQuery(Passport.class);
Root<Person> personPassportRoot = passportCriteria.from(Person.class);
passportCriteria.select(personPassportRoot.get(\"passport\"));
em.createQuery(passportCriteria)
.getResultList()
.forEach(System.out::println);
Этот запрос аналогичен JPQL запросу «select passport from Person» и показывает, что класс, из которого запрашиваются данные и класс, который вернёт запрос, могут быть разными.
Metamodel и типобезопасность.
Все примеры выше решают проблему с программным созданием запросов, но всё ещё бессильны перед изменениями сущностей. В самом деле, изменив в сущности Person поле workingPlaces на jobs - развалится этот запрос:
CriteriaQuery<Person> personWorkCriteria = cb.createQuery(Person.class);
Root<Person> personWorkRoot = personWorkCriteria.from(Person.class);
Join<Person, Company> company = personWorkRoot.join(\"workingPlaces\");
personWorkCriteria.select(personWorkRoot);
personWorkCriteria.where(cb.equal(company.get(\"name\"), \"Acme Ltd\"));
em.createQuery(personWorkCriteria)
.getResultList()
.forEach(System.out::println);
И мы не узнаем, что он развалится, пока не попробуем его исполнить.
Metamodel решает эту проблему, создавая специальные описательные классы, которые используются в Criteria API вместо имён полей. Сам Metamodel класс выглядит примерно вот так:
@StaticMetamodel(Company.class)
public abstract class Company_ extends AbstractIdentifiableObject_ {
public static volatile SingularAttribute<Company, String> name;
public static volatile CollectionAttribute<Company, Person> workers;
}
В Metamodel классе описываются, какие поля присутствуют в сущности, какого они типа, коллекция это или нет и т.д. Для каждой сущности создаётся свой класс Metаmodel.
Создаются классы Metamodel разумеется не вручную. То есть можно их и вручную создать, но тогда пропадает автоматичность проверки и теряется смысл всей этой затеи.
Обычно же классы Metamodel генерируются на этапе компиляции тем или иным методом. Конкретная реализация генерации зависит от конкретной реализации JPA и может меняться.
Предыдущий вопрос: 34. Как работать с кэшем 2 уровня?
Следующий вопрос: 37. Расскажите про проблему N+1 Select и путях ее решения.
Все вопросы по теме: список
Все темы: список
Вопросы/замечания/предложения/нашли ошибку: напишите мне