【问题标题】:Mapping to JSON Fields with Spring Data R2DBC and Reactive MySQL Driver使用 Spring Data R2DBC 和响应式 MySQL 驱动程序映射到 JSON 字段
【发布时间】:2021-08-02 14:49:19
【问题描述】:

我正在尝试使用 JSON 数据类型将 json 数据存储在 MySQL 数据库的特定列中。

我关注了这个guide,并且存储和读取 JSON 工作正常,但现在在尝试保存另一个具有 boolean 列/字段的实体时出现此错误:

Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.Byte] to type [boolean]
    at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:322)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:175)
    at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.getPotentiallyConvertedSimpleRead(MappingR2dbcConverter.java:277)
    at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.readValue(MappingR2dbcConverter.java:201)
    at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.readFrom(MappingR2dbcConverter.java:182)
    at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.read(MappingR2dbcConverter.java:136)
    at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.read(MappingR2dbcConverter.java:119)
    at org.springframework.data.r2dbc.convert.EntityRowMapper.apply(EntityRowMapper.java:46)
    at org.springframework.data.r2dbc.convert.EntityRowMapper.apply(EntityRowMapper.java:29)
    at dev.miku.r2dbc.mysql.MySqlResult.processRow(MySqlResult.java:176)
    at dev.miku.r2dbc.mysql.MySqlResult.handleResult(MySqlResult.java:149)
    at dev.miku.r2dbc.mysql.MySqlResult.lambda$map$1(MySqlResult.java:93)
    at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:102)
    at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
    at dev.miku.r2dbc.mysql.util.DiscardOnCancelSubscriber.onNext(DiscardOnCancelSubscriber.java:70)
    at reactor.core.publisher.FluxWindowPredicate$WindowFlux.drainRegular(FluxWindowPredicate.java:667)
    at reactor.core.publisher.FluxWindowPredicate$WindowFlux.drain(FluxWindowPredicate.java:745)
    at reactor.core.publisher.FluxWindowPredicate$WindowFlux.onNext(FluxWindowPredicate.java:787)
    at reactor.core.publisher.FluxWindowPredicate$WindowPredicateMain.onNext(FluxWindowPredicate.java:265)
    at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:184)
    at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
    at dev.miku.r2dbc.mysql.util.DiscardOnCancelSubscriber.onNext(DiscardOnCancelSubscriber.java:70)
    at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
    at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:250)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:199)
    at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:118)
    at reactor.core.publisher.FluxPeekFuseable$PeekConditionalSubscriber.onNext(FluxPeekFuseable.java:854)
    at reactor.core.publisher.EmitterProcessor.drain(EmitterProcessor.java:491)
    at reactor.core.publisher.EmitterProcessor.tryEmitNext(EmitterProcessor.java:299)
    at reactor.core.publisher.InternalManySink.emitNext(InternalManySink.java:27)
    at reactor.core.publisher.EmitterProcessor.onNext(EmitterProcessor.java:265)
    at dev.miku.r2dbc.mysql.client.ReactorNettyClient$ResponseSink.next(ReactorNettyClient.java:340)
    at dev.miku.r2dbc.mysql.client.ReactorNettyClient.lambda$new$0(ReactorNettyClient.java:103)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:184)
    at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:280)
    at reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:389)
    at reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:405)
    at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at dev.miku.r2dbc.mysql.client.MessageDuplexCodec.handleDecoded(MessageDuplexCodec.java:187)
    at dev.miku.r2dbc.mysql.client.MessageDuplexCodec.channelRead(MessageDuplexCodec.java:95)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:311)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:432)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1368)
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1234)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1280)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:507)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:446)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:834)

如果我不创建 R2dbcCustomConversions bean(如指南中所述),那么我不会收到此错误,但 JSON 不起作用...

在我的场景中还有其他使用 JSON 的方法吗?

