codememo

코덱 구성Spring Boot >= 2.0.1.REASE로 ZoneDateTime을 MongoDB에 저장할 때 예외 발생

tipmemo 2023. 6. 27. 22:15
반응형

코덱 구성Spring Boot >= 2.0.1.REASE로 ZoneDateTime을 MongoDB에 저장할 때 예외 발생

MongoDB로 데이터 액세스를 위한 공식 스프링 부트 가이드를 최소한으로 수정하여 문제를 재현할 수 있었습니다. https://github.com/thokrae/spring-data-mongo-zoneddatetime 을 참조하십시오.

a를 추가java.time.ZonedDateTimeCustomer 클래스의 필드에서 가이드의 예제 코드를 실행하면 코덱 구성이 실패합니다.예외:

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.REASE에서 2.0.1.REASE로 변경하여 해결할 수 있습니다.

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

이제 예외가 사라지고 ZoneDateTime 필드를 포함한 Customer 개체가 MongoDB에 기록됩니다.

저는 스프링 데이터-몽고드 프로젝트에 버그(DATAAMONO-2106)를 제출했지만, 이 행동을 변경하는 것이 원하지 않거나 우선순위가 높은지 이해할 것입니다.

가장 좋은 해결 방법은 무엇입니까?Duckduck이 예외 메시지를 보낼 때 사용자 정의 코덱, 사용자 정의 변환기 또는 Jackson JSR 310을 사용하는 것과 같은 몇 가지 접근 방식을 발견합니다.java.time 패키지의 클래스를 처리하기 위해 프로젝트에 사용자 지정 코드를 추가하지 않습니다.

DATAMONO-2106에서 Oliver Drotbom이 직접 언급한 바와 같이, 표준 시간대가 있는 날짜 시간 유형을 유지하는 것은 Spring Data MongoDB에서 결코 지원되지 않았습니다.

알려진 해결 방법은 다음과 같습니다.

  1. 시간대가 없는 날짜 시간 유형(예: java.time)을 사용합니다.Instant. (일반적으로 백엔드에서만 UTC를 사용하는 것이 바람직하지만, 다른 접근 방식을 따르는 기존 코드 기반을 확장해야 했습니다.)
  2. 사용자 지정 변환기를 작성하고 AbstractMongoConfiguration을 확장하여 등록합니다.실행 예는 테스트 저장소의 분기 변환기를 참조하십시오.

    @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. 사용자 정의 코덱을 작성합니다.적어도 이론적으로는.내 코덱 테스트 브랜치는 스프링 부트 2.0.1과 정상적으로 작동하는 동안 스프링 부트 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;
        }
    }
    

언급URL : https://stackoverflow.com/questions/52677253/codecconfigurationexception-when-saving-zoneddatetime-to-mongodb-with-spring-boo

반응형