Hibernate — ORM, JPA, JPQL
ORM (Object-Relational Mapping, объектно-реляционное отображение) — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования. В объектно-ориентированном программировании объекты приложения представляют объекты реального мира. ORM предназначена для преобразования объектов в форму для их сохранения в файлах или базах данных, а также их дальнейшего извлечения в программе с сохранением свойств объектов и отношений между ними. Такие объекты называют «хранимыми» (persistent). ORM освобождает программиста от работы с SQL-скриптами и позволяет сосредоточиться на ООП.
В программе основой ORM является конфигурационный файл, в котором описывается порядок хранения объекта в БД. Если с обычными полями все прозрачно : строковое поле записывается в колонку с типом varchar, а целочисленное — в int, то со связями между объектами все гораздо интересней. Например, в программе используются классы «Город» и «Улица». Для обоих классов созданы конфигурационные файлы, в которых описаны их поля и связи с таблицами БД. Но кроме этого «Улица» имеет ссылку на «Город». В таком случае таблицы в БД будут связаны по внешнему ключу, что также должно быть описано в одном из конфигурационных файлов.
JPA — Java Persistence API, javax.persistence
Для сохранения Java-объектов в базе данных и чтения из БД следует использовать JPA. JPA (Java Persistence API) — программный интерфейс API, входящий с версии Java 5 в состав платформ Java SE и Java EE. Существует несколько реализаций интерфейса JPA, среди которых наиболее популярным является Hibernate.
Непосредственно JPA представлен в пакете javax.persistence и включает платформо-независимый объектно-ориентированный язык запросов JPQL.
JPA оперирует таким понятием, как сущность Entity, которая является POJO-классом и связана с БД с помощью аннотации @Entity или через файл конфигурации XML. К такому классу предъявляются следующие требования :
- наличие пустого public или protected конструктора;
- не должен быть вложенным, являться интерфейсом или enum;
- не должен быть final и не должен содержать final-свойств;
- должен включать хотя бы одно @Id-поле.
При этом сущность может иметь непустые конструкторы, наследоваться или быть наследованной, содержать методы, не связанные со свойствами (методы get/set), и реализовывать интерфейсы. Entities могут быть связаны друг с другом (один-к-одному, один-ко-многим, многие-к-одному и многие-ко-многим).
Примеры сущностей Entity
В самом простом виде (без методов определения и получения значения свойств) JPA сущности можно представить следующим образом :
@Entity public class Author < @Id private Integer id; private String firstName; private String lastName; @ManyToMany private Listbooks; > @Entity public class Book < @Id private Integer id; private String title; private String isbn; @ManyToOne private Publisher publisher; @ManyToMany private Listauthors; > @Entity public class Publisher < @Id private Integer id; private String name; private String address; @OneToMany(mappedBy = "publisher") private Listbooks; >
При описании сущностей были использованы аннотации. Пример использования аннотаций при описании сущности представлено здесь.
JPQL — Java Persistence Query Language
Java Persistence Query Language (JPQL) — платформо-независимый объектно-ориентированный язык запросов являющийся частью спецификации JPA.
JPQL используется для написания запросов к сущностям, хранящимся в реляционной базе данных. JPQL во многом похож на SQL, но в отличие от последнего, оперирует запросами, составленными по отношению к сущностям JPA, в отличие от прямых запросов к таблицам базы данных. В дополнение к получению объектов (запросы SELECT), JPQL поддерживает запросы, основанные на операторах UPDATE и DELETE. JPA реализует концепцию ORM.
JPQL основан на Hibernate Query Language (HQL), более раннем не стандартизованном языке запросов, включённом в библиотеку объектно-реляционного отображения Hibernate. Hibernate и HQL были созданы до появления спецификации JPA. JPQL является подмножеством языка запросов HQL.
Примеры JPQL
JPQL запрос получения списка авторов сущности «Author», упорядоченных в алфавитном порядке, имеет вид :
SELECT a FROM Author a ORDER BY a.firstName, a.lastName
Запрос получения списка авторов, когда-либо опубликованных издательством «Publisher Press» :
SELECT DISTINCT a FROM Author a INNER JOIN a.books b WHERE b.publisher.name = 'Publisher Press'
JPQL поддерживает именованные параметры, которые начинаются с символа двоеточия ‘:’. Следующая функция, возвращающая список авторов с определенной фамилией, будет иметь следующий вид :
import javax.persistence.Query; import javax.persistence.EntityManager; import org.apache.commons.lang.StringUtils; . @SuppressWarnings(«unchecked») public List
JDBC vs JPA
В мире разработки программного обеспечения очень любят аббревиатуры. И работа с базами данных в Java — не исключение.
Наличие множества вариантов работы с БД может запутать: что же я использую на самом деле? Все используют JPA? Мне тоже стоит его использовать? Но я еще слышал о Spring Data JDBC. А как насчет Spring Data JPA?
В этой статье мы поговорим о JDBC и JPA: истории появления и некоторых особенностях.
Давным-давно был JDBC
JDBC — это Java Database Connectivity API. Это старый стандарт, уходящий корнями в 1997 год во времена Java 1.1. И этот API сослужил нам добрую службу.
Абсолютно все библиотеки, взаимодействующие с базами данных, используют JDBC. В этом есть как преимущества, так и недостатки. По сути, JDBC — это Java-реализация стандартного подхода к работе с базами данных, реализованного во всех подобных технологиях:
- Открыть соединение.
- Открыть курсор.
- Отправить запрос.
- Обработать набор данных.
- Закрыть результирующий набор / курсор.
- Закрыть соединение.
И вы наверняка сталкивались с ситуацией, когда, выполняя десятки запросов, забывали закрыть соединение. А шесть недель спустя получали ужасный стек-трейс из-за того, что каким-то образом в базе данных закончились соединения или курсоры.
Вы быстро понимали в чем дело, чинили и отправляли в релиз. Но через восемь недель — та же самая ошибка.
Разработчики Spring обратили на это внимание, и в Spring появился паттерн JdbcTemplate. Template позволил вам сосредоточиться непосредственно на запросах и их результатах.
Теперь за управление ресурсами отвечал фреймворк. Вам больше никогда не позвонят в 3 часа ночи, потому что ваша база данных/приложение упали.
Ладно, возможно, вы все еще получаете звонки среди ночи. Но не потому, что забыли закрыть соединение!
Я думаю, что после написания десятков запросов к одному и тому же объекту Item, вы задумывались: «Почему Java, зная тип объекта, не может сгенерировать запрос за меня?»
И здесь появляется Hibernate — предвестник JPA.
Hibernate
В течение многих лет мы мучились с маппингом строк таблиц БД на Java-объекты, а также с различиями между разными СУБД.
При переходе с Oracle на Postgres мне придется переписывать все запросы? Иногда, да. Потому что в каждой СУБД есть свои особенности и реализации стандарта ANSI SQL отличаются от СУБД к СУБД.
Hibernate принес унифицированный подход для персистентности Java-объектов — надо только настроить маппинг таблиц БД на Java-классы. Изначально для конфигурации маппинга использовался XML, но с появлением Java 5 перешли на аннотации!
И все понеслось с космической скоростью!
Унификация общения с разными СУБД, когда один и тот же запрос можно выполнить на любой СУБД — это действительно круто. Но осталась одна проблема, которую мы не осознавали до конца.
Хорошо, хорошо, были те, кто понимали это, но большинство — нет.
Вы никогда не сможете по-настоящему избежать настройки маппинга реляционных таблиц. Если вы используете Hibernate, это еще не значит, что можно перестать думать в терминах реляционных баз данных.
Да, для простейших запросов проблем нет.
select i from Item i where i.description like ‘%:partialDescription%’
Это весьма простой запрос. Он может быть универсальным. Как раз в таких ситуациях Hibernate приводит в полный восторг.
В итоге Hibernate стандартизовали из-за его популярности.
Hibernate, теперь тебя зовут JPA
JPA — это Java Persistence API (аббревиатура внутри аббревиатуры). Hibernate стал основой для него. Большинство разработчиков, использующих JPA, на самом деле используют Hibernate.
JPA был настолько продуманным, что окрылял вас.
Но мы неизбежно обнаружим, что в JPA есть страшная тайна, о которой мы где-то прочитали или догадались сами: вы никогда, никогда не уйдете от концепции реляционных таблиц. По мере написания более сложных, более запутанных и более бизнес-ориентированных запросов вы обнаружите, что Java-объекты не всегда соответствуют этой парадигме.
То есть вы просто променяли свои знания SQL на знания JPA.
На самом деле, он довольно мощный. Но иногда этой силе требуется небольшая помощь, поэтому и был создан Spring Data JPA.
Spring Data JPA помогает вам с простыми запросами и избавляет от необходимости работать с EntityManager из JPA.
Хотя рано или поздно вам все-равно придется писать JPQL-запросы вручную.
Но если вы думаете, что написав, сложный и тонко настроенный JPQL-запрос вы сможете каким-то образом настроить генерацию оптимального SQL-запроса, вас ждет большое разочарование.
В былые времена администраторы баз данных помогали найти медленные запросы. А проанализировав план выполнения, вы могли понять, как запрос выполняется и где тратит время.
И можно было заняться оптимизацией БД и запроса:
- создать индексы;
- актуализировать статистику;
- переписать JOIN;
- избавиться от функций вроде UPPER (или LOWER), чтобы избежать полных сканирований таблиц;
- убрать десятки JOIN одной и той же таблицы (да, однажды я видел и такое).
А также использовать десяток других приемов. После оптимизации ваш двадцатиминутный запрос мог выполняться за секунду. Это было обычным делом при обслуживании баз данных/приложений.
Но с SQL-запросами, генерируемыми через JPA, вы не сможете этого сделать.
Некоторые стали называть это девятым кругом JPA.
Может для вас это допустимо, но для многих — весьма неприятно.
Поэтому многих воодушевил Spring Data JDBC, появившийся в 2017 году. На конференции SpringOne в 2018 году зал был набит людьми, жаждущими услышать новости об этом.
Spring Data JBDC делает много работы за вас, но не все, что делает старый добрый Hibernate. Предполагается, что вручную вы напишете более оптимальный запрос как во времена чистого JDBC. У вас появляется возможность видеть SQL-запросы и настраивать их в соответствии с вашими потребностями.
Теперь, имея представление об этих технологиях (JdbcTemplate, Spring Data JPA, Spring Data JDBC) вы сможете сделать осознанный выбор в отношении того, что лучше подойдет вам в вашей ситуации.
Когда говорим про память в Java, то чаще всего вспоминают Heap и Garbage Collector. Но у нас есть больше не менее интересного в памяти, о чем мы и поговорим на открытом занятии «Не хипом единым живёт Java». Приглашаем зарегистрироваться всех желающих.