如何使用 JOOQ 以通用方式在 PostgreSQL 中使用 JSON/JSONB 列转换任何类型的 Java 对象?

How to convert any type of Java object with JSON/JSONB column in PostgreSQL using JOOQ on a generic way?

我在问如何编写一个通用的解决方案,用于在 JSONJSONB PostgreSQL 中插入和 selecting 任何类型的 Java 对象数据库列。 此外,我必须使用(注入)我的 Jackson ObjectMapper,因为它是一个预配置的 Spring bean。 我尝试使用自定义 ConverterProvider 来解决它,它在 select 端工作正常,但在我插入时没有调用我的转换器提供程序。我错过了什么吗?

我查看了 jOOQ 文档和一些关于 Biding 解决方案的堆栈溢出文章,但我不想确切地指定 userType。所以我无法注册 Binding,因为我无法声明确切的 userType.

我的目标是: 我想消除存储库 类 中每个 JSONJSONB 字段对 Java 对象字符串序列化的要求(以及注入我的 ObjectMapper 进入每个存储库)。我不想为每个 JSONB 字段编写此代码行,jOOQ 应该使用我的 JSONConverterProvider.

.set(myJSONBTableField, JSONB.jsonb(objectMapper.writeValueAsString(myJavaObjectToStore))

我想简单地使用:

.set(myJSONBTableField, myJavaObjectToStore)

我想请求 jOOQ 在我的自定义 ObjectMapper bean 的帮助下存储任何类型的 Java 对象,如果数据库列类型是 JSONJSONB.我的JSONBConverterProvider是在读阶段调用的,写阶段没有调用。 JSONBConverterProvider 也有一个用于 vica 转换的转换器,但 jOOQ 不使用它。

我的 jOOQ 配置的相关部分:

@Bean
public DefaultConfigurationCustomizer ziffitJooqCustomizer(JSONConverterProvider converterProvider) {
        return configuration -> configuration
            .set(converterProvider)
            .settings()...

我的自定义转换器提供商:

@ConditionalOnClass(DSLContext.class)
@Component
public class JSONConverterProvider implements ConverterProvider {

    private final ConverterProvider delegate = new DefaultConverterProvider();
    private final ObjectMapper mapper;

    public JSONConverterProvider(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public <T, U> Converter<T, U> provide(Class<T> dbClass, Class<U> entityClass) {
        if (dbClass == JSON.class) {
            return getJSONConverter(dbClass, entityClass);
        } else if (dbClass == JSONB.class) {
            return getJSONBConverter(dbClass, entityClass);
        } else {
            // Delegate all other type pairs to jOOQ's default
            return delegate.provide(dbClass, entityClass);
        }
    }

    private <T, U> Converter<T, U> getJSONConverter(Class<T> dbClass, Class<U> entityClass) {
        return Converter.ofNullable(dbClass, entityClass,
            t -> {
                try {
                    return mapper.readValue(((JSON) t).data(), entityClass);
                } catch (Exception e) {
                    throw new DataTypeException("JSON deserialization error", e);
                }
            },
            u -> {
                try {
                    return (T) JSON.valueOf(mapper.writeValueAsString(u));
                } catch (Exception e) {
                    throw new DataTypeException("JSON serialization error", e);
                }
            }
        );
    }

    private <T, U> Converter<T, U> getJSONBConverter(Class<T> dbClass, Class<U> entityClass) {
        return Converter.ofNullable(dbClass, entityClass,
            t -> {
                try {
                    return mapper.readValue(((JSONB) t).data(), entityClass);
                } catch (Exception e) {
                    throw new DataTypeException("JSONB deserialization error", e);
                }
            },
            u -> {
                try {
                    return (T) JSONB.valueOf(mapper.writeValueAsString(u));
                } catch (Exception e) {
                    throw new DataTypeException("JSONB serialization error", e);
                }
            }
        );
    }
}

我正在使用这样的读取查询:

dslCtx
  .select(myJSONBTableField)
  .from(myTable)
  .where(...)
  .fetchOptionalInto(myClazz);

这工作正常,请致电我的 JSONBConverterProvider

但我的写入查询没有。 我想像这样使用写入查询:

dslCtx
  .insertInto(myTable)
  .set(myJSONBTableField, myJavaObjectToStore)
  .execute();

为什么 ConverterProvider 对您不起作用

ConverterProvider SPI(以及 RecordMapperProviderRecordUnmapperProvider SPI)覆盖 [=47= 上所有基于反射的 auto-data 类型转换功能的行为] /成员匹配基础,主要影响Record::from and Record::into方法,以及其他类型上的相关方法。

请注意,如果您在不传递 class 文字的情况下投影您的字段,您将获得 Record1<JSONB> 记录,并且您不会期望它使用您的 JSONBConverterProvider。因此,它不会影响任何其他类型安全的方法,例如来自 INSERTUPDATEset(),也不影响 select(...).fetch().

什么对你有用?

相反,请将 Converter<JSONB, Object> 或更好的 (to avoid problems like this),一个 Converter<JSONB, MyMarkerInterface> 转换器附加到所有 JSONB 列,这些列应使用您的映射通用 JSON 转换逻辑。我认为您遇到的问题是您不想将任何 specific Java class 附加到 JSONB 列,因为具体class可以依赖JSONB的内容。但你不必! jOOQ 不关心 Converter<T, U> 中的 U 类型是什么。它仅用于类型安全,因此任何 MyMarkerInterface 类型都可以。

通过这种方法,您仍然可以使用 fetchOptionalInto(myClazz) 调用,它只会实现从 MyMarkerInterfaceMyClazz

的不安全向下转换