是否可以更改 jOOQ DSL 查询的 SELECT/WHERE

Posted

技术标签:

【中文标题】是否可以更改 jOOQ DSL 查询的 SELECT/WHERE【英文标题】:Is it possible to alter the SELECT/WHERE of jOOQ DSL query 【发布时间】:2021-11-03 21:38:18 【问题描述】:

我想操作一个 jOOQ DSL 查询来改变它的 SELECT 列和 WHERE 条件。

例如:

DSLContext ctx = ...;
SelectHavingStep query = ctx.select(MyEntity.MY_ENTITY.ZIP, DSL.count(MyEntity.MY_ENTITY.ZIP))
   .from(MyEntity.MY_ENTITY)
   .where(MyEntity.MY_ENTITY.ID.gt("P"))
   .groupBy(MyEntity.MY_ENTITY.ZIP);

用例 1:

我想将上述查询传递给一个实用程序类,该实用程序类将使用不同的 SELECT 生成相同的查询,例如:

ctx.select(DSL.count())
   .from(MyEntity.MY_ENTITY)
   .where(MyEntity.MY_ENTITY.ID.gt("P"))
   .groupBy(MyEntity.MY_ENTITY.ZIP);

这个特殊的例子是为了能够创建显示查询总行数的分页结果。

用例 2:

我想将上述查询传递给一个实用程序类,该实用程序类将使用修改后的 WHERE 子句生成相同的查询,例如:

SelectHavingStep query = 
  ctx.select(MyEntity.MY_ENTITY.ZIP, DSL.count(MyEntity.MY_ENTITY.ZIP))
     .from(MyEntity.MY_ENTITY)
     .where(
        MyEntity.MY_ENTITY.ID.gt("P")
        .and(MyEntity.MY_ENTITY.ZIP.in("100", "200", "300"))
      )
     .groupBy(MyEntity.MY_ENTITY.ZIP);

这个特定示例是为了进一步限制基于某些条件的查询(即基于执行查询的用户的数据可见性)。

这可能吗?

目前,我在应用程序代码中的查询构造时使用帮助类来执行此操作。我想将责任转移到图书馆,以便可以透明地对应用程序执行。

谢谢。

【问题讨论】:

【参考方案1】:

您不应该尝试更改 jOOQ 对象,而应该尝试以功能方式动态创建它们。有多种方法可以实现您的用例,例如

用例 1:

可以在此处查看通用分页查询的方法:https://blog.jooq.org/calculating-pagination-metadata-without-extra-roundtrips-in-sql/

理想情况下,您将避免COUNT(*) 查询的额外往返并使用COUNT(*) OVER () 窗口函数。如果这在您的 SQL 方言中不可用,那么您可以这样做:

public ResultQuery<Record> mySelect(
    boolean count,
    Supplier<List<Field<?>>> select,
    Function<? super SelectFromStep<Record>, ? extends ResultQuery<Record>> f
) 
    return f.apply(count ? ctx.select(count()) : ctx.select(select.get()));

然后像这样使用它:

mySelect(false, 
    () -> List.of(MY_ENTITY.ZIP),
    q -> q.from(MY_ENTITY)
          .where(MY_ENTITY.ID.gt("P"))
          .groupBy(MY_ENTITY.ZIP)
).fetch();

这只是一种方法。还有很多其他的,请参阅下面的链接。

用例 2:

只需将上述示例更进一步,提取用于在另一个函数中创建WHERE 子句的逻辑,例如

public Condition myWhere(Function<? super Condition, ? extends Condition> f) 
    return f.apply(MY_ENTITY.ID.gt("P"));

现在使用如下:

mySelect(false, 
    () -> List.of(MY_ENTITY.ZIP),
    q -> q.from(MY_ENTITY)
          .where(myWhere(c -> c.and(MY_ENTITY.ZIP.in("100", "200", "300")))
          .groupBy(MY_ENTITY.ZIP)
).fetch();

同样,有许多不同的方法可以解决这个问题,具体取决于什么是“公共部分”,以及什么是“用户定义的部分”。您还可以对 MY_ENTITY 表进行抽象并传递生成实际表的函数。

更多信息

另请参阅以下资源:

https://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql/ https://blog.jooq.org/a-functional-programming-approach-to-dynamic-sql-with-jooq/

【讨论】:

卢卡斯,感谢您的及时回复。目前,我正在做类似于您建议的事情,使用提供包装器功能(用于条件)并强制调用包装器的命令模式。我的想法是事后(在创建查询之后)这样做,以减少开发人员错误将包装器放在错误位置的机会。再次感谢。 @AlejandroAbdelnur:嗯,您始终可以通过DSLContext.selectQuery() 使用“经典”模型 API,它会生成(仅附加的)可变对象。我一般不推荐它,因为:1)它会在(非常)长期内被移除,2)它远不如 FP 方法优雅。 我有一个案例,一个辅助函数想要同时添加一个 JOIN 子句和一个 WHERE 子句项,我不想让调用者担心这个,我只想申请导致将这两个级别的项目插入查询树的逻辑。因此,当您尝试消除冗余代码和出错机会时,动态查询并不是一条友好的路径。

以上是关于是否可以更改 jOOQ DSL 查询的 SELECT/WHERE的主要内容,如果未能解决你的问题,请参考以下文章

不兼容的类型不允许在 JOOQ dsl 中合并子查询

jOOQ - 选择查询数组

如何在使用 jooq 创建查询字符串时转义单引号?

无法在 Kotlin/Java 中使用 jooq DSL 执行 where 子句

JOOQ - 在选择查询中选择计数

在 jOOq 中,为啥连接与语句构造高度耦合?