Java 编码对象时未使用MongoDB BSON编解码器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30569228/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 09:50:24  来源:igfitidea点击:

MongoDB BSON codec not being used while encoding object

javamongodbencodingbson

提问by desrepair

I'm attempting to store an object in a MongoDB database (using MongoDB 3.0.2) and am getting a CodecConfigurationExceptionwhen attempting to encode the object with error message

我正在尝试将对象存储在 MongoDB 数据库中(使用 MongoDB 3.0.2),并且CodecConfigurationException在尝试使用错误消息对对象进行编码时得到一个

Can't find a codec for class java.time.LocalDate. 

I have written and included a codec for the LocalDateobjects. Details follow.

我已经为这些LocalDate对象编写并包含了一个编解码器。详情如下。

The object, DutyBlock, that I'm attempting to store has these member variables:

DutyBlock我试图存储的对象具有以下成员变量:

public class DutyBlock {
    private LocalDate startDate;
    private LocalDate endDate; //Inclusive
    private int blockLength;
    private double pointValue;
    private ArrayList<Ra> assigned;
}

I wrote the following codec to encode the DutyBlockobjects within the database:

我编写了以下编解码器来对DutyBlock数据库中的对象进行编码:

public class DutyBlockCodec implements Codec<DutyBlock> {

    @Override
    public void encode(BsonWriter writer, DutyBlock t, EncoderContext ec) {
        Document document = new Document();
        document.append("startDate", t.getStartDate());
        document.append("endDate", t.getEndDate());
        document.append("blockLength", t.getBlockLength());
        document.append("pointValue", t.getPointValue());
        document.append("assigned", t.getRasOnDuty());

        writer.writeString(document.toJson());  //Line 27 in the error message.
    }

    @Override
    public Class<DutyBlock> getEncoderClass() {
        return DutyBlock.class;
    }

    @Override
    public DutyBlock decode(BsonReader reader, DecoderContext dc) {
        String json = reader.readString();
        return new DutyBlock(Document.parse(json));
    }

}

Since MongoDB currently does not support the java.time.LocalDate class, I've written the following codec to encode the LocalDateobjects within the database:

由于 MongoDB 目前不支持java.time.LocalDate class,我编写了以下编解码器来对LocalDate数据库中的对象进行编码:

public class LocalDateCodec implements Codec<LocalDate> {

    @Override
    public void encode(BsonWriter writer, LocalDate t, EncoderContext ec) {
        writer.writeString(t.toString());
    }

    @Override
    public Class<LocalDate> getEncoderClass() {
        return LocalDate.class;
    }

    @Override
    public LocalDate decode(BsonReader reader, DecoderContext dc) {
        String date = reader.readString();
        return LocalDate.parse(date);
    }
}

I've added the two Codec's (along with one for the Ratype) to the CodecRegistryat the MongoClient level while instantiating the MongoClient.

在实例化 MongoClient 时,我在 MongoClient 级别添加了两个Codec(以及一个用于Ra类型)CodecRegistry

public class DutyScheduleDB {
    private MongoClient mongoClient;
    private MongoDatabase db;

    public DutyScheduleDB() {
        CodecRegistry codecRegistry = 
                CodecRegistries.fromRegistries(
                        CodecRegistries.fromCodecs(new LocalDateCodec(), new DutyBlockCodec(), new RaCodec()),
                        MongoClient.getDefaultCodecRegistry());
        MongoClientOptions options = MongoClientOptions.builder()
                .codecRegistry(codecRegistry).build();
        mongoClient = new MongoClient(new ServerAddress(), options);
        db = mongoClient.getDatabase("DutySchedulerDB");
    }
    . (More code not shown)
    .
    .
}

I attempt to store an ArrayListof DutyBlockobjects as part of a org.bson.Documentwithin the MongoDB database.

我尝试将存储ArrayListDutyBlock对象作为一部分org.bson.DocumentMongoDB的数据库中。

public void storeScheduledCalendar(String id,
        String calendarName,
        ArrayList<DutyBlock> cal) {
    //Access collection of scheduled calendars.
    MongoCollection collection = db.getCollection("ScheduledCalendars");
    //Query parameter is uuid + calendarName.
    Document doc = new Document("name", id + calendarName);
    doc.append("dutyBlocks", cal);
    //Insert doc to collection.
    collection.insertOne(doc); //Line 59 in the error message.
}

However, I'm running into this error message:

但是,我遇到了此错误消息:

