Работа с БД через Hibernate
В предыдущей главе мы разобрали, какие вообще технологии есть в хранении данных. В этой главе разберем наиболее популярную технологию именно в Java-мире: фреймворк Hibernate.
Hibernate - это очень продвинутый и навороченный ORM-фреймворк. Он берет на себя всю работу по превращению ваших Java-объектов в таблицы и запросы реляционной базы данных. Все что вам надо для начала - это описать ваши сущности, необходимые запросы и задать несколько свойств подключения к БД, таких как адрес, логин/пароль, тип БД (как уже говорилось, реляционных БД много разных, для каждой есть свой диалект языка SQL).
В этой главе мы используем возможности модуля spring data JPA, полная документация по нему доступна по ссылке https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference
Настройки подключения к БД
Для того, чтобы использоваться все высокоуровневые фичи, описанные ниже, достаточно следующих шагов:
В зависимостях проекта подключить spring-boot-starter-data-jpa
Добавить аннотацию @EnableJpaRepositories на ваш класс конфигурации (помеченный как @SpringBootApplication или @Configuration)
В настройках проекта (application.yml) прописать параметры подключения к БД. В случае с MySQL базой на локальном хосте это будет что-то вроде:
spring: datasource: # JDBC ссылка на подключение к БД url: jdbc:mysql://localhost:3306/test_db # Класс JDBC драйвера для работы с нашей БД driver-class-name: com.mysql.cj.jdbc.Driver # Логин и пароль (если нужны) username: user password: 12345 jpa: # Диалект SQL, поддерживаемый нашей БД database-platform: org.hibernate.dialect.MySQL8Dialect hibernate: # Что делать, если структура БД отличается от структуры классов в нашем коде # update означает что надо переделать структуру БД # Другие значения - create (создать БД если ее нет, но не менять если есть) и # none - не делать ничего (чтобы не попортить уже лежащие в БД данные) и выдать ошибку ddl-auto: update
Описание сущностей с помощью JPA аннотаций
Как рассказывалось в предыдущей главе, JPA - это стандартный способ описания структуры сущностей в БД. Вы создаете класс, который будете хранить, а затем помечаете его поля аннотациями, подсказывающими как именно его хранить.
Все аннотации находятся в пакете javax.persistence.
Рассмотрим такой пример. Пусть мы делаем сервис для хранения своего вишлиста. В списке у нас есть товары, у которых есть описание, цена и ссылка на магазин. Такой объект можно представить в БД как:
Вуаля, этого достаточно, чтобы Spring на пару с Hibernate на старте приложения нашли этот класс, прочитали аннотации, залезли в БД, создали там таблицу с четырьмя столбцами и подготовили все необходимые select/insert/delete и прочие запросы.
Для более сложных случаев есть множество других аннотаций (например, для задания отношений между объектами, создания дополнительных индексов и вторичных ключей и т.п.), полный список можно найти в документации по JPA и во множестве статей в интернете, например https://thorben-janssen.com/key-jpa-hibernate-annotations/
Репозитории
Основы
Окей, мы создали класс в Java и Hibernate создал нам пустую таблицу в БД. Что делать дальше, как нам что-то сохранить и что-то прочитать из нашей базы?
Hibernate предоставляет множество низкоуровневых механизмов работы с объектами (там и до голой JDBC можно добраться), однако на высоком уровне проще и приятнее работать с т.н. репозиториями.
Репозиторий это интерфейс, помеченный специальной аннотацией, отнаследованный от базового класса Repository и содержащий методы для сохранения и загрузки объектов из БД. Используются именно интерфейсы, так как нам не надо писать в них реализацию этих методов. Все что от нас требуется - описать что мы хотим сделать, а Spring Boot и Hibernate под капотом за нас сгенерируют весь необходимый код того, как именно это сделать.
В модуле spring-data-jpa уже есть набор различных базовых интерфейсов репозиториев, от которых можно отнаследовать свой репозиторий и сразу получить готовую реализацию типовых методов.
Например, если все что нам нужно - типичные CRUD операции (создание, удаление, изменение, выборка по первичному ключу, выборка всех объектов в таблице), то нам вообще не надо будет ничего писать, достаточно отнаследоваться от JpaRepository, где все это уже есть:
Вот и все.
Наш репозиторий является бином в контексте приложения, мы можем получать доступ к нему из других классов с помощью @Autowired аннотации. И затем использовать методы для работы с БД:
Дополнительные методы
Если нам недостаточно стандартных операций, полученных нами из JpaRepository, мы можем дополнительно определить в репозитории собственные запросы. Для этого есть два варианта.
В случае простых выборок можно использовать магию с именами методов. Все что нам нужно - это создать в репозитории метод, чье имя будет содержать в себе запрос. Например, если мы хотим сделать выборку по точному значению поля "цена" то такой запрос будет выглядеть как
Имя метода в таком случае начинается с find, затем идет перечисление полей объекта (их может быть несколько, в таком случае нужно использовать and, например findByPriceAndUrl) и ограничений на них (равно, не равно, больше, меньше и т.п.). Полный список того, какие именно запросы можно писать через имена методов, доступен в документации https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
Возвращаемые значения такого метода могут быть разные:
Если возвращаемый тип - это объект, то вернется либо он (если в БД нашлась хотя бы одна запись, подходящая под условия, если их несколько - вернется первая), либо null
Можно использовать Optional<тип> чтобы избегать nullов.
Можно возвращать список - тогда вернутся все объекты, удовлетворяющие условию, либо пустой список если их нет.
Можно использовать спринговый класс Pageable для диапазонов.
Написание запросов вручную
Если по какой-то причине нас не устроило написание запросов путем задания имен методов, или нас не устраивает тот запрос, который генерирует Hibernate, мы можем создавать запросы вручную.
В таком случае мы точно так же определяем метод в нашем классе репозитория, но теперь название у него может быть любое, затем помечаем его аннотацией, в которой пишем сам запрос.
Параметры метода надо аннотировать с помощью @Param, затем их имя можно использовать внутри текста запроса.
По умолчанию запросы пишутся на языке HQL, который затем обрабатывается Hibernate и превращается в нативный запрос в зависимости от выбранного диалекта БД. Но у аннотации @Query есть параметр nativeQuery, если его выставить - тогда запрос будет трактоваться именно как нативный, и будет отправляться в СУБД без изменений именно в таком виде:
При использовании нативных запросов вы теряете гибкость. Вы больше не сможете прозрачно для вашего кода заменить СУБД, так как разные СУБД имеют разные разновидности языка SQL, и их запросы несовместимы друг с другом. С другой стороны, как и любой стандарт, пытающийся объединить много разных технологий, HQL является неким минимальным общим подмножеством всех разновидностей SQL, и не позволяет использовать вашу конкретную СУБД максимально эффективно.
Заключение
Итак, подведем итоги главы. Мы выяснили, что Hibernate - ORM фреймворк, использующий технологию JPA для описания сущностей в БД. Для добавления работы с БД в свой Spring Boot проект вам надо:
Подключить зависимость - модуль spring-boot-data-jpa
Настроить параметры подключения к БД в конфиге
Создать классы сущностей, проставить на них аннотации
Создать классы репозиториев, прописать недостающие необходимые методы для выборок в них
Получить экземпляр нужного репозитория через стандартные механизмы спринга (@Autowired) и использовать его методы для сохранения и загрузки объектов
В простых случаях вам вообще не надо думать о том, как ваши объекты будут располагаться в БД, как будут выглядеть запросы. Вы работаете только с Java-кодом и аннотациями.
По этой причине в большинстве случаев нет нужды глубоко изучать SQL или вашу СУБД. Однако есть такая вещь, как "закон протекающих абстракций", хорошо описанный в статье Джоэля Спольски https://habr.com/ru/company/selectel/blog/512796/. Время от времени вы будете сталкиваться с проблемами, когда Java-код вроде написан правильно, а запросы к БД люто тормозят. Потому что какие-то действия, правильные на верхнем уровне, приводят к неоптимальностям "под капотом", внутри реализации. И хотя фреймворки высокого уровня, как Hibernate, изо всех сил детали реализации от вас прячут, иногда все-таки приходится в них залезать и разбираться. Поэтому базовые знания SQL, оптимизации и настройки БД вам все-таки пригодятся в дальнейшем.
Last updated