jpa запрос с параметрами
Spring Data JPA: запросы, генерируемые по имени метода
Spring Data JPA может генерировать метод по его имени. Мы декларируем в репозитории метод, используя в названии метода имена полей сущности и ключевые слова. А Spring по ним создает реальные запросы к базе данных.
Все запросы так не напишешь, но простые можно. Ниже рассмотрим примеры.
Модель
У пользователя может быть несколько счетов: они хранятся в коллекции accounts.
Заполним таблицы данными.
Данные
Данные находятся в файле data.sql. Скрипт data.sql (и schema.sql) запускается благодаря настройке в файле application.properties:
Итак, с помощью файла data.sql добавим 5 пользователей (два с именем John). Они получат последовательные автоматически сгенерированные id с 1 по 5.
Только для пользователей с id=1 и id=2 добавим счета:
Простейший пример – поиск пользователей по имени – find
Чтобы декларировать методы, необходимо расширить CrudRepository (или JpaRepository).
Сначала найдем пользователей по имени:
В консоли отобразится запрос, который сгенерировал Spring Data:
Сгенерированный SQL отображается благодаря настройке:
Помимо find…, есть также ключевые слова get…query…, read… – все они имеют один и тот же смысл.
Можно также писать findAll…, findUsers. findUser..
То есть наш метод можно записать любым из возможных вариантов:
Вышеперечисленные методы генерируют тот же самый запрос.
Возвращаемый тип
Кроме того, тип возвращаемого значения можно сменить на просто User или Optional :
Правда, в этом случае при нахождении более одного User будет выброшено NonUniqueResultException.
Ограничение количества: findFirst
Зато можно выбрать только первую запись:
И вышеприведенный метод не выбросит исключений.
Выбрать первые N записей (например, 2) можно так:
Обращение к полям вложенной сущности: знак “_”
Можно в запросе учесть не только поля самого User, но и accounts. Например, следующий запрос ищет всех пользователей, у которых в названии одного из счетов есть заданная строка:
Знак подчеркивания перед Name в данном случае можно и не писать – оно необходимо в случае неоднозначности (отделения названия полей родительской от вложенной сущности). Но для читаемости в любом случае полезно.
Сравнение: containing, startsWith, greaterThan
Мы уже использовали ключевое слово containing, что означало “поле содержит строку”. Есть также startingWith:
А для чисел есть GreaterThan. Найдем пользователей, у которых сумма счета превышает заданное число:
Другие ключевые слова в документации.
Логические условия or, and
Можно соединять условия для полей символами or, and. Например, найдем пользователей с заданным именем, либо (еще) тех у которых имеется счет с суммой больше заданной:
Iterable как параметр
Можно задать список в качестве параметра:
Желательно параметр делать именно Iterable, а не Set и List, чтобы при вызове метода не требовалось лишних преобразований.
Pagination
Можно создавать запросы для постраничного извлечения сущности:
Ниже пример использования: в запросе передается PageRequest.of(0,2) с номером страницы и количеством элементов на странице:
Страницы нумеруются с 0.
Стоит отметить, что метод постраничного получения всех подряд сущностей уже есть в PagingAndSortingRepository:
Сортировка: orderBy и Sort
Найти пользователей, что имя содержит заданную строку и упорядочить результат по имени:
Или передать в параметре объект Sort:
Пример использования второго метода:
Оба метода генерируют одинаковый SQL:
Подсчет количества: count
Есть еще ключевое слово count для подсчета строк. Например, найти количество пользователей с заданным именем:
Spring Data генерирует следующий SQL:
Найти количество пользователей с названием аккаунта, содержащим строку:
Удаление сущности: remove и delete
Наконец, можно писать и методы удаления сущности с помощью ключевых слов remove и delete:
Какое ключевое слово выбрать значения не имеет. А вот возвращаемый тип int вернет количество удаленных строк.
Исходный код
Код примеров есть на GitHub.
Spring Data JPA: запросы, генерируемые по имени метода: 3 комментария
Обращение в полям вложенной сущности: знак “_” – ошибка в тексте. “Обращение к полям…”
Делаем динамический отчет средствами JPA Criteria.Api
Очень часто в корпоративной разработке происходит диалог:
В данной статье мы рассмотрим, каким образом можно сделать запросы по таблице с изменяющимся списком критериев в среде Spring+JPA/Hibernate без прикручивания дополнительных библиотек.
Основных вопросов всего два:
Specification – итоговые ограничения запроса, содержит объекты Predicate как условия WHERE, HAVING. Предикаты – конечные выражения, которые могут принимать значения true или false.
Одиночное условие состоит из поля, оператора сравнения и значения для сравнения. Также условия могут быть вложенными. Опишем полностью условие классом SearchCriteria:
Теперь опишем сам построитель. Он будет уметь строить спецификацию на основании поданного списка условий, а также объединять несколько спецификаций определенным образом:
Теперь осталось реализовать рекурсивный разбор нашей структуры SearchCriteria. Отмечу, метод buildPath, который по Root – области определения объекта T будет находить путь к полю, на которое ссылается SearchCriteria.key:
Напишем тестовый кейс для нашего построителя:
Итого, мы научили наше приложение разбирать логическое выражение, используя Criteria.API. Набор операций в текущей реализации ограничен, но читатель может самостоятельно реализовать нужные ему. На практике решение применено, но пользователям не интересно(у них лапки) строить выражение глубже первого уровня рекурсии.
ДИСКЛЭЙМЕР обработчик не претендует на полную универсальность, при необходимости добавлять усложненные JOIN-ы придется лезть в реализацию.
Реализованную версию с расширенными тестами вы можете найти в моем репозитории на Github
Написание запросов в Spring Data JPA
Содержание
Помимо стандартных методов вы также можете добавить в этот интерфейс свои собственные. Причём если вы будете следовать соглашениям об именовании методов, то Spring Data будет автоматически генерировать по ним sql-запросы. То есть вы определяете запросы к БД в декларативном стиле. Это, во-первых, позволяет давать методам удобочитаемые имена, а во-вторых, позволяет абстрагироваться от конкретной СУБД и специфики написания запросов к ней.
Таблица, сущность и репозиторий
Итак, возьмём классы из уже упомянутой статьи. Приложение, рассмотренное в ней, работает с музыкальными группами. Напоминаю, что таблица в БД определяется следующим sql (пример для postgres):
Класс-сущность к ней выглядит следующим образом:
@Entity
@Table (name = «band» )
data class Band(
val created: LocalDate
)
Здесь поле playersCount допускает неопределённые значения (null). Если имя таблицы совпадает с названием класса, то аннотацию @Table можно не использовать. Аналогично можно сказать и про имена: если имя поля в таблице совпадает с именем поля в сущности, то аннотацию @Column можно пропустить.
Репозиторий, работающий с этой сущностью, представлен следующим интерфейсом:
Как видите, он уже типизирован классом Band.
Для наглядности вы можете в application.properties установить параметр spring.jpa.properties.hibernate.show_sql в true. Тогда в консоли во время выполнения запроса вы будете видеть sql, который будет генерить Spring Data.
Фильтрация по одному параметру
Для того, чтобы выбрать все записи, одно из полей которых равно определённому значению, имя метода должно начинаться с findBy. Затем добавляем название нужного поля и соответствующий параметр:
В данном случае мы выбираем все группы, название которых соответствует указанной строке (фактически, ищем определённую группу) и возвращаем список. Сгенерированный sql будет выглядеть примерно так:
Регистронезависимый поиск
В рассмотренном выше примере поиска по названию группы очень важно соблюсти регистр названия. Например, если вместо «Queen» передать в качестве параметра «queen», то метод вернёт пустой результат. Чтобы этого избежать, добавим к имени метода IgnoreCase:
На уровне sql регистронезависимый поиск достигается путём приведения и параметра, и значения поля к верхнему регистру с помощью sql-функции upper():
Объединение условий через И
Если мы хотим фильтровать не только по имени, а ещё и, например, по количеству участников музыкальной группы, то можно объединить два разных условия через And. В репозиторий добавим такой метод:
Sql будет выглядеть так:
Параметры запроса должны следовать в том же порядке, в каком они перечислены в имени метода.
Объединение условий через ИЛИ
А что, если мы хотим выбрать две конкретные группы по их названию? Мы можем объединить условия через Or.
Spring Data сгенерит такой sql:
Фильтрация по множеству значений
А как быть, если нам нужно выбрать по определённым названиям ещё больше групп? Можно было бы конечно добавить ещё больше Or, но это сделает имя метода нечитаемым. В sql это можно сделать с помощью конструкции in. Такому же соглашению следует и Spring Data.
Параметр метода в данном случае я типизировал как Set, чтобы исключить наличие дублей при вызове метода. Однако вы можете типизировать такой параметр и более общим интерфейсом Collection. В любом случае сгенерится следующий sql:
При этом даже если мы передадим пустую коллекцию, ошибки не будет и метод ожидаемо вернёт пустой список в качестве результата.
Инверсия фильтрации
Если мы хотим выбрать все группы, кроме какой-то определённой, мы можем применить инверсию, добавив Not к имени метода. По смыслу это означает «не равно»:
Фильтрация по null
Поскольку значение поля playersCount может быть не определено, нам может потребоваться выбрать только те группы, для которых это значение указано. Нам поможет конструкция IsNotNull.
Если же нам нужно выбрать только группы, у которых количество участников не определено, просто убираем Not:
На уровне sql также будет использовать конструкция is null/is not null.
Поиск по подстроке
Давайте теперь найдём все группы, начинающиеся с определённой буквы. Воспользуемся конструкцией StartingWith:
Если подстроку нужно искать не в начале, а в середине имени, тогда нам поможет Containing:
Если же нужно искать по окончанию имени, тогда будем использовать EndingWith
На уровне sql во всех трёх случаях будет использоваться выражение like. При этом к самой подстроке добавлять символ процента, в отличие от sql, не требуется.
Фильтрация по диапазонам чисел
Если мы хотим фильтровать группы не по точному значению количества участников, а по некоторым допустимым интервалам, то нам помогут greaterThan, lessThan и between.
Выберем все группы, в которых больше четырёх участников (т.е. 5 и более):
Сгенерится следующий sql:
Теперь выберем все группы, в которых меньше четырёх участников (от 0 до 3-х):
Если же хотим включать граничное значение, просто добавим слово Equal:
А если мы хотим выбрать группы, у которых от 3 до 4 участников (включительно), то мы бы конечно могли назвать метод таким образом:
И он превратился бы в такой sql:
На уровне sql это также транслируется в between:
Фильтрация по диапазонам дат
// все группы, созданные до 1 января 1990
// bandDao.findByCreatedBefore(LocalDate.of(1990, 1, 1))
fun findByCreatedBefore(date: LocalDate): List
// все группы, созданные после 1 января 1990
// bandDao.findByCreatedAfter(LocalDate.of(1990, 1, 1))
fun findByCreatedAfter(date: LocalDate): List
На уровне sql эти методы будут генерить точно такой же sql, как и в предыдущем разделе:
В БД будет выполнен следующий запрос:
Можно также сортировать в обратном порядке, т.е. от «Я» до «А». Просто добавим слово Desc в конце:
Постраничный вывод
В случае больших списков встаёт также вопрос и в разбиении результата на страницы для удобства отображения (т.н. «пагинация»). То есть вы можете указать порядковый номер страницы (начиная с нуля) и количество записей на странице и в результате получите фрагмент списка. Spring Data позволяет сделать это путём добавления в метод всего одного параметра типа Pageable. При вызове метода указания номера страницы и размер странице делается с помощью PageReqest.
На уровне sql для postgres постраничный вывод достигается путём использования limit и offset:
Обратите внимание, что для постраничного вывода необходимо использовать сортировку, иначе один конкретный элемент при выполнении двух одинаковых запросов может оказываться то на одной странице, то на другой, т.е. сортировка по факту окажется случайной.
Если вам требуется кроме самих элементов данной страницы также дополнительная мета-информация, вроде общего количества записей, просто замените List на Page в типе возвращаемого значения:
Объект Page содержит несколько полезных полей:
Явное указание запросов
Если следовать рассмотренным выше соглашениям об именовании методов по каким-то причинам невозможно, например, название становится слишком длинным, вы можете указать JPA-запрос в явном виде с помощью аннотации @Query. От sql-запроса jpa-запрос отличается тем, что вы оперируете не полями таблицы, а полями объекта, и выборку вы также делаете как бы из объекта. Например, аналог фильтрации группы по имени, рассмотренной выше, будет выглядеть так:
Алиас b, который используется в этом запросе, может быть любым. Он определяется сразу после Band. Если вы используете явное указание запроса через @Query, то само имя метода вы можете выбрать произвольно, исходя из своих потребностей. В данном случае следовать соглашениям об именовании уже не требуется.
Если нам нужно фильтровать по двум полям, то просто добавляем в метод ещё один параметр и добавляем условие в запрос. При этом порядок следования параметров в сигнатуре метода может быть произвольным. Главное, чтобы его имя совпадало с именем параметра в jpa-запросе.
Однако если вам потребуется изменить имя параметра в сигнатуре метода, это также возможно. Просто укажите через аннотацию @Param имя этого параметра в jpa-запросе:
Как видите, Spring Data при помощи аннотаций поддаётся довольно гибкой настройке. Однако в какой-то момент таких аннотаций может стать слишком много и преимущества декларативного написания запросов могут быть потеряны.
Выводы
Spring Data JPA предоставляет широкие возможности по кастомизации запросов к БД благодаря соглашениям об именовании. Декларативный способ объявления запросов позволяет абстрагироваться от особенностей конкретной СУБД, что делает ваше приложении более гибким. К недостаткам такого подхода можно отнести порой слишком длинные имена методов. В таком случае бывает удобнее явно указать запрос через аннотацию @Query и дать методу более краткое название.
Параметр набора запросов JPA и Hibernate – Руководство пользователя
Узнайте, как метод JPA и Hibernate Query setParameter работает для базовых свойств сущностей и пользовательских типов столбцов, таких как JSON или МАССИВ.
Вступление
В этой статье я собираюсь показать вам, как метод setParameter запроса JPA работает для основных атрибутов сущностей и как вы можете использовать его при работе с пользовательскими типами гибернации.
Если вы когда-нибудь натыкались на проблему PostgreSQL “столбец имеет тип jsonb, но выражение имеет тип bytea” и не знали, как ее исправить, то вам определенно следует прочитать эту статью.
Модель предметной области
Давайте предположим, что наше приложение использует следующую Книгу сущность:
Обзор книги – это простой POJO (обычный старый объект Java) использование API в стиле Fluent :
Свойства книги также являются типом POJO:
Сущность Книга сопоставляет Список из Рецензии на книгу и Свойства книги атрибуты типам столбцов JSON:
Мы также собираемся сохранить одну Книгу сущность, которая выглядит следующим образом:
Метод набора параметров запроса JPA
Интерфейс JPA Query позволяет задавать значения параметров привязки JDBC с помощью нескольких перегруженных setParameter методов.
При выполнении вышеупомянутого JPQL-запроса Hibernate генерирует следующий SQL-запрос:
Привязка списка с помощью метода setParameter запроса JPA
Например, вы можете отфильтровать объекты Книга по их издателю :
И, Hibernate собирается выполнить следующий SQL-запрос:
Метод набора параметров запроса JPA и собственные SQL-запросы
При выполнении собственного SQL-запроса Hibernate больше не знает связанный тип столбца. Для основных типов, которые охватываются типами JDBC ` интерфейс, Hibernate управляет привязкой значений параметров, так как он знает, как обрабатывать основные свойства.
Проблема возникает, когда вы используете пользовательские типы, такие как JSON, МАССИВ или типы столбцов перечисления для конкретной базы данных.
Исправление проблемы “проверка столбцов имеет тип jsonb, но выражение имеет тип записи”
Вы получите следующее сообщение об ошибке:
Исправить это очень просто. Нам просто нужно развернуть JPA Запрос в режим гибернации org.hibernate.запрос. Запросите и вызовите setParameter метод, который принимает режим гибернации Тип экземпляр:
И теперь Hibernate собирается связать отзывы столбец с использованием типа Json
Исправление проблемы “проверка столбцов имеет тип jsonb, но выражение имеет тип bytea”
Давайте предположим, что мы хотим задать свойства столбец, использующий следующую инструкцию обновления SQL:
Мы получаем следующее сообщение об ошибке PostgreSQL:
Чтобы исправить это, мы должны установить тип Json явно, используя параметр set для режима гибернации Запрос метод:
И теперь инструкция SQL UPDATE успешно выполняется:
Вывод
JPA Заданный параметр Метод запроса очень полезен для базовых свойств сущностей, которые могут быть сопоставлены с использованием типов ORM Hibernate по умолчанию.
Spring Data JPA
В статье опишу использование Spring Data.
Spring Data — дополнительный удобный механизм для взаимодействия с сущностями базы данных, организации их в репозитории, извлечение данных, изменение, в каких то случаях для этого будет достаточно объявить интерфейс и метод в нем, без имплементации.
1. Spring Repository
Основное понятие в Spring Data — это репозиторий. Это несколько интерфейсов которые используют JPA Entity для взаимодействия с ней. Так например интерфейс
public interface CrudRepository extends Repository
обеспечивает основные операции по поиску, сохранения, удалению данных (CRUD операции)
Есть и другие абстракции, например PagingAndSortingRepository.
Т.е. если того перечня что предоставляет интерфейс достаточно для взаимодействия с сущностью, то можно прямо расширить базовый интерфейс для своей сущности, дополнить его своими методами запросов и выполнять операции. Сейчас я покажу коротко те шаги что нужны для самого простого случая (не отвлекаясь пока на конфигурации, ORM, базу данных).
1. Создаем сущность
2. Наследоваться от одного из интерфейсов Spring Data, например от CrudRepository
3. Использовать в клиенте (сервисе) новый интерфейс для операций с данными
Здесь я воспользовался готовым методом findById. Т.е. вот так легко и быстро, без имплементации, получим готовый перечень операций из CrudRepository:
Понятно что этого перечня, скорее всего не хватит для взаимодействия с сущностью, и тут можно расширить свой интерфейс дополнительными методами запросов.
2. Методы запросов из имени метода
Запросы к сущности можно строить прямо из имени метода. Для этого используется механизм префиксов find…By, read…By, query…By, count…By, и get…By, далее от префикса метода начинает разбор остальной части. Вводное предложение может содержать дополнительные выражения, например, Distinct. Далее первый By действует как разделитель, чтобы указать начало фактических критериев. Можно определить условия для свойств сущностей и объединить их с помощью And и Or. Примеры
В документации определен весь перечень, и правила написания метода. В качестве результата могут быть сущность T, Optional, List, Stream. В среде разработки, например в Idea, есть подсказка для написания методов запросов.
Достаточно только определить подобным образом метод, без имплементации и Spring подготовит запрос к сущности.
3. Конфигурация и настройка
Весь проект доступен на github
github DemoSpringData
Здесь лишь коснусь некоторых особенностей.
В context.xml определенны бины transactionManager, dataSource и entityManagerFactory. Важно указать в нем также
путь где определены репозитории.
EntityManagerFactory настроен на работу с Hibernate ORM, а он в свою очередь с БД Oracle XE, тут возможны и другие варианты, в context.xml все это видно. В pom файле есть все зависимости.
4. Специальная обработка параметров
В методах запросов, в их параметрах можно использовать специальные параметры Pageable, Sort, а также ограничения Top и First.
5. Пользовательские реализации для репозитория
Предположим что в репозиторие нужен метод, который не получается описать именем метода, тогда можно реализовать с помощью своего интерфейса и класса его имплементирующего. В примере ниже добавлю в репозиторий метод получения сотрудников с максимальной оплатой труда.
Имплементирую интерфейс. С помощью HQL (SQL) получаю сотрудников с максимальной оплатой, возможны и другие реализации.
А также расширяю Crud Repository Employees еще и CustomizedEmployees.
Здесь есть одна важная особенность. Класс имплементирующий интерфейс, должен заканчиваться (postfix) на Impl, или в конфигурации надо поставить свой postfix
Проверяем работу этого метода через репозиторий
Другой случай, когда надо изменить поведение уже существующего метода в интерфейсе Spring, например delete в CrudRepository, мне надо что бы вместо удаления из БД, выставлялся признак удаления. Техника точно такая же. Ниже пример:
Теперь если в employeesCrudRepository вызвать delete, то объект будет только помечен как удаленный.
6. Пользовательский Базовый Репозиторий
В предыдущем примере я показал как переопределить delete в Crud репозитории сущности, но если это надо делать для всех сущностей проекта, делать для каждой свой интерфейс как то не очень. тогда в Spring data можно настроить свой базовый репозиторий. Для этого:
Объявляется интерфейс и в нем метод для переопределения (или общий для всех сущностей проекта). Тут я еще для всех своих сущностей ввел свой интерфейс BaseEntity (это не обязательно), для удобства вызова общих методов, его методы совпадают с методами сущности.
В конфигурации надо указать этот базовый репозиторий, он будет общий для всех репозиториев проекта
Теперь Employees Repository (и др.) надо расширять от BaseRepository и уже его использовать в клиенте.
Проверяю работу EmployeesBaseRepository
Теперь также как и ранее, объект будет помечен как удаленный, и это будет выполняться для всех сущностей, которые расширяют интерфейс BaseRepository. В примере был применен метод поиска — Query by Example (QBE), я не буду здесь его описывать, из примера видно что он делает, просто и удобно.
7. Методы запросов — Query
Ранее я писал, что если нужен специфичный метод или его реализация, которую нельзя описать через имя метода, то это можно сделать через некоторый Customized интерфейс ( CustomizedEmployees) и сделать реализацию вычисления. А можно пойти другим путем, через указание запроса (HQL или SQL), как вычислить данную функцию.
Для моего примера c getEmployeesMaxSalary, этот вариант реализации даже проще. Я еще усложню его входным параметром salary. Т.е. достаточно объявить в интерфейсе метод и запрос вычисления.
Упомяну лишь еще, что запросы могут быть и модифицирующие, для этого к ним добавляется еще аннотация @Modifying
Так например в моем гипотетическом примере, когда мне надо для всех сущностей иметь признак “удален», я сделаю базовый интерфейс с методом получения списка объектов с признаком «удален» или «активный»
Далее все репозитории для сущностей можно расширять от него. Интерфейсы которые не являются репозиториями, но находятся в «base-package» папке конфигурации, надо аннотировать @NoRepositoryBean.
Теперь когда будет выполняться запрос, в тело запроса будет подставлено имя сущности T для конкретного репозитория который будет расширять ParentEntityRepository, в данном случае Employees.