自定义数据类型绑定
适用于 ✅ 开源版 ✅ 专业版 ✅ 企业版
jOOQ 支持所有标准的 SQL 数据类型,即 java.sql.Types 中包含的类型。但您的领域模型可能更具体,或者您可能正在使用供应商特定的数据类型,例如 JSON、HSTORE 或其他数据结构。如果是这种情况,本节将适合您,我们将了解如何创建 org.jooq.Converter 类型和 org.jooq.Binding 类型。
转换器
注入自定义数据类型最简单的用例是使用 org.jooq.Converter。 Converter 可以从数据库类型 <T> 转换为用户定义的类型 <U>,反之亦然。您将实现此 SPI
public interface Converter<T, U> {
// Your conversion logic goes into these two methods, that can convert
// between the database type T and the user type U:
U from(T databaseObject);
T to(U userObject);
// You need to provide Class instances for each type, too:
Class<T> fromType();
Class<U> toType();
}
例如,如果您想使用 Java 8 的 java.time.LocalDate 用于 SQL DATE 和 java.time.LocalDateTime 用于 SQL TIMESTAMP,您可以编写如下转换器
import java.sql.Date;
import java.time.LocalDate;
import org.jooq.Converter;
public class LocalDateConverter implements Converter<Date, LocalDate> {
@Override
public LocalDate from(Date t) {
return t == null ? null : LocalDate.parse(t.toString());
}
@Override
public Date to(LocalDate u) {
return u == null ? null : Date.valueOf(u.toString());
}
@Override
public Class<Date> fromType() {
return Date.class;
}
@Override
public Class<LocalDate> toType() {
return LocalDate.class;
}
}
此转换器现在可以在各种 jOOQ API 中使用,最重要的是创建新的数据类型
DataType<LocalDate> type = DATE.asConvertedDataType(new LocalDateConverter());
反过来,数据类型可以与任何 org.jooq.Field 一起使用,即与任何 列表达式一起使用,包括 Plain SQL 或基于 名称的表达式
DataType<LocalDate> type = DATE.asConvertedDataType(new LocalDateConverter());
// Plain SQL based
Field<LocalDate> date1 = DSL.field("my_table.my_column", type);
// Name based
Field<LocalDate> date2 = DSL.field(name("my_table", "my_column"), type);
绑定
虽然转换器对于简单的用例非常有用,但当您需要在 JDBC 级别自定义数据类型交互时,org.jooq.Binding 会很有用,例如,当您要绑定 PostgreSQL JSON 数据类型时。自定义绑定实现以下 SPI
public interface Binding<T, U> extends Serializable {
// A converter that does the conversion between the database type T
// and the user type U (see previous examples)
Converter<T, U> converter();
// A callback that generates the SQL string for bind values of this
// binding type. Typically, just ?, but also ?::json, etc.
void sql(BindingSQLContext<U> ctx) throws SQLException;
// Callbacks that implement all interaction with JDBC types, such as
// PreparedStatement, CallableStatement, SQLOutput, SQLinput, ResultSet
void register(BindingRegisterContext<U> ctx) throws SQLException;
void set(BindingSetStatementContext<U> ctx) throws SQLException;
void set(BindingSetSQLOutputContext<U> ctx) throws SQLException;
void get(BindingGetResultSetContext<U> ctx) throws SQLException;
void get(BindingGetStatementContext<U> ctx) throws SQLException;
void get(BindingGetSQLInputContext<U> ctx) throws SQLException;
}
下面是一个完整的示例实现,它使用 Google Gson 在 Java 中建模 JSON 文档
import java.sql.*;
import java.util.*;
import org.jooq.*;
import org.jooq.conf.*;
import org.jooq.impl.DSL;
import com.google.gson.*;
// We're binding <T> = JSON (or JSONB), and <U> = JsonElement (user type)
// Alternatively, extend org.jooq.impl.AbstractBinding to implement fewer methods.
public class PostgresJSONGsonBinding implements Binding<JSON, JsonElement> {
private final Gson gson = new Gson();
// The converter does all the work
@Override
public Converter<JSON, JsonElement> converter() {
return new Converter<JSON, JsonElement>() {
@Override
public JsonElement from(JSON t) {
return t == null ? JsonNull.INSTANCE : gson.fromJson(t.data(), JsonElement.class);
}
@Override
public JSON to(JsonElement u) {
return u == null || u == JsonNull.INSTANCE ? null : JSON.json(gson.toJson(u));
}
@Override
public Class<JSON> fromType() {
return JSON.class;
}
@Override
public Class<JsonElement> toType() {
return JsonElement.class;
}
};
}
// Rending a bind variable for the binding context's value and casting it to the json type
@Override
public void sql(BindingSQLContext<JsonElement> ctx) throws SQLException {
// Depending on how you generate your SQL, you may need to explicitly distinguish
// between jOOQ generating bind variables or inlined literals.
if (ctx.render().paramType() == ParamType.INLINED)
ctx.render().visit(DSL.inline(ctx.convert(converter()).value())).sql("::json");
else
ctx.render().sql(ctx.variable()).sql("::json");
}
// Registering VARCHAR types for JDBC CallableStatement OUT parameters
@Override
public void register(BindingRegisterContext<JsonElement> ctx) throws SQLException {
ctx.statement().registerOutParameter(ctx.index(), Types.VARCHAR);
}
// Converting the JsonElement to a String value and setting that on a JDBC PreparedStatement
@Override
public void set(BindingSetStatementContext<JsonElement> ctx) throws SQLException {
JSON json = ctx.convert(converter()).value();
ctx.statement().setString(ctx.index(), json == null ? null : json.data());
}
// Getting a String value from a JDBC ResultSet and converting that to a JsonElement
@Override
public void get(BindingGetResultSetContext<JsonElement> ctx) throws SQLException {
ctx.convert(converter()).value(JSON.json(ctx.resultSet().getString(ctx.index())));
}
// Getting a String value from a JDBC CallableStatement and converting that to a JsonElement
@Override
public void get(BindingGetStatementContext<JsonElement> ctx) throws SQLException {
ctx.convert(converter()).value(JSON.json(ctx.statement().getString(ctx.index())));
}
// Setting a value on a JDBC SQLOutput (useful for Oracle OBJECT types)
@Override
public void set(BindingSetSQLOutputContext<JsonElement> ctx) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
// Getting a value from a JDBC SQLInput (useful for Oracle OBJECT types)
@Override
public void get(BindingGetSQLInputContext<JsonElement> ctx) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
}
代码生成
手册中有一个专门的部分解释了如何自动将您的 Converters 和 Bindings 绑定到您生成的代码。相关章节是
反馈
您对此页面有任何反馈吗? 我们很乐意听取您的意见!