Рубрики
Без рубрики

Использование запросов критериев в приложении данных Spring

Краткое руководство по использованию запросов критериев JPA с использованием Spring Data JPA.

Автор оригинала: 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

    List findBooksByAuthorNameAndTitle(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 {
    List findBooksByAuthorNameAndTitle(String authorName, String title);
}

И интерфейс @Репозиторий :

interface BookRepository extends JpaRepository, BookRepositoryCustom {}

Кроме того, мы должны изменить наш предыдущий класс DAO, чтобы реализовать Пользовательский репозиторий книг и переименовать его в BookRepositoryImpl :

@Repository
class BookRepositoryImpl implements BookRepositoryCustom {

    EntityManager em;

    // constructor

    @Override
    List findBooksByAuthorNameAndTitle(String authorName, String title) {
        // implementation
    }

}

Когда мы объявляем BookRepository в качестве зависимости, Spring находит BookRepositoryImpl и использует его при вызове пользовательских методов.

Допустим, мы хотим выбрать, какие предикаты использовать в нашем запросе. Например, когда мы не хотим находить книги по автору и названию, нам нужно только, чтобы автор соответствовал.

Существует несколько способов сделать это, например, применяя предикат только в том случае, если переданный аргумент не является null :

@Override
List findBooksByAuthorNameAndTitle(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 Specification hasAuthor(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 .