MongoDB BSON codec not being used while encoding object

后端 未结 1 505

I\'m attempting to store an object in a MongoDB database (using MongoDB 3.0.2) and am getting a CodecConfigurationException when attempting to encode the object

相关标签:
1条回答
  • 2020-12-08 05:39

    After several days of research, I've figured out a solution.

    The DutyBlockCodec depends on the LocalDateCodec (which I created) in order to encode/decode. This dependency isn't satisfied just by adding the two codecs into the same codec registry. The solution is to pass a CodecRegistry object containing the codecs that DutyBlockCodec depends on (e.g. a CodecRegistry containing within it the LocalDateCodec) to the DutyBlockCodec's constructor, which is stored as a member variable. In order to use the LocalDateCodec to encode, I use the EncoderContext.encodeWithChildContext() method, passing in the codec, writer, and element to encode. Additionally, I write individual fields rather than writing a Document as a String (as in my original code). Thus the DutyBlock codec ends up looking like this:

    public class DutyBlockCodec implements Codec<DutyBlock> {
        private final CodecRegistry codecRegistry;
    
        public DutyBlockCodec(final CodecRegistry codecRegistry) {
            this.codecRegistry = codecRegistry;
        }
    
        @Override
        public void encode(BsonWriter writer, DutyBlock t, EncoderContext ec) {
            writer.writeStartDocument();
                Codec dateCodec = codecRegistry.get(LocalDate.class);
                writer.writeName("startDate");
                ec.encodeWithChildContext(dateCodec, writer, t.getStartDate());
                writer.writeName("endDate");
                ec.encodeWithChildContext(dateCodec, writer, t.getEndDate());
                writer.writeName("blockLength");
                writer.writeInt32(t.getBlockLength());
                writer.writeName("pointValue");
                writer.writeDouble(t.getPointValue());
    
                //Writing ArrayList of RAs
                writer.writeName("assigned");
                writer.writeStartArray();
                    for (Ra ra : t.getRasOnDuty()) {
                        Codec raCodec = codecRegistry.get(Ra.class);
                        ec.encodeWithChildContext(raCodec, writer, ra);
                    }
                writer.writeEndArray();
            writer.writeEndDocument();
        }
    
        @Override
        public Class<DutyBlock> getEncoderClass() {
            return DutyBlock.class;
        }
    
        @Override
        public DutyBlock decode(BsonReader reader, DecoderContext dc) {
            reader.readStartDocument();
                Codec<LocalDate> dateCodec = codecRegistry.get(LocalDate.class);
                reader.readName();
                LocalDate startDate = dateCodec.decode(reader, dc);
                reader.readName();
                LocalDate endDate = dateCodec.decode(reader, dc);
                reader.readName();
                int blockLength = reader.readInt32();
                reader.readName();
                double pointValue = reader.readDouble();
    
                //Reading ArrayList of RAs
                reader.readName();
                Codec<Ra> raCodec = codecRegistry.get(Ra.class);
                ArrayList<Ra> rasOnDuty = new ArrayList<>();
                reader.readStartArray();
                    while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
                        rasOnDuty.add(raCodec.decode(reader, dc));
                    }
                reader.readEndArray();
            reader.readEndDocument();
    
            return new DutyBlock(startDate, endDate, blockLength, pointValue, rasOnDuty);
        }
    
    }
    

    DutyBlockCodec depends on another codec, and so requires a CodecRegistry to be passed in on its constructor. While I believe it is possible to create a CodecRegistry with the LocalDateCodec, then pass this as an argument to DutyBlockCodec's constructor, then create another CodecRegistry containing both LocalDateCodec and DutyBlockCodec, this is rather confusing, and MongoDB provides a functionality, the CodecProvider to facilitate this process.

    Using the CodecProvider interface, I wrote a DutyBlockCodecProvider

    public class DutyBlockCodecProvider implements CodecProvider {
        @Override
        public <T> Codec<T> get(Class<T> type, CodecRegistry cr) {
            if (type == DutyBlock.class) {
                return (Codec<T>) new DutyBlockCodec(cr);
            }
            return null;
        }
    }
    

    I added these CodecProviders to the MongoDB Client using the CodecRegistries.fromProviders() method.

    CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
                CodecRegistries.fromCodecs(new LocalDateCodec()),
                CodecRegistries.fromProviders(
                        new RaCodecProvider(),
                        new DutyBlockCodecProvider(),
                        new ScheduledDutyCodecProvider()),
                MongoClient.getDefaultCodecRegistry());  
        MongoClientOptions options = MongoClientOptions.builder()
                .codecRegistry(codecRegistry).build();
        mongoClient = new MongoClient(new ServerAddress(), options);
        db = mongoClient.getDatabase("DutySchedulerDB");
    

    My source code for this project can be found at https://github.com/desrepair/DutyScheduler I'm open to answering any questions people may have.

    0 讨论(0)
提交回复
热议问题