Exception in thread "main" org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.time.LocalDate.
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 org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:174)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:189)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:131)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
at org.bson.Document.toJson(Document.java:294)
at org.bson.Document.toJson(Document.java:268)
at org.bson.Document.toJson(Document.java:255)
at SchedulingHeuristic.DutyBlockCodec.encode(DutyBlockCodec.java:27)
at SchedulingHeuristic.DutyBlockCodec.encode(DutyBlockCodec.java:16)
at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:175)
at org.bson.codecs.DocumentCodec.writeIterable(DocumentCodec.java:197)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:170)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:189)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:131)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:63)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:29)
at com.mongodb.connection.InsertCommandMessage.writeTheWrites(InsertCommandMessage.java:99)
at com.mongodb.connection.InsertCommandMessage.writeTheWrites(InsertCommandMessage.java:43)
at com.mongodb.connection.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.java:112)
at com.mongodb.connection.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.java:35)
at com.mongodb.connection.RequestMessage.encode(RequestMessage.java:132)
at com.mongodb.connection.BaseWriteCommandMessage.encode(BaseWriteCommandMessage.java:89)
at com.mongodb.connection.WriteCommandProtocol.sendMessage(WriteCommandProtocol.java:170)
at com.mongodb.connection.WriteCommandProtocol.execute(WriteCommandProtocol.java:73)
at com.mongodb.connection.InsertCommandProtocol.execute(InsertCommandProtocol.java:66)
at com.mongodb.connection.InsertCommandProtocol.execute(InsertCommandProtocol.java:37)
at com.mongodb.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:155)
at com.mongodb.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:219)
at com.mongodb.connection.DefaultServerConnection.insertCommand(DefaultServerConnection.java:108)
at com.mongodb.operation.MixedBulkWriteOperation$Run.executeWriteCommandProtocol(MixedBulkWriteOperation.java:416)
at com.mongodb.operation.MixedBulkWriteOperation$Run$RunExecutor.execute(MixedBulkWriteOperation.java:604)
at com.mongodb.operation.MixedBulkWriteOperation$Run.execute(MixedBulkWriteOperation.java:363)
at com.mongodb.operation.MixedBulkWriteOperation.call(MixedBulkWriteOperation.java:148)
at com.mongodb.operation.MixedBulkWriteOperation.call(MixedBulkWriteOperation.java:141)
at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:186)
at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:177)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.java:141)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.java:72)
at com.mongodb.Mongo.execute(Mongo.java:747)
at com.mongodb.Mongo.execute(Mongo.java:730)
at com.mongodb.MongoCollectionImpl.executeSingleWriteRequest(MongoCollectionImpl.java:482)
at com.mongodb.MongoCollectionImpl.insertOne(MongoCollectionImpl.java:277)
at DutyScheduleDB.storeScheduledCalendar(DutyScheduleDB.java:59)
at DutyScheduleDB.main(DutyScheduleDB.java:106)

It seems that my codec for LocalDateisn't being used when attempting to encode DutyBlockobjects, though I've verified that the collection that I am attempting to store the org.bson.Documentin does indeed contain the LocalDateCodecvia a

似乎LocalDate在尝试对DutyBlock对象进行编码时没有使用我的编解码器,尽管我已经验证我尝试存储的集合org.bson.Document确实包含LocalDateCodecvia a

System.out.println(collection.getCodecRegistry().get(LocalDate.class));

Can anyone can provide some insight on why this is happening?

任何人都可以提供一些有关为什么会发生这种情况的见解吗?

采纳答案by desrepair

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

经过几天的研究,我找到了解决方案。

The DutyBlockCodecdepends 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 CodecRegistryobject containing the codecs that DutyBlockCodecdepends on (e.g. a CodecRegistrycontaining within it the LocalDateCodec) to the DutyBlockCodec's constructor, which is stored as a member variable. In order to use the LocalDateCodecto 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 Documentas a String(as in my original code). Thus the DutyBlockcodec ends up looking like this:

DutyBlockCodec取决于LocalDateCodec(我创建的)以进行编码/解码。仅通过将两个编解码器添加到同一个编解码器注册表中并不能满足这种依赖性。解决方案是将CodecRegistry包含DutyBlockCodec依赖于的编解码器的对象(例如CodecRegistry包含在其中的LocalDateCodec)传递给DutyBlockCodec的构造函数,该构造函数存储为成员变量。为了使用LocalDateCodec编码,我使用了EncoderContext.encodeWithChildContext()方法,传入了编解码器、编写器和要编码的元素。此外,我编写单个字段而不是将 a 编写Document为 a String(如在我的原始代码中)。因此DutyBlock编解码器最终看起来像这样:

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);
    }

}

DutyBlockCodecdepends on another codec, and so requires a CodecRegistryto be passed in on its constructor. While I believe it is possible to create a CodecRegistrywith the LocalDateCodec, then pass this as an argument to DutyBlockCodec's constructor, then create another CodecRegistrycontaining both LocalDateCodecand DutyBlockCodec, this is rather confusing, and MongoDB provides a functionality, the CodecProviderto facilitate this process.

DutyBlockCodec取决于另一个编解码器,因此需要CodecRegistry在其构造函数中传入a 。虽然我相信可以创建一个CodecRegistrywith LocalDateCodec,然后将其作为参数传递给DutyBlockCodec的构造函数,然后创建另一个CodecRegistry包含LocalDateCodecand 的DutyBlockCodec,这相当令人困惑,而 MongoDB 提供了一个功能,CodecProvider来促进这个过程。

Using the CodecProviderinterface, I wrote a DutyBlockCodecProvider

使用CodecProvider界面,我写了一个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 CodecProvidersto the MongoDB Client using the CodecRegistries.fromProviders()method.

CodecProviders使用该CodecRegistries.fromProviders()方法将这些添加到 MongoDB 客户端。

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/DutySchedulerI'm open to answering any questions people may have.

我的这个项目的源代码可以在https://github.com/desrepair/DutyScheduler找到 我愿意回答人们可能提出的任何问题。