【问题讨论】:

    标签: java spring-boot spring-data-r2dbc r2dbc-mysql


    【解决方案1】:

    回答我自己的问题 - 看起来像是 spring-data-r2dbc 中的某种错误:

    R2dbcDataAutoConfiguration 中有这个 bean 定义:

    @Bean
    @ConditionalOnMissingBean
    public R2dbcCustomConversions r2dbcCustomConversions() {
        List<Object> converters = new ArrayList<>(this.dialect.getConverters());
        converters.addAll(R2dbcCustomConversions.STORE_CONVERTERS);
        return new R2dbcCustomConversions(
                CustomConversions.StoreConversions.of(this.dialect.getSimpleTypeHolder(), converters),
                Collections.emptyList());
    }
    

    这是我们在注册转换器时覆盖的 bean(这是我的代码):

    @Bean
    public R2dbcCustomConversions r2dbcCustomConversions(JsonEventsReadingConverter readingConverter,
                                                         JsonEventsWritingConverter writingConverter) {
        val converters = new ArrayList<>();
        converters.add(readingConverter);
        converters.add(writingConverter);
        return new R2dbcCustomConversions(converters);
    }
    

    CTOR new R2dbcCustomConversions(converters) 负责添加一些基本转换器 (STORE_CONVERSIONS & R2dbcConverters),但它不添加 MySqlDialect.BooleanToByteConverterMySqlDialect.ByteToBooleanConverter,后者负责将 MySQL 值转换为 Java。

    我找到了一个棘手的解决方案(在我的 bean 中手动添加这些转换器),但我没有看到其他方法(欢迎提出建议):

    @Configuration
    @RequiredArgsConstructor
    public class R2dbcConvertorsConfig {
        private final DatabaseClient databaseClient;
        @Bean
        public R2dbcCustomConversions r2dbcCustomConversions(JsonEventsReadingConverter readingConverter,
                                                             JsonEventsWritingConverter writingConverter) {
            val dialect = DialectResolver.getDialect(this.databaseClient.getConnectionFactory());
            val converters = new ArrayList<>();
            converters.add(readingConverter);
            converters.add(writingConverter);
            converters.addAll(dialect.getConverters());
            return new R2dbcCustomConversions(converters);
        }
    }
    

    根据要求:

    package com.mycompany.platform.webhookdelivery.core.dal.converters;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.mycompany.platform.webhookdelivery.core.dal.entities.webhookfilters.JsonEvents;
    import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.core.serializer.support.SerializationFailedException;
    import org.springframework.data.convert.ReadingConverter;
    import org.springframework.lang.NonNull;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    @Slf4j
    @ReadingConverter
    @AllArgsConstructor
    @Component
    public class JsonEventsReadingConverter implements Converter<String, JsonEvents> {
    
        private final ObjectMapper objectMapper;
    
        @Override
        public JsonEvents convert(@NonNull String target) {
            try {
                String json = " {\"events\":" + target + "}";
                return objectMapper.readValue(json, JsonEvents.class);
            } catch (IOException e) {
                log.debug("Failed to deserialise  JSON: " + target, e);
                throw new SerializationFailedException("Failed to deserialize  JSON: " + target, e);
            }
        }
    }
    
    package com.mycompany.platform.webhookdelivery.core.dal.converters;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.mycompany.platform.webhookdelivery.core.dal.entities.webhookfilters.JsonEvents;
    import lombok.AllArgsConstructor;
    import lombok.extern.log4j.Log4j2;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.core.serializer.support.SerializationFailedException;
    import org.springframework.data.convert.WritingConverter;
    import org.springframework.stereotype.Component;
    
    @Log4j2
    @WritingConverter
    @AllArgsConstructor
    @Component
    public class JsonEventsWritingConverter implements Converter<JsonEvents, String> {
    
        private final ObjectMapper objectMapper;
    
        @Override
        public String convert(JsonEvents source) {
    
            try {
                return objectMapper.writeValueAsString(source.getEvents());
            } catch (JsonProcessingException e) {
                log.debug("Failed to serialise JSON: " + source, e);
                throw new SerializationFailedException("Failed to serialize JSON: " + source, e);
            }
        }
    }
    

    【讨论】:

    • R2dbcCustomConversions 已弃用。你改变了这个吗? JsonEventsReadingConverter & JsonEventsWritingConverter --> 你是否创建了这两个类。你能发布这两个课程吗?
    • 按要求将 2 个转换类添加到答案中
    猜你喜欢
    • 2021-09-23
    • 2021-05-17
    • 1970-01-01
    • 2023-03-10
    • 2017-05-18
    • 1970-01-01
    • 2017-06-12
    • 2021-10-10
    • 2020-09-26
    相关资源
    最近更新 更多