用jOOQ 3.15进行临时的数据类型转换

jOOQ 3.15带有大量的新功能,其中最重要的是。

一个非常有用的、鲜为人知的新特性是"临时数据类型转换"。数据类型转换器和绑定在jOOQ中已经存在很长时间了。他们的目标是允许为常见的JDBC类型使用自定义的数据类型,如StringInteger 。因此,如果你有一个这样的表。

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 附在代码生成器上时,提供上述所有内容总是好的。在TU 之间转换的两个转换函数,以及类字面。你永远不知道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中很有用。

猜你喜欢

转载自juejin.im/post/7126368562373427207