用jOOQ 3.15進行臨時的數據類型轉換

語言: CN / TW / HK

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 ) {}

```

你可以在你的代碼生成器上附加一個轉換器,比如説。

```
com.example.Dimension <![CDATA[ org.jooq.Converter.ofNullable( BigDecimal.class, Dimension.class, Dimension::new, Dimension::value ) ]]> LENGTH|WIDTH|HEIGHT

```

這將使你能夠像這樣查詢你的數據庫。

``` Result> 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 length = field(name("furniture", "length"), NUMERIC); Field width = field(name("furniture", "width"), NUMERIC); Field height = field(name("furniture", "height"), NUMERIC);

Result> result = ctx.select(length, width, height) .from(furniture) .fetch();

```

像往常一樣,通常的靜態導入是隱含的。

``` import static org.jooq.impl.DSL.; import static org.jooq.impl.SQLDataType.;

```

但是代碼生成最終只是為了方便。你總是可以用jOOQ的代碼生成器實現一切,也可以不用它(儘管我建議你儘可能地使用代碼生成!)。因此,為了重新使用我們的Dimension 數據類型,在歷史上,你可以這樣做。

``` DataType type = NUMERIC.asConvertedDataType( Converter.ofNullable( BigDecimal.class, Dimension.class, Dimension::new, Dimension::value )
);

Table<?> furniture = table(name("furniture")); Field length = field(name("furniture", "length"), type); Field width = field(name("furniture", "width"), type); Field height = field(name("furniture", "height"), type);

Result> result = ctx.select(length, width, height) .from(furniture) .fetch();

```

這已經非常整齊了。但同樣的,你要創建一個Field 的引用,總是 使用這個轉換器。也許,你希望轉換隻適用於這一個查詢?有了臨時轉換器,就沒有問題了寫下這個。

``` Table<?> furniture = table(name("furniture")); Field length = field(name("furniture", "length"), NUMERIC); Field width = field(name("furniture", "width"), NUMERIC); Field height = field(name("furniture", "height"), NUMERIC);

Result> 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 { U from(T databaseObject); T to(U userObject); Class fromType(); Class 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> 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中很有用。