jOOQ 3.15带有大量的新功能,其中最重要的是。
- 支持MULTISET(类型安全,嵌套集合)
- 通过R2DBC支持Reactive SQL
一个非常有用的、鲜为人知的新特性是"临时数据类型转换"。数据类型转换器和绑定在jOOQ中已经存在很长时间了。他们的目标是允许为常见的JDBC类型使用自定义的数据类型,如String
或Integer
。因此,如果你有一个这样的表。
CREATE TABLE furniture (
id INT NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
length NUMERIC,
width NUMERIC,
height NUMERIC
);
而不是使用BigDecimal
来表示这些维度,你可能更喜欢一个自定义的、更有语义的数字包装类型,比如。
record Dimension(BigDecimal value) {}
而你对Furniture
的Java表示将是。
record Furniture(
Integer id,
String name,
Dimension length,
Dimension width,
Dimension height
) {}
你可以在你的代码生成器上附加一个转换器,比如说。
<configuration>
<generator>
<database>
<forcedTypes>
<forcedType>
<userType>com.example.Dimension</userType>
<converter><![CDATA[
org.jooq.Converter.ofNullable(
BigDecimal.class,
Dimension.class,
Dimension::new,
Dimension::value
)
]]></converter>
<includeExpression>LENGTH|WIDTH|HEIGHT</includeExpression>
</forcedType>
</forcedTypes>
</database>
</generator>
</configuration>
这将使你能够像这样查询你的数据库。
Result<Record3<Dimension, Dimension, Dimension>> result =
ctx.select(FURNITURE.LENGTH, FURNITURE.WIDTH, FURNITURE.HEIGHT)
.from(FURNITURE)
.fetch();
但有时,你不能利用代码生成器。
- 由于某种原因,你无法访问代码生成器的配置
- 你不想在每次查询时都给你的列附加一个转换器
- 你不使用代码生成器,因为你有一个只在运行时才知道的动态模式
进入临时转换器
从jOOQ 3.15开始,我们支持以各种方式为你的Field<T>
表达式注册一个方便的临时转换器。这个功能主要是为了允许将MULTISET
嵌套的集合映射到自定义数据类型的列表(我们敦促你尝试这个功能,你不会再回头了!)。
但是你也可以把这个功能用于任何其他的Field
表达式。假设你不能对上述查询使用代码生成(主要原因还是因为你的模式是动态的)。你可能会写这样的东西。
Table<?> furniture = table(name("furniture"));
Field<BigDecimal> length = field(name("furniture", "length"), NUMERIC);
Field<BigDecimal> width = field(name("furniture", "width"), NUMERIC);
Field<BigDecimal> height = field(name("furniture", "height"), NUMERIC);
Result<Record3<BigDecimal, BigDecimal, BigDecimal>> result =
ctx.select(length, width, height)
.from(furniture)
.fetch();
像往常一样,通常的静态导入是隐含的。
import static org.jooq.impl.DSL.*;
import static org.jooq.impl.SQLDataType.*;
但是代码生成最终只是为了方便。你总是可以用jOOQ的代码生成器实现一切,也可以不用它(尽管我建议你尽可能地使用代码生成!)。因此,为了重新使用我们的Dimension
数据类型,在历史上,你可以这样做。

DataType<Dimension> type = NUMERIC.asConvertedDataType(
Converter.ofNullable(
BigDecimal.class,
Dimension.class,
Dimension::new,
Dimension::value
)
);
Table<?> furniture = table(name("furniture"));
Field<Dimension> length = field(name("furniture", "length"), type);
Field<Dimension> width = field(name("furniture", "width"), type);
Field<Dimension> height = field(name("furniture", "height"), type);
Result<Record3<Dimension, Dimension, Dimension>> result =
ctx.select(length, width, height)
.from(furniture)
.fetch();
这已经非常整齐了。但同样的,你要创建一个Field
的引用,总是 使用这个转换器。也许,你希望转换只适用于这一个查询?有了临时转换器,就没有问题了写下这个。
Table<?> furniture = table(name("furniture"));
Field<BigDecimal> length = field(name("furniture", "length"), NUMERIC);
Field<BigDecimal> width = field(name("furniture", "width"), NUMERIC);
Field<BigDecimal> height = field(name("furniture", "height"), NUMERIC);
Result<Record3<BigDecimal, BigDecimal, Dimension>> result =
ctx.select(length, width, height.convertFrom(Dimension::new))
// ad-hoc conversion here: ^^^^^^^^^^^^^^^^^^^^^^^^^^^
.from(furniture)
.fetch();
有各种不同的overloads of [Field.convert()](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Field.html#convert(org.jooq.Converter))
的各种重载,其中最强大的是接受一个完整的 [Binding](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Field.html#convert(org.jooq.Binding))
或 [Converter](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Field.html#convert(org.jooq.Converter))
的引用。上面这个非常方便,因为它允许你只提供转换器的 "from "Function<T, U>
,省略了Class<T>
、Class<U>
、和 "to "Function<U, T>
。
什么是转换器?
一个Converter
,毕竟是什么?它是对此的一种实现。
public interface Converter<T, U> {
U from(T databaseObject);
T to(U userObject);
Class<T> fromType();
Class<U> toType();
}
其中。
T
是 "JDBC类型",即JDBC API所理解的技术类型,如 或String
BigDecimal
U
是 "用户类型",即你选择的语义类型,用于在你的客户端应用程序中表示数据。Class<T>
是 的一个类字,用于反射目的,例如,在运行时创建一个数组 。T
T[]
Class<U>
是 的一个类字,出于反射的目的需要,例如在运行时创建一个数组 。U
U[]
当把一个Converter
附在代码生成器上时,提供上述所有内容总是好的。在T
和U
之间转换的两个转换函数,以及类字面。你永远不知道jOOQ是否在某些特定的操作中需要它们。
但在临时转换的情况下,你通常只需要from
(读)或to
(写)中的一个函数。为什么要重复其他所有的呢?因此,这些选项。
// A "read-only" field converting from BigDecimal to Dimension
height.convertFrom(Dimension::new);
// Like above, but with an explicit class literal, if needed
height.convertFrom(Dimension.class, Dimension::new);
// A "write-only" field converting from Dimension to BigDecimal
height.convertTo(Dimension::value);
// Like above, but with an explicit class literal, if needed
height.convertTo(Dimension.class, Dimension::value);
// Full read/write converter support
height.convert(Dimension.class, Dimension::new, Dimension::value);
height.convert(Converter.ofNullable(
BigDecimal.class,
Dimension.class,
Dimension::new,
Dimension::value
));
"只读 "和 "只写 "转换之间有什么区别?很简单。看看这些查询。
Result<Record1<Dimension>> result =
ctx.select(height.convertFrom(Dimension::new))
.from(furniture)
.fetch();
ctx.insertInto(furniture)
.columns(height.convertTo(Dimension::value))
.values(new Dimension(BigDecimal.ONE))
.execute();
所以,综上所述。
- 只读的临时转换器在投影中很有用 (
SELECT
) - 只写的特设转换器在谓词(
WHERE
)或DML中很有用。