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

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

На Kotlin на JPA Criteria Query пример мог бы выглядеть так:

    fun findSomeone(): List<Person> {
        return repository.findAll { root, query ->
            val firstName = root[Person_.firstName]
            val lastName = root[Person_.lastName]
            or((firstName equal "Олег") and (lastName equal "Петров"),
               (firstName equal "Мария") and (lastName equal "Попова"))
        }
    }

Если воспользоваться hibernate-jpamodelgen, включить context receivers и написать немного своих расширений.

Наверное это индивидуально, но я перестаю понимать запросы на Criteria API, где есть хотя бы пару джоинов и условий по ним. Причем, пока ты сам это пишешь, все понятно. Но через месяц сходу непонятно даже самому, не говоря уже о коллегах.

есть JPA который в своем деле очень хорош, но слишком много тянет на себя одеяла и забирает контроль

Для таких случаев есть Spring Data Jdbc. Простые запросы не надо писать руками, достаточно просто правильно назвать метод в интерфейсе, как в Spring Data JPA. С другой стороны, у вас больше контроля и нет монструозности JPA с сессиями, контекстами, кэшами и вот этим всем.

Больше года использую jooq. Пришёл к выводу, что наши запросы либо слишком простые, чтобы использовать jpa и hibernate, либо слишком сложные, чтобы использовать jpa и hibernate.

Что понравилось в жуке: в нём сильно меньше магии и конвенций, которые нужно изучать, плюс отличная документация и автор, который отвечает на вопросы на SO.

Какой генератор кода вы используете ? Может есть то что было удобно в JPA и чего нет в JOOQ ?

Уже года 3 знаком с jooq и теперь везде стараюсь использовать его. Поначалу вымораживала концепция «table first», но потом, после некоторой возни с gradle и настройки регенерации сущностей по одной команде стало норм. Даже более удобно, чем в хибере/jpa, где сущности надо все же руками кодить. Конечно не хватает спринговой магии, где простые запросы можно через интерфейсный метод описать, но с другой стороны у jooq в части сложных запросов гибкости в разы больше, да и визуально легче читать.

Один большой минус у jooq - это то что в сложных проектах логика слоя данных течёт во все соседние слои, будь то сервисный слой с БЛ, слой контроллеров. В спринг дата все же слой репозиториев более явно выделен, чем в случае с jooq, где запрос можно писать в любом месте, чем ленивые разработчики и пользуются: «да это же маленький запросик, напишу-ка я его прям в контролере, чо ходить-то за данными далеко». И получается бардак, непонятно что где лежит. Исправляется кодостайлом и дисциплиной, но все же…

«table first» подразумевается что база данных с таблицами должна существовать на момент генерации классов или вообще в принципе необходима для старта разработки ? Или можете, пожалуйста, конкретнее описать, я отмечу это в следующей статье.

Ну да. Вы сначала создаете бд, таблицы, хранимые запросы и т.д., а потом запускаете генерацию jooq классов, предварительно настроив сам jooq на соединение с БД. В принципе, существование бд для генерации сущностей, а потом компиляции проекта не обязательно. Я себе настроил gradle так, чтобы он во время компиляции сначала поднимал БД в testcontainers, потом флайвейем накатывал миграции и после генерил jooq классы. Это удобно, когда, например, внес изменения в flyway и хочешь сразу эти изменения увидеть во время разработки. Запустил генерацию и получил новые поля в сущностях. Доступная бд при этом не нужна, но при этом получаешь 100% рабочий код, который еще не проверен на реальной базе данных, в отличии от такого же подхода (без рабочей бд) в JPA/ Hibernate, когда написал entity руками, но мог и ошибиться в чем угодно при описании. Такой же механизм у меня работает в CI/CD. Файлы jooq я в репу не кладу, они генерятся на лету.

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

Просто есть разные способы генерации кода и один из них это с помощью DDL на основе скриптов миграции, к примеру Flyway или Liquibase, вот тут в документации можете ознакомится. То есть в вашем случае получается вы делаете лишнюю работу тем что поднимаете тест контейнер вместо того что бы сразу, относительно DDL скриптов, генерировать классы. Хотя и ваш способ имеет место быть.

Прикольная конечно штука, но смущает один момент - "it applies all your DDL increments to an in-memory H2 database". По моему опыту не все SQL стейтменты разных БД могут быть отражены в H2 корректно. Сталкивался с таким несколько раз, в эпоху, когда еще testcontainers не были распространены, но в тестах приходилось использовать H2. По памяти, постгресовский тип UUID неправильно будет отражен в моих jooq классах. Вроде INT4, INT8 того же постгреса смапятся чуть по другому. Если у меня в БД еще подключены модули, например гео-типов postgis, то H2 их не поймет. Так что инструмент довольно ограниченный, хотя я могу ошибаться, не пользовался же.

А, оказывается внизу уже ответили

Я обычно на Gradle или Maven делаю пайплайн, который подымает в docker целевую СУБД, потом с помощью Flyway или Liquibase накатывает схему, а потом запускает кодогенерацию jOOQ. Пайплайн запускается отдельной командой, сгенерированный код коммитится в Git.

Логика следующая - схема меняется не часто, поэтому при каждой сборке нет смысла запускать кодогенерацию. Когда нужно, редактирую схему, запускаю пайплайн кодогенерации, правлю зависимости, результат комичу в Git.

Можно поинтересоваться, почему именно так ? Почему не через то же дополнение для генерации кода - DDLDatabase, который я описал выше ? Может в тот момент когда вы столкнулись с этим, еще не было реализовано данное дополнение или вы считаете, что использовать реальную базу, на которую вы накатили самостоятельно скрипты лучше чем просто относительно скриптов ?

Насколько я понял, DDLDatabase завязан на H2. А в моих схемах часто встречаются специфичные для вендора СУБД конструкции - типы данных, хранимые процедуры, индексы. И мне важно, чтобы по ним генерировался корректный DSL. А DDLDatabase предлагает просто игнорировать такие конструкции - мне это не подходит.

да, вы правы

Все то же самое. Генерация отдельной командой, вне обычного билда. Только я не коммичу таблицы, так как запуск контейнера и генерация занимают секунд 10. Не критично мне. Как раздуется БД, так, что генерация будет идти в разы дольше, то наверное нужно будет все же складывать jooq классы в репу.

Как вы отслеживаете ситуации, когда у вас новая версия сервиса применила миграции к бд, а старый сервис при этом перезапустился? Например, при а/б тестах.

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

Я не тестировщик, для меня правильный код существует только в одной точке временной оси, но насколько я знаю - разные тестовые стенды. Плюс, если какие-то реально breaking changes в структуре БД, то тестировщики явно предупреждаются насчет сложных миграций, там уже они сами разбираются.

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

Единственный минус жука: мне не всегда подходят pojo, которые он генерирует, но эта проблема легко решается собственными dto и мапперами (бойлерплейт или контроль?). В жуке есть встроенный механизм для маппинга результата запроса буквально во что угодно. Таким образом, абсолютно всё, что касается работы с бд, не выходит дальше репозитория.

С этой точки зрения, жук, на мой взгляд, не отличается от других орм в части структурирования кода.

Я в комментарии описал пример как так получается. Может я немного неправильно выразился - jooq позволяет вольности в написании кода запросов где угодно, в отличии от Спринг дата, где программист вынужден писать запросы под аннотацией '@Repository' , да еще и в специальном наследуемом интерфейсе. Короче, бьет по рукам и не дает писать где захотел. Конечно же, дисциплиной, код-ревью течку логики в жуке остановить можно, но когда проект старый, сложный, написанный не одним поколением разработчиков разного уровня - то такое всплывает. У меня так было на проекте одной криптобиржи, запросы в то в сервисном слое, то в контроллерах. У меня зудило и я создавал внеплановый ПР и переносил эту логику в репослой.

Начинаем через 10 минут! Подключайтесь к трансляции!

https://t.me/oraclemasters

 

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории