Spring Framework и работа с базами данных: Spring Data JPA

В предыдущих постах о работе с базами данных в Spring Framework я поверхностно описал использование JdbcTemplate и NamedParameterJdbcTemplate. Пользоваться данными инструментами безусловно удобно, но у них есть определённые ограничения, среди которых:

  • Зависимость SQL-запросов от конкретной СУБД
  • Необходимость в самостоятельной реализации преобразования данных из БД в экземпляры классов-сущностей
  • Увеличение и усложнение кода при появлении новых таблиц и столбцов в таблицах

В стеке Spring существует проект Spring Data, реализующий большую часть тривиальных задач и упрощающий работу с источниками данных. В качестве источников данных могут использоваться как стандартные реляционные базы данных, так и NoSQL-хранилища вроде MongoDB или Redis.

В мире Java EE стандартом дефакто для работы с базами данных является JPA (Java Persistence API). Spring Data JPA, будучи частью Spring Data, реализует взаимодействие с реляционными СУБД, основываясь на JPA.

Настройка проекта

Для использования Spring Data JPA потребуются следующие зависимости:

Если используется Spring Boot:

Если Spring Boot не используется:

Классы-сущности (Entities)

Классы-сущности дополняются стандартными для JPA аннотациями. В качестве примера возьмём класс Person, использованный уже ранее:

Я добавил в этот класс несколько JPA-аннотаций:

  • @Entity указывает, что данный класс должен быть спроецирован в БД
  • @Id указывает, что свойство id является первичным ключом
  • @Column позволяет произвести более тонкую настройку проецирования свойства email класса в колонку таблицы БД

Репозитории

Главными компонентами для взаимодействий с БД в Spring Data являются репозитории. Каждый репозиторий работает со своим классом-сущностью. Самым простым способом создания репозитория является создание интерфейса с наследованием от CrudRepository, как показано в примере:

Первый тип (Person), переданный в дженерик CrudRepository — класс-сущность, с которым должен работать данный репозиторий, второй (String) — тип первичного ключа.

Никаких дополнительных аннотаций для работы данного репозитория не требуется, более того, не требуется даже реализация. При инициализации контекста приложения Spring Data найдёт данный интерфейс и самостоятельно сгенерирует компонент (bean), реализующий данный интерфейс.

Существует несколько типов репозиториев, различающихся по набору возможностей:

  • CrudRepository, указанный в примере, предоставляет базовый набор методов для доступа к данным. Данный интерфейс является универсальным и может быть использован не только в связке с JPA.
  • Repository — базовый тип репозиториев, не содержит каких-либо методов, так же является универсальным.
  • PagingAndSortingRepository — универсальный интерфейс, расширяющий CrudRepository и добавляющий поддержку пейджинации и сортировки.
  • JpaRepository — репозиторий, добавляющий возможности, специфичные для JPA.

И две реализации, которые можно использовать для каких-нибудь нетривиальных задач, вроде написания реализации какого-нибудь метода с нестандартным поведением:

  • QueryDslJpaRepository — реализация JpaRepository для взаимодействия с QueryDsl.
  • SimpleJpaRepository — простая реализация JpaRepository.

Запросы

Стандартный набор методов для работы с данными, предоставляемый Spring Data, достаточно лаконичен. Мы можем найти все записи класса Person или найти запись по первичному ключу. А что, если нам требуется найти запись Person по email? Данную задачу можно решить несколькими способами.

Аннотация @NamedQuery в классе-сущности

Стандартное для JPA решение — описать именованный запрос в классе-сущности:

Останется только в репозитории добавить метод queryByEmail:

Главный минус такого подхода — большое количество @NamedQuery в классе-сущности.

Аннотация @Query в репозитории

Альтернативный вариант — описывать запросы в репозитории:

Выглядит значительно удобнее, так как именованные запросы в данном случае привязаны к конкретным методам.

Волшебные методы

Ещё один способ создания запросов, реализованный в Spring Data — волшебные методы. Если в репозитории существуют методы, поведение которых не описано именованными запросами, а так же имена которых начинаются с findBy, countBy или deleteBy, то Spring Data автоматически преобразует их именованные запросы. Например, следующий метод будет преобразован в запрос «from Person p where name like ?»:

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

Тестирование

Тестировать необходимо самостоятельно описанные методы,  в то время как стандартные методы репозиториев в тестировании не нуждаются (логика их работы протестирована уже разработчиками Spring Data). Соответственно, в данном случае применимо только интеграционное тестирование. Короткий пример тестирования нашего репозитория:

Полезные ссылки