在Spring Data MongoDB中注册一个新的日期转换器Auditable,以便支持ZonedDateTime。

18

我希望我的可审核(包括 @CreatedDate@LastModifiedDate)MongoDB 文档能够使用 ZonedDateTime 字段。

显然,Spring Data 不支持这种类型(请查看 org.springframework.data.auditing.AnnotationAuditingMetadata)。

框架版本:Spring Boot 2.0.0Spring Data MongoDB 2.0.0

Spring Data 审计错误:

java.lang.IllegalArgumentException: Invalid date type for member <MEMBER NAME>!
Supported types are [org.joda.time.DateTime, org.joda.time.LocalDateTime, java.util.Date, java.lang.Long, long].

Mongo配置:

@Configuration
@EnableMongoAuditing
public class MongoConfiguration {

}

可审计实体:

public abstract class BaseDocument {

    @CreatedDate
    private ZonedDateTime createdDate;

    @LastModifiedDate
    private ZonedDateTime lastModifiedDate;

}

我尝试过的事情

我还尝试为ZonedDateTime创建自定义转换器,但是Spring Data不予考虑。类DateConvertingAuditableBeanWrapper具有ConversionService,该服务在构造方法中配置了JodaTimeConvertersJsr310ConvertersThreeTenBackPortConverters

自定义转换器:

@Component
public class LocalDateTimeToZonedDateTimeConverter implements Converter<LocalDateTime, ZonedDateTime> {

    @Override
    public ZonedDateTime convert(LocalDateTime source) {
        return source.atZone(ZoneId.systemDefault());
    }

}

Spring Data DateConvertingAuditableBeanWrapper:

class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory {

    abstract static class DateConvertingAuditableBeanWrapper implements AuditableBeanWrapper {

        private final ConversionService conversionService;

    }
}

是否有可能审计ZonedDateTime字段?

我该如何注册转换器?


这个答案不可行,因为Spring Data Auditing使用自己的转换器,而不是在MappingMongoConverter中注册的转换器。Java 8日期/时间(JSR-310)类型映射与Spring Data MongoDB - ciri-cuervo
它可以工作,因为当值类型与目标类型相同时,无需转换,因此您可以创建自己的datetimeprovider来生成用于审计的ZonedDateTime,我已经尝试过它可以工作。 - Gary Gan
1个回答

23
创建一个DateTimeProvider,用于在审核时提供当前时间:
@Component("dateTimeProvider")
public class CustomDateTimeProvider implements DateTimeProvider {
    
    @Override
    public Optional<TemporalAccessor> getNow() {
        return Optional.of(ZonedDateTime.now());
    }
}

然后:

@Configuration
@EnableMongoAuditing(dateTimeProviderRef = "dateTimeProvider")
public class MongoConfiguration {
    
    @Bean
    public MongoCustomConversions customConversions() {
        List<Converter<?, ?>> converters = new ArrayList<>();
        converters.add(new DateToZonedDateTimeConverter());
        converters.add(new ZonedDateTimeToDateConverter());
        return new MongoCustomConversions(converters);
    }

    class DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {

        @Override
        public ZonedDateTime convert(Date source) {
            return source == null ? null : 
                    ZonedDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault());
        }
    }

    class ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, Date> {

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

然而,我不会为此目的使用ZonedDateTime。我会坚持使用OffsetDateTime

OffsetDateTimeZonedDateTimeInstant都以纳秒精度存储时间线上的瞬间。 Instant是最简单的,只表示瞬时。 OffsetDateTime将偏移量从UTC /格林威治添加到瞬时时间中,这允许获取本地日期时间。 ZonedDateTime添加了完整的时区规则。

旨在在更简单的应用程序中使用ZonedDateTimeInstant来模拟数据。在更详细地建模日期时间概念或与数据库通信或网络协议中,可以使用此类。


1
完美运行。 - Bogdan Pisarenko
当使用这些自定义转换时,您会失去精度,因为正如您所说:OffsetDateTime,ZoneDateTime和Instant具有纳秒精度,但Date仅具有毫秒精度。其结果是持久化例如ZonedDateTime,然后再次读取它,会导致对象不相等。 - Andi
这是正确的。保存/检索后,纳秒精度将丢失。这就是为什么在我的DateTimeProvider中,我将精度截断到毫秒级别。Optional.of(ZonedDateTime.now(ZoneOffset.UTC)) .map(now -> now.withNano((now.getNano() / 1_000_000) * 1_000_000)) - egelev

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