В мире разработки программного обеспечения очень любят аббревиатуры. И работа с базами данных в 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». Приглашаем зарегистрироваться всех желающих.
Комментарии (10)
Xobotun
02.09.2022 19:54+5Незаслуженно неупомянут любимый мной jooq. Позволяет писать типизированные sql-запросы без неявных преобразований и с поддержкой конкретных фич баз данных, например, постгреса.
Но да, простые апдейты и селекты приходится делать руками. Зато поддержка cte, exists и какого-нибудь array_unnest позволяет писать эффективные запросы, имхо.
feoktant
02.09.2022 20:30+1вы наверняка сталкивались с ситуацией, когда, выполняя десятки запросов, забывали закрыть соединение
Интересно увидеть людей, которым так не везло. Может я просто везучий.
gsaw
04.09.2022 09:20+1На первой картинке персонаж по имени Data, актер уже в годах, потому old Data. Игра слов. Если бы не перевели, было бы понятнее :)
souls_arch
04.09.2022 11:48+1В комбо вся сила. Шутка. Никто не мешает парраллельно с jpa писать @query, открывать сессии фабрик и прочая-прочая, ежели возникнет необходимость. Зато у jpa 100% совместимость с любыми sql бд. А у индивид запросов - болт на рыло. Если ошибаюсь - поправьте. Конфиги так и так править при переключении, а смена дб на проекте вещь очень редкая. Проблем от jpa, кстати, хватает. Но они решаются.
agoncharov
04.09.2022 19:23Но с SQL-запросами, генерируемыми через JPA, вы не сможете этого сделать
Смогу, совершенно ничто не мешает
rdo
Не знаю, почему в статьях про JPA ВСЕ авторы набрасываются на "страшные, неоптимизированные запросы, которые выполняются по десять минут и кладут базу данных", но никогда не упоминают, что JPA избавляет от надобности писать однотипные select where и update where на каждый чих при работе с БД.
Запросы, которыми может подавиться jpa в типичных энтерпрайзных приложениях составляют не более пары процентов от типовых операций "достать из бд сущность и обносить ее данными из пришедшего json"
vooft
Проблема с JPA в том, что он может внезапно накинуть 2-3 запроса сверху, в зависимости от графа сущностей. Там, где кажется, что должен быть один запрос, легко может быть пять.
Есть неплохая библиотека, чтобы защититься от такого, она позволяет убедиться, что такой-то запрос выполняет 2 селекта, а не 10 https://github.com/quick-perf/quickperf
bay73
Написать много однотипных select where и update where проблемы не составляет. А вот лечить приложение, которое умерло из-зв того, что ленивые разработчики положились на JPA и не потестировали его на реальных данных - это реальная проблема. И даже на один такой кривой запрос уходит намного больше сил и времени (к тому же высококвалифицированного), чем на написание сотни однотипных запросов.
St_one
Плюс есть jooq, который сильно упрощает написание запросов. А запросы писать придется в любом случае, даже с hibernate.
gsaw
Нынче зарплату платить дороже разработчикам, чем железо оплачивать или потом посадить спеца и все оптимизировать. Сейчас важно быстрее выдать результат. И jpa упрощает все. Написал контейнер с данными и магия начинает работать. Разработчик концентрируется на логике больше, чем на приземленном.