用jOOQ创建空的可选SQL条款

当使用jOOQ创建动态SQL语句时(jOOQ的核心价值主张之一),经常需要有条件地添加查询元素,并有一个默认的 "No-op "行为。对于第一次使用的用户来说,这种默认的 "无操作 "行为并不总是显而易见的,因为jOOQ的API非常庞大,就像任何庞大的API一样,有许多不同的选项来做类似的事情。

如何不这样做

一个常见的陷阱是被诱惑去使用许多XYZStep 类型。这些类型是什么?开发者通常看不到它们,因为开发者以流畅的方式使用jOOQ的DSL API,就像JDK的Stream API一样。比如说。


DSLContext ctx = ...;

Result<?> result =
ctx.select(T.A, T.B)
   .from(T)
   .where(T.C.eq(1))
   .and(T.D.eq(2))
   .fetch();


让我们来分解上面的查询,看看在API中会发生什么。我们可以把每个方法的结果分配给一个局部变量。


SelectFromStep<?> s1 = ctx.select(T.A, T.B);
SelectWhereStep<?> s2 = s1.from(T);
SelectConditionStep<?> s3 = s2.where(T.C.eq(1));
SelectConditionStep<?> s4 = s3.and(T.D.eq(2))

Result<?> result = s4.fetch();


我们之前的流畅API设计博文解释了这种API设计技术。 这不是人们通常对 "静态SQL "语句所做的,但如果他们想有条件地添加最后一个谓词(T.D = 2),他们可能会想这样做,例如:


DSLContext ctx = ...;

SelectConditionStep<?> c =
ctx.select(T.A, T.B)
   .from(T)
   .where(T.C.eq(1));

if (something)
    c = c.and(T.D.eq(2));

Result<?> result = c.fetch();


这是完全有效的API用法,但我们不推荐这样做,因为它非常混乱,导致客户端代码难以维护。而且,这也是绝对没有必要的,因为有更好的方法。

由其部分组成查询

上述方法的问题在于,它试图使用一种强制性的方法,一步一步地将东西添加到查询中。这是许多开发者倾向于构建他们的代码的方式,但是对于SQL(以及由此产生的jOOQ)来说,这可能会变成很难做到的事情。功能性的方法往往效果更好。 注意,不仅整个DSL结构可以分配给局部变量,而且各个SELECT 子句的参数也可以。比如说。


DSLContext ctx = ...;

List<SelectField<?>> select = Arrays.asList(T.A, T.B);
Table<?> from = T;
Condition where = T.C.eq(1).and(T.D.eq(2));

Result<?> result =
ctx.select(select)
   .from(from)
   .where(where)
   .fetch();


事实上,每个jOOQ查询都是一个动态SQL查询。由于jOOQ的API设计,许多查询只是碰巧看起来像静态查询。 同样,我们不会把每个 SELECT 子句参数分配给局部变量,只有真正的动态参数。比如说。


DSLContext ctx = ...;

Condition where = T.C.eq(1);

if (something)
    where = where.and(T.D.eq(2));

Result<?> result =
ctx.select(T.A, T.B)
   .from(T)
   .where(where)
   .fetch();


这看起来已经很不错了。

避免破坏可读性

很多人对这种方法也不满意,因为它使查询的组件变得非本地,破坏了查询的可读性。查询中的谓词是在前面声明的,远离了查询本身。这不是很多人喜欢的推理SQL的方式,而且你也不必这样做!我们完全可以像这样将条件直接嵌入到WHERE 子句中。


DSLContext ctx = ...;

Result<?> result =
ctx.select(T.A, T.B)
   .from(T)

   // We always need this predicate
   .where(T.C.eq(1))

   // This is only added conditionally
   .and(something
      ? T.D.eq(2)
      : DSL.noCondition())
   .fetch();


