Как стать автором
Обновить

Спасаемся от Spring: есть ли альтернативы репозиторным фреймворкам? Часть вторая. Альтернативы

Уровень сложностиСредний
Время на прочтение19 мин
Количество просмотров5.6K
Всего голосов 17: ↑17 и ↓0+24
Комментарии32

Комментарии 32

Больше 10 лет назад, в одном банке на проекте, на коленке за пару дней был собран SQL парсер для FileNET, yже не припомню деталей, чем то похожим на JavaCC.

Похожий пример грамматики SQL

Использовалось для проверки и модификации запросов в репозиторий документов сторонними сервисами. Репозиторий увеличивался на пару миллиардов документов в год.

С JPA тоже наигрались тогда, закончилось рукописными запросами.

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

А вот самое интересное не рассмотрели. Может быть конечно что-то изменилось, но как помню, самой большой проблемой JOOQ было отсутствие нормального маппинга результатов запроса. Если результаты плоские или не сильно сложные то нет проблем, если же у нас что-то чуть сложнее, то JOOQ превращается в тыкву.
Вот сейчас посмотрел у них на сайте, а воз и ныне там:

Не очень понятно, что имеется в виду под более сложными структурами. Типа в поле лежал JSONB и его хочется сразу в разухабистый объект десериализовать? Ну да, так сделать нельзя, но я б не назвал это "большой проблемой". Всегда можно перемапить рекорд джука в другой класс, String распарсить как JSON и перемапить в объект. Если, конечно, не стоит вопрос дикого перфоманса и экономии памяти.

Не очень понятно, что имеется в виду под более сложными структурами.

Множественные вложения в отношении many-to-one|one-to-many|many-to-many

Ну да, так сделать нельзя, но я б не назвал это "большой проблемой". Всегда можно перемапить рекорд джука в другой класс

А можно просто cразу взять MyBatis. Да нет типизации запросов, зато можно написать запрос любой сложности и получить автоматический маппинг с коробки и не городить городьбу там где JOOQ превращается в тыкву.

JOOQ хорош там где плоские связи или где сложные запросы с простым результатом. Для более сложных систем брать библиотеку которая просто игнорирует данную проблему смысла нет.

К слову про MyBatis, а как сейчас в нем реализуется выборка значений по нескольким спискам? Условно, для запроса вроде where user_id in (:userIds) and group_id in (:groupIds).

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

У меня был подобный in house проект, язык запросов для поиска, там пришлось указывать source для полей, для маппинга. По аналогии с алиасами таблиц в sql.

Поиск был в гетерогенных репозиториях, черт ногу сломит, и rdbms и nosql, и иерархические агрегаторы с merge типа union в sql. Иначе пришлось бы всю кухню выносить на бизнес уровень.

Тема связанных сущностей не раскрыта. Как, например, выглядит запрос на получение заказа (по id) и всех его блюд? В Spring Data JPA это весьма просто.

Кстати, так и не понял зачем right join в запросе из примера:

SELECT restaurants.id, restaurants.name 
FROM restaurants
    RIGHT JOIN dishes ON restaurants.id = dishes.restaurant_id
    RIGHT JOIN orders ON dishes.id = orders.dish_id
WHERE orders.user_id = :userId

Еще есть Jimmer: https://github.com/babyfish-ct/jimmer
Jimmer стремится заполнить нишу между Hibernate и jOOQ, сочетая:
• Легкость запросов реляционных данных (как в Hibernate)
• Типобезопасность и прозрачность (как в jOOQ)
• Поддержку для DTO с вложенными связями

Использовали Jooq в проде в 2019 - 2020 годах. Все было хорошо пока не уперлись в специфичный для БД синтаксиc, который не переводился на DSL ферймворка. Например работа с json в Postgresql.

В итоге переехали тогда на SpringJPA где спец. запросы задавали через Query аннотацию.

Чем старше становлюсь, тем больше убеждаюсь, что лучший вариант это Spring JDBC + тесты с поднятием контекста и БД в testcontainers самый надежный вариант. Если лень писать тесты, то можно попросить LLM :)

Есть либы расширяющии jooq и добавляющая возможность работы с json

Вот честно, не понимаю я смысла в ОРМ. В частности, в примерах автора SQL читается намного лучше, чем эквивалентный ему код – при том, что запросы все очень простые. Как уложить в ОРМ по-настоящему сложный запрос и не сойти с ума (особенно на этапе поддержки, когда понадобится его чуток поменять) – для меня загадка. А полный перевод сколько-нибудь сложного проекта на другую базу нужен примерно никогда. Оно точно того сто̀ит?

Ну не все пишут сложные проекты. Да и что значит "сложный проект" - как раз таки на сложных проектах где сотни таблицы ОРМ помогает управлять этим зоопарком. Просто не нужно упираться в ОРМ как единственный источник обращения к базе, никто не запрещает комбинировать инструменты.

Просто не нужно упираться в ОРМ как единственный источник обращения к базе

Так по-моему, на джаве уже и не пишут иначе, независимо от сложности. Нужна база – цепляй ОРМ. Могу ошибаться, конечно – давненько с ней дела не имел.

смысла в ОРМ

Он понятен из названия - отображение реляционных данных из БД в программные сущности (и обратно).

SQL читается намного лучше, чем эквивалентный ему код

Во-первых, это дело привычки. Во-вторых, код гарантирует синтаксически верный запрос, а также проверку типов, ещё на этапе компиляции.

