1. Обзор
В этом кратком руководстве мы обсудим более продвинутую функцию Spring Data JPA Specifications, которая позволяет нам объединять таблицы при создании запроса.
Давайте начнем с краткого обзора спецификаций JPA и их использования.
2. Спецификации JPA
Spring Data JPA представила интерфейс Specification
, позволяющий нам создавать динамические запросы с повторно используемыми компонентами.
Для примеров кода в этой статье мы будем использовать классы Author
и Book :
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
@OneToMany(cascade = CascadeType.ALL)
private List<Book> books;
// getters and setters
}
Чтобы создать динамический запрос для сущности Author
, мы можем использовать реализации интерфейса Specification :
public class AuthorSpecifications {
public static Specification<Author> hasFirstNameLike(String name) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.like(root.<String>get("firstName"), "%" + name + "%");
}
public static Specification<Author> hasLastName(String name) {
return (root, query, cb) ->
cb.equal(root.<String>get("lastName"), name);
}
}
Наконец, нам понадобится AuthorRepository
для расширения JpaSpecificationExecutor
:
@Repository
public interface AuthorsRepository extends JpaRepository<Author, Long>, JpaSpecificationExecutor<Author> {
}
В результате теперь мы можем объединить две спецификации и создать с ними запросы:
@Test
public void whenFindByLastNameAndFirstNameLike_thenOneAuthorIsReturned() {
Specification<Author> specification = hasLastName("Martin")
.and(hasFirstNameLike("Robert"));
List<Author> authors = repository.findAll(specification);
assertThat(authors).hasSize(1);
}
3. Объединение таблиц со спецификациями JPA
Из нашей модели данных мы можем наблюдать, что сущность Author
имеет отношение «один ко многим» с сущностью Book :
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
// getters and setters
}
API Criteria Query позволяет нам соединить две таблицы при создании спецификации
.
В результате мы сможем включать поля из сущности Book
в наши запросы:
public static Specification<Author> hasBookWithTitle(String bookTitle) {
return (root, query, criteriaBuilder) -> {
Join<Book, Author> authorsBook = root.join("books");
return criteriaBuilder.equal(authorsBook.get("title"), bookTitle);
};
}
Теперь давайте объединим эту новую спецификацию с созданными ранее:
@Test
public void whenSearchingByBookTitleAndAuthorName_thenOneAuthorIsReturned() {
Specification<Author> specification = hasLastName("Martin")
.and(hasBookWithTitle("Clean Code"));
List<Author> authors = repository.findAll(specification);
assertThat(authors).hasSize(1);
}
Наконец, давайте взглянем на сгенерированный SQL и посмотрим на предложение JOIN
:
select
author0_.id as id1_1_,
author0_.first_name as first_na2_1_,
author0_.last_name as last_nam3_1_
from
author author0_
inner join author_books books1_ on author0_.id = books1_.author_id
inner join book book2_ on books1_.books_id = book2_.id
where
author0_.last_name = ?
and book2_.title = ?
4. Вывод
В этой статье мы узнали, как использовать спецификации JPA для запроса таблицы на основе одной из связанных с ней сущностей.
Спецификации Spring Data JPA обеспечивают плавный, динамичный и многоразовый способ создания запросов.
Как обычно, исходный код доступен на GitHub .