JOOQ:遇到错误&quort“ java.lang.illegalargumentException:...不是接口”
在我的项目中,我将PostgreSQL和JOOQ用于所有CRUD操作。一些数据通过JSONB数据类型存储在JSON中。在应用方面,数据由抽象类“ com.fasterxml.jackson.databind.jsonnode”的子类表示。
如上所述 我添加了自定义转换器并绑定以启用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代码生成器上注册这些转换器/绑定,我生成的模型看起来像:
public final TableField<RecordValueRecord, JsonNode> RCV_RECORD_VALUE = createField(DSL.name("rcv_record_value"), SQLDataType.JSONB.nullable(false), this, "...", new PostgresJSONJacksonJsonNodeBinding());
然后尝试从DB中选择一些值。这里是一个简单的提取:
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetchInto(JsonNode.class)
在Jooq 3.14.7中,它可以正常工作。 JOOQ调用我的转换器/绑定,我得到了JSONNODE子类的实例。
但是在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)
原因是在这里。 JOOQ检查JSONNODE是一个抽象类,并创建了近距离apper。我的转换器/绑定不再被调用,而接头崩溃了。
怎么了?我该怎么做才能使它起作用?
当然,明确指定转换器有帮助,但是我每次使用JSON时都无法指定它:
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetch(0, new PostgresJSONJacksonJsonNodeConverter());
谢谢!
In my project I use PostgreSQL and JOOQ for all CRUD operations. Some data stored in JSON via JSONB data type. On the application side that data is represented by subclasses of an abstract class "com.fasterxml.jackson.databind.JsonNode".
As a described in official docs I have added my custom converter and binding to enable JsonNode support. Here some code:
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;
}
}
I register these converter/binding on the JOOQ code generator and my generated model looks like:
public final TableField<RecordValueRecord, JsonNode> RCV_RECORD_VALUE = createField(DSL.name("rcv_record_value"), SQLDataType.JSONB.nullable(false), this, "...", new PostgresJSONJacksonJsonNodeBinding());
Then I try to select some values from DB. Here a simple fetch:
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetchInto(JsonNode.class)
In JOOQ 3.14.7 it works fine. JOOQ invokes my converter/binding and I get an instance of subclass of JsonNode.
But in 3.14.15 I get the error:
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)
The reason is here. JOOQ checks that JsonNode is an abstract class and creates ProxyMapper. My converter/binding is no longer being called and ProxyMapper crashes with an error.
What's wrong? What can i do to make it work?
Of course, explicitly specifying the converter helps, but I cannot specify it every time when working with JSON:
dslContext
.select(RECORD_VALUE.RCV_RECORD_VALUE)
.from(RECORD_VALUE)
.where(...)
.fetch(0, new PostgresJSONJacksonJsonNodeConverter());
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我不确定这是否真的是回归,还是以前是偶然起作用的 - 我倾向于说它是偶然起作用的。
无论如何,
proxymapper
不应仅适用于抽象类,仅适用于接口,因此这里有一个错误(尽管可能无法解决您的问题):https://github.com/jooq/jooq/jooq/jooq/issues/13551
有各种各样的将单列结果获取到JOOQ中的
lt; t&gt;
的方法。您选择了一个不安全的类型,并且依赖于defaultrecordmapper
的反射功能,这些功能往往具有像这样的边缘案例行为,用于Abstract> Abstract
类。那呢?或以下:
I'm not sure if this is really a regression, or if it worked by accident before - I'm inclined to say it worked by accident.
In any case, the
ProxyMapper
shouldn't apply for abstract classes, only for interfaces, so there's a bug here (it might not solve your problem, though):https://github.com/jOOQ/jOOQ/issues/13551
There are various ways to fetch single column results into a
List<T>
in jOOQ. You chose one that isn't type safe and relies on theDefaultRecordMapper
's reflection capabilities, which tend to have edge case behaviour like this one, forabstract
classes. How about this, instead?Or this: