Создание классов для ключей с помощью jOOQ

Иногда бывает неприятно, когда функции имеют такую ​​сигнатуру:

fun doStuff(firstKey: UUID, secondKey: UUID, ...)

Для компилятора все UUID одинаковы, поэтому, надеюсь, проблема обнаруживается базой данных во время выполнения.

Мне нравится, как jOOQ выявляет множество проблем во время компиляции, и я хотел бы заняться этим тоже. Моя цель состоит в том, чтобы для каждого ключа каждой таблицы был свой собственный класс, а также правильно генерировать pojos с этими полями.

Как лучше всего этого добиться? Я придумал следующее:

  • Полноценная реализация JavaGenerator
  • Converters с множеством принудительных сопоставлений типов и вручную созданными ключевыми классами

У кого-нибудь есть опыт в подобном?


person Ynv    schedule 17.01.2019    source источник
comment
Из вашего примера очень сложно понять, какую проблему вы пытаетесь решить. Не могли бы вы подробнее рассказать об этом и показать, как бы вы хотели решить эту проблему на каком-нибудь псевдоязыке?   -  person Alexey Soshin    schedule 17.01.2019


Ответы (2)


Функция jOOQ в дорожной карте

Вы не первый, кому пришла в голову эта идея. Есть ожидающий запрос функции для создания таких классов «оболочки ключа» из коробки (пока недоступен в 3.11): https://github.com/jOOQ/jOOQ/issues/6124

Или это обсуждение: https://groups.google.com/forum/#!topic/jooq-user/53RZqoewa3g

Для этой функции есть дополнительные приложения к вашему. Когда такие типы существуют:

  • Вы можете присоединиться только путем сопоставления первичных / внешних ключей, так как они больше не будут моделироваться произвольными числовыми типами.
  • При объединении или фильтрации невозможно будет забыть столбец в составном ключе, так как составной ключ станет одним значением.

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

Реализуем сами

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

Другие могли сделать что-то подобное, но я не знаю ни одной общедоступной реализации.

Реализация в базе данных

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

create type pk_a as (id bigint);
create type pk_b as (id bigint);

create table t_a(id pk_a primary key);
create table t_b(id pk_b primary key, a pk_a references t_a);

insert into t_a values(row(1)::pk_a);
insert into t_b values(row(2)::pk_b, row(1)::pk_a);

Генератор кода jOOQ должен улавливать эти типы и делать то, что вам нужно. Конечно, есть несколько предостережений, учитывая, что я почти никогда не видел эту практику в дикой природе :-)

person Lukas Eder    schedule 18.01.2019
comment
Спасибо за подробный ответ! Я использую PostgreSQL, и «реализация его в базе данных» выглядит простым решением. Вы, может быть, знаете, есть ли у этого подхода какие-либо предостережения, например производительность порок? - person Ynv; 18.01.2019
comment
Я опробовал предложения с составными клавишами, и они выглядят очень красиво! Однако у меня есть одна проблема: как сравнить эти ключи с полями? Например. .where(TEST.TEST_KEY.eq(TestKey(...)), похоже, не работает, поскольку ожидается TestKeyRecord. - person Ynv; 18.01.2019
comment
Подход UDT также не работает со сгенерированными DAO: github.com/jOOQ/jOOQ/ issues / 5401 - person Ynv; 18.01.2019
comment
@Ynv: На самом деле я не знаю о производительности :) Конечно, будут предостережения, так как я почти не видел эту практику в сети. Это просто то, что я придумал прямо сейчас, и я обязательно изучу это дальше, но я думаю, вам придется выбрать одно из других решений по практическим соображениям. Конечно, теперь вам нужно будет обернуть свои UUID в new TestKeyRecord(uuid), чтобы он работал ... - person Lukas Eder; 18.01.2019
comment
Хорошо спасибо! Думаю тогда дождусь официального решения :) - person Ynv; 18.01.2019

У кого-нибудь есть опыт с чем-то подобным?

Да, я сделал это для проектов Java и Kotlin. Во всех таблицах используются суррогатные ключи на основе UUID и соблюдаются следующие соглашения об именах:

  • Первичный ключ: ADDRESS.ID
  • Внешний ключ: ORDER.ADDRESS_ID или ORDER.SHIPPING_ADDRESS_ID

Для проекта Kotlin я использовал

  • вручную написанные классы XXXId, но для определения Id-класса и его фабричного объекта требуется не более 4 строк Kotlin.
  • вручную написанные классы XXXJooqIdConverter, по 1 строке в каждом. Все сложные части и утилиты спрятаны в общий суперкласс, который рефлексивно извлекает целевой тип из его общих параметров.
  • статический список всех сопоставлений принудительного типа (по одной строке в каждой с небольшой вспомогательной функцией, которая принимает только полное имя класса Id) просто потому, что это очень просто. Вы можете сгенерировать эти сопоставления, полагаясь на соглашения об именах, если хотите.

На правильную настройку уходит около 2 дней, что значительно повысило безопасность типов и ясность кода. ИМХО, это стоит усилий для проекта, рассчитанного на> 1 человеко-год.

(На самом деле я пошел еще дальше и написал небольшой инструмент для создания шаблонов кода, который генерировал указанные выше три артефакта. Это избавляет меня от возни с файлами в разных местах, что может быть подвержено ошибкам, но в основном раздражает. Это потребовало от меня еще одного день для написания, хотя это во многом зависит от настроек вашего проекта.)

Причина, по которой мы решили не решать эту проблему с помощью специальной реализации JooqGenerator, заключается в том, что мы используем те же классы XXXId и в коде домена, который не должен зависеть от артефактов уровня данных по архитектурным причинам. Мы используем почти идентичный подход для перечислений, где оказалось особенно полезным использовать тип домена в модели JOOQ, а не наоборот. Например, легко задокументировать вручную определенное значение перечисления в коде, чего я бы пропустил в сгенерированном классе. Мы также смогли тривиально определить отношения подтипов между ContactId и CustomerId, что было бы очень громоздко для сгенерированных классов.

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

person blubb    schedule 18.12.2019