Команда 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 и всего, что с ним связано