Команда Spring АйО в новом переводе разобрала популярный аргумент «я просто использую SQL» и объяснила, почему Hibernate — это не замена, а дополнение к нативным запросам. А ещё — когда ORM действительно необходим, а когда можно без него обойтись.
Иногда мы сталкиваемся со следующим утверждением:
«Мне не нужен ORM, я могу просто использовать SQL.»
Существует две ключевые проблемы с таким подходом:
Во-первых, решение на основе ORM, такое как Hibernate, не конкурирует с SQL. На самом деле, в Hibernate мы чаще всего пишем запросы на HQL — это диалект SQL DML с расширенным функционалом. HQL обычно значительно более лаконичен и гораздо более переносим между различными СУБД, чем нативный диалект SQL конкретной базы данных. Кроме того, в Hibernate Data Repositories запросы на HQL могут проходить проверку типов во время компиляции. Но если вы предпочитаете писать запросы на нативном SQL-диалекте вашей базы данных, Hibernate это тоже поддерживает (native queries). В любом месте, где используется HQL, можно применять и SQL. На практике приложения часто используют комбинацию HQL и SQL.
Комментарий от команды Spring АйО
Здесь очень важно, что в статье речь про конкретно Hibernate Data Repositories. Это конкретная реализация Jakarta Data. И в запросах к Hibernate Data Repositories может использоваться не только JDQL, но и HQL, например. И Hibernate, с помощью своего annotation processor способен проверить ваши JDQL/HQL запросы на этапе сборки проекта на предмет их типобезопасности, например, не обращаетесь ли вы в запросе к property, которой нет в entity.
Во-вторых, основное предназначение ORM — это преобразование прямоугольных результирующих наборов SQL во взаимосвязанные графы объектов Java. SQL сам по себе не способен на это.
Комментарий от команды Spring АйО
Большая часть решений наподобие Jooq или Exposed делать эту конверсию для графов сущностей не умеет, только для тривиальных кейсов. В этом их корневое отличие от ОRМ.
Ручное написание подобного кода, как правило, приводит к громоздкому, хрупкому решению, которое трудно оптимизировать впоследствии. ORM позволяет описывать переиспользуемые преобразования декларативно. В Hibernate такие преобразования задаются через аннотации или XML. Аспекты, связанные с производительностью, такие как загрузка ассоциаций и кэширование, могут быть заданы отдельно, что снижает затраты на последующую оптимизацию.
В крайнем случае, некоторые разработчики используют Hibernate только для отображения объектов, а все запросы пишут на нативном SQL. Это редкий, не рекомендуемый, но допустимый подход.
На шкале вариантов использования
Разные прикладные программы используют классы сущностей в разной степени. Здесь можно выделить два крайних подхода:
Одни программы никогда не возвращают графы сущностей в результате запросов. Вместо этого результаты представляются в упрощённой, «плоской» форме — возможно, в виде Java-records. В этом случае каждая запись отражает результат запроса, а не сущность. Связи между сущностями в такой плоской структуре явно не представлены. Тем не менее, такие программы могут использовать классы сущностей для простых CRUD операций.
Комментарий от команды Spring АйО
Это use case для Jooq и Exposed и тому подобных решений. Это как раз их ниша.
Другие программы полностью опираются на предметную модель, основанную на сущностях. То есть каждый запрос возвращает граф экземпляров сущностей, при этом некоторые связи подгружаются через SQL JOIN, а другие остаются незагруженными. В таких программах крайне важно иметь типизированное представление предметной области, реализованное в Java.
Очевидно, что второй тип программ получает значительно больше выгоды от использования ORM-решений вроде Hibernate. Хотя Hibernate можно применять и в первом типе программ, где он тоже даёт определённые преимущества, называть ORM необходимостью в этом случае нельзя.
Мы называем эти сценарии «крайними» не случайно: на практике гораздо чаще встречается промежуточный вариант, когда одни запросы возвращают графы сущностей, а другие — плоские представления. Именно для такого смешанного подхода и предназначен Hibernate — это его оптимальная зона применения.
Необходим ли ORM?
Возможно, вы подумаете, что мы так и не ответили на главный вопрос: действительно ли ORM когда-либо нужен?
В определённом смысле, ответ может быть «нет» — и сразу по двум причинам.
Во-первых, строго говоря, нам вовсе не обязательно использовать более высокоуровневую абстракцию, если доступен низкоуровневый API. Однако суть программной инженерии как раз и состоит в том, чтобы создавать осмысленные и полезные абстракции, которые снижают трудозатраты. Поэтому подобное утверждение не особенно полезно. Напротив, если высокоуровневая абстракция делает код моего приложения более удобным для сопровождения, то да — она мне «нужна».
Во-вторых, вопрос о том, делает ли ORM код действительно более сопровождаемым, зависит от контекста. Существует класс приложений, которым нет необходимости представлять постоянные данные в виде графов объектов-сущностей — им достаточно использовать record types, отражающие строки SQL-результатов. С другой стороны, если моё приложение всё-таки использует классы сущностей, тогда мне так или иначе потребуется объектно-реляционное отображение. А значит, выбор будет между полноценным, зрелым и мощным решением вроде Hibernate — и… собственным самодельным ORM, к которому я неизбежно приду в попытках решить те же задачи.
До появления Hibernate совсем не было так, что корпоративные Java-разработчики «просто использовали SQL», не сталкивались с объектно-реляционным «несоответствием» и не испытывали трудностей с реализацией сохранения данных. Напротив, самодельные ORM или их прототипы были повсеместны — и почти всегда весьма неудачны. Hibernate пришёл на смену не «чистому SQL», а множеству гораздо более плохих реализаций ORM.
В конечном счёте подавляющее большинство Java-разработчиков по-прежнему считает ORM полезным инструментом для типичных сценариев, встречающихся в корпоративной разработке. Это вовсе не означает, что вам обязательно нужен ORM для той конкретной задачи, над которой вы работаете прямо сейчас. Если ваше решение уже хорошо работает, мы точно не станем советовать что-то менять. Но когда ручное сопровождение объектно-реляционных преобразований начинает вас тормозить — Hibernate всегда готов прийти на помощь.
С другой стороны, если ORM или Hibernate мешает вам, не стесняйтесь использовать что-то другое. Замечательно, что вы применяете Hibernate — но это не значит, что его нужно использовать повсюду.
Заключение
Просто используйте SQL. Вместе с Hibernate — там и тогда, где это действительно полезно.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано
Комментарии (20)
ncix
21.08.2025 12:54самодельные
А чем, собственно, самодельные решения отличаются от не-самодельных? Их какие-то боги пишут вместо простых смертных?
poxvuibr
21.08.2025 12:54А чем, собственно, самодельные решения отличаются от не-самодельных?
Тем, что в не-самодельных решениях накапливаются оптимальные решения для всех типичных кейсов
Их какие-то боги пишут вместо простых смертных?
Собственно да. Не боги, но люди, которые годами занимаются именно этими решениями.
Kerman
21.08.2025 12:54и… собственным самодельным ORM, к которому я неизбежно приду в попытках решить те же задачи
Собственно, да. Проблема реляционных данных в том, что они напрямую на экран пользователя не попадают. Они проходят через промежуточный слой - слой приложения. Которое, как правило, объектно-ориентированное. И в нём удобно работать со строкой из таблицы, как с объектом. Поэтому, как ни крути, а объекто-реляционный маппинг данных будет.
Homyakin
21.08.2025 12:54Основная проблема ORM в том, что это лишняя абстракция, нюансы работы которой нужно знать. И SQL тоже нужно знать обязательно. В итоге получается, что эту абстракцию можно выкинуть и не ухудшать свой developer expirience. Проблема мапперов на мой взгляд переоценена.
poxvuibr
21.08.2025 12:54Основная проблема ORM в том, что это лишняя абстракция, нюансы работы которой нужно знать
Абстракция, нюансы работы которой нужно знать, да. Но что она лишняя это ещё надо доказать.
И SQL тоже нужно знать обязательно.
Знание SQL это общее место. Неожиданно, что ещё надо знать, как работает jdbc.
В итоге получается, что эту абстракцию можно выкинуть и не ухудшать свой developer expirience.
Разрабатывать с помощью голого jdbc без Hibernate? Я подозреваю, что вы уже очень давно этого не делали. Экспириенс ухудшится и ещё как
CBR
21.08.2025 12:54Голый JDBC вовсе не обязателен. Spring Data JDBC умеет маппить результаты SQL-запросов прямо в DTO. По соответствию имен полей DTO и имен (псевдонимов) полей таблицы.
Zlatoverov
21.08.2025 12:54Голый Spring JDBC тоже умеет мапить имен полей DTO и имен (псевдонимов) полей таблицы. Прям из коробки, есть RowMapper для этого, называется - org.springframework.jdbc.core.DataClassRowMapper (если через data class/record) или org.springframework.jdbc.core.BeanPropertyRowMapper (если через setter). При желании всегда можно написать свой универсальный RowMapper под любые цели.
poxvuibr
21.08.2025 12:54Spring Data JDBC умеет маппить результаты SQL-запросов прямо в DTO
И вот дискуссия из ORM не нужны плавно перетекает в плоскость - я не люблю Хибернейт. А люблю Spring Data JDBC. То есть Hibernate, который правильно приготовлен ))
Homyakin
21.08.2025 12:54Разрабатывать с помощью голого jdbc без Hibernate? Я подозреваю, что вы уже очень давно этого не делали.
Уже много лет использую только jdbcTemplate и его производные
poxvuibr
21.08.2025 12:54Уже много лет использую только jdbcTemplate и его производные
А какие производные имеются в виду? Интересно, что получается всё-таки, какая-то ещё абстракция вам нужна и вы почему-то не считаете её лишней. Любопытно чего вам хватает
Homyakin
21.08.2025 12:54JdbcTemplate/NamedParametersJdbcTemplate и в последнее время JdbcClient. Используются чистые sql запросы и именованные параметры внутри них.
ncix
21.08.2025 12:54Но что она лишняя это ещё надо доказать.
Вообще-то наоборот. Следуя принципу KISS и бритве Оккама, доказывать надо, что она нужна.
gizur
21.08.2025 12:54А еще это как правило runtime расходы (да я знаю, что там многое оптимизировано и кэшировано) дополнительно.
Та же генерация кода тоже позволяет делать проверки правильности моделей, но время тратится один раз при компиляции, а не каждый раз (при запуске как минимум).
Kahelman
21.08.2025 12:54Особенно порадовало про прелесть работы с графом объектов вместо «табличного» представления данных.
Вот тут то проблемы и начинаются.
Визуализировать и правильно работать с табличными данными может любая обезьяна, включая чат гпт.А с графами у каждого первого проблемы
ncix
21.08.2025 12:54Может, проблема в том, что объекты и "графы" частенько тащат туда где они не нужны? Или наоборот, объектные данные со сложной структурой, раскидывают по десяткам хитроизнасилованных таблиц педантично следуя всем известным нормальным формам, там где стоило бы сохранить JSON в блоб (или jsonb)?
justmara
21.08.2025 12:54гораздо более переносим между различными СУБД
вот этот аргумент у хибернейта я особенно люблю. а вот эта переносимость между разными субд с сохранением структуры данных и запросов - она сейчас с нами в этой комнате? её кто-нибудь когда-нибудь в реальной жизни встречал?
или всё же речь про смену базы заходит, когда выясняются какие-то фундаментальные проблемы текущего решения и надо полностью переделывать структуру данных под новые требования?
bugy
21.08.2025 12:54Мы для своего решения делали: у разных заказчиков стояли разные БД (oracle и postgres), и по требованиям наша система должна была работать с их базой данных. Наша система устанавливалась на их сервера и не имела доступа в интернет.
Но я с вами согласен, что это прям очень редкий кейс. Более крупные on premise решения могут диктовать заказчикам свои условия.
Для Saas это ещё менее актуально. Хотя иметь возможность свалить на что-то более удобное/дешёвое, это всегда приятно. Никто не любит vendor lock
Max_Kosh
21.08.2025 12:54Мне в современном мире Java скорее SQL — это дополнение к Hibernate. В остальном, как обычно, всё очень непросто определить, как обычно, качели между «дольше разработка, но быстрее скорость работы» и «ввяжемся в бой побыстрее, а потом поймём, что нам делать».
panzerfaust
На слове "громоздкий" сложно на засмеяться. Хибер-то зато легковесный, да. Наш самописный маппер ResultSet -> T это 1 класс на 350 строк, а вся магия в 1 функции на 70 строк. Написано и протестировано один раз более 3 лет назад - работает как часы. Не думаю, что у хибера аналогичная логика принципиально компактнее и быстрее. Как бы не наоборот.
Такие вот пассажи демотивируют доверчивых новичков и заставляют тянуть в проекты тонну зависимостей вместо простых самописных решений.
bugy
Если вам повезло, что за 3 года у вас не усложнилась модель работы с БД, это не значит, что у других тоже не усложнится.
Из проектов с самописной ОРМ, которые я видел, в такую систему изменения вносились несколько раз в год, и каждый раз через страдания.
Хибернейт такой большой не потому что его писали неумелые разработчики, которым платили за каждую строчку кода. А потому что там покрыто огромное количество edge cas'ов, которые вам, к счастью, не попадались. Ну, либо, вы лукавите и за 3 года вносили не одно изменение, но успешно про это забыли.