JOOQ:出现错误 "java.lang.IllegalArgumentException: ... is not an interface"
JOOQ: Getting an error "java.lang.IllegalArgumentException: ... is not an interface"
在我的项目中,我使用 PostgreSQL 和 JOOQ 进行所有 CRUD 操作。一些数据通过 JSONB 数据类型存储在 JSON 中。在应用程序方面,数据由抽象 class“com.fasterxml.jackson.databind.JsonNode”的子classes 表示。
如所述 in official docs 我已经添加了我的自定义转换器和绑定以启用 JsonNode 支持。这里有一些代码:
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Types;
import java.util.Objects;
import org.jooq.Binding;
import org.jooq.BindingGetResultSetContext;
import org.jooq.BindingGetSQLInputContext;
import org.jooq.BindingGetStatementContext;
import org.jooq.BindingRegisterContext;
import org.jooq.BindingSQLContext;
import org.jooq.BindingSetSQLOutputContext;
import org.jooq.BindingSetStatementContext;
import org.jooq.Converter;
import org.jooq.JSONB;
import org.jooq.conf.ParamType;
import org.jooq.impl.DSL;
import com.fasterxml.jackson.databind.JsonNode;
public class PostgresJSONJacksonJsonNodeBinding implements Binding<JSONB, JsonNode> {
@Override
public Converter<JSONB, JsonNode> converter() {
return new PostgresJSONJacksonJsonNodeConverter();
}
@Override
public void sql(BindingSQLContext<JsonNode> ctx) {
if (ctx.render().paramType() == ParamType.INLINED) {
ctx.render().visit(DSL.inline(ctx.convert(converter()).value())).sql("::jsonb");
} else {
ctx.render().sql(ctx.variable()).sql("::jsonb");
}
}
@Override
public void register(BindingRegisterContext<JsonNode> ctx) throws SQLException {
ctx.statement().registerOutParameter(ctx.index(), Types.VARCHAR);
}
@Override
public void set(BindingSetStatementContext<JsonNode> ctx) throws SQLException {
ctx.statement().setString(ctx.index(), Objects.toString(ctx.convert(converter()).value(), null));
}
@Override
public void get(BindingGetResultSetContext<JsonNode> ctx) throws SQLException {
ctx.convert(converter()).value(org.jooq.JSONB.jsonb(ctx.resultSet().getString(ctx.index())));
}
@Override
public void get(BindingGetStatementContext<JsonNode> ctx) throws SQLException {
ctx.convert(converter()).value(org.jooq.JSONB.jsonb(ctx.statement().getString(ctx.index())));
}
@Override
public void set(BindingSetSQLOutputContext<JsonNode> ctx) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void get(BindingGetSQLInputContext<JsonNode> ctx) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
}
import java.io.IOException;
import org.jooq.Converter;
import org.jooq.JSONB;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.NullNode;
public class PostgresJSONJacksonJsonNodeConverter implements Converter<JSONB, JsonNode> {
private static final ObjectMapper OBJECT_MAPPER = JsonUtils.createObjectMapper();
@Override
public JsonNode from(JSONB dbObj) {
try {
return dbObj == null ? NullNode.instance : OBJECT_MAPPER.readTree(dbObj.data());
} catch (IOException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
@Override
public JSONB to(JsonNode appObj) {
try {
return appObj == null || appObj.equals(NullNode.instance)
? null
: JSONB.jsonb(OBJECT_MAPPER.writeValueAsString(appObj));
} catch (IOException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
@Override
public Class<JSONB> fromType() {
return JSONB.class;
}
@Override
public Class<JsonNode> toType() {
return JsonNode.class;
}
}
我在 JOOQ 代码生成器上注册了这些 converter/binding,我生成的模型如下所示:
public final TableField<RecordValueRecord, JsonNode> RCV_RECORD_VALUE = createField(DSL.name("rcv_record_value"), SQLDataType.JSONB.nullable(false), this, "...", new PostgresJSONJacksonJsonNodeBinding());
然后我尝试 select 来自数据库的一些值。这是一个简单的获取:
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetchInto(JsonNode.class)
在 JOOQ 3.14.7 中它工作正常。 JOOQ 调用我的 converter/binding 并且我得到了 JsonNode 的 subclass 实例。
但是在 3.14.15 中我得到了错误:
java.lang.IllegalArgumentException: com.fasterxml.jackson.databind.JsonNode is not an interface
at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:706)
...
at org.jooq.impl.DefaultRecordMapper$ProxyMapper.proxy(DefaultRecordMapper.java:733)
原因是here。 JOOQ 检查 JsonNode 是一个抽象 class 并创建 ProxyMapper。我的 converter/binding 不再被调用,ProxyMapper 因错误而崩溃。
怎么了?我该怎么做才能让它发挥作用?
当然,明确指定转换器有帮助,但我不能每次在使用 JSON:
时都指定它
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetch(0, new PostgresJSONJacksonJsonNodeConverter());
谢谢!
我不确定这是否真的是回归,或者它之前是否偶然起作用 - 我倾向于说它偶然起作用。
无论如何,ProxyMapper
不应该申请抽象 类,只能申请接口,所以这里有一个错误(虽然它可能无法解决你的问题):
https://github.com/jOOQ/jOOQ/issues/13551
在 jOOQ 中有多种方法可以将单列结果提取到 List<T>
中。您选择了一个类型不安全的并且依赖于 DefaultRecordMapper
的反射功能,对于 abstract
类,它往往具有像这个这样的边缘情况行为。换成这个怎么样?
List<JsonNode> list =
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetch(r -> r.value1())
或者这个:
List<JsonNode> list =
dslContext.fetchValues(
select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
);
在我的项目中,我使用 PostgreSQL 和 JOOQ 进行所有 CRUD 操作。一些数据通过 JSONB 数据类型存储在 JSON 中。在应用程序方面,数据由抽象 class“com.fasterxml.jackson.databind.JsonNode”的子classes 表示。
如所述 in official docs 我已经添加了我的自定义转换器和绑定以启用 JsonNode 支持。这里有一些代码:
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Types;
import java.util.Objects;
import org.jooq.Binding;
import org.jooq.BindingGetResultSetContext;
import org.jooq.BindingGetSQLInputContext;
import org.jooq.BindingGetStatementContext;
import org.jooq.BindingRegisterContext;
import org.jooq.BindingSQLContext;
import org.jooq.BindingSetSQLOutputContext;
import org.jooq.BindingSetStatementContext;
import org.jooq.Converter;
import org.jooq.JSONB;
import org.jooq.conf.ParamType;
import org.jooq.impl.DSL;
import com.fasterxml.jackson.databind.JsonNode;
public class PostgresJSONJacksonJsonNodeBinding implements Binding<JSONB, JsonNode> {
@Override
public Converter<JSONB, JsonNode> converter() {
return new PostgresJSONJacksonJsonNodeConverter();
}
@Override
public void sql(BindingSQLContext<JsonNode> ctx) {
if (ctx.render().paramType() == ParamType.INLINED) {
ctx.render().visit(DSL.inline(ctx.convert(converter()).value())).sql("::jsonb");
} else {
ctx.render().sql(ctx.variable()).sql("::jsonb");
}
}
@Override
public void register(BindingRegisterContext<JsonNode> ctx) throws SQLException {
ctx.statement().registerOutParameter(ctx.index(), Types.VARCHAR);
}
@Override
public void set(BindingSetStatementContext<JsonNode> ctx) throws SQLException {
ctx.statement().setString(ctx.index(), Objects.toString(ctx.convert(converter()).value(), null));
}
@Override
public void get(BindingGetResultSetContext<JsonNode> ctx) throws SQLException {
ctx.convert(converter()).value(org.jooq.JSONB.jsonb(ctx.resultSet().getString(ctx.index())));
}
@Override
public void get(BindingGetStatementContext<JsonNode> ctx) throws SQLException {
ctx.convert(converter()).value(org.jooq.JSONB.jsonb(ctx.statement().getString(ctx.index())));
}
@Override
public void set(BindingSetSQLOutputContext<JsonNode> ctx) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void get(BindingGetSQLInputContext<JsonNode> ctx) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
}
import java.io.IOException;
import org.jooq.Converter;
import org.jooq.JSONB;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.NullNode;
public class PostgresJSONJacksonJsonNodeConverter implements Converter<JSONB, JsonNode> {
private static final ObjectMapper OBJECT_MAPPER = JsonUtils.createObjectMapper();
@Override
public JsonNode from(JSONB dbObj) {
try {
return dbObj == null ? NullNode.instance : OBJECT_MAPPER.readTree(dbObj.data());
} catch (IOException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
@Override
public JSONB to(JsonNode appObj) {
try {
return appObj == null || appObj.equals(NullNode.instance)
? null
: JSONB.jsonb(OBJECT_MAPPER.writeValueAsString(appObj));
} catch (IOException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
@Override
public Class<JSONB> fromType() {
return JSONB.class;
}
@Override
public Class<JsonNode> toType() {
return JsonNode.class;
}
}
我在 JOOQ 代码生成器上注册了这些 converter/binding,我生成的模型如下所示:
public final TableField<RecordValueRecord, JsonNode> RCV_RECORD_VALUE = createField(DSL.name("rcv_record_value"), SQLDataType.JSONB.nullable(false), this, "...", new PostgresJSONJacksonJsonNodeBinding());
然后我尝试 select 来自数据库的一些值。这是一个简单的获取:
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetchInto(JsonNode.class)
在 JOOQ 3.14.7 中它工作正常。 JOOQ 调用我的 converter/binding 并且我得到了 JsonNode 的 subclass 实例。
但是在 3.14.15 中我得到了错误:
java.lang.IllegalArgumentException: com.fasterxml.jackson.databind.JsonNode is not an interface
at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:706)
...
at org.jooq.impl.DefaultRecordMapper$ProxyMapper.proxy(DefaultRecordMapper.java:733)
原因是here。 JOOQ 检查 JsonNode 是一个抽象 class 并创建 ProxyMapper。我的 converter/binding 不再被调用,ProxyMapper 因错误而崩溃。
怎么了?我该怎么做才能让它发挥作用?
当然,明确指定转换器有帮助,但我不能每次在使用 JSON:
时都指定它dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetch(0, new PostgresJSONJacksonJsonNodeConverter());
谢谢!
我不确定这是否真的是回归,或者它之前是否偶然起作用 - 我倾向于说它偶然起作用。
无论如何,ProxyMapper
不应该申请抽象 类,只能申请接口,所以这里有一个错误(虽然它可能无法解决你的问题):
https://github.com/jOOQ/jOOQ/issues/13551
在 jOOQ 中有多种方法可以将单列结果提取到 List<T>
中。您选择了一个类型不安全的并且依赖于 DefaultRecordMapper
的反射功能,对于 abstract
类,它往往具有像这个这样的边缘情况行为。换成这个怎么样?
List<JsonNode> list =
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetch(r -> r.value1())
或者这个:
List<JsonNode> list =
dslContext.fetchValues(
select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
);