Язык запросов HQL
HQL (Hibernate Query Language) – это объекто-ориентированный язык запросов, который очень похож на SQL. Главное различие языков HQL и SQL связано с тем, что SQL формирует запросы из наименований таблиц в базе данных и их столбцов, а HQL работает с сущностями (классами) и их полями (аттрибутами класса).
Hibernate транслирует HQL–запросы в понятные для БД SQL–запросы, которые и выполняют необходимые действия в БД.
Рассмотрим основные ключевые операторы языка HQL :
FROM
Оператор FROM используется для загрузки (чтения) набора объектов. Пример кода :
String hql = "FROM User"; Query query = session.createQuery(hql); List users = query.list();
User представляет POJO класс (User.java), который ассоциирован с таблицей в БД.
WHERE
Оператор WHERE накладывает условие на выборку определенных записей из БД. В следующем коде оператор WHERE используется точно также, как и в обычном SQL :
Query query = session.createQuery("FROM User where name = 'Иван'"); List users = query.list();
Hibernate может использовать оператор WHERE с именованными параметрами (Named Parameters), определяя значение в режиме run-time. Для подстановки соответствующего значения в запрос используется метод setParameter объекта Query, которому в качестве параметров необходимо передать значения :
String hql = "FROM User where name = :paramName"; Query query = session.createQuery(hql); query.setParameter("paramName", "Alex"); List users = query.list();
Запросы HQL не чувствительны к регистру операторов, за исключением названий Java классов и их свойств. Т.е. ‘SeLect’ будет эквивалентен ‘Select’. Но вот с наименованием сущностей/классов и их полями это не проходит; они должны соответствовать описаниям.
INSERT
В HQL поддерживается только форма INSERT INTO . SELECT . , которая имеет серьезные ограничения для вставки записей. С точки зрения SQL данная форма позволяет вставить одну или несколько записей в таблицу из запроса SELECT . . Но это характерно для SQL, а Hibernate (HQL) работает только с сущностями. Попробуем использовать данную форму запроса для вставки записи в Hibernate, и рассмотрим следующий код вставки записи пользователя :
String hql = "insert into User (login, name) " + "select 'oleg', 'Олег' from User"; int rows = session.createQuery (hql).executeUpdate(); System.out.println("rows : " + rows);
В коде выполняется попытка сохранения сущности User со значениями login=’oleg’ и name=’Олег’. Структуру таблицы и описание сущности можно увидеть в примере связанных сущностей, который описан здесь. Выполнение данного кода выведет в консоль следующую информацию :
Hibernate: insert into USERS ( id, login, name ) select SEQ_USER.nextval, 'oleg' as col_0_0_, 'Олег' as col_1_0_ from USERS user0_ rows : 2
Hibernate сформировал запрос insert и вставил 2 записи. Если посмотреть содержимое таблицы БД, то там можно будет увидеть новые две записи. Полагаю, что вторая запись лишняя, но как от нее избавиться. Если вместо наименования сущности User в первой или во второй позиции подставить наименование таблицы Users, то Hibernate вызовет QuerySyntaxException :
Exception in thread "main" \ org.hibernate.hql.internal.ast.QuerySyntaxException: \ Users is not mapped [ \ insert into Users(login, name) \ select 'oleg', 'Олег' from net.common.model.User]
Таким образом, можно заключить, что вставку записей в таблицы при использовании HQL лучше выполнять с применением сущностей и стандартных методов save или saveUpdate объекта сессии, как это представлено в примере.
UPDATE
Ключевое слово UPDATE используется для обновления одного или нескольких полей объекта. При формировании SQL-запроса можно использовать параметры (Named Parameters), рассмотренные выше. Следующий код демонстрирует динамическое определение параметров при обновлении записи. Результат выполнения запроса — количество обновленных (затронутых) записей :
String hql = "update Contact " + "SET firstName = :name " + ", lastName = :lastName " + ", date = :dateParam " + " where query = session.createQuery(hql); query.setParameter("idParam" , 48); query.setParameter("name" , "Киса"); query.setParameter("lastName" , "Воробьянинов"); query.setParameter("dateParam", new Date()); int result = query.executeUpdate();
DELETE
Ключевое слово DELETE используется для удаления одного или нескольких объектов из таблицы. Пример использования :
String hql = "DELETE User WHERE login = :lg"; Query query = session.createQuery(hql); query.setParameter("lg", "oleg"); int rows = query.executeUpdate();
Представленный выше код будет выполнен, если сущность User не имеет связанных объектов, т.е. из таблицы БД будет удалена сущность. Но, если к примеру, User имеет один или несколько связанных объектов Autos, как это представлено в примере связанных сущностей, то сервер БД не сможет удалить запись из таблицы Users, поскольку в таблице Autos имеются «ссылочные» записи. В этом случае Hibernate вызовет org.hibernate.exception.ConstraintViolationException : could not execute statement.
Для удаления связанных сущностей необходимо использовать объект сессии Session. Следующий код демонстрирует удаление пользователя User, у которого имеются связанные объекты (сущности):
User user = session.load(User.class, 50); if (user != null)
В консоль будут выведены следующие сформированные Hibernate запросы удаления сущности User и связанных с ней сущностей Auto :
Hibernate: delete from autos where aid=? Hibernate: delete from autos where aid=? Hibernate: delete from USERS where >Обратите внимание, что дочерние/связанные сущности удаляются в первую очередь.
Использование алиаса AS
Оператор AS можно использовать в качестве алиаcа в HQL запросе, особенно, если запрос получается длинным. Следующий код демонстрирует определение алиаса пользователя User как ‘u’ (без его использования в запросе) :
String hql = "FROM User AS u"; Query query = session.createQuery(hql); List results = query.list();
Оператор AS можно не включать в запрос, и сразу же после наименования сущности определить ее алиас :
String hql = "FROM User u"; Query query = session.createQuery(hql); List results = query.list();
Ниже представлены примеры кода с использованием алиаса.
GROUP BY
При использовании в HQL агрегатных функций необходимо выполнять группировку по определенным в запросе полям, аналогично SQL. Следующий код демонстрирует использование агрегатной функции SUM с группировкой по полю firstName сущности Employee :
String hql = "SELECT SUM (e.salary), e.firtName " + "FROM Employee e " + "GROUP BY e.firstName"; Query query = session.createQuery(hql); List results = query.list();
Агрегатные функции HQL
В таблице представлены агрегатные функции, поддерживаемые HQL :
| Функция | Описание |
|---|---|
| avg (property name) | получение среднего значения |
| count (property name or *) | получение количества записей |
| max (property name) | получение максимального значения |
| min (property name) | получение минимального значения |
| sum (property name) | вычисление суммарного значения |
Использование оператора DISTINCT позволяет выделить уникальные значения. Следующий запрос вернет количество уникальных имен сотрудников :
String hql = "SELECT COUNT (distinct e.firstName) " + "FROM Employee e"; Query query = session.createQuery(hql); List results = query.list();
ORDER BY
Сортировка результатов запроса в HQL выполняется с использованием ORDER BY аналогично SQL. Сортировать значения можно как по возрастанию (ASC ascending), так и по убыванию (DESC descending). Следующий код демонстрирует чтение сотрудников с сортировкой по убыванию :
String hql = "FROM Employee e " + "WHERE e.id > 10 ORDER BY e.salary DESC"; Query query = session.createQuery(hql); List results = query.list();
Если необходимо выполнить сортировку более чем по одному из полей, то можно после оператора ORDER BY указать список полей с порядком сортировки, разделенных запятой :
String hql = "FROM Employee e " + "WHERE e.id > 10 " + "ORDER BY e.firstName DESC, e.salary DESC"; Query query = session.createQuery(hql); List results = query.list();
Разбиение на страницы
Объект Query включает два метода, позволяющие организовать разбиение данных по-страницам :
- setFirstResult (int start) — параметр start определяет первую извлекаемую запись в запросе (отсчет от 0);
- setMaxResults (int max) — параметр max определяет количество извлекаемых записей в результирующем запросе.
Используя вышеописанные методы можно организовать постраничное представление набора данных. Следующий пример выбирает из БД 8 сотрудников, начиная с 5-ой записи :
String hql = "FROM Employee"; Query query = session.createQuery(hql); query.setFirstResult(5); query.setMaxResults (8); List results = query.list();
Класс Query
Метод для поиска документа на основании сформированной выборки.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| callback | CallbackFindDocument | Обязательный | Callback, который будет вызван после выполнения запроса. |
Если вы хотите чтобы вместе с id найденных документов возвращались их поля, задайте название необходимых полей с помощью функции setFieldsForSearch(fields)
Данный метод по-умолчанию возвращает не более 50 документов коллекции.
Пример
Query query = new Query(“mycollection”) .equalTo("number3", 10) .exists("number2"); query.findDocuments(new CallbackFindDocument() < @Override public void onDocumentFound(ListdocumentInfos) < //found. See document list what match query >@Override public void onDocumentNotFound(String errorCode, String errorMessage) < //no documents what match query >>);
.countDocuments(callback)
Метод для подсчета документов из сформированной выборки.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| callback | CallbackCountDocument | Обязательный | Callback, который будет вызван после выполнения запроса. |
Пример
Query query = new Query(“mycollection”); query.greaterThan("rating", 10); query.countDocuments(new CallbackCountDocument() < @Override public void onDocumentsCounted(ResponseCount responseCount) < //see responseCount.getResult() to find how many documents was found. >@Override public void onCountFailed(String errorCode, String errorMessage) < //error during count >>);
.updateDocument(update, callback)
Метод для обновления документов из сформированной выборки.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| update | Update | Обязательный | Объект Update | |
| callback | CallbackUpdateDocument | Обязательный | Callback, который будет вызван после выполнения запроса. |
Данный метод обновляет не более 1000 документов
Пример
Query query = new Query(“mycollection”); query.equalTo("number3", 10); Update update = new Update() .set("number2", 199) .set("numberField", 111) .addToSet("array1", 900); query.updateDocument(update, new CallbackUpdateDocument() < @Override public void onUpdateSucceed(ResponseUpdate responseUpdate) < //documents updated successful >@Override public void onUpdateFailed(String errorCode, String errorMessage) < //error during update >>);
.removeDocument(callback)
Метод для удаления документов на основании сформированной выборки.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| callback | CallbackRemoveDocument | Обязательный | Callback, который будет вызван после выполнения запроса. |
Данный метод удаляет не более 1000 документов
Пример
Query query = new Query(“mycollection”); query.equalTo("_id", "aJfkipJags"); query.removeDocument(new CallbackRemoveDocument() < @Override public void onRemoveSucceed(ResponseRemove responseRemove) < //succeed. See responseRemove to findout how many documents was removed //and get list of removed documents >@Override public void onRemoveFailed(String errorCode, String errorMessage) < //error during remove operation >>);
.setLimit(limit)
Метод для установки лимита на количество удаляемых, обновляемых и искомых документов.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| limit | Integer | Обязательный | Лимит для установки | 15 |
Данный метод принимает 100 в качестве максимального значения limit
Пример
Query query = new Query(“mycollection”); query.setLimit(15); //query.findDocuments(…);
.setSkip(skip)
Метод для пропуска части объектов перед возвратом результата совершенной выборки
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| skip | Integer | Обязательный | Количество пропускаемых документов | 100 |
Пример
Query query = new Query(“mycollection”); query.setSkip(12); //query.findDocuments(…);
.setPage(page)
Метод для постраничного вывода результатов выборки
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| page | Integer | Обязательный | Номер страницы | 2 |
Пример
Query query = new Query(“mycollection”); //query.setLimit(15); query.setPage(1); //query.findDocuments(…);
.equalTo(field, value)
Метод для добавления условия выборки: только документы для которых значение поля равно значению в условии выборки.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «orderNumber» |
| value | Object | Обязательный | Значение для сравнения | 22 |
Пример
Query query = new Query(“mycollection”); query.equalTo(“orderNumber”, 22); // query.findDocuments(…);
Если вы пытаетесь задать данное условие для поля id документа, помните, что, поскольку id – первичный ключ для документа, вам нужно добавить “_” перед именем поля.
Query query = new Query(“mycollection”); query.equalTo(“_id”, “dasds12dskm”); //query.findDocuments(…);
.notEqualTo(field, value)
Метод для добавления условия выборки: только документы для которых значение поля не равно значению в условии выборки.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «orderNumber» |
| value | Object | Обязательный | Значение для сравнения | 22 |
Пример
Query query = new Query(“mycollection”); query.notEqualTo(“orderNumber”, 22); //query.findDocuments(…);
Если вы пытаетесь задать данное условие для поля id документа, помните, что, поскольку id – первичный ключ для документа, вам нужно добавить “_” перед именем поля.
Query query = new Query(“mycollection”); query.equalTo(“_id”, “dasds12dskm”); //query.findDocuments(…);
.containedIn(field, values)
Метод для добавления условия выборки: только документы у которых значение поля совпадает с одним из значений заданного массива.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «orderNumbers» |
| value | List | Обязательный | Массив элементов | см.пример ниже |
Пример
List numbers = new ArrayList<>(); numbers.add(1); numbers.add(5); numbers.add(10); numbers.add(15); Query query = new Query(“mycollection”).containedIn("number3", numbers); //query.findDocuments(…);
.containsAll(field, values)
Метод для добавления условия выборки:только документы у которых поле (типа массив) содержит все элементы заданного массива
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «orderNumbers» |
| value | List | Обязательный | Массив элементов | см.пример ниже |
Пример
List containsAllNumbers = new ArrayList<>(); containsAllNumbers.add(1); containsAllNumbers.add(2); containsAllNumbers.add(3); containsAllNumbers.add(900); Query query = new Query(“mycollection”).containsAll("array1", containsAllNumbers); //query.findDocuments(…);
.notContainedIn(field, values)
Метод для добавления условия выборки: только документы у которых
- значение поля не совпадает ни с одним из значений заданного массива.
- значение поля не задано (not exist).
Пример
List notContainsInList = new ArrayList<>(); notContainsInList.add(1); notContainsInList.add(111); notContainsInList.add(11); notContainsInList.add(50); Query query = new Query(“mycollection”).notContainedIn("orderNumbers", notContainsInList) //query.findDocuments(…);
.greaterThan(field, value)
Метод для добавления условия выборки: только документы у которых значение поля больше указанного в value значения
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| value | Integer / Double / Date | Обязательный | Значение для сравнения | 22 |
Пример
Query query = new Query(“mycollection”).greaterThan("number", 22) //query.findDocuments(…);
.greaterThenOrEqualTo(field, value)
Метод для добавления условия выборки: только документы у которых значение поля больше или равно указанного в value значения
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| value | Integer / Double / Date | Обязательный | Значение для сравнения | 22 |
Пример
Query query = new Query(“mycollection”).greaterThenOrEqualTo ("number", 22) //query.findDocuments(…);
.lessThan(field, value)
Метод для добавления условия выборки: только документы у которых значение поля меньше указанного в value значения
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| value | Integer / Double / Date | Обязательный | Значение для сравнения | 22 |
Пример
Query query = new Query(“mycollection”). lessThan("number", 22) //query.findDocuments(…);
.lessThanOrEqualTo(field, value)
Метод для добавления условия выборки: только документы у которых значение поля меньше или равно указанного в value значения
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| value | Integer / Double / Date | Обязательный | Значение для сравнения | 22 |
Пример
Query query = new Query(“mycollection”).lessThanOrEqualTo ("number", 22) //query.findDocuments(…);
.exists(field)
Метод для добавления условия выборки: только документы у которых задано значение поля, т.е поле существует и не является пустым.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
Пример
Query query = new Query(“mycollection”).exists("phoneNumber") //query.findDocuments(…);
.doesNotExist(field)
Метод для добавления условия выборки: только документы у которых не задано значение поля, т.е поле не существует или является пустым.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
Пример
Query query = new Query(“mycollection”).doesNotExist("phoneNumber") //query.findDocuments(…);
.contains(field, regEx, options)
Метод для добавления условия выборки: только документы у которых значение поля соответствуют заданному регулярному выражению.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| regEx | String | Обязательный | Регулярное выражение | “aB” |
| options | RegexOptions | Необязательный | Опции регулярного выражения | см.пример ниже |
Пример
RegexOptions regexOptions = new RegexOptions(); regexOptions.setRegexCaseInsenssitive(); Query query = new Query(“mycollection”).contains("exampleField", "BC", regexOptions) //query.findDocuments(…);
.startsWith(field, regEx, options)
Метод для добавления условия выборки: только документы у которых значение поля начинается с заданной строки
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| regEx | String | Обязательный | Строка с которой должно начинаться значение поля | “aB” |
| options | RegexOptions | Необязательный | Опции регулярного выражения | см.пример ниже |
Параметр options позволяет, к примеру, искать все документы начинающиеся с “aB” не зависимо от регистра, т.е искать документы, начинающиеся на ab, aB, Ab, AB.
Пример
RegexOptions regexOptions = new RegexOptions(); regexOptions.setRegexCaseInsenssitive(); Query query = new Query(“mycollection”).startsWith ("exampleField", "a", regexOptions) //query.findDocuments(…);
.endsWith(field, regEx, options)
Метод для добавления условия выборки: только документы у которых значение поля заканчивается с заданной строки
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| regEx | String | Обязательный | Строка, на которую должно заканчиваться значение поля | “aaB” |
| options | RegexOptions | Необязательный | Опции регулярного выражения | см.пример ниже |
Параметр options позволяет, к примеру, искать все документы начинающиеся с “aB” не зависимо от регистра, т.е искать документы, начинающиеся на ab, aB, Ab, AB.
Пример
RegexOptions regexOptions = new RegexOptions(); regexOptions.setRegexCaseInsenssitive(); Query query = new Query(“mycollection”).startsWith ("exampleField", "a", regexOptions) //query.findDocuments(…);
.and(field, query)
Метод логического умножения нескольких условий выборки по одному полю
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| query | Query | Обязательный | Объект класса Query, содержащий данные для выборки | см.пример ниже |
Пример
Query query1 = new Query(COLLECTION_NAME).greaterThan("raiting", 50); Query query2 = new Query(COLLECTION_NAME).lessThan("raiting", 100); query1.and("number3", query2); //query1.findDocuments(…);
.or(field, query)
Метод логического сложения нескольких условий выборки по одному полю
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
| query | Query | Обязательный | Объект класса Query, содержащий данные для выборки | см.пример ниже |
Пример
Query query1 = new Query(“mycollection”).greaterThan("raiting", 50); Query query2 = new Query(“mycollection”).equalTo("status", 0); query1.or("number3", query2); //query1.findDocuments(…);
.raw(json)
Метод для задания условий выборки в формате json.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| json | String | Обязательный | Условия выборки в формате json | «>» |
Пример
Query query = new Query(“mycollection”); query.raw(">"); //query.findDocuments(…)
.reset()
Метод для очистки условия выборки.
Пример
Query query = new Query(“mycollection”); query.equalTo(“_id”, “dsads123sd”); query.reset(); query.equalTo(“_id”, “ds54522sd”); //query.findDocuments(…)
.ascending(field)
Метод для сортировки данных указанного поля в порядке возрастания перед совершением выборки.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
Пример
Query query = new Query("ordersCollection"); query.ascending("itemId"); //query.findDocuments(. )
.descending(field)
Метод для сортировки данных указанного поля в порядке убывания перед совершением выборки.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| field | String | Обязательный | Имя поля, которому задается условие | «fieldname» |
Пример
Query query = new Query("ordersCollection"); query.descending("itemId"); //query.findDocuments(. )
.setFieldsForSearch(fields)
Метод для задания списка возвращаемых полей.
| Параметр | Тип | Свойства | Описание | Пример значения |
|---|---|---|---|---|
| fields | List | Обязательный | Имена полей | см.пример ниже |
Пример
List fieldNames = new ArrayList<>(); fieldNames.add(“orderId”); fieldNames.add(“buyerName”); fieldNames.add(“phoneNumber”); Query query = new Query(“mycollection”).setFieldsForSearch(fieldNames); //query.findDocuments(…);
.getQueryInfo()
Метод для получения информации о условиях выборки
Пример
Query query = new Query(“mycollection”); query.equalTo(“_id”, “dsads123sd”); QueryInfo queryInfo = query.getQueryInfo();
Аннотация @Query
Мы рассматривали запросы, генерируемые из имени метода. Так удобно писать простые запросы. Но если в запросе более 1-2 параметров или несколько join, то удобнее использовать аннотацию @Query.
Для этого в репозитории создаем метод, аннотированный @Query, и внутри аннотации прописываем запрос — можно как JPQL, так и native SQL.
Ниже приведен пример.
Использование @Query
public interface PostRepository extends JpaRepository < @Transactional(readOnly = true) @Query("select p from Post p where p.user.id=:id and p.title like :title") ListfindMySuperPosts(String title, long id); >
Запрос выбирает посты данного пользователя по заголовку.
Посты и пользователи находятся в отношении @ManyToOne. Класс Post:
@Entity @Data @NoArgsConstructor public class Post
@Data @Entity @NoArgsConstructor public class User
Native Query
Такой же запрос можно написать на SQL, указав nativeQuery = true:
@Transactional(readOnly = true) @Query(nativeQuery = true, value = "select * from post p where p.user_id=:id and p.title like :title") List findMySuperPostsNative(String title, long id);
Pagination и Sorting
Можно получать результат постранично, указав в качестве параметра Pagable:
@Transactional(readOnly = true) @Query("select p from Post p where p.title like :title") List findSuperPosts(String title, Pageable pageable);
Создавать запрос страницы можно так:
Pageable pageable= PageRequest.of(0, 2, Sort.by("title"));
Выше приведен запрос первой страницы (страницы нумеруются с 0), при этом на странице должно быть два элемента. Задано упорядочивание выборки по title. Подробнее о постраничном выводе и сортировке тут.
Формируемый SQL такой:
select post0_.id as id1_0_, post0_.text as text2_0_, post0_.title as title3_0_, post0_.user_id as user_id4_0_ from post post0_ where post0_.title like ? order by post0_.title asc limit ? select user0_.id as id1_1_0_, user0_.email as email2_1_0_, user0_.locked as locked3_1_0_, user0_.nickname as nickname4_1_0_, user0_.password as password5_1_0_, user0_.role as role6_1_0_ from user user0_ where user0_.id=?
Аннотация @Modifying
С помощью @Query можно не только читать, но и обновлять базу. Но в таких запросах необходима дополнительная к @Query аннотация @Modifying.
update
Пример обновления заголовков всех постов пользователя:
@Modifying @Transactional @Query("update Post p set p.title=:title where p.id=:id") int setFixedPostTitleFor(String title, long id);
delete
Пример удаления всех постов пользователя:
@Modifying @Transactional @Query("delete from Post p where p.user.id=:id") int deleteInBulkByUserId(long id);
Стоит отметить, что так посты удаляются пакетно, поскольку генерируется один SQL :
delete from post where user_id=?
Если же использовать генерируемый по имени метод, то посты удаляются по одному:
@Transactional int deleteByUserId(long id);
SQL отправляется свой для каждого поста:
Hibernate: delete from post where delete from post where delete from post where >Обратите внимание, что методы мы аннотировали @Transactional, поскольку писали их сами. (Если бы использовали стандартные методы репозитория, унаследованные от SimpleJpaRepository, то они уже @Transactional.)
Все методы должны быть в рамках транзакции, другое дело что @Transactional может быть на уровне сервиса или теста. Но в данном случае тест у нас не @Transactional, класс аннотирован @SpringBootTest, а не @DataJpaTest (внутри которого есть @Transactional). Это потому, что @DataJpaTest откатывает транзакции и некоторые SQL не логируются (возможно, потому что не выполняются?). А хотелось бы видеть все сгенерированные SQL.
@SpringBootTest public class PostRepositoryTest < @Autowired private PostRepository postRepository; @DirtiesContext @DisplayName("посты должны удаляться по user.id единым запросом в @Query") @Test void titlesShouldBeDeletedByIdInBulk() < int n = postRepository.deleteInBulkByUserId(1l); Assertions.assertEquals(3, n); >. >
А для того, чтобы подтвержденные транзакции не портили базу после каждого теста, каждый тест аннотирован @DirtiesContext — это пересоздает H2 базу. Не оптимально, зато наглядная консоль с выводом всех SQL-запросов.
Итоги
Исходный код есть на GitHub.
Изучаем класс Query
Кстати, еще один важный момент – это вспомогательный класс Query. Ты мог его видеть вот в этом примере:
public List getAllEmployes() < try (Session session = sessionFactory.openSession()) < Queryquery = session.createQuery("from Employee", Employee.class); return query.list(); > >
На самом деле, Query – это интерфейс и у него есть несколько реализаций на разные случаи. Но для простоты я буду продолжать называть его классом. Это, скажем так, класс в широком смысле – в терминах ООП.
Примечание. Раньше было два класса:
- Query для описания запроса.
- TypedQuery для описания запроса с заранее известным типом.
Первый появился, когда Hibernate уже был, а дженериков еще не было. Потом, после выхода JDK 5, в Hibernate добавили еще один класс – TypedQuery, который уже поддерживал типизацию результата запроса.
Но, насколько я помню, начиная с 5-й версии Hibernate оставили только один типизированный класс, и он теперь называется Query.
Стандартный способ создания Query:
Query query = session.createQuery("from Employee", Employee.class);
Объекты Query ты создавать научился, а как эти запросы выполнить?
Тут все еще проще – мы просто вызываем метод list() у объекта Query:
Query query = session.createQuery("from Employee", Employee.class); List resultLіst = query.list();
У метода list() есть JPA-синоним, метод который делает тоже самое, но называется getResultList(). Ты можешь иногда встретить его в коде, написанном другими программистами.
Кстати, если запрос подразумевает, что результат будет в единственном результате, то для вызова запроса проще использовать метод uniqueResult().
Query query = session.createQuery("from Employee where Employee.class); Employee result = query.uniqueResult();
У метода uniqueResult() есть JPA-синоним – метод singleResult(). Он появился для совместимости Hibernate со стандартом JPA. Делает он абсолютно то же самое.
2.2 Методы класса Query
На самом деле, у класса Query есть очень много различных методов. Ниже я расскажу еще о трех из них.
Во-первых, это метод stream(). И его JPA-синоним getResultStream().
Оба этих метода возвращают поток данных вместо списка. Такой подход может быть очень эффективным, когда тебе не нужны сразу все объекты, полученные в результате выполнения запроса. Или есть вероятность, что будут использованы только первые из них.
Query query = session.createQuery("from Employee where id > 100", Employee.class); Stream stream = query.stream();
Второй метод – это метод executeUpdate(). Ты можешь написать запрос, который что-то изменит в базе данных. На этот случай нужно, чтобы Hibernate не использовал read-only транзакцию при обращении к базе данных.
Пример запроса: мы решили поднять уровень всех пользователей на 1.
Query query = session.createQuery("update User set level=level+1", User.class); int count = query.executeUpdate();
Метод executeUpdate() вернет количество строк, которые реально были изменены.
И наконец третий метод – это scroll(). О нем мы расскажем немного подробнее.
2.3 Методы класса Scroll
Этот метод чем-то похож на метод stream(). Только он позволяет перемещаться по списку результатов, не вытаскивая результаты вообще. То есть ты можешь выполнить запрос, потом проскролить его на миллионную строку результата и начать читать оттуда данные.
Такой продвинутый итератор.
Query query = session.createQuery("from Employee where id > 100", Employee.class); ScrollableResults scroll = query.scroll();
У объекта ScrollableResults есть такие методы:
| Метод | Описание |
|---|---|
| R get() | Возвращает текущий элемент |
| next() | Перемещает указатель на следующий элемент |
| previous() | Перемещает указатель на предыдущий элемент |
| scroll(int size) | Скролит на size строк вперед |
| position(int pos) | Делает текущим элементом элемент номер pos |
| last() | Текущий элемент теперь последний |
| first() | Текущий элемент теперь первый |
| getRowNumber() | Возвращает номер текущей строки |
| setRowNumber() | Устанавливает номер текущей строки |
Допустим, ты выполнил запрос и хочешь получить последний элемент. Вот как это можно сделать:
Query query = session.createQuery("from Employee where id > 100", Employee.class); ScrollableResults scroll = query.scroll(); scroll.last(); Employee lastEmployee = scroll.get();