JOOQ:遇到错误&quort“ java.lang.illegalargumentException:...不是接口”

发布于 2025-01-28 18:31:43 字数 5697 浏览 2 评论 0原文

在我的项目中,我将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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

绾颜 2025-02-04 18:31:43

我不确定这是否真的是回归,还是以前是偶然起作用的 - 我倾向于说它是偶然起作用的。

无论如何,proxymapper不应仅适用于抽象类,仅适用于接口,因此这里有一个错误(尽管可能无法解决您的问题):
https://github.com/jooq/jooq/jooq/jooq/issues/13551

有各种各样的将单列结果获取到JOOQ中的lt; t&gt;的方法。您选择了一个不安全的类型,并且依赖于defaultrecordmapper的反射功能,这些功能往往具有像这样的边缘案例行为,用于Abstract> 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(...)
);

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 the DefaultRecordMapper's reflection capabilities, which tend to have edge case behaviour like this one, for abstract classes. How about this, instead?

List<JsonNode> list =
dslContext
    .select(RECORD_VALUE.RCV_RECORD_VALUE)
    .from(RECORD_VALUE)
    .where(...)
    .fetch(r -> r.value1())

Or this:

List<JsonNode> list =
dslContext.fetchValues(
     select(RECORD_VALUE.RCV_RECORD_VALUE)
    .from(RECORD_VALUE)
    .where(...)
);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文