web-dev-qa-db-ja.com

Spring Boot> = 2.0.1.RELEASEでMondDBにZonedDateTimeを保存すると、CodecConfigurationException

MongoDBを使用したデータへのアクセス の公式のSpring Bootガイドの最小限の変更で問題を再現できました。参照 https://github.com/thokrae/spring-data-mongo -zoneddatetime

Java.time.ZonedDateTimeフィールドをCustomerクラスに追加した後、ガイドからのサンプルコードの実行がCodecConfigurationExceptionで失敗します。

Customer.Java:

    public String lastName;
    public ZonedDateTime created;

    public Customer() {

出力:

...
Caused by: org.bson.codecs.configuration.CodecConfigurationException`: Can't find a codec for class Java.time.ZonedDateTime.
at org.bson.codecs.configuration.CodecCache.getOrThrow(CodecCache.Java:46) ~[bson-3.6.4.jar:na]
at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.Java:63) ~[bson-3.6.4.jar:na]
at org.bson.codecs.configuration.ChildCodecRegistry.get(ChildCodecRegistry.Java:51) ~[bson-3.6.4.jar:na]

これは、pom.xmlでSpring Bootバージョンを2.0.5.RELEASEから2.0.1.RELEASEに変更することで解決できます。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>

これで例外はなくなり、ZonedDateTimeフィールドを含むCustomerオブジェクト MongoDBに書き込まれます になります。

私はspring-data-mongodbプロジェクトにバグ( DATAMONGO-2106 )を提出しましたが、この動作を変更する必要がないか、優先度が高いかどうかは理解できます。

最良の回避策は何ですか?例外メッセージのためにduckduckgoingするとき、 カスタムコーデックカスタムコンバーター を登録する、または Jackson JSR 31 を使用するなどのいくつかのアプローチを見つけます。 Java.timeパッケージのクラスを処理するために、プロジェクトにカスタムコードを追加したくない。

11
tdkBacke

DATAMONGO-2106 でOliver Drotbohm自身が述べたように、タイムゾーンを持つ日付時刻タイプの永続化は、Spring Data MongoDBではサポートされていませんでした。

これらは既知の回避策です:

  1. タイムゾーンのない日時タイプを使用してください。 Java.time.Instant。 (通常、バックエンドではUTCのみを使用することをお勧めしますが、別のアプローチに従っていた既存のコードベースを拡張する必要がありました。)
  2. カスタムコンバーターを作成し、AbstractMongoConfigurationを拡張して登録します。実行例については、テストリポジトリのブランチ converter を参照してください。

    @Component
    @WritingConverter
    public class ZonedDateTimeToDocumentConverter implements Converter<ZonedDateTime, Document> {
        static final String DATE_TIME = "dateTime";
        static final String ZONE = "zone";
    
        @Override
        public Document convert(@Nullable ZonedDateTime zonedDateTime) {
            if (zonedDateTime == null) return null;
    
            Document document = new Document();
            document.put(DATE_TIME, Date.from(zonedDateTime.toInstant()));
            document.put(ZONE, zonedDateTime.getZone().getId());
            document.put("offset", zonedDateTime.getOffset().toString());
            return document;
        }
    }
    
    @Component
    @ReadingConverter
    public class DocumentToZonedDateTimeConverter implements Converter<Document, ZonedDateTime> {
    
        @Override
        public ZonedDateTime convert(@Nullable Document document) {
            if (document == null) return null;
    
            Date dateTime = document.getDate(DATE_TIME);
            String zoneId = document.getString(ZONE);
            ZoneId zone = ZoneId.of(zoneId);
    
            return ZonedDateTime.ofInstant(dateTime.toInstant(), zone);
        }
    }
    
    @Configuration
    public class MongoConfiguration extends AbstractMongoConfiguration {
    
        @Value("${spring.data.mongodb.database}")
        private String database;
    
        @Value("${spring.data.mongodb.Host}")
        private String Host;
    
        @Value("${spring.data.mongodb.port}")
        private int port;
    
        @Override
        public MongoClient mongoClient() {
            return new MongoClient(Host, port);
        }
    
        @Override
        protected String getDatabaseName() {
            return database;
        }
    
        @Bean
        public CustomConversions customConversions() {
            return new MongoCustomConversions(asList(
                    new ZonedDateTimeToDocumentConverter(),
                    new DocumentToZonedDateTimeConverter()
            ));
        }
    }
    
  3. カスタムコーデックを記述します。少なくとも理論的には。私の コーデックテストブランチ は、Spring Boot 2.0.1で正常に動作しているときにSpring Boot 2.0.5を使用すると、データを非整列化できません。

    public class ZonedDateTimeCodec implements Codec<ZonedDateTime> {
    
        public static final String DATE_TIME = "dateTime";
        public static final String ZONE = "zone";
    
        @Override
        public void encode(final BsonWriter writer, final ZonedDateTime value, final EncoderContext encoderContext) {
            writer.writeStartDocument();
            writer.writeDateTime(DATE_TIME, value.toInstant().getEpochSecond() * 1_000);
            writer.writeString(ZONE, value.getZone().getId());
            writer.writeEndDocument();
        }
    
        @Override
        public ZonedDateTime decode(final BsonReader reader, final DecoderContext decoderContext) {
            reader.readStartDocument();
            long epochSecond = reader.readDateTime(DATE_TIME);
            String zoneId = reader.readString(ZONE);
            reader.readEndDocument();
    
            return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSecond / 1_000), ZoneId.of(zoneId));
        }
    
        @Override
        public Class<ZonedDateTime> getEncoderClass() {
            return ZonedDateTime.class;
        }
    }
    
    @Configuration
    public class MongoConfiguration extends AbstractMongoConfiguration {
    
        @Value("${spring.data.mongodb.database}")
        private String database;
    
        @Value("${spring.data.mongodb.Host}")
        private String Host;
    
        @Value("${spring.data.mongodb.port}")
        private int port;
    
        @Override
        public MongoClient mongoClient() {
            return new MongoClient(Host + ":" + port, createOptions());
        }
    
        private MongoClientOptions createOptions() {
            CodecProvider pojoCodecProvider = PojoCodecProvider.builder()
                    .automatic(true)
                    .build();
    
            CodecRegistry registry = CodecRegistries.fromRegistries(
                    createCustomCodecRegistry(),
                    MongoClient.getDefaultCodecRegistry(),
                    CodecRegistries.fromProviders(pojoCodecProvider)
            );
    
            return MongoClientOptions.builder()
                    .codecRegistry(registry)
                    .build();
        }
    
        private CodecRegistry createCustomCodecRegistry() {
            return CodecRegistries.fromCodecs(
                    new ZonedDateTimeCodec()
            );
        }
    
        @Override
        protected String getDatabaseName() {
            return database;
        }
    }
    
7
tdkBacke