【问题标题】:Generate classes for keys with jOOQ使用 jOOQ 为键生成类
【发布时间】:2019-06-11 21:32:04
【问题描述】:

当函数具有这样的签名时,有时会变得很麻烦:

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

对于编译器来说,所有 UUID 都是相同的,所以这里的问题希望在运行时被数据库捕获。

我喜欢 jOOQ 在编译时捕获许多问题的方式,我也想解决这个问题。我的目标是让每个表的每个键都有自己的类,并使用这些字段正确生成 pojo。

实现这一目标的最佳方法是什么?我想出了以下几点:

  • 完整的JavaGenerator 实现
  • Converters 有很多强制类型映射和手动创建的关键类

有人有类似的经验吗?

【问题讨论】:

  • 从您的示例中很难理解您要解决什么问题。请您详细说明一下,并说明您希望如何用某种伪语言解决它?

标签: kotlin jooq


【解决方案1】:

路线图上的 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 代码生成器应该选择这些类型并为您做正确的事情。当然,可能有很多警告,因为我在野外几乎从未见过这种做法 :-)

【讨论】:

  • 感谢您的详尽回答!我确实使用 PostgreSQL,并且“在数据库中实现它”看起来像是一个简单的解决方案。您是否知道这种方法是否有任何警告,例如性能恶习?
  • 我已经尝试了使用复合键的建议,它看起来真的很棒!我确实有一个问题:如何将这些键与字段进行比较?例如。 .where(TEST.TEST_KEY.eq(TestKey(...)) 似乎没有像预期的那样工作。
  • UDT 方法似乎也不适用于生成的 DAO:github.com/jOOQ/jOOQ/issues/5401
  • @Ynv:我实际上不知道性能 :) 当然,会有一些警告,因为我几乎没有在网上看到这种做法。这只是我现在想出的东西,我肯定会进一步调查,但我认为出于实际原因,您将不得不选择其他解决方案之一。当然,您现在必须将 UUID 包装在 new TestKeyRecord(uuid) 中才能正常工作...
  • 好的,谢谢!我想我会等待官方的解决方案:)
【解决方案2】:

有人有类似的经验吗?

是的,我为 Java 和 Kotlin 项目做过。所有表都使用基于 UUID 的代理键并遵循以下命名约定:

  • 主键:ADDRESS.ID
  • 外键:ORDER.ADDRESS_IDORDER.SHIPPING_ADDRESS_ID

对于 Kotlin 项目,我使用了

  • 手动编写的 XXXId 类,但定义一个 Id-Class 及其工厂对象不超过 4 行 Kotlin。
  • 手动编写的 XXXJooqIdConverter 类,每个 1 行。所有困难的部分和实用程序都隐藏在一个泛型超类中,该超类反射性地从其泛型参数中提取目标类型。
  • 所有强制类型映射的静态列表(每行有一个小帮助函数,只接受完全限定的 Id 类名),仅仅是因为它非常简单。如果愿意,您可以根据命名约定生成这些映射。

这需要大约 2 天的时间才能正确设置,并极大地提高了我们的类型安全性和代码清晰度。恕我直言,对于估计 > 1 人年的项目来说,这是非常值得的。

(实际上,我更进一步,编写了一个小的“代码脚手架”工具,可以生成上述三个工件。这使我不必在不同的地方摆弄文件,这可能容易出错但很烦人。这个花了我一天的时间来写,虽然这很大程度上取决于你的项目设置。)

我们选择不通过自定义JooqGenerator 实现来解决这个问题的原因是我们在域代码中也使用了相同的 XXXId 类,出于架构原因,这些类不能依赖于数据层工件。我们对枚举使用了几乎相同的方法,事实证明,在 JOOQ 模型中使用域类型而不是反之亦然特别有用。例如,在代码中记录手动定义的枚举值是微不足道的,这是我在生成的类中会错过的。我们还能够轻松地定义 ContactIdCustomerId 之间的子类型关系,这对于生成的类来说会非常麻烦。

我可以提供一些零碎的东西来给你一个更好的主意,但不幸的是不是整个实现,因为那是我自己不拥有的专有代码。

【讨论】:

    猜你喜欢
    • 2019-03-18
    • 2021-12-29
    • 2021-01-27
    • 2021-01-22
    • 1970-01-01
    • 2014-08-12
    • 2017-08-15
    • 2018-03-07
    • 2015-09-16
    相关资源
    最近更新 更多