Используйте критерии критериев в приложении данных Spring
1. Вступление
Spring Data JPA предоставляет множество способов работы с объектами, включаяquery methods и пользовательскийJPQL queries. Однако иногда нам нужен более программный подход: например,Criteria API илиQueryDSL.
Criteria API offers a programmatic way to create typed queries, что помогает нам избежать синтаксических ошибок. Более того, когда мы используем его с Metamodel API, он выполняет проверку во время компиляции, использовали ли мы правильные имена и типы полей.
Однако, у этого есть свои недостатки: мы должны написать многословную логику, раздутую с помощью стандартного кода.
В этом руководстве мы увидим, как реализовать нашу настраиваемую логику DAO с помощью критериальных запросов и как Spring помогает сократить шаблонный код.
2. Образец заявки
Для простоты в примерах мы реализуем один и тот же запрос несколькими способами: поиск книг по имени автора и названию, содержащемуString.
СущностьBook для этого выглядит так:
@Entity
class Book {
@Id
Long id;
String title;
String author;
// getters and setters
}
Поскольку мы хотим, чтобы все было просто, мы не используем Metamodel API в этом руководстве.
3. @Repository Класс
Как известно, в модели компонента Springwe should place our data access logic in @Repository beans. Конечно, эта логика может использовать любую реализацию, например, Criteria 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();
}
}
Приведенный выше код соответствует стандартному рабочему процессу Criteria API:
-
Сначала мы получаем ссылкуCriteriaBuilder, которую мы можем использовать для создания различных частей запроса.
-
ИспользуяCriteriaBuilder, мы создаемCriteriaQuery<Book>, который описывает, что мы хотим сделать в запросе. Кроме того, он объявляет тип строки в результате
-
С помощьюCriteriaQuery<Book> мы объявляем начальную точку запроса (сущностьBook) и сохраняем ее в переменнойbook для дальнейшего использования.
-
Затем с помощьюCriteriaBuilder мы создаем предикаты для нашей сущностиBook. Обратите внимание, что эти предикаты пока не действуют.
-
Мы применяем оба предиката к нашемуCriteriaQuery..CriteriaQuery.where(Predicate…) объединяет свои аргументы в логическийand. Это тот момент, когда мы связываем эти предикаты с запросом
-
После этого мы создаем экземплярTypedQuery<Book> из нашегоCriteriaQuery
-
Наконец, мы возвращаем все подходящие объектыBook
Обратите внимание: поскольку мы отметили класс DAO с помощью@Repository, тоSpring enables exception translation для этого класса.
4. Расширение хранилища с помощью пользовательских методов
Наличиеautomatic custom queries - это мощная функция Spring Data. Однако иногда нам нужна более сложная логика, которую невозможно создать с помощью автоматических методов запроса.
Мы можем реализовать эти запросы в отдельных классах DAO (как в предыдущем разделе).
Кроме того,if we want a @Repository interface to have a method with a custom implementation, we can use composable repositories.
Пользовательский интерфейс выглядит так:
interface BookRepositoryCustom {
List findBooksByAuthorNameAndTitle(String authorName, String title);
}
И интерфейс@Repository:
interface BookRepository extends JpaRepository, BookRepositoryCustom {}
Кроме того, мы должны изменить наш предыдущий класс DAO для реализацииBookRepositoryCustom и переименовать его в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();
}
Однако этот подходmakes the code hard to maintain, особенно если у нас много предикатов и мы хотим сделать их необязательными.
Было бы практическим решением воплотить эти предикаты во внешний вид. С помощью спецификаций JPA мы можем сделать именно это; и даже больше.
5. Использование спецификаций JPA
Spring Data представила интерфейсorg.springframework.data.jpa.domain.Specification для инкапсуляции одного предиката:
interface Specification {
Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb);
}
Мы можем предоставить методы для создания экземпляровSpecification:
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<T>:
interface BookRepository extends JpaRepository, JpaSpecificationExecutor {}
Это интерфейсdeclares handy methods to work with specifications. Например, теперь мы можем найти все экземплярыBook с указанным автором с помощью этого однострочника:
bookRepository.findAll(hasAuthor(author));
К сожалению, у нас нет методов, которым можно было бы передать несколько аргументовSpecification. Скорее мы получаем служебные методы в интерфейсеorg.springframework.data.jpa.domain.Specification.
Например, объединение двух экземпляровSpecification с логическим и:
bookRepository.findAll(where(hasAuthor(author)).and(titleContains(title)));
В приведенном выше примереwhere() - статический метод классаSpecification.
Таким образом мы можем сделать наши запросы модульными. Кроме того, нам не нужно было писать шаблон Criteria API: Spring предоставил его для нас.
Обратите внимание: это не означает, что нам больше не нужно писать шаблон критериев; этот подход способен обрабатывать только рабочий процесс, который мы видели: выбор сущностей, которые удовлетворяют предоставленным условиям.
Запрос может иметь множество структур, которые он не поддерживает, например группирование, возврат другого класса, из которого мы выбираем, или подзапросы.
6. Заключение
В этом руководстве мы увидели три способа использования критериальных запросов в нашем приложении Spring:
-
создание класса DAO - самый простой и гибкий способ
-
расширение интерфейса@Repository для полной интеграции с автоматическими запросами
-
использование предикатов в экземплярахSpecification, чтобы сделать простые случаи более понятными и менее подробными
Как обычно доступны примерыover on GitHub.