что такое spring data jpa
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 Data JPA: что такое хорошо, и что такое плохо
Крошка-сын к отцу пришел
И спросила кроха
— Что такое хорошо
и что такое плохо
Владимир Маяковский
Эта статья о Spring Data JPA, а именно в подводных граблях, встретившихся на моём пути, ну и конечно же немного о производительности.
Примеры, описанные в статье можно запустить в проверочном окружении, доступном по ссылке.
select t.* from t where t.id in (. )
Один из наиболее распространённых запросов — это запрос вида «выбери все записи, у которых ключ попадает в переданное множество». Уверен, почти все из вас писали или видели что-то вроде
Это рабочие, годные запросы, здесь нет подвоха или проблем с производительность, но есть небольшой, совсем неприметный недостаток.
Недостаток заключается в использовании слишком «узкого» интерфейса для передачи ключей. «Ну и?» — скажете вы. «Ну список, ну набор, я не вижу здесь проблемы». Однако, если мы посмотрим на методы корневого интерфейса, принимающие множество значений, то везде увидим Iterable :
«Ну и что? А я хочу список. Чем он хуже?»
Ни чем не хуже, только будьте готовы к появлению на вышестоящем уровне вашего приложения подобного кода:
Этот код не делает ничего, кроме перезаворачивания коллекций. Может получиться так, что аргументом метода будет список, а репозиторный метод принимает набор (или наоборот), и перезаворачивать придётся просто для прохода компиляции. Разумеется это не станет проблемой на фоне накладных расходов на сам запрос, речь скорее о ненужных телодвижениях.
Поэтому хорошей практикой является использование Iterable :
Лишний код: неповторяющиеся ключи
В продолжение прошлого раздела хочу обратить внимание на распространённое заблуждение:
Другие проявления этого же заблуждения:
На первый взгляд, ничего необычного, верно?
который всегда вернёт одно и тоже безотносительно наличия повторов в аргументе. Поэтому обеспечивать уникальность ключей не нужно. Есть один особый случай — «Оракл», где попадание >1000 ключей в in приводит к ошибке. Но если вы пытаетесь уменьшить количество ключей исключением повторов, то стоит скорее задуматься о причине их возникновения. Скорее всего ошибка где-то уровнем выше.
Итого, в хорошем коде используйте Iterable :
Самопись
Внимательно посмотрите на этот код и найдите здесь три недостатка и одну возможную ошибку:
Гарри Поттер и составной ключ
Взгляните на два примера и выберите предпочтительный для вас:
На первый взгляд, разницы нет. Теперь попробуем первый способ и запустим простой тест:
В логе запросов (вы ведёте его, не так ли?) увидим вот это:
Теперь второй пример
Журнал запросов выглядит иначе:
Вот и вся разница: в первом случае всегда получаем 1 запрос, во втором — n запросов.
Причина этого поведения кроется в SimpleJpaRepository::findAllById :
Какой из способов лучше — определять вам, исходя из того, насколько важно количество выполняемых запросов.
Лишний CrudRepository::save
Часто в коде встречается такой антипаттерн:
In the copyValues method call, the hydrated state is copied again, so a new array is redundantly created, therefore wasting CPU cycles. If the entity has child associations and the merge operation is also cascaded from parent to child entities, the overhead is even greater because each child entity will propagate a MergeEvent and the cycle continues.
Иными словами делается работа, которую можно не делать. В итоге наш код можно упростить, одновременно улучшив его производительность:
Конечно, постоянно держать это в голове при разработке и вычитке чужого кода неудобно, поэтому нам хотелось бы внести изменения на уровне каркаса, чтобы метод JpaRepository::save утратил свои вредные свойства. Возможно ли это?
Однако, искушенный читатель наверняка уже почуял неладное. Действительно, указанное изменение ничего не сломает, но только в простом случае, когда отсутствуют дочерние сущности:
Теперь положим, что к счёту привязан его владелец:
Существует метод, позволяющий открепить пользователя от счёта и передать последний новому пользователю:
Что произойдёт теперь? Проверка em.contains(entity) вернёт истину, а значит em.merge(entity) не будет вызван. Если ключ сущности User создаётся на основе последовательности (один из наиболее частых случаев), то он не будет создан вплоть до завершения транзакции (или ручного вызова Session::flush ) т. е. пользователь будет пребывать в состоянии DETACHED, а его родительская сущность (счёт) — в состоянии PERSISTENT. В некоторых случаях это может сломать логику приложения, что и произошло:
«Слепой» CrudRepository::findById
Продолжаем рассматривать всё ту же модель данных:
В приложении есть метод, создающий новый счёт для указанного пользователя:
С версией 2.* указанный стрелкой антипаттерн не так бросается в глаза — чётче его видно на старых версиях:
Первым запросом мы достаём пользователя по ключу. Дальше получаем из базы ключ для новорожденного счёта и вставляем его в таблицу. И единственное, что мы берём от пользователя — это ключ, который у нас и так есть в виде аргумента метода. С другой стороны, BankAccount содержит поле «пользователь» и оставить его пустым мы не можем (как порядочные люди мы выставили ограничение в схеме). Опытные разработчики наверняка уже видят способ и рыбку съесть, и на лошадке покататься и пользователя получить, и запрос не делать:
JpaRepository::getOne возвращает обёртку над ключом, имеющую тот же тип, что и живая «сущность». Этот код даёт всего два запроса:
Когда создаваемая сущность содержит множество полей с отношением «многие к одному» / «один к одному» этот приём поможет ускорить сохранение и снизить нагрузку на базу.
Исполнение HQL запросов
Это отдельная и интересная тема :). Доменная модель та же и есть такой запрос:
Рассмотрим «чистый» HQL:
При его исполнении будет создан вот такой SQL запрос:
Проблема здесь не сразу бросается в глаза даже умудрённым жизнью и хорошо понимающим SQL разработчикам: inner join по ключу пользователя исключит из выборки счета с отсутствующим user_id (а по-хорошему вставка таковых должна быть запрещена на уровне схемы), а значит присоединять таблицу user вообще не нужно. Запрос может быть упрощён (и ускорен):
Существует способ легко добиться этого поведения в c помощью HQL:
Этот метод создаёт «облегчённый» запрос.
Аннотация Query против метода
Одна из основных фишек Spring Data — возможность создавать запрос из имени метода, что очень удобно, особенно в сочетании с умным дополнением от IntelliJ IDEA. Запрос, описанный в предыдущем примере может быть легко переписан:
Вроде бы и проще, и короче, и читаемее, а главное — не нужно смотреть сам запрос. Имя метода прочитал — и уже понятно, что он выбирает и как. Но дьявол и здесь в мелочах. Итоговый запрос метода, помеченного @Query мы уже видели. Что же будет во втором случае?
«Какого лешего!?» — воскликнет разработчик. Ведь выше мы уже убедились, что скрипач join не нужен.
Если вы ещё не обновились до версий с исправлением, а присоединение таблицы тормозит запрос здесь и сейчас, то не отчаивайтесь: есть сразу два способа облегчить боль:
хороший способ заключается в добавлении optional = false (если схема позволяет):
Теперь запрос-из-метода станет приятнее:
чего мы и добивались.
Ограничение выборки
Для своих целей нам нужно ограничивать выборку (например, хотим возвращать Optional из метода *RepositoryCustom ):
Указанный код обладает одной неприятной особенностью: в том случае, если запрос вернул пустую выборку будет брошено исключение
В проектах, которые я видел, это решалось двумя основными способами:
И очень редко я видел правильное решение:
EntityManager — часть стандарта JPA, в то время как Session принадлежит Хибернейту и является ИМХО более продвинутым средством, о чём часто забывают.
[Иногда] вредное улучшение
Когда нужно достать одно маленькое поле из «толстой» сущности мы поступаем так:
Запрос позволяет достать одно поле типа boolean без загрузки всей сущности (с добавлением в кэш-первого уровня, проверкой изменений по завершению сессии и прочими расходами). Иногда это не только не улучшает производительность, но и наоборот — создаёт ненужные запросы на пустом месте. Представим код, выполняющий некоторые проверки:
Этот код делает по меньшей мере 2 запроса, хотя второго можно было бы избежать:
Вывод простой: не пренебрегайте кэшем первого уровня, в рамках одной транзакции только первый JpaRepository::findById обращается к базе, т. к. кэш первого уровня включен всегда и привязан к сессии, которая, как правило, привязана к текущей транзакции.
Тесты, на которых можно поиграться (ссылка на репозиторий дана в начале статьи):
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.
Доступ к данным через JPA
Этот урок освещает процесс создания приложения, которое использует Spring Data JPA для сохранения и получения данных из реляционной БД.
Что вы создадите
Вы создадите приложение, которое сохраняет Customer POJO в БД в ОЗУ.
Что вам потребуется
Как проходить этот урок
Как и большинство уроков по Spring, вы можете начать с нуля и выполнять каждый шаг, либо пропустить базовые шаги, которые вам уже знакомы. В любом случае, вы в конечном итоге получите рабочий код.
Чтобы начать с нуля, перейдите в Настройка проекта.
Настройка проекта
Для начала вам необходимо настроить базовый скрипт сборки. Вы можете использовать любую систему сборки, которая вам нравится для сборки проетов Spring, но в этом уроке рассмотрим код для работы с Gradle и Maven. Если вы не знакомы ни с одним из них, ознакомьтесь с соответсвующими уроками Сборка Java-проекта с использованием Gradle или Сборка Java-проекта с использованием Maven.
Создание структуры каталогов
Создание файла сборки Gradle
Ниже представлен начальный файл сборки Gradle. Файл pom.xml находится здесь. Если вы используете Spring Tool Suite (STS), то можете импортировать урок прямо из него.
Spring Boot gradle plugin предоставляет множество удобных возможностей:
Описание простой сущности
В этом примере, вы сохраняете Customer объекты, аннотированные как JPA сущность.
Другие два свойства, firstName и lastName остались не аннотированными. Это значит, что они будут соответствовать колонкам с такими же названиями и параметрами.
Метод tostring будет печатать свойства объекта.
Создание простых запросов
Spring Data JPA ориентирована на использование JPA для сохранения данных в реляционную БД. Наиболее полезной её возможностью является автоматическое создание реализаций репозитория во время выполнения из интерфейса.
Чтобы увидеть, как это работает, создайте интерфейс репозитория, который работает с сущностями Customer :
Приступим к работе и посмотрим, что он найдет!
Создание класса Application
Создайте класс Application со всеми компонентами.
Сборка исполняемого JAR
Вы можете собрать единый исполняемый JAR-файл, который содержит все необходимые зависимости, классы и ресурсы. Это делает его легким в загрузке, версионировании и развертывании сервиса как приложения на протяжении всего периода разработки, на различных средах и так далее.
Затем вы можете запустить JAR-файл:
Если вы используете Gradle, вы можете запустить ваш сервис из командной строки:
Как вариант, вы можете запустить ваш сервис напрямую из Gradle примерно так:
Вы должны увидеть следующее:
Spring Data JPA
Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.
Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.
Features
Sophisticated support to build repositories based on Spring and JPA
Support for Querydsl predicates and thus type-safe JPA queries
Transparent auditing of domain class
Pagination support, dynamic query execution, ability to integrate custom data access code
Validation of @Query annotated queries at bootstrap time
Support for XML based entity mapping
Quickstart Your Project
Documentation
2.6.0 CURRENT GA | Reference Doc. | API Doc. |
2.7.0-SNAPSHOT SNAPSHOT | ||
2.6.1-SNAPSHOT SNAPSHOT | ||
2.5.8-SNAPSHOT SNAPSHOT | ||
2.5.7 GA | Reference Doc. | API Doc. |
2.4.16-SNAPSHOT SNAPSHOT | ||
2.4.15 GA | Reference Doc. | API Doc. |
Guides
OSS support
Free security updates and bugfixes with support from the Spring community. See VMware Tanzu OSS support policy.
Commercial support
Business support from Spring experts during the OSS timeline, plus extended support after OSS End-Of-Life.
Publicly available releases for critical bugfixes and security issues when requested by customers.
Future release
Generation not yet released, timeline is subject to changes.
About commercial support (*)
A few examples to try out:
Get ahead
VMware offers training and certification to turbo-charge your progress.
Get support
Spring Runtime offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription.
Upcoming events
Check out all the upcoming events in the Spring community.