jOOQ最大的优势之一是它是一个类型安全的SQL API。"类型安全",在这里,意味着你放在jOOQ查询中的每个对象都有一个定义好的类型,比如:
[Condition](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Condition.html)
[Field](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Field.html)
[Table](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Table.html)
这些可以在jOOQ中以类型安全的方式使用,如:
ctx.select(T.A) // A Field
.from(T) // A Table
.where(T.B.eq(1)) // A Condition
.fetch();
然而,在一些情况下,无论出于什么原因,你想绕过类型安全,包括扩展jOOQ,例如使用普通的SQL模板。在这些情况下,你会传递一个 "字符串 "对象给jOOQ API。但不是每一个这样的字符串对象都是一样的。有哪些不同类型的字符串?在jOOQ API中,有4种主要的字符串类型。
1.绑定值
最明显的字符串类型是绑定值或字面意义。你可以使用明确地创建这些:
// As always, this static import is implied
import static org.jooq.impl.DSL.*;
Field<String> bind = val("abc");
Field<String> literal = inline("xyz");
默认情况下,第一个值会在生成的SQL中产生一个绑定参数标记"?"
,而第二个值会产生一个转义的字符串字面'xyz'
。你可能已经在隐含地这样做了。每当你传递一个String
,而jOOQ API期望的是一个T
,你就会隐含地使用String
来包装你的值。 DSL.val(T)
:
ctx.select(T.A)
.from(T)
.where(T.C.eq("xyz")) // Implicit bind value
.fetch();
这仍然是一个String
值的类型安全用法,因为它确实被包装成了一个Field<String>
。
2.普通SQL模板
每当jOOQ缺少一些供应商的特定功能时,后门就是使用普通SQL模板。你可以为最流行的类型明确地创建普通SQL模板,如上图所示,像这样:
Field<Integer> field = field("(1 + 2)", SQLDataType.INTEGER);
Table<?> table = table("generate_series(1, 10)");
Condition condition = condition("some_function() = 1");
这些表达式现在可以嵌入你的查询中,就像其他的一样:
ctx.select(field)
.from(table)
.where(condition)
.fetch();
另外,在一些查询方法上也存在方便的重载,使之更加简单:
ctx.select(field("(1 + 2)", SQLDataType.INTEGER)) // Not on SELECT
.from("generate_series(1, 10)")
.where("some_function() = 1")
.fetch();
请注意,select()
方法还没有这样的便利API,截至jOOQ 3.13
重要的免责声明:使用这些API,你将把自己暴露在通常的SQL注入风险中,在使用JDBC或JPQL时,从字符串组成的SQL也存在这种风险。千万不要连接纯SQL模板,也不要在这些字符串中使用用户输入。使用模板语言,并将所有的用户输入变成绑定变量。例子:
ctx.select(...) .from(...) .where("some_function() = ?", 1) // Bind variable .fetch(); ctx.select(...) .from(...) .where("some_function() = {0}", val(1)) // Templating .fetch();
如果你在jOOQ的大多数查询API上遇到字符串类型,那是用于纯SQL模板的。所有这些API都被注释为 @org.jooq.PlainSQL
以供额外的文档使用,并通过静态检查器进行预处理,该检查器可用于默认禁止此类API的使用,以增加安全性。
3.名称(标识符)
但是在jOOQ的一些查询API上,字符串对于普通的SQL模板并不方便,而是用于名称和标识符。也就是说,所有的DDL语句在其API中都是这样使用字符串的。 你可以按以下方式明确地创建合格或不合格的标识符:
// Unqualified table identifier
Name table = name("t");
// Qualified column identifier
Name field = name("t", "col");
然后,在你的DDL语句中使用这些标识符,例如,创建表:
ctx.createTable(table)
.column(field, SQLDataType.INTEGER)
.execute();
取决于上下文,限定是必要的或不需要的。在这种情况下,字段的限定是没有必要的。 为了方便起见,你也可以只使用字符串类型在 createTable(String)
API。
ctx.createTable("t")
.column("col", SQLDataType.INTEGER)
.execute();
这些字符串将简单地被包裹在 DSL.name(String)
注意:在jOOQ中,所有的标识符都被默认为带引号(
RenderQuotedNames.EXPLICIT_DEFAULT_QUOTED
)。这有两个好处:
- 特殊字符,更重要的是,关键词冲突在开箱时就得到了正确的处理
- 引号可以防止SQL注入
- 在那些支持引号标识符的方言中,大小写敏感性得到了正确的处理
为这种便利付出的代价是,引号标识符可能会变成大小写敏感,而这是不希望的。为了解决这个问题,你可以使用你的设置关闭引号,例如,通过设置
RenderQuotedNames.EXPLICIT_DEFAULT_UNQUOTED
。但要注意,如果你不先对标识符的名称进行消毒,这将使你再次面临SQL注入的风险。
4.关键词
在jOOQ中,关键词也是字符串。在极少数情况下,你可能想把你的关键词作为字符串表示在一个 org.jooq.Keyword
类型中。主要的好处(从jOOQ 3.13开始)是一个一致的关键词风格。这方面没有方便的API,因为客户端代码很少使用这个功能。只有 DSL.keyword(String)
:
Keyword current = keyword("current");
Keyword current = keyword("time");
你现在可以在纯SQL模板中使用关键字:
Field<Time> currentTime = field(
"{0} {1}",
SQLDataType.TIME,
current, time
);