Hibernate Query Language это аналог SQL в мире Hibernate, ориентированный на запросы не к таблицам, а к классам. Идеологически он очень похож на Java Persistence Query Language, так что любой JPQL запрос является одновременно и корректным HQL запросом. Обратное может быть не верно.
Все запросы в данном примере основываются на модели данных использованной ранее в примере JPQL
Простые запросы
HQL запрос всегда начинается с получения объекта Query из Session вызовом метода createQuery(), в который передаётся текст запроса:
1
2
3
|
session.createQuery("from Person")
.list()
.forEach(System.out::println);
|
1
|
Person{firstName='Test', lastName='Testoff', dob=2016-06-28, passport=Passport{series='AS', no='123456', issueDate=2016-06-28, validity=P20Y, owner=Testoff}, primaryAddress=Address{city='Kickapoo', street='Main street', building='1', tenants=Test}, workingPlaces=[Company{name='Acme Ltd', workers=Test}]}
|
Запрос from Person буквально означает «дай-ка мне сущностей класса Person без каких-либо ограничений». У объекта Query обычно вызывают метод list(), чтобы получить список выбранных сущностей или uniqueResult(), в случае, если сущность предполагается одна. При этом uniqueResult() проверяет, что запрос возвращает ровно одну сущность и если нет, кидает исключение NonUniqueResultException.
В HQL можно выбирать не просто объекты, но и конкретные поля объектов:
1
2
3
4
|
System.out.println(
session.createQuery("select passport from Person ")
.setTimeout(100)
.uniqueResult());
|
1
|
Passport{series='AS', no='123456', issueDate=2016-06-28, validity=P20Y, owner=Testoff}
|
В данном примере я говорю Hibernate «А дай-ка мне все поля passport из сущностей класса Person без каких-либо ограничений» и в ответ мне возвращаются именно сущности Passport, а не Person, даже не смотря на from Person.
Кроме того, вызывая методы объекта Query можно настраивать, как именно будет выполняться запрос. В примере выше я указал, что запрос должен выполниться не более чем за 100 секунд. Кроме таймаута можно настроить блокировки запроса, использованием запросом кэша, пейджинг с помощью setFirstResult()/setMaxResults() и так далее. Обо всём этом я расскажу в соответствующих статьях.
Условия и параметры
Запросы вида «выбери мне свойство» прикольные, но бесполезные. Полезнее было бы по этим свойствам фильтровать:
1
2
3
4
5
|
ScrollableResults passports = session.createQuery("from Passport as p where p.owner.lastName='Testoff'")
.scroll();
while (passports.next()) {
System.out.println(passports.get()[0]);
}
|
1
|
Passport{series='AS', no='123456', issueDate=2016-06-28, validity=P20Y, owner=Testoff}
|
Этот запрос означает «найди мне все сущности класса Passport, в поле owner которых хранятся сущности, поле lastName которых равно Testoff». Практически как обычный SQL, только в условиях пишутся не имена столбцов в таблицах, а имена полей в классах.
В запросе выше показан вариант загрузки данных не целиком, а по одному, аналогично JDBC Scrollable ResultSet. Метод scroll() возвращает объект особого типа, ScrollableResults, по которому можно перемещаться и получать данные из базы данных по мере перемещения. Чаще всего ScrollableResults реализован с использованием курсоров на стороне базы данных или ещё какого-либо механизма, позволяющего получать результаты запроса по частям. Объект ScrollableResults должен быть либо закрыт явно, вызовом собственного метогда close() (он реализует Closable), либо вызовом Hibernate.close(), либо будет закрыт неявно при потере persistence context.
Последний вариант получения данных из объекта Query, это получение их в форме итератора:
1
2
3
4
5
6
7
|
Iterator passportIterator = session.createQuery("from Passport as p where p.owner.lastName like :name")
.setString("name", "Te%")
.iterate();
while (passportIterator.hasNext()) {
System.out.println(passportIterator.next());
}
Hibernate.close(passportIterator);
|
1
|
Passport{series='AS', no='123456', issueDate=2016-06-28, validity=P20Y, owner=Testoff}
|
В запросе выше показано, что в HQL поддерживаются и обычные SQL выражение, такие как is null, between, в данном случае like и так далее. Параметры в запросах поддерживаются автоматически и не требуют особого обращения, в отличие от jdbc. Достаточно написать имя параметра, предварённое двоеточием, и задать его значение методом setParameter().
Вызов iterate() концептуально не отличается от вызова list(): в обоих случаях данные вначале будут загружены в память, список или итератор будет полностью сконструирован и только после этого его вернут. Только scroll() и ScrollableResults позволяют проходит по возвращённым данным без их полной загрузки. Разумеется во всех случаях, и со scroll(), и с list(), и с iterator() и даже с uniqueResult() работает отложенная загрузка полей, так что возвращённый объект может быть не полностью загружен до обращения к его полям.
Возвращаясь к параметризованным запросам, Hibernate поддерживает как JPA стиль позиционных параметров, так и JDBC стиль. Последний, впрочем, не рекомендуется использовать:
1
2
3
4
|
session.createQuery("Select p from Person as p, IN(p.workingPlaces) as wp where wp.name = ?")
.setParameter(0, "Acme Ltd")
.list()
.forEach(System.out::println);
|
1
2
|
WARN: [DEPRECATION] Encountered positional parameter near line 1, column 104 in HQL: [Select p from ru.easyjava.data.hibernate.entity.Person as p, IN(p.workingPlaces) as wp where wp.name = ?]. Positional parameter are considered deprecated; use named parameters or JPA-style positional parameters instead.
Person{firstName='Test', lastName='Testoff', dob=2016-06-28, passport=Passport{series='AS', no='123456', issueDate=2016-06-28, validity=P20Y, owner=Testoff}, primaryAddress=Address{city='Kickapoo', street='Main street', building='1', tenants=Test}, workingPlaces=[Company{name='Acme Ltd', workers=Test}]}
|
В коде выше показано, что нет необходимости чётко указывать тип передаваемого параметра, как это было сделано в предыдущем примере с помощью setString(). Метод setParameter() готов принять параметр любого типа, но ценой проверки этого типа во время исполнения, а не во время компиляции.
Именованные запросы
В Hibernate, помимо обычных HQL запросов, предусмотрены и статические именованные запросы, которые задаются отдельно, обычно поближе к сущности:
1
2
3
4
5
6
|
@NamedQueries({
@NamedQuery(
name = "findCompaniesWithWorkerPassport",
query = "Select c from Company as c, IN(c.workers) as w where w.passport.series = :series")
})
|
1
2
3
4
|
session.getNamedQuery("findCompaniesWithWorkerPassport")
.setParameter("series", "AS")
.list()
.forEach(System.out::println);
|
Такие запросы можно задавать и в XML описаниях сущностей:
1
2
3
|
<query name="findCompaniesWithWorkerPassport">
<![CDATA[Select c from Company as c, IN(c.workers) as w where w.passport.series = :series]]>
</query>
|
1
|
Company{name='Acme Ltd', workers=Test}
|
Именованные запросы позволяют повторно использовать один и тот же запрос в разных местах кода, тем самым уменьшая количество повторений в программе и упрощая его редактирование в будущем. Кроме того, отделение кода запроса от места его исполнения позволяет последнему не заботиться вовсе о том, какой запрос фактически исполняется, уменьшая тем самым связность кода.
Для создания именованного запроса используется метод getNamedQuery() объекта Session, который принимает в себя имя запроса. В остальном с именованным запросом можно обходиться как и с обычным HQL запросом.
Код примера доступен на github.