Что такое spring data
Spring Data на примере JPA
Введение
Spring Data позволяет легче создавать Spring-управляемые приложения которые используют новые способы доступа к данным, например нереляционные базы данных, map-reduce фреймворки, cloud сервисы, а так же уже хорошо улучшенную поддердку реляционных баз данных.
В этой статье будет рассмотрен один из под-проектов Spring Data — JPA
Что может Spring Data — JPA
Для чего вам может понадобиться Spring Data — JPA
Я бы ответил так — если вам нужно быстро в проекте создать Repository слой базируемый на JPA, предназначенный в основном для CRUD операций, и вы не хотите создавать абстрактные дао, интерфейсы их реализации, то Spring Data — JPA это хороший выбор.
С чего начать
Будем считать у вас уже есть maven проект с подключенным Spring, базой данных, настроенным EntityManager-ом.
1. Добавьте артефакт со Spring Data — JPA
2. В ваш applicationContext.xml нужно добавить путь где будут храниться ваши Repository интерфейсы
3. Создать Entity и Repository интерфейс для него
4. Теперь вы можете использовать созданный интерфейс в вашем приложении
Наследовавшись от CrudRepository вы получили возможность вызывать такие методы как:
без необходимости реализовывать их имплементацию.
Работа с запросами, сортировкой, порционной загрузкой
Рассмотрим на примере: вам нужно сделать запрос, который выберет все Test записи, у которых поле «dummy» установленно в false, и записи отсортированны по полю «tries» в порядке ABC.
Для решения такой задачи вы можете выбрать один из нескольких вариантов:
Если с первым способом все предельно просто и это знакомый запрос, то второй способ заключается в том, чтобы составить имя метода, особым способом использую ключевые слова, такие как: «find», «order», имя переменных и тд. Разработчики Spring Data — JPA постарались учесть большинство возможных вариантов, которые могут вам понадобится.
Specification и CriteriaBuilder
Если вам нужно написать действительно сложный запрос для этого вы можете использовать Specification.
Пример в котором в зависимости от «retries» будут выбраны данные с разными значениями «dummy».
Следующий пример покажет как можно использовать созданный Specification для фильтра всех данных.
Расширим ваш интерфейс при помощи JpaSpecificationExecutor
и вызовем метод findAll передав ему созданную Specification
Spring Framework и работа с базами данных: Spring Data JPA
В предыдущих постах о работе с базами данных в Spring Framework я поверхностно описал использование JdbcTemplate и NamedParameterJdbcTemplate. Пользоваться данными инструментами безусловно удобно, но у них есть определённые ограничения, среди которых:
В стеке 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-аннотаций:
Репозитории
Главными компонентами для взаимодействий с БД в Spring Data являются репозитории. Каждый репозиторий работает со своим классом-сущностью. Самым простым способом создания репозитория является создание интерфейса с наследованием от CrudRepository, как показано в примере:
Первый тип (Person), переданный в дженерик CrudRepository — класс-сущность, с которым должен работать данный репозиторий, второй (String) — тип первичного ключа.
Никаких дополнительных аннотаций для работы данного репозитория не требуется, более того, не требуется даже реализация. При инициализации контекста приложения Spring Data найдёт данный интерфейс и самостоятельно сгенерирует компонент (bean), реализующий данный интерфейс.
Существует несколько типов репозиториев, различающихся по набору возможностей:
И две реализации, которые можно использовать для каких-нибудь нетривиальных задач, вроде написания реализации какого-нибудь метода с нестандартным поведением:
Запросы
Стандартный набор методов для работы с данными, предоставляемый Spring Data, достаточно лаконичен. Мы можем найти все записи класса Person или найти запись по первичному ключу. А что, если нам требуется найти запись Person по email? Данную задачу можно решить несколькими способами.
Аннотация @NamedQuery в классе-сущности
Стандартное для JPA решение — описать именованный запрос в классе-сущности:
Spring Data JPA
В статье опишу использование Spring Data.
Spring Data — дополнительный удобный механизм для взаимодействия с сущностями базы данных, организации их в репозитории, извлечение данных, изменение, в каких то случаях для этого будет достаточно объявить интерфейс и метод в нем, без имплементации.
1. Spring Repository
Основное понятие в Spring Data — это репозиторий. Это несколько интерфейсов которые используют JPA Entity для взаимодействия с ней. Так например интерфейс
public interface CrudRepository extends Repository
обеспечивает основные операции по поиску, сохранения, удалению данных (CRUD операции)
Есть и другие абстракции, например PagingAndSortingRepository.
Т.е. если того перечня что предоставляет интерфейс достаточно для взаимодействия с сущностью, то можно прямо расширить базовый интерфейс для своей сущности, дополнить его своими методами запросов и выполнять операции. Сейчас я покажу коротко те шаги что нужны для самого простого случая (не отвлекаясь пока на конфигурации, ORM, базу данных).
1. Создаем сущность
2. Наследоваться от одного из интерфейсов Spring Data, например от CrudRepository
3. Использовать в клиенте (сервисе) новый интерфейс для операций с данными
Здесь я воспользовался готовым методом findById. Т.е. вот так легко и быстро, без имплементации, получим готовый перечень операций из CrudRepository:
Понятно что этого перечня, скорее всего не хватит для взаимодействия с сущностью, и тут можно расширить свой интерфейс дополнительными методами запросов.
2. Методы запросов из имени метода
Запросы к сущности можно строить прямо из имени метода. Для этого используется механизм префиксов find…By, read…By, query…By, count…By, и get…By, далее от префикса метода начинает разбор остальной части. Вводное предложение может содержать дополнительные выражения, например, Distinct. Далее первый By действует как разделитель, чтобы указать начало фактических критериев. Можно определить условия для свойств сущностей и объединить их с помощью And и Or. Примеры
В документации определен весь перечень, и правила написания метода. В качестве результата могут быть сущность T, Optional, List, Stream. В среде разработки, например в Idea, есть подсказка для написания методов запросов.
Достаточно только определить подобным образом метод, без имплементации и Spring подготовит запрос к сущности.
3. Конфигурация и настройка
Весь проект доступен на github
github DemoSpringData
Здесь лишь коснусь некоторых особенностей.
В context.xml определенны бины transactionManager, dataSource и entityManagerFactory. Важно указать в нем также
путь где определены репозитории.
EntityManagerFactory настроен на работу с Hibernate ORM, а он в свою очередь с БД Oracle XE, тут возможны и другие варианты, в context.xml все это видно. В pom файле есть все зависимости.
4. Специальная обработка параметров
В методах запросов, в их параметрах можно использовать специальные параметры Pageable, Sort, а также ограничения Top и First.
5. Пользовательские реализации для репозитория
Предположим что в репозиторие нужен метод, который не получается описать именем метода, тогда можно реализовать с помощью своего интерфейса и класса его имплементирующего. В примере ниже добавлю в репозиторий метод получения сотрудников с максимальной оплатой труда.
Имплементирую интерфейс. С помощью HQL (SQL) получаю сотрудников с максимальной оплатой, возможны и другие реализации.
А также расширяю Crud Repository Employees еще и CustomizedEmployees.
Здесь есть одна важная особенность. Класс имплементирующий интерфейс, должен заканчиваться (postfix) на Impl, или в конфигурации надо поставить свой postfix
Проверяем работу этого метода через репозиторий
Другой случай, когда надо изменить поведение уже существующего метода в интерфейсе Spring, например delete в CrudRepository, мне надо что бы вместо удаления из БД, выставлялся признак удаления. Техника точно такая же. Ниже пример:
Теперь если в employeesCrudRepository вызвать delete, то объект будет только помечен как удаленный.
6. Пользовательский Базовый Репозиторий
В предыдущем примере я показал как переопределить delete в Crud репозитории сущности, но если это надо делать для всех сущностей проекта, делать для каждой свой интерфейс как то не очень. тогда в Spring data можно настроить свой базовый репозиторий. Для этого:
Объявляется интерфейс и в нем метод для переопределения (или общий для всех сущностей проекта). Тут я еще для всех своих сущностей ввел свой интерфейс BaseEntity (это не обязательно), для удобства вызова общих методов, его методы совпадают с методами сущности.
В конфигурации надо указать этот базовый репозиторий, он будет общий для всех репозиториев проекта
Теперь Employees Repository (и др.) надо расширять от BaseRepository и уже его использовать в клиенте.
Проверяю работу EmployeesBaseRepository
Теперь также как и ранее, объект будет помечен как удаленный, и это будет выполняться для всех сущностей, которые расширяют интерфейс BaseRepository. В примере был применен метод поиска — Query by Example (QBE), я не буду здесь его описывать, из примера видно что он делает, просто и удобно.
7. Методы запросов — Query
Ранее я писал, что если нужен специфичный метод или его реализация, которую нельзя описать через имя метода, то это можно сделать через некоторый Customized интерфейс ( CustomizedEmployees) и сделать реализацию вычисления. А можно пойти другим путем, через указание запроса (HQL или SQL), как вычислить данную функцию.
Для моего примера c getEmployeesMaxSalary, этот вариант реализации даже проще. Я еще усложню его входным параметром salary. Т.е. достаточно объявить в интерфейсе метод и запрос вычисления.
Упомяну лишь еще, что запросы могут быть и модифицирующие, для этого к ним добавляется еще аннотация @Modifying
Так например в моем гипотетическом примере, когда мне надо для всех сущностей иметь признак “удален», я сделаю базовый интерфейс с методом получения списка объектов с признаком «удален» или «активный»
Далее все репозитории для сущностей можно расширять от него. Интерфейсы которые не являются репозиториями, но находятся в «base-package» папке конфигурации, надо аннотировать @NoRepositoryBean.
Теперь когда будет выполняться запрос, в тело запроса будет подставлено имя сущности T для конкретного репозитория который будет расширять ParentEntityRepository, в данном случае Employees.
Представляем Spring Data JDBC
В предстоящий релиз Spring Data под кодовым именем Lovelace мы собираемся включить новый модуль: Spring Data JDBC.
Идея Spring Data JDBC заключается в том, чтобы предоставить доступ к реляционным базам данных без использования всей сложности JPA.
JPA предлагает такие функции, как ленивая загрузка (lazy loading), кеширование и отслеживание изменений (dirty tracking). Не смотря на то, что эти фичи очень крутые, если они, конечно, вам действительно нужны, они могут сильно усложнить понимание логики доступа к данным.
Мехнизм ленивой загрузки может внезапно выполнить ресурсоемкие запросы, или и вовсе упасть с исключением. Кэширование может встать на вашем пути когда вы решите сравнить две версии entity, а вкупе с отслеживанием изменений помешает понять — в какой же момент реально выполнятся все операции с базой данных?
Spring Data JDBC фокусируется на гораздо более простой модели. Не будет кеширования, отслеживания изменений, или ленивой загрузки. Вместо этого, SQL запросы будут выполнены тогда и только тогда, когда вы вызваете метод репозитория. Возвращаемый результат будет полностью загружен в память после выполнения метода. Не будет и механизма «сесии» или прокси-объектов для entities. И все это должно сделать Spring Data JDBC более простым и понятным инструментов для доступа к данным.
Разумеется, такой упрощенный подход выливается в целый ряд ограничений, о которых мы поговорим в следующих постах. Грядущий релиз это самая первая версия библиотеки, у нас есть много планов и замыслов, которые мы хотим реализовать, но мы вынуждены их отложить, чтобы дать вам возможность начать пользоваться Spring Data JDBC как можно раньше.
Пример
Для начала, нам нужно определить entity:
Наконец, нужно сконфигурировать ApplicationContext чтобы реализация этого интерфейса была созданы автоматически:
Давайте разберем конфигурацию более детально.
Это все, что нужно, чтобы начать работать со Spring Data JDBC. Теперь напишем тест, чтобы посмотреть, как это все работает:
Аннотация @Query
Только со стандартными методами CRUD репозитория из класса CrudRepository далеко не уедешь. Мы намеренно решили отложить автоматическую генерацию запроса — популярную фичу Spring Data, когда SQL запрос генерится на основе имени метода — на будущие релизы. А на текущий момент, вы можете просто использовать уже знакомую аннотацию @Query чтобы точно указать, какой SQL запрос должен быть выполнен.
Давайте напишем тест, чтобы посмотреть, как работает наш новый метод.
В завершение
Конечно, о Spring Data JDBC можно рассказать гораздо больше, и мы обязательно расскажем в следующих статьях.
А пока, вы можете изучить код примеров, документацию, и, разумеется, исходный код. Если у вас появятся вопросы — не стесняйтесь их задавать на StackOverflow. А если вы нашли баг или хотите запросить новую фичу — пожалуйста, создайте тикет.
Введение в Spring Data JDBC
Для будущих студентов курса «Java Developer. Professional» подготовили перевод полезного материала.
Spring Data JDBC был анонсирован в 2018 году. Целью было предоставить разработчикам более простую альтернативу JPA, продолжая при этом следовать принципам Spring Data. Подробнее узнать о мотивах, лежащих в основе проекта, вы можете в документации.
В этой статье я покажу несколько примеров использования Spring Data JDBC. Здесь не будет подробного руководства, но, надеюсь, приведенной информации хватит, чтобы попробовать его самостоятельно. Очень хорошо, если вы уже знакомы со Spring Data JPA. Исходный код вы можете найти в github.
Для быстрого старта я использовал этот шаблон.
Предварительная подготовка
Из зависимостей нам нужны data-jdbc — стартер, flyway для управления схемой и драйвер postgres для подключения к базе данных.
Далее настраиваем приложение для подключения к базе данных:
Маппинг сущностей
Для этого примера будем использовать следующую таблицу:
И соответствующий java-класс (обратите внимание, что @Id импортируется из org.springframework.data.annotation.Id ):
То увидим ошибку — ERROR: null value in column «id» violates not-null constraint. Это происходит, потому что мы не определили ни способ генерации id ни значение по умолчанию. Поведение Spring Data JDBC в части идентификаторов немного отличается от Spring Data JPA. В нашем примере нужно определить ApplicationListener для BeforeSaveEvent :
Теперь тест пройдет, потому что поле Id заполняется. Полный список поддерживаемых событий жизненного цикла смотрите в документации.
Методы запросов
Одной из особенностей проектов Spring Data является возможность определять методы запросов в репозиториях. Spring Data JDBC использует здесь несколько иной подход. Для демонстрации определим метод запроса в BookRepository :
И если запустим соответствующий тест:
Тест пройден! Не забывайте об этом при создании репозиториев.
Примечание переводчика: в Spring Data JDBC 2.0 появилась поддержка генерации запросов по именам методов.
Связи
Для работы со связями Spring Data JDBC также использует другой подход. Основное отличие в том, что отсутствует ленивая загрузка. Поэтому если вам не нужна связь в сущности, то просто не добавляйте ее туда. Такой подход основан на одной из концепций предметно-ориентированного проектирования (Domain Driven Design), согласно которой сущности, которые мы загружаем, являются корнями агрегатов, поэтому проектировать надо так, чтобы корни агрегатов тянули за собой загрузку других классов.
Один-к-одному
Класс UserAccount выглядит примерно так:
Один-ко-многим
Вот sql, который будем использовать для демонстрации связи «один-ко-многим»:
В этом примере на складе (warehouse) есть много товаров/объектов (inventoryitems). Поэтому в классе Warehouse мы также будем использовать @MappedCollection для InventoryItem :
Многие-к-одному и многие-ко-многим
В рамках этого руководства я не буду вдаваться в подробности о связях «многие-к-одному» или «многие ко многим». Я советую избегать связей «многие-ко-многим» и использовать их только в крайнем случае. Хотя иногда они могут быть неизбежны. Оба этих типа связей реализуются в Spring Data JDBC через ссылки на Id связанных сущностей. Поэтому имейте ввиду, что здесь вам предстоит еще немного потрудиться.
Заключение
Если вы использовали Spring Data JPA, то большая часть из того, что я рассказал, должна быть вам знакома. Я уже упоминал ранее, что Spring Data JDBC стремится быть проще, и поэтому отсутствует ленивая загрузка. Помимо этого, отсутствует кеширование, отслеживание «грязных» объектов (dirty tracking) и сессии (session). Если в Spring Data JDBC вы загружаете объект, то он загружается полностью (включая связи) и сохраняется тогда, когда вы сохраняете его в репозиторий. Примеры, которые я показал, очень похожи на свои аналоги в JPA, но помните, что многие концепции Spring Data JPA отсутствуют в Spring Data JDBC.
В целом мне нравится Spring Data JDBC. Признаю, что это может быть не лучший выбор для всех приложений, однако я бы рекомендовал его попробовать. Как человек, который в прошлом боролся с ленивой загрузкой и dirty tracking, я ценю его простоту. Я думаю, что это хороший выбор для простых предметных областей, которые не требуют большого количества нестандартных запросов.
На этом пока все, спасибо за чтение! Надеюсь, вы нашли это руководство полезным и оно будет отправной точкой для использования Spring Data JDBC.