web-dev-qa-db-ja.com

オブジェクトのエンコード中にMongoDB BSONコーデックが使用されていません

MongoDBデータベースにオブジェクトを保存しようとしていますが(MongoDB 3.0.2を使用)、エラーメッセージでオブジェクトをエンコードしようとするとCodecConfigurationExceptionを取得しています

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

LocalDateオブジェクト用のコーデックを作成して組み込みました。詳細は次のとおりです。

格納しようとしているオブジェクトDutyBlockには、次のメンバー変数があります。

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

データベース内の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));
    }

}

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

MongoClientのインスタンス化中に、MongoClientレベルでCodecに2つのRaを(CodecRegistryタイプの1つと共に)追加しました。

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

MongoDBデータベース内のorg.bson.Documentの一部としてArrayListオブジェクトのDutyBlockを保存しようとしました。

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.
}

ただし、次のエラーメッセージが表示されます。

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$2.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$1.call(MixedBulkWriteOperation.Java:148)
at com.mongodb.operation.MixedBulkWriteOperation$1.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$2.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)

LocalDateオブジェクトをエンコードしようとするとDutyBlockのコーデックは使用されていないようですが、org.bson.Documentを保存しようとしているコレクションが確かにLocalDateCodecを含む

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

誰がこれが起こっているのかについての洞察を提供できますか?

26
desrepair

数日間の調査の後、私は解決策を見つけました。

DutyBlockCodecは、エンコード/デコードするためにLocalDateCodec(私が作成した)に依存します。この依存関係は、2つのコーデックを同じコーデックレジストリに追加するだけでは満たされません。解決策は、CodecRegistryが依存するコーデックを含むDutyBlockCodecオブジェクト(たとえば、CodecRegistryを含むLocalDateCodec)をDutyBlockCodecのコンストラクター。メンバー変数として保存されます。 LocalDateCodecを使用してエンコードするには、EncoderContext.encodeWithChildContext()メソッドを使用して、エンコードするコーデック、ライター、および要素を渡します。さらに、(元のコードのように)DocumentStringとして記述するのではなく、個々のフィールドを記述します。したがって、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);
    }

}
_

DutyBlockCodecは別のコーデックに依存するため、コンストラクターでCodecRegistryを渡す必要があります。 CodecRegistryLocalDateCodecを作成し、これを引数としてDutyBlockCodecのコンストラクターに渡してから、両方を含む別のCodecRegistryを作成することは可能だと思いますがLocalDateCodecDutyBlockCodec、これはかなり混乱しやすいもので、MongoDBはこのプロセスを容易にするCodecProviderという機能を提供します。

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

これらのCodecProvidersCodecRegistries.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");
_

このプロジェクトのソースコードは https://github.com/desrepair/DutyScheduler で見つけることができます。

22
desrepair