带有MongoDB的ZonedDateTime

18
尝试使用ZonedDateTimeMongoDB。我可以将ZonedDateTime保存在MongoDB中,但当我查看记录时,里面有很多不必要的东西。
> "timestamp" : {
>             "dateTime" : ISODate("2016-12-13T13:45:53.991Z"),
>             "offset" : {
>                 "_id" : "-05:00",
>                 "totalSeconds" : -18000
>             },
>             "zone" : {
>                 "_class" : "java.time.ZoneRegion",
>                 "_id" : "America/New_York",
>                 "rules" : {
>                     "standardTransitions" : [ 
>                         NumberLong(-2717650800)
>                     ],
>                     "standardOffsets" : [ 
>                         {
>                             "_id" : "-04:56:02",
>                             "totalSeconds" : -17762
>                         }, 
>                         {
>                             "_id" : "-05:00",
>                             "totalSeconds" : -18000
>                         }
>                     ],
>                     "savingsInstantTransitions" : [ 
>                         NumberLong(-2717650800), 
>                         NumberLong(-1633280400), 
>                         NumberLong(-1615140000), 
>                         NumberLong(-1601830800), 
>                         NumberLong(-1583690400), 
>                         NumberLong(-1570381200),
> and so on....

当我尝试检索相同的日期时,它给了我以下结果:

> org.springframework.data.mapping.model.MappingException: No property
> null found on entity class java.time.ZonedDateTime to bind constructor
> parameter to!

在使用 LocalDateTime 时,我没有遇到这个问题。第一个问题是:我们是否可以更改某些设置,以便只保留带有 ZonedDateTimeISODate?第二个问题是:是否有类似于 Jsr310JpaConverters 的东西适用于 mongodb

更新:参考以下问卷调查,我创建了自定义转换器并注册了它们,但问题仍然存在。 Spring Data MongoDB with Java 8 LocalDate MappingException

public class ZonedDateTimeToLocalDateTimeConverter implements Converter<ZonedDateTime, LocalDateTime> {
    @Override
    public LocalDateTime convert(ZonedDateTime source) {
        return source == null ? null : LocalDateTime.ofInstant(source.toInstant(), ZoneId
                .systemDefault());
    }
}

public class LocalDateTimeToZonedDateTimeConverter implements Converter<LocalDateTime,
        ZonedDateTime> {
    @Override
    public ZonedDateTime convert(LocalDateTime source) {
        return source == null ? null : ZonedDateTime.of(source, ZoneId.systemDefault());
    }
}

按以下方式进行注册:

@Bean
public CustomConversions customConversions(){
        List<Converter<?,?>> converters = new ArrayList<Converter<?,?>>();
        converters.add(new ZonedDateTimeToLocalDateTimeConverter());
        converters.add(new LocalDateTimeToZonedDateTimeConverter());
        return new CustomConversions(converters);
    }

@Bean
public MongoTemplate getMongoTemplate() throws UnknownHostException {
        MappingMongoConverter converter = new MappingMongoConverter(
                new DefaultDbRefResolver(getMongoDbFactory()), new MongoMappingContext());
        converter.setCustomConversions(customConversions());
        converter.afterPropertiesSet();
        return new MongoTemplate(getMongoDbFactory(), converter);
    }

JPA API 的相关性是什么?如果使用 Spring-Data-MongoDB,那么 JPA API 就没有任何用处。如果使用其他东西,则需要定义您正在使用的内容。 - Neil Stockton
@NeilStockton 我正在使用 <artifactId>spring-data-mongodb</artifactId>。在 JPA 的情况下,提到的转换器会执行转换,但是在这种情况下,我需要创建一个自定义转换器吗? - Gurkha
Spring Data MongoDB与JPA API无关,因此没有AttributeConverter。它使用MongoDB自己的API。请查看Spring Data MongoDB文档。 - Neil Stockton
当我注册这些转换器时,出现了“无法解析方法getMongoDbFactory()”的错误,我应该添加或调用其他类吗? - Rolando F
2个回答

15

看起来Spring支持除了ZonedDateTime转换器之外的所有Java时间转换器。您可以按以下方式注册其中一个。

@Bean
public CustomConversions customConversions(){
    List<Converter<?,?>> converters = new ArrayList<>();
    converters.add(new DateToZonedDateTimeConverter());
    converters.add(new ZonedDateTimeToDateConverter());
    return new CustomConversions(converters);
}

@Bean
public MongoTemplate getMongoTemplate() throws UnknownHostException {
    MappingMongoConverter converter = new MappingMongoConverter(
            new DefaultDbRefResolver(getMongoDbFactory()), new MongoMappingContext());
    converter.setCustomConversions(customConversions());
    converter.afterPropertiesSet();
    return new MongoTemplate(getMongoDbFactory(), converter);
}
    
class DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {
    
     @Override
     public ZonedDateTime convert(Date source) {
              return source == null ? null : ofInstant(source.toInstant(), systemDefault());
         }
     }
    
class ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, Date> {
    
    @Override
    public Date convert(ZonedDateTime source) {
             return source == null ? null : Date.from(source.toInstant());
       }
   }

另外一个可行的解决办法是只使用ZonedDateTime并将其更改为日期,同时持久化到MongoDB。在提取时,您可以很容易地将其从日期更改回带时区的日期时间。

下面是相关的方法,可用于转换。

ZoneId zoneID = ZoneId.of("America/Chicago");

从ZonedDateTime转换为java util date。

Instant instant = Instant.now();
ZonedDateTime zonedDateTime = instant.atZone(zoneId);
Date date = Date.from(zdt.toInstant());

从 Date 到 ZonedDateTime

Instant instant = date.toInstant();
ZonedDateTime zonedDateTime = instant.atZone(zoneId);

另一种选择是实现自定义编解码器以帮助进行转换。我已经为YearMonth创建了一个用于从Mongo文档中过滤YearMonth的自定义编解码器。如果读者想要为Zoned Date Time创建自定义编解码器,则可以将其作为练习。

您可以使用以下库进行基于编解码器的方法。

https://github.com/ylemoigne/mongo-jackson-codec


它可将ZonedDateTime转换为Date,但在我的Mongo模板配置中,您的模板方法给了我“不支持的转换器”异常。您能否使用我的模板更新您的答案,我会将其标记为正确。 - Gurkha
我相信这两个@Bean条目应该放在我的Application类中,对吗? - Thom
@Thom 是的,应该放在应用配置类中。 - s7vr
是的,这样你可以将其持久化,但你会失去精度!"Date类表示具有毫秒精度的特定时间点。" 现在看看ZonedDateTime文档中说了什么"此类存储所有日期和时间字段,精确到纳秒..." - Andi
为了防止保存到数据库时的UTC转换并保留本地日期时间(即在Mongo数据库中使用带有单独时区属性的LocalDateTime),我使用了Chamindu在chamindu.dev/posts/localdatetime-spring-mongodb中所描述的技术。只需确保按照他指示的那样扩展AbstractMongoClientConfiguration而不是AbstractMongoConfiguration。 - tlarson
显示剩余5条评论

13

在花费了太多时间调试后,我终于找到了最新版本的Spring Boot / Spring Data的可行解决方案。这对我目前正在使用的Spring Boot 2.0.0.M7有效。

使用veeram的被接受答案时,我遇到了“无法找到类型的PersistentEntity”的问题。

我希望这能帮助某人避免深入进入棘手的问题。

@Configuration
public class MongoConfiguration {

    @Bean
    public MongoCustomConversions customConversions(){
        List<Converter<?,?>> converters = new ArrayList<>();
        converters.add(DateToZonedDateTimeConverter.INSTANCE);
        converters.add( ZonedDateTimeToDateConverter.INSTANCE);
        return new MongoCustomConversions(converters);
    }

    enum DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {

        INSTANCE;

        @Override
        public ZonedDateTime convert(Date source) {
            return ofInstant(source.toInstant(), systemDefault());
        }
    }

    enum ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, Date> {

        INSTANCE;

        @Override
        public Date convert(ZonedDateTime source) {
            return Date.from(source.toInstant());
        }
    }
}

5
你的解决方案修复了我的问题。Spring Boot 2.0.0.M7可以使用@Bean public CustomConversions customConversions(){...}。但是当我升级到Spring Boot 2.0.1.RELEASE时,它不起作用了。需要使用@Bean public MongoCustomConversions customConversions(){...}。这让我浪费了很多时间。 - RJ.Hwang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接