Как уложить в ОРМ по-настоящему сложный запрос и не сойти с ума

Да как угодно: c помощью типизированных запросов, с помощью запросов на SQL-like языке (JPQL/HQL), да хоть с помощью сырого SQL.

код гарантирует синтаксически верный запрос, а также проверку типов, ещё на этапе компиляции.

Ошибку синтаксиса SQL, наверное, тоже непросто пропустить – если код хоть как-то тестируется. Проверка типов – замечательно. Но не получается, что снимаем одну проблему, а взамен получаем кучу других? На том же хабре весьма регулярно появляются статьи об их успешном (или не очень) преодолении. Как вот эта, например.
Никого не отговариваю, просто интересно.

да хоть с помощью сырого SQL

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

Ошибку синтаксиса SQL, наверное, тоже непросто пропустить – если код хоть как-то тестируется.

Только для этого нужно тестировать, а это уже после компиляции и цикл разработки дольше.

Но не получается, что снимаем одну проблему, а взамен получаем кучу других?

Инженерия она вообще очень часто про компромиссы.

Если технология не позволяет получить нужный результат

Почему не позволяет. Если вам, например, не хочется сложные типобезопасные criteria писать - пишите SQL - вас же никто не ограничивает, а только предоставляет возможность.

Анекдот вспомнился

Дремучим сибирским лесорубам подарили новую японскую бензопилу. Подставили доску:

- Вжик! - сказала японская бензопила.

- Ух ты! - сказали дремучие сибирские лесорубы.

Подставили бревно:

- Вжик! - сказала японская бензопила.

- Ух ты! - сказали дремучие сибирские лесорубы.

Подставили железный лом:

- Крррр....! - сказала японская бензопила.

- Ага бля! - сказали дремучие сибирские лесорубы и ушли рубить лес топорами…

Только для этого нужно тестировать, а это уже после компиляции и цикл разработки дольше.

Да ладно. Вот SQL как раз можно в любой момент проверить – причём не только на синтаксис, а ещё и на логическую правильность. Заходишь в шелл тестовой базы, запускаешь свой запрос и смотришь, что вернул. В случае же с ОРМ результат можно будет увидеть только после сборки, и если ошибся, придётся снова пересобирать всё, или как минимум, какую-то часть проекта. Так что где тут бензопила (вжик...), а где топор – вовсе не так однозначно.

Для простых применений, которых в практике большинство, ОРМ просто позволяет экономить время и довольно удобен. Если говорить о JPA, то ты просто из коробки получаешь целую кучу функционала без написания лишнего бойлеркода. Задал источники данных, описал сущности, репозитории и ты уже можешь делать 80% всего того что тебе потребуется делать на БД. Это реально удобно и реально экономит массу времени.

Ну а если нужно что-то особенно то всегда можно прикрутить нативный запрос или JDBC, или тот-же Жук, никто не запрещает использовать все это в одно проекте.

Как по мне самый удобный и правильный выбор - это JPA + нативные запросы для особо сложных случаев, и JDBC там, где надо прямо что-то совсем специфичное, где нужен полный контроль и максимум производительности.

Для простых применений, которых в практике большинство

Так в том-то и противоречие. Вроде написать

SELECT * FROM users;

и так несложно, и никакого особого бойлерплэйта не требует. А вот в сложных случаях, во-первых, ОРМ реализация становится крайне запутанной; во-вторых, вообще непонятно, как её оптимизировать. У меня, например, сидит классный DBA, который может выдернуть из кода любой запрос, погонять его в шелле, подкрутить SQL, добавить индексы и вообще всё, что угодно с ним сделать, и тут же вернуть оптимизированную версию обратно. Если же это ОРМ, то ему для начала пришлось бы преобразовывать код в сырой SQL – и при этом нигде не ошибиться, потом оптимизировать, а потом переносить обратно в формат ОРМ. Очень много ненужных телодвижений и возможностей для ошибки.

Написать несложно, только результаты вам потом надо из рекрдсета вытащить, запихнуть в ДТО, привести все типы, и т.д. и т.п. Это не сложно, конечно, но зачем писать лишний код?

С другой стороны если у вас проект на JPA, то вам никто не помешает в нужных местах использовать нативные запросы на этом же JPA, для особых случаев, либо вовсе использовать JDBC если без него никак.

Написать несложно, только результаты вам потом надо из рекрдсета вытащить, запихнуть в ДТО, привести все типы, и т.д.

Так для этого делается один метод по сути, который принимает на вход строку запроса, список его параметров и тот самый ДТО, который нужно заполнить. Реально чуть сложнее, но всё равно делается один раз.

Более корректно сравнивать не с jpa, а с QueryDsl

Смотрю на это всё и понимаю, как же хорош Entity Framework

Ждём статью сравнение.

Посмотрите еще на fluent validator для полного прозрения :)

Посмотрел, но не понял как это относится к обсуждаемой теме ORM.

Магия синтаксиса, в джаве так сделать не получится. Из за этого можно более выразительно строить статически типизированные запросы

Не увидел магии синтаксиса во FluentValidation. Лямбды в Java уже более 10 лет существуют.

Смотрю на это все и думаю, как же хорош Ruby on Rails.

А вот как код будет выглядеть на ORM в Ruby on Rails:

Restaurant
  .select('restaurants.id, restaurants.name')
  .right_joins(dishes: :orders)
  .where(orders: { user_id: userId })
Зарегистрируйтесь на Хабре, чтобы оставить комментарий