CodecConfigurationException when saving ZonedDateTime to MongoDB with Spring Boot >= 2.0.1.RELEASE

后端 未结 1 1916
南旧
南旧 2020-12-11 15:17

I was able to reproduce my problem with a minimal modification of the official Spring Boot guide for Accessing Data with MongoDB, see https://github.com/thokrae/spring-data-

相关标签:
1条回答
  • 2020-12-11 15:43

    Persisting date time types with time zones was never supported by Spring Data MongoDB, as stated by Oliver Drotbohm himself in DATAMONGO-2106.

    These are the known workarounds:

    1. Use a date time type without a time zone, e.g. java.time.Instant. (It is generally advisable to only use UTC in the backend, but I had to extend an existing code base which was following a different approach.)
    2. Write a custom converter and register it by extending AbstractMongoConfiguration. See the branch converter in my test repository for a running example.

      @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. Write a custom codec. At least in theory. My codec test branch is unable to unmarshal the data when using Spring Boot 2.0.5 while working fine with Spring Boot 2.0.1.

      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;
          }
      }
      
    0 讨论(0)
提交回复
热议问题