что такое spring data 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 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.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
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.
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 обращается к базе, т. к. кэш первого уровня включен всегда и привязан к сессии, которая, как правило, привязана к текущей транзакции.
Тесты, на которых можно поиграться (ссылка на репозиторий дана в начале статьи):
Разница между JDBC, JPA, Hibernate, Spring Data JPA
В этой статье я собираюсь объяснить разницу между JDBC, JPA, Hibernate, Spring Data Jpa.
После общения со многими своими студентами на курсах и менторской программе, я обнаружил, что даже самые опытные из учеников не до конца понимают разницу между инструментами для подключения Java приложения к базе данных.
Вот используем мы например любимый мною Spring Boot фреймворк и его библиотеку Data Jpa, и студент не может понять — нужно ли ему подключать зависимости для Hibernate. Подключать ли зависимости для JDBC если мы используем Hibernate? Сегодня попытаюсь собрать все воедино и наконец-то положить конец путаницам с библиотеками и инструментами для баз данных и Java.
Большинство программ Java используют реляционные базы данных (MySQL, PostgreSQL) для хранения данных для дальнейшей обработки. Это обычный факт. Особенно если речь идет о веб приложениях, которые имеют очень широкий спектр взаимодействия с пользователем.
Подключение базы данных к Java приложению процесс непростой. Нужно учесть пул соединений, уровень доступа к данным, правильный маппинг таблицы базы данных и Java объекта.
К счастью, уже имеются серьезные наработки в виде библиотек и фреймворков, которые программисты могут использовать, чтобы упростить себе работу с вышеперечисленными задачами.
В этой статье я постараюсь раскрыть только разницу между JDBC, JPA, Hibernate, Spring Data JPA в рамках выполнения запросов через Java, без учета пулов соединений и других нюансов.
Самый первый способ подключить джава приложение к базе данных — это использование JDBC (Java Database Connectivity).
Sun Microsystems выпустила JDBC как часть JDK в феврале 1997 года. Классы JDBC содержатся в пакетах java.sql и javax.sql для подключения и управления данными в базах данных.
Можно сказать, что JDBC — это мост между миром Java и миром баз данных. Ведь первое, что мы ищем когда хотим подключить базу данных к нашему приложению — jdbc драйвер. Если мы работаем с Maven, тогда мы ищем зависимость для драйвера к определенной базе.
Одним из недостатков JDBC является то, что код, который получается в конце — выглядит очень большим по объему (хотя работы он выполняет не много). Также, очень сложно приходится, когда объект джава который мы пытаемся сохранить в базе или достать — достаточно большой. Нужно правильно замапить поля базы данных и поля класса Java. Бывает такое, что в процессе работы нужно добавить поле к готовой таблице в базе. И потом нужно находить все запросы в базу в коде приложения чтобы добавить это поле. Чтобы прочувствовать всю «боль» работы с JDBC, нужно с ним поработать. А для тех, кто уже знаком с этим функционалом я думаю объяснения будут излишне.
Для пущего эфекта я просто продемонстрирую сколько нужно кода чтобы достать список статтей из одной таблицы:
Это все чтобы просто выполнить один SELECT
JPA призвана устранить вышеперечисленные недостатки.
Спецификация Java Persistence API — это технология, которая позволяет удобно мапить объект Java и таблицу базы данных.
В JDBC при написании каждого запроса, Вам необходимо указать в коде все детали, необходимые для операций CRUD, такие как имена таблиц, имена столбцов. В JPA (которая использует JDBC «под капотом») Вы также указываете эти данные, но уже только один раз, когда навешиваете аннотации на Java класс:
Сама по себе спецификация JPA не является инструментом или фреймворком; скорее, она определяет набор концепций, которые могут и должны быть реализованы любым другим инструментом.
Так как JPA — это просто спецификация, Вам нужен инструмент для ее реализации. Этим инструментом может быть Hibernate, TopLink, iBatis и т. д.
Так как Hibernate самый популярный ORM (Object Relational Mapping) фреймворк для работы с базой данных, я оставил свой выбор на нем. Но то же самое касается и других библиотек ORM о которых Вы скорее всего даже не слышали.
Вы можете рассматривать JPA как интерфейс, а Hibernate — как реализацию. Без Hibernate, от JPA в Вашем коде будет мало полезности. Хотя как Hibernate так и JPA можно использовать по отдельности в связке с другими инструментами.
Более подробно о Hibernate иJPA мы говорили в статье: Что такое hibernate.
Что насчет Spring Data Jpa?
Эта библиотека является частью Spring Framework — одного из самых популярных Java фреймворков на сегодняшний день.
Цель Spring Data — уменьшить объем стандартного кода, необходимого для реализации уровней доступа к данным для различных баз данных.
Spring Data JPA — это библиотека, которая добавляет дополнительный уровень абстракции поверх ORM реализации JPA. По умолчанию Spring Data JPA использует Hibernate, в качестве ORM провайдера (чтобы выполнять запросы). Это, кстати, можно изменить используя настройки Spring. Хотя делать это неопытным пользователям я бы не советовал.
Если Вы используете Spring Boot вместе с Spring Data JPA, то имеете все необходимые настройки подключения Java приложения к базе данных “из коробки”. Единственное, что нужно указать — это хост для Вашей базы данных, имя пользователя и пароль для доступа к ней. Spring Boot обеспечивает автоматическую настройку для всего подключения к базе. В том числе и пул соединений.
Более подробно с вышеописанной библиотекой мы познакомились статье: Spring Boot — пример с Postgres и JPA
Как видите, со временем, JDBC которую зарелизили в 1997 году обросла слоями абстракций, которые позволяют программистам сосредоточиться на том, чтобы решить проблему бизнеса вместо того, чтобы тратить свое время на написание кучи конфигурационных классов настроек к базе данных.
Сейчас у Вас есть возможность подключить любое хранилище данных (не только реляционную базу) в пару кликов, без необходимости включать “танцы с бубном”, как это делалось раньше.
В каком-то роде, мы платим тем, что SQL запросы должны будут пройти через множество слоев прежде чем достигнут самой базы. Но по своему опыту могу сказать, что в большинстве случаев падение скорости выполнения запросов не наблюдается если использовать JDBC или Spring Data JPA. Нужно только быть внимательным со сложными и большими запросами.
Для новичков, я советую попробовать сначала JDBC. Чтобы можно было сравнить потом с Hibernate и Spring. Разница будет реально ощутима. При этом Вы будете знать как все фреймворки примерно работают под капотом. Так как все они основаны на JDBC, который был разработан изначально.
Кому интересно, я также записал видео на английском, в котором объясняю вышеописанное. Также, не забывайте подписываться на канал в ютубе и телеграмм:)