使用 Micronaut 为 MongoDB 注册编解码器

Posted

技术标签:

【中文标题】使用 Micronaut 为 MongoDB 注册编解码器【英文标题】:Registering codecs for MongoDB using Micronaut 【发布时间】:2019-12-30 17:51:01 【问题描述】:

我正在使用 Micronaut 和 MongoDB 创建一个新应用程序。关于我的一些数据库对象,我不得不陷入僵局。

我有一个包含枚举字段的对象,我需要将其转换为可以保存的值(我打算将其保存为字符串)。

根据我的理解和收到的错误消息,我需要创建一个新的编解码器来处理这个问题(类似于 JPA 转换器)。虽然我找到了显示这一点的示例,但我有点困惑我应该如何为 MongoClient 注册转换器。

我正在使用框架和各自的 MongoClient 的最新 Micronaut 版本。

以下是我要参考的例外情况:

org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class fts.marketing.models.CampaignEmailStatus.
    at org.bson.codecs.configuration.CodecCache.getOrThrow(CodecCache.java:46)
    at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:63)
    at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37)
    at com.mongodb.client.model.BuildersHelper.encodeValue(BuildersHelper.java:37)
    at com.mongodb.client.model.Filters$SimpleEncodingFilter.toBsonDocument(Filters.java:1109)
    at com.mongodb.client.model.Filters$AndFilter.toBsonDocument(Filters.java:946)
    at com.mongodb.internal.operation.Operations.createFindOperation(Operations.java:142)
    at com.mongodb.internal.operation.Operations.find(Operations.java:130)
    at com.mongodb.internal.operation.AsyncOperations.find(AsyncOperations.java:85)
    at com.mongodb.async.client.FindIterableImpl.createFindOperation(FindIterableImpl.java:229)
    at com.mongodb.async.client.FindIterableImpl.asAsyncReadOperation(FindIterableImpl.java:225)
    at com.mongodb.async.client.MongoIterableImpl.batchCursor(MongoIterableImpl.java:161)
    at com.mongodb.async.client.MongoIterableSubscription.requestInitialData(MongoIterableSubscription.java:46)
    at com.mongodb.async.client.AbstractSubscription.tryRequestInitialData(AbstractSubscription.java:151)
    at com.mongodb.async.client.AbstractSubscription.request(AbstractSubscription.java:84)
    at com.mongodb.reactivestreams.client.internal.ObservableToPublisher$1$1.request(ObservableToPublisher.java:50)
    at io.reactivex.internal.operators.flowable.FlowableToListSingle$ToListSubscriber.onSubscribe(FlowableToListSingle.java:84)
    at com.mongodb.reactivestreams.client.internal.ObservableToPublisher$1.onSubscribe(ObservableToPublisher.java:39)
    at com.mongodb.async.client.MongoIterableSubscription.<init>(MongoIterableSubscription.java:40)
    at com.mongodb.async.client.Observables$1.subscribe(Observables.java:47)
    at com.mongodb.reactivestreams.client.internal.ObservableToPublisher.subscribe(ObservableToPublisher.java:36)
    at com.mongodb.reactivestreams.client.internal.FindPublisherImpl.subscribe(FindPublisherImpl.java:189)

【问题讨论】:

【参考方案1】:

我认为使用编解码器创建一个类并使其可注入就足够了:

@Singleton
public class EnumCodec implements Codec<CampaignEmailStatus> 

  @Override
  public CampaignEmailStatus decode(BsonReader reader, DecoderContext decoderContext) 
    String enumString = reader.readString();
    return CampaignEmailStatus.valueOf(enumString);
  

  @Override
  public void encode(BsonWriter writer, CampaignEmailStatus value, EncoderContext encoderContext) 
    String enumString = value.name();
    writer.writeString(enumString);
  

  @Override
  public Class<CampaignEmailStatus> getEncoderClass() 
    return CampaignEmailStatus.class;
  

解释编解码器的 Mongo 驱动程序文档在这里:https://mongodb.github.io/mongo-java-driver/3.11/bson/codecs/

编解码器的注入发生在这里

io.micronaut.configuration.mongo.reactive.DefaultReactiveMongoConfiguration#codecs

如果它不工作,你应该可以从那里调试它

PS:我没有测试上面的代码,但它应该给你一个想法

【讨论】:

非常感谢。你的建议效果很好。我没想到做tbh会这么容易。可惜他们的文档中没有这样的例子。 @Aris_Kortex 很高兴它对你有用。 Micronaut 是一个开源框架。如果您愿意,您可以随时提供进一步的文档。其他人将从您的学习中受益。 @Marco 对我来说,这是行不通的。你是怎么注册编解码器***.com/questions/68559101/…【参考方案2】:

MongoDB java 客户端在处理枚举方面不一致,仍然没有修复,请参阅JAVA-2720。

对于作为对象属性出现的枚举,有自动的EnumPropertyCodecProvider 编解码器实现,它开箱即用。但是,如果您将 enum 与 com.mongodb.client.model.Filters 一起使用,则它不起作用并且最终会出现问题中的异常。

为 Micronaut 注册特定枚举编解码器的解决方案有效,但它需要为您要处理的每个枚举定义一个编解码器 bean。这是适用于所有枚举的更通用的解决方案:

@Singleton
public class EnumCodecRegistry implements CodecRegistry 

    @SuppressWarnings("unchecked")
    @Override
    public <T> Codec<T> get(Class<T> clazz) 
        if (Enum.class.isAssignableFrom(clazz)) 
            return new EnumCodec(clazz);
        
        return null;
    

    @Override
    public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) 
        return get(clazz);
    

    private static class EnumCodec<T extends Enum<T>> implements Codec<T> 
        private final Class<T> clazz;

        EnumCodec(final Class<T> clazz) 
            this.clazz = clazz;
        

        @Override
        public void encode(final BsonWriter writer, final T value, final EncoderContext encoderContext) 
            writer.writeString(value.name());
        

        @Override
        public Class<T> getEncoderClass() 
            return clazz;
        

        @Override
        public T decode(final BsonReader reader, final DecoderContext decoderContext) 
            return Enum.valueOf(clazz, reader.readString());
        
    


【讨论】:

以上是关于使用 Micronaut 为 MongoDB 注册编解码器的主要内容,如果未能解决你的问题,请参考以下文章

使用 java 在 Micronaut 中将 MongoDb ObjectId _id 更改为字符串

处理 bean 注册时出错 [io.micronaut.rabbitmq.intercept.RabbitMQConsumerAdvice]

使用 Junit 5 和 Micronaut 设置测试 MongoDBContainer

在 Micronaut 应用程序中使用 TestContainer 的测试环境的 ApplicationContext 不起作用

Micronaut 数据:没有为存储库配置支持 RepositoryOperations

使用 Micronaut 应用程序为 JUnit5 中的每个单元/集成测试运行一次 TestContainer