神奇之处在于上面使用的 [DSL.noCondition](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/impl/DSL.html#noCondition())的用法,它是一个伪谓词,不产生任何内容。它是一个占位符,在这里需要一个org.jooq.Condition 类型,但实际上并没有实现一个类型。还有。

  • [DSL.trueCondition](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/impl/DSL.html#trueCondition()):TRUE1 = 1 在SQL中,身份为AND 操作减少。
  • [DSL.falseCondition](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/impl/DSL.html#falseCondition()):FALSE1 = 0 在SQL中,是指OR 操作的缩减。

......但这需要一直考虑这些身份和减法的问题。另外,如果你在一个查询中附加许多这样的trueCondition()falseCondition() ,产生的SQL往往是相当难看的,例如对于需要分析生产性能的人来说。noCondition() 根本就不会产生任何内容。

注意,noCondition()

作为一个身份。如果你的noCondition()WHERE 子句中唯一的谓词,那么就不会有任何WHERE 子句,无论你是使用AND 谓词还是OR 谓词。

jOOQ中的无操作表达式

当使用这样的动态SQL,并在查询中添加条件性的东西时,这样的 "no-op表达式 "就成了强制性的。在前面的例子中,我们已经看到了如何在WHERE 子句中添加一个 "no-op predicate"(同样的方法显然也适用于HAVING 和所有其他使用布尔表达式的子句)。 三个最重要的jOOQ查询类型是。

  • [org.jooq.Field](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Field.html):用于列表达式
  • [org.jooq.Condition](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Condition.html): 用于布尔列表达式/谓词
  • [org.jooq.Table](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Table.html):用于表的表达式

用户可能想在查询中添加所有这些条件。org.jooq.Condition我们已经看到了如何用org.jooq.Conditionorg.jooq.Field投影中的动态列表达式(SELECT 子句)是怎么回事?假设你只想在某些情况下对列进行投影。在我们的例子中,T.B 列是我们并不总是需要的。这很简单!同样的方法也可以使用(假设T.B 是一个字符串列)。


DSLContext ctx = ...;

Result<Record2<String, String>> result =
ctx.select(T.A, something ? T.B : DSL.inline("").as(T.B))
   .from(T)
   .where(T.C.eq(1))
   .and(T.D.eq(2))
   .fetch();


通过DSL.inline()使用内联参数,如果你不想修改投影的行类型,你可以很容易地在投影中产生一个无操作的值。这样做的好处是,你现在可以在一个期望有两列的联盟中使用这个子查询。


DSLContext ctx = ...;

Result<Record2<String, String>> result =

// First union subquery has a conditionally projected column
ctx.select(T.A, something ? T.B : DSL.inline("").as(T.B))
   .from(T)
   .where(T.C.eq(1))
   .and(T.D.eq(2))

   .union(

// Second union subquery has no such conditions
    select(U.A, U.B)
   .from(U))
   .fetch();


你可以更进一步,以这种方式使整个联合子查询成为有条件的!


DSLContext ctx = ...;

Result<Record2<String, String>> result =

// First union subquery has a conditionally projected column
ctx.select(T.A, T.B)
   .from(T)
   .union(
      something
        ? select(U.A, U.B).from(U)
        : select(inline(""), inline("")).where(falseCondition())
   )
   .fetch();


这在语法上要多做一些工作,但很高兴看到在不使查询完全不可读的情况下向jOOQ查询添加条件性的东西是多么容易。所有东西都是在本地使用的。因为现在所有的东西都是一个表达式(而不是一个语句/没有控制流),我们可以把这个查询的一部分分解成辅助方法,这样就可以重用了。org.jooq.Table条件表表达式通常出现在做条件连接的时候。这通常不是孤立的,而是与查询中的其他条件元素一起完成的。例如,如果一些列被有条件地预测,这些列可能需要一个额外的连接,因为它们来自另一个表,而不是无条件地使用的表。比如说。


DSLContext ctx = ...;

Result<?> result =
ctx.select(
      T.A, 
      T.B, 
      something ? U.X : inline(""))
   .from(
      something
      ? T.join(U).on(T.Y.eq(U.Y))
      : T)
   .where(T.C.eq(1))
   .and(T.D.eq(2))
   .fetch();


没有一个更简单的方法来产生条件性的JOIN ,因为JOINON 需要独立提供。对于如上所示的简单情况,这完全没有问题。在更复杂的情况下,可能需要一些辅助方法,或者视图。

结论

在这篇文章中,有两个重要的信息。

  1. XYZStep类型只是辅助类型。它们的存在是为了使你动态构造的SQL语句看起来像静态SQL。但是你不应该觉得有必要把它们分配给局部变量,或者从方法中返回它们。虽然这样做没有错,但几乎总是有更好的方法来编写动态SQL。
  2. 在jOOQ中,每个查询都是一个动态查询。这就是使用表达式树来组成SQL查询的好处,就像jOOQ内部使用的表达式树。你可能看不到表达式树,因为jOOQ DSL API模仿了静态SQL语句的语法。但在幕后,你正在有效地构建这个表达式树。表达式树的每一部分都可以动态地产生,从局部变量、方法或表达式,如条件表达式。我很期待在动态SQL中使用新的JEP 361开关表达式。就像SQLCASE 表达式一样,一些SQL语句部分可以在客户端动态构建,然后再传递给服务器。

一旦这两点被内化,你就可以写出非常花哨的动态SQL,包括使用FP方法来构造数据结构,比如jOOQ查询对象

猜你喜欢

转载自juejin.im/post/7126381088301613093