jOOQ 3.15.1 新的 ad-hoc 转换 API 是不是在 Kotlin 中工作?

Posted

技术标签:

【中文标题】jOOQ 3.15.1 新的 ad-hoc 转换 API 是不是在 Kotlin 中工作?【英文标题】:Does the jOOQ 3.15.1 new ad-hoc conversion API work in Kotlin?jOOQ 3.15.1 新的 ad-hoc 转换 API 是否在 Kotlin 中工作? 【发布时间】:2021-10-24 10:29:15 【问题描述】:

我正在尝试将工作示例从 jOOQ 3.15's New Multiset Operator Will Change How You Think About SQL 翻译成 Kotlin,但遇到一些类型推断问题阻止我进行编译。

到此为止:

使用sakila 架构 从 jOOQ 代码生成步骤生成 Kotlin 使用来自kotlinx.coroutines.jdk-8async() extension method 使Java 的CompletionStage 未来适应Kotlin 协同例程
    data class Actor(val firstName: String?, val lastName: String?)
    data class Film(val title: String?, val actors: List<Actor>, val 
    categories: List<String>)

    ...

    val future = dsl.select(
        FILM.TITLE,
        multiset(
            select(
                FILM_ACTOR.actor().FIRST_NAME,
                FILM_ACTOR.actor().LAST_NAME
            )
                .from(FILM_ACTOR)
                .where(FILM_ACTOR.FILM_ID.eq(FILM.FILM_ID))
        ).`as`("actors").convertFrom  it.map( mapping(::Actor) ) , // <--[1]
        multiset(
            select(FILM_CATEGORY.category().NAME)
                .from(FILM_CATEGORY)
                .where(FILM_CATEGORY.FILM_ID.eq(FILM.FILM_ID))
        ).`as`("films").convertFrom  it.map  r -> r.getValue(0)  
    )
        .from(FILM)
        .orderBy(FILM.TITLE)
        .fetchAsync()

    val result = future.await().map( mapping(::Film) ) // <--[2]

我遇到的问题在代码中的 [1] 和 [2] 处注明。

在 [1] 中,当获取构造函数引用时,它归结为 Kotlin 与 Java:

Type mismatch.
Required:  ((String?, String?) → Actor!)!
Found:     KFunction2<String, String, Actor>

... 很接近,但没有雪茄。可能有一种我缺少的简单方法来适应它(除了手动调用构造函数)。

在 [2] 处,Kotlin 无法决定选择 mapping(...) 的哪个重载。

我目前不明白为什么每个案例的问题都不同。

请问mapping(/*constructor reference*/): RecordManager&lt;R,U&gt; 是否有望在 jOOQ 3.15.1 中与 Kotlin 一起使用? 如果没有,我可以合理地做些什么来解决它?

【问题讨论】:

我只想说,除了这个小的互操作边缘案例,我完全对 MULTISET 功能感到敬畏。使用 jOOQ 就像驾驶火箭! 关于await() 增加了上述内容的复杂性,我将讨论移至SO: Does jOOQ play nicely with kotlin coroutines? 【参考方案1】:

关于问题 [1],有时不能使属性为空:您的整个应用程序将被此可空属性污染。即使该列在数据库中可能被指定为 NOT NULL,jOOQ (3.15.1) 目前不提供非空记录字段(因为它们不能保证在例如外连接中是非空的)。

对于不可为空的属性,到目前为止我发现的最简单的解决方案是这种构造:

    multiset(
        select(
            FILM_ACTOR.actor().FIRST_NAME,
            FILM_ACTOR.actor().LAST_NAME
        )
    .from(FILM_ACTOR)
    .where(FILM_ACTOR.FILM_ID.eq(FILM.FILM_ID))
    ).`as`("actors").convertFrom(List::class.java)  it.map  s -> Actor(s.value1()!!, s.value2()!!)  ,

【讨论】:

谢谢,这很有帮助。只需要多几十个字符就可以得到你想要的类型,如果你经常这样做,你可以写一个(Record2&lt;X?,Y?&gt;) -&gt; Z函数并重复使用它。【参考方案2】:

你的错误[1]

您确定您在测试中使数据类中的属性为空吗?错误信息是:

