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(...)
);