Rob*_*gan 3 converters jooq jooq-codegen
在jOOQ 代码生成中,可以将转换器分配给NOT NULL字段,如下所示:
<forcedType>
<includeTypes>(?i)^varchar\(\d+\)$</includeTypes>
<userType>String</userType>
<nullability>NOT_NULL</nullability><!-- Converter applies only to NOT NULL columns! -->
<converter>StringCaseConverter</converter>
</forcedType>
Run Code Online (Sandbox Code Playgroud)
然后转换器可以像这样实现:
public class StringCaseConverter extends org.jooq.impl.AbstractConverter<String, String> {
public StringCaseConverter() {
super(String.class, String.class);
}
@Override
public String from(String databaseObject) {
return databaseObject.toLowerCase(); // FIXME: this throws NPE if argument is ever null!
}
@Override
public String to(String userObject) {
return userObject.toUpperCase(); // FIXME: this throws NPE if argument is ever null!
}
}
Run Code Online (Sandbox Code Playgroud)
在典型场景中,如果只是对具有此类列的表执行标准 CRUD,则databaseObject永远不可能null,因此这样的实现似乎就足够了。
然而,Converter API 的 Javadoc(现在)说:
无论上述值
Converter的编码如何null,实现都必须能够处理null值。
这样的转换器可以通过简单地在每个方法中检查并返回 null 来实现 null 安全,如下所示:
public class StringCaseConverter extends org.jooq.impl.AbstractConverter<String, String> {
public StringCaseConverter() {
super(String.class, String.class);
}
@Override
public String from(String databaseObject) {
return databaseObject == null ? null : databaseObject.toLowerCase();
}
@Override
public String to(String userObject) {
return userObject == null ? null : userObject.toUpperCase();
}
}
Run Code Online (Sandbox Code Playgroud)
或者,可以使用它Converter.ofNullable(String.class, String.class, String::toLowerCase, String::toUpperCase)来执行空检查。
但为什么这是必要的呢?在什么情况下该Converter.from(databaseObject)方法可能会接收并预期处理null?
这个问题在概念上与为什么 jOOQ 不能通过类型系统保证可空性并没有真正的不同,自从 jOOQ 支持KotlinGenerator. 然而,它确实从一个有趣的新角度说明了这个问题。
有关打字问题的讨论,请参阅问题 #13999。
\nNOT NULL列变为空的简单情况假设:
\nCREATE TABLE a (i INT NOT NULL PRIMARY KEY);\nCREATE TABLE b (i INT REFERENCES a); -- Optional foreign key\nRun Code Online (Sandbox Code Playgroud)\n简单的查询案例
\n// Explicit left join\nctx.select(B.I, A.I)\n .from(B)\n .leftJoin(A).on(B.I.eq(A.I))\n .fetch();\n\n// Implicit left join\nctx.select(B.I, B.a().I)\n .from(B)\n .fetch();\nRun Code Online (Sandbox Code Playgroud)\n在这两种情况下,尽管在表定义中声明了它,但A.I在查询结果中结果可以为空。NOT NULL
同样,当使用显式UNION或隐式的,例如via GROUPING SETS(包括ROLLUP或CUBE语法糖)时,我们得到相同的行为:
ctx.select(A.I, count())\n .from(A)\n .groupBy(rollup(A.I))\n .fetch();\nRun Code Online (Sandbox Code Playgroud)\n这只是语法糖:
\nctx.select(A.I, count())\n .from(A)\n .groupBy(A.I)\n .unionAll(\n select(inline(null, A.I.getDataType()), count())\n .from(A)\n .groupBy())\n .fetch();\nRun Code Online (Sandbox Code Playgroud)\n当结果行出现时,不可能知道是第一UNION ALL个子查询还是第二个子查询产生了它(我们可以NULL在这种特殊情况下实现检查来识别子查询,但投影可能不可用,或者可能有其他这是不可行的原因)。
简而言之,在 SQL 中,可以在一个上下文中注释为的NOT NULL表达式突然不能在另一个上下文中注释。这就是为什么可空性信息不能被认为是可信的,至少不能通过 Java 的类型系统。
当然,即使 Java 编译器(或 kotlin / scala 编译器)无法强制执行,也可以在整个运行时类型表示中传播可空性。该领域已经做了一些工作,并且还会有更多工作:#11070。在某种程度上,这就是你要问的。您将该Converter实例附加到特定列,并且希望 jOOQ 将 SQL 的代数传播到您的Converter,避免NULL在它看起来是正确的事情时传递给它。
但这“正确的事”是什么?我们之前已经看到,原来的相同表达式NOT NULL可以突然变成NULL。在 jOOQ 查询情况下,A.I仍然是一个简单的查询,但是、、和一些其他运算符NOT NULL的存在将改变查询中的情况。LEFT JOINUNIONGROUPING SETS
即使 jOOQ 确实实现了巧妙的逻辑来以某种方式记住这一点(至少,在可能的情况下),它也不会是一半(?) jOOQ 用户想要的,而且它也不会总是有效。上面的ROLLUP查询产生一个:Result<Record2<Integer, Integer>>。当您将 a 附加Converter<Integer, MyType>到A.ID列时,它将变成Result<Record2<MyType, Integer>>.
您可以使用代码生成将其附加到Converter,A.I也可以使用 临时转换器Converter将其附加到A.I查询中:
Result<Record2<MyType, Integer>> result =\nctx.select(A.I.convertFrom(new MyConverter()), count())\n .from(A.I)\n .fetch();\nRun Code Online (Sandbox Code Playgroud)\n或者,您可以将ResultQuery::coerce其附加Converter到内容甚至类型不安全的查询(例如纯 SQL 模板),这与 jOOQ 相同。
当 jOOQRecord从底层 JDBC 获取 jOOQ 值时,有关该值如何形成的ResultSet信息就会“丢失”。Record特别是,该UNION案例表明不可能知道该列现在是否可以为空。
\n\n作为旁注,该
\nUNION案例还表明只有第一UNION个子查询转换器可用于获取。一个“有趣”的警告。
所以,自从:
\nUNION等等)Converter是用于通用数据类型转换的通用 SPI T <-> U,与上下文无关。Converter使用非空“对象”表示。NULLU您只需在NULL每个实现中处理该情况即可Converter。jOOQ 无法代表您做出任何假设。
这是 jOOQ 为可预测性、逻辑性、简单性而做出的权衡,而不是偶尔巧妙的“改进”(这将不可避免地产生非常奇怪的警告和边缘情况)
\n| 归档时间: |
|
| 查看次数: |
762 次 |
| 最近记录: |