Type mismatch.
Required:  ((String?, String?) → Actor!)!
Found:     KFunction2<String, String, Actor>

后者应该改为KFunction2&lt;String?, String?, Actor&gt;

你的错误[2]

不确定 kotlinx-coroutines 库在这里做了什么,但如果您使用 JDK CompletionStage API,这也适用于我(应用了正确的可空性):

.fetchAsync().thenApply  mapping(::Film) 

也许这也是应用了错误的可空性的副作用

【讨论】:

您对#1 的看法是正确的。我的错 - 我在用我自己的 mapping() 函数添加我的答案时对其进行了排序,然后将这些行添加到问题中。 #2 需要 2 次修复:a) IDEA 已从 jOOQ 生成的表类中导入符号 Film,甚至没有使用我的 data class Film(...),并且 b) 我不得不将 r -&gt; r.getValue(0) 更改为 @ 987654331@。我会修改我的答案来写这个。 @DavidBullock 啊,是的,getValue(int) 方法不知道列的类型。感谢您用自己的答案记录您的发现!我认为这个问题对未来的访问者仍然非常有用,因为他们可能会遇到类似的打字问题,尤其是可空性问题。【参考方案3】:

答:是的!

这是与jOOQ 3.15's New Multiset Operator Will Change How You Think About SQL 的最终工作示例最直接的 Kotlin 等价物,而不会增加 fetchAsync() 的任何复杂性。

数据类声明的属性必须都是可以为空的类型。

data class Actor(val firstName: String?, val lastName: String?)
data class Film(val title: String?, val actors: List<Actor>?, val categories: List<String>?)

然后

    val result = dsl.select(
            FILM.TITLE,
            multiset(
                select(
                    FILM_ACTOR.actor().FIRST_NAME,
                    FILM_ACTOR.actor().LAST_NAME
                )
                    .from(FILM_ACTOR)
                    .where(FILM_ACTOR.FILM_ID.eq(FILM.FILM_ID))
            ).`as`("actors").convertFrom  it?.map( mapping(::Actor) ) ,
            multiset(
                select(FILM_CATEGORY.category().NAME)
                    .from(FILM_CATEGORY)
                    .where(FILM_CATEGORY.FILM_ID.eq(FILM.FILM_ID))
            ).`as`("films").convertFrom  it?.map  r -> r.getValue1()  
        )
            .from(FILM)
            .orderBy(FILM.TITLE)
            .fetch( mapping(::Film) )

陷阱

我包含的问题:

不让我的数据/记录类的所有属性都使用可为空的类型 不小心使用Record1#getValue()而不是Record1#getValue1()来映射电影的名称,因此有一个Any!而不是String! 我的 data class Film(...) 没有被使用,因为 IDEA 导入了 jOOQ 生成的同名类而不是使用我的类

在上述所有情况下,问题都表现为“错误类型推断”问题以及令人眼花缭乱的演示。

我也有:

convertFrom... 块中的 NPE,根据表中的数据,it 可能为空! (在未修改的 Sakila 数据库中,我遇到了author 的问题)

麻烦调用Record#map(RecordMapper) ...有时使用mapping(::MyClass) 来获得RecordMapper(在这种情况下,map() 是正确的调用方式),有时使用带有签名 R -&gt; T(在这种情况下为 map)的 lambda 是正确的调用样式。使用后一种风格,但通过mapping() 提供RecordMapper 稍后会给您错误的类型!例如。

convertFrom  it?.map( mapping(::Actor)  // <- gives List<Actor> in the object graph's type after fetch()

convertFrom  it?.map  mapping(::Actor)  // <- gives List<RecordMapper<Record3<...etc...>,Actor>> in the object's graph type after fetch()

【讨论】:

以上是关于jOOQ 3.15.1 新的 ad-hoc 转换 API 是不是在 Kotlin 中工作?的主要内容,如果未能解决你的问题,请参考以下文章

新的 jooq/gradle 配置不生成任何 jooq 类

如何在 JOOQ 中公开新的 SQL 函数

如何在JOOQ中公开新的SQL函数

Airwatch ad-hoc 分发,无需生成新的 IPA

新的 Flyway 迁移破坏了现有的 jOOQ 生成的代码

Jooq 与 POJO 转换器