githubEdit

Работа с БД через Hibernate

В предыдущей главе мы разобрали, какие вообще технологии есть в хранении данных. В этой главе разберем наиболее популярную технологию именно в Java-мире: фреймворк Hibernate.

Hibernate - это очень продвинутый и навороченный ORM-фреймворк. Он берет на себя всю работу по превращению ваших Java-объектов в таблицы и запросы реляционной базы данных. Все что вам надо для начала - это описать ваши сущности, необходимые запросы и задать несколько свойств подключения к БД, таких как адрес, логин/пароль, тип БД (как уже говорилось, реляционных БД много разных, для каждой есть свой диалект языка SQL).

В этой главе мы используем возможности модуля spring data JPA, полная документация по нему доступна по ссылке https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#referencearrow-up-right

Настройки подключения к БД

Для того, чтобы использоваться все высокоуровневые фичи, описанные ниже, достаточно следующих шагов:

  1. В зависимостях проекта подключить spring-boot-starter-data-jpa

  2. Добавить аннотацию @EnableJpaRepositories на ваш класс конфигурации (помеченный как @SpringBootApplication или @Configuration)

  3. В настройках проекта (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/arrow-up-right

Репозитории

Основы

Окей, мы создали класс в 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-creationarrow-up-right

Возвращаемые значения такого метода могут быть разные:

  • Если возвращаемый тип - это объект, то вернется либо он (если в БД нашлась хотя бы одна запись, подходящая под условия, если их несколько - вернется первая), либо null

  • Можно использовать Optional<тип> чтобы избегать nullов.

  • Можно возвращать список - тогда вернутся все объекты, удовлетворяющие условию, либо пустой список если их нет.

  • Можно использовать спринговый класс Pageable для диапазонов.

Написание запросов вручную

Если по какой-то причине нас не устроило написание запросов путем задания имен методов, или нас не устраивает тот запрос, который генерирует Hibernate, мы можем создавать запросы вручную.

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

Параметры метода надо аннотировать с помощью @Param, затем их имя можно использовать внутри текста запроса.

По умолчанию запросы пишутся на языке HQL, который затем обрабатывается Hibernate и превращается в нативный запрос в зависимости от выбранного диалекта БД. Но у аннотации @Query есть параметр nativeQuery, если его выставить - тогда запрос будет трактоваться именно как нативный, и будет отправляться в СУБД без изменений именно в таком виде:

При использовании нативных запросов вы теряете гибкость. Вы больше не сможете прозрачно для вашего кода заменить СУБД, так как разные СУБД имеют разные разновидности языка SQL, и их запросы несовместимы друг с другом. С другой стороны, как и любой стандарт, пытающийся объединить много разных технологий, HQL является неким минимальным общим подмножеством всех разновидностей SQL, и не позволяет использовать вашу конкретную СУБД максимально эффективно.

Заключение

Итак, подведем итоги главы. Мы выяснили, что Hibernate - ORM фреймворк, использующий технологию JPA для описания сущностей в БД. Для добавления работы с БД в свой Spring Boot проект вам надо:

  1. Подключить зависимость - модуль spring-boot-data-jpa

  2. Настроить параметры подключения к БД в конфиге

  3. Создать классы сущностей, проставить на них аннотации

  4. Создать классы репозиториев, прописать недостающие необходимые методы для выборок в них

  5. Получить экземпляр нужного репозитория через стандартные механизмы спринга (@Autowired) и использовать его методы для сохранения и загрузки объектов

В простых случаях вам вообще не надо думать о том, как ваши объекты будут располагаться в БД, как будут выглядеть запросы. Вы работаете только с Java-кодом и аннотациями.

По этой причине в большинстве случаев нет нужды глубоко изучать SQL или вашу СУБД. Однако есть такая вещь, как "закон протекающих абстракций", хорошо описанный в статье Джоэля Спольски https://habr.com/ru/company/selectel/blog/512796/arrow-up-right. Время от времени вы будете сталкиваться с проблемами, когда Java-код вроде написан правильно, а запросы к БД люто тормозят. Потому что какие-то действия, правильные на верхнем уровне, приводят к неоптимальностям "под капотом", внутри реализации. И хотя фреймворки высокого уровня, как Hibernate, изо всех сил детали реализации от вас прячут, иногда все-таки приходится в них залезать и разбираться. Поэтому базовые знания SQL, оптимизации и настройки БД вам все-таки пригодятся в дальнейшем.

Last updated