Автор оригинала: Attila Fejér.
1. введение
Spring Data JPA предоставляет множество способов работы с сущностями, включая методы запросов и пользовательские запросы JPQL . Однако иногда нам нужен более программный подход: например, API критериев или QueryDSL .
API критериев предлагает программный способ создания типизированных запросов , который помогает нам избежать синтаксических ошибок. Более того, когда мы используем его с API метамодели, он проверяет во время компиляции, правильно ли мы использовали имена и типы полей.
Однако у этого есть свои недостатки: нам приходится писать подробную логику, раздутую шаблонным кодом.
В этом уроке мы увидим, как мы можем реализовать нашу пользовательскую логику DAO с помощью запросов критериев и как Spring помогает сократить стандартный код.
2. Пример Приложения
Для простоты в примерах мы будем реализовывать один и тот же запрос несколькими способами: находить книги по имени автора и названию, содержащему строку |.
Сущность Книга для этого выглядит следующим образом:
@Entity class Book { @Id Long id; String title; String author; // getters and setters }
Поскольку мы хотим, чтобы все было просто, мы не используем API метамодели в этом руководстве.
3. Класс @Репозитория
Как мы знаем, в компонентной модели Spring мы должны поместить нашу логику доступа к данным в @Репозиторий компоненты . Конечно, эта логика может использовать любую реализацию, например, API критериев.
Для этого нам нужен только экземпляр EntityManager , который мы можем подключить автоматически:
@Repository class BookDao { EntityManager em; // constructor ListfindBooksByAuthorNameAndTitle(String authorName, String title) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Book.class); Root book = cq.from(Book.class); Predicate authorNamePredicate = cb.equal(book.get("author"), authorName); Predicate titlePredicate = cb.like(book.get("title"), "%" + title + "%"); cq.where(authorNamePredicate, titlePredicate); TypedQuery query = em.createQuery(cq); return query.getResultList(); } }
Приведенный выше код соответствует стандартному рабочему процессу API критериев:
- Во-первых, мы получаем Конструктор критериев ссылку, которую мы можем использовать для создания различных частей запроса
- Используя Конструктор критериев , мы создаем Запрос критериев<Книга> , в котором описывается, что мы хотим сделать в запросе. Кроме того, он объявляет тип строки в результате
- С помощью CriteriaQuery<Книга> мы объявляем начальную точку запроса ( Книга сущность) и сохраняем ее в переменной книга для последующего использования
- Затем с помощью Построителя критериев мы создаем предикаты для нашей Книги сущности. Обратите внимание, что эти предикаты пока не имеют никакого эффекта
- Мы применяем оба предиката к нашему запросу Критерии. Критерий.где(Предикат…) объединяет свои аргументы в логическую и . Это тот момент, когда мы привязываем эти предикаты к запросу
- После этого мы создадим Запрос типа<Книга> экземпляр из нашего CriteriaQuery
- Наконец, мы возвращаем все совпадающие Книги сущности
Обратите внимание, что, поскольку мы отметили класс DAO с @Репозиторием , Spring позволяет перевод исключений для этого класса.
4. Расширение репозитория с помощью пользовательских методов
Наличие автоматических пользовательских запросов-это мощная функция данных Spring. Однако иногда нам нужна более сложная логика, которую мы не можем создать с помощью автоматических методов запроса.
Мы можем реализовать эти запросы в отдельных классах DAO (как в предыдущем разделе).
Кроме того, если мы хотим, чтобы @Репозиторий интерфейс имел метод с пользовательской реализацией, мы можем использовать составные репозитории .
Пользовательский интерфейс выглядит следующим образом:
interface BookRepositoryCustom { ListfindBooksByAuthorNameAndTitle(String authorName, String title); }
И интерфейс @Репозиторий :
interface BookRepository extends JpaRepository, BookRepositoryCustom {}
Кроме того, мы должны изменить наш предыдущий класс DAO, чтобы реализовать Пользовательский репозиторий книг и переименовать его в BookRepositoryImpl :
@Repository class BookRepositoryImpl implements BookRepositoryCustom { EntityManager em; // constructor @Override ListfindBooksByAuthorNameAndTitle(String authorName, String title) { // implementation } }
Когда мы объявляем BookRepository в качестве зависимости, Spring находит BookRepositoryImpl и использует его при вызове пользовательских методов.
Допустим, мы хотим выбрать, какие предикаты использовать в нашем запросе. Например, когда мы не хотим находить книги по автору и названию, нам нужно только, чтобы автор соответствовал.
Существует несколько способов сделать это, например, применяя предикат только в том случае, если переданный аргумент не является null :
@Override ListfindBooksByAuthorNameAndTitle(String authorName, String title) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Book.class); Root book = cq.from(Book.class); List predicates = new ArrayList<>(); if (authorName != null) { predicates.add(cb.equal(book.get("author"), authorName)); } if (title != null) { predicates.add(cb.like(book.get("title"), "%" + title + "%")); } cq.where(predicates.toArray(new Predicate[0])); return em.createQuery(cq).getResultList(); }
Однако такой подход затрудняет поддержку кода , особенно если у нас много предикатов и мы хотим сделать их необязательными.
Было бы практичным решением экстернализировать эти предикаты. С помощью спецификаций JPA мы можем сделать именно это; и даже больше.
5. Использование спецификаций JPA
Spring Data представила домен org.springframework.data.jpa.домен.Спецификация интерфейс для инкапсуляции одного предиката:
interface Specification{ Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb); }
Мы можем предоставить методы для создания Спецификации экземпляров:
static SpecificationhasAuthor(String author) { return (book, cq, cb) -> cb.equal(book.get("author"), author); } static Specification titleContains(String title) { return (book, cq, cb) -> cb.like(book.get("title"), "%" + title + "%"); }
Чтобы использовать их, нам нужно расширить наш репозиторий org.springframework.data.jpa.repository.JpaSpecificationExecutor :
interface BookRepository extends JpaRepository, JpaSpecificationExecutor {}
Этот интерфейс объявляет удобные методы для работы со спецификациями . Например, теперь мы можем найти все Книги экземпляры с указанным автором с помощью этой однострочной:
bookRepository.findAll(hasAuthor(author));
К сожалению, мы не получаем никаких методов, которым мы могли бы передать несколько аргументов Specification . Скорее, мы получаем служебные методы в домене org.springframework.data.jpa..Спецификация интерфейс.
Например, объединение двух экземпляров Specification с логическими и:
bookRepository.findAll(where(hasAuthor(author)).and(titleContains(title)));
В приведенном выше примере где() является статическим методом класса Спецификации .
Таким образом, мы можем сделать ваши запросы модульными. Кроме того, нам не нужно было писать шаблон API критериев: Весна предоставила его нам.
Обратите внимание, что это не означает, что нам больше не придется писать стандартные критерии; этот подход способен обрабатывать только рабочий процесс, который мы видели: выбор объектов, удовлетворяющих предоставленным условиям.
Запрос может иметь множество структур, которые он не поддерживает, например, группировку, возврат другого класса, из которого мы выбираем, или подзапросы.
6. Заключение
В этом уроке мы рассмотрели три способа использования запросов критериев в нашем приложении Spring:
- создание класса DAO - это самый простой и гибкий способ
- расширение интерфейса @Репозитория для плавной интеграции с автоматическими запросами
- использование предикатов в Спецификации экземплярах, чтобы сделать простые случаи более чистыми и менее подробными
Как обычно, примеры доступны на GitHub .