Hibernate SQL запросы

hibernate-sql-queryHQL запросы и прямая загрузка сущностей, это очень здорово, но хорошо бы иметь озможность и выполнять запросы напрямую, используя всю мощь и вашей базы данных. Однако, такие запросы могут вернуть данные которые не ожидает увидеть или которые даже не отображены на существующие сущности. Поэтому для поддержки таких запросов требуется особая реализация.

Простые (скалярные) запросы

Самый простой запрос, это запрос который возвращает какой-либо один столбец.  Я использую модель данных из примера с HQL и посчитаю в ней количество людей в базе:

На SQL запросе я подробно останавливаться не буду, лучше рассмотрю особенности Hibernate. Вызов createSQLQuery() создаёт sql запрос, равно как и createQuery() создаёт HQL запрос. Вызов addScalar("c", IntegerType.INSTANCE) сообщает Hibernate что в результате запроса вернётся простой столбец, не объектб и что он будет типа Integer.

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

В это случае возвращается список из массива «сырых» объектов, которые Hibernate и не пытается обрабатывать. Этот подход похож на возврат результатов в Spring JDBC.

Запросы сущностей

Сырые данные из базы это хорошо, но у нас всё таки объектный язык. Результаты SQL запроса можно преобразовать в сущность, выбрав поля, которые её составляют.

Вызов addEntity() указывает Hibernate, какую сущность из данных запроса надо построить. Имена столбцов в ответе должны совпадать с именами, заданными при отображении.

Разумеется, с SQL запросами (и с скалярными и с запросами сущностей) можно использовать стандартный функционал — параметры, пейджинг и т.д.:

Запросы выше кроме использования параметров показывает, как загружать связанные объъекты. Вызов addEntity() указывает Hibernate, какой объект мы загружаем (корневой объект), вызов(ы) addJoin() говорят Hibernate, какие связанные объекты загружаются вместе с корневым и как они к нему относятся.

Преобразование результатов

Вызов setResultTransformer() в предыдущем примере говорит Hibernate, что мы хотим получить конкретно экземпляр корневого объекта, а не набор загруженных данных. Но это не единственное и не главное предназначение setResultTransformer(). Если говорить обще, этот вызов позволяет задать код, который будет преобразовывать загруженные из базы данных. Один из вариантов использования этого функционала — загрузка данных во временные объекты, которые даже не имеют отображения (DTO — data transfer objects)

Для начала я создам такой DTO:

Для использования DTO с ResultTransfomer он должен иметь конструктор по умолчанию и сеттеры на полях. Затем мы трансформируем результат запроса в DTO:

Выражение  name as \"name\" в данном случае критично важно, так как имя столбца в ответе должно совпадать с именем поля в DTO.

Именованные запросы

Именованные запросы с SQL работают так же, как и с HQL и даже используют тот же вызов. Но определение запроса меняется: во-первых аннотация теперь называется @NamedNativeQuery, во-вторых необходимо явно задавать тип возвращаемой сущности:

В остальном именованные SQL запросы ведут себя так же, как и именованные HQL запросы и поддерживают тот же самый функционал.

Кроме того, именованные SQL запросы поддерживают и DTO:

Для использования DTO и именованных SQL запросов требуется определить соответствующее отображение аннотацией @SqlResultSetMapping и связать его с именованным запросом. Очевидно, что одно отображение может быть использовано в нескольких запросах.

Для того, чтобы DTO можно быть использовать с @ConstructorResult, класс DTO должен иметь конструктор, инициализирующий его значения.

Код примера доступен на github.