在 Spring Boot 和 ElasticSearch 中使用 Instant、LocalDateTime 和 ZonedDateTime

Posted

技术标签:

【中文标题】在 Spring Boot 和 ElasticSearch 中使用 Instant、LocalDateTime 和 ZonedDateTime【英文标题】:Using Instant, LocalDateTime and ZonedDateTime with Spring Boot and ElasticSearch 【发布时间】:2019-09-24 05:10:42 【问题描述】:

我正在使用带有 ElasticSearch 的 Spring Boot 2.1.4 和 Spring Data Jest。我最初使用 Java Date 来处理一些带有以下注释的属性:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ")

这样保存到 ElasticSearch 中:

"creationDate": "2019-04-10T14:49:05.672+0000"

现在,我正在从 Date 迁移到 LocalDateTime 和 ZonedDateTime。现在将数据保存到 ElasticSearch 时,我保存了以下属性:

"creationDate": 
    "dayOfYear": 123,
    "dayOfWeek": "FRIDAY",
    "month": "MAY",
    "dayOfMonth": 3,
    "year": 2019,
    "monthValue": 5,
    "hour": 11,
    "minute": 54,
    "second": 12,
    "nano": 238000000,
    "chronology": 
        "id": "ISO",
        "calendarType": "iso8601"
    
,

我需要做些什么来更改它,以便获得与以前的 LocalDateTime 和 ZonedDateTime 相同的 ElasticSearch 数据格式?

我尝试了以下方法:

    自定义对象映射器如下:

    public class CustomEntityMapper implements EntityMapper 
        private final ObjectMapper objectMapper;
    
        public CustomEntityMapper(ObjectMapper objectMapper)   
            this.objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            objectMapper.registerModule(new CustomGeoModule());
            objectMapper.registerModule(new JavaTimeModule());
        
    
        @Override
        public String mapToString(Object object) throws IOException 
            return objectMapper.writeValueAsString(object);
        
    
        @Override
        public <T> T mapToObject(String source, Class<T> clazz) throws IOException 
            return objectMapper.readValue(source, clazz);
        
    
    

    在对象映射器中添加以下内容:

    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    

任何我出错的帮助或指示将不胜感激。

【问题讨论】:

永远不要使用LocalDateTime 来表示片刻,因为该类故意缺少任何时区或UTC偏移量的癌症。使用InstantOffsetDateTimeZonedDateTime 表示时间轴上的特定点。 感谢您的评论,为什么不应该使用 LocalDateTime?我的示例是创建日期的字段。 您的 JSON 输入代表一个时刻,即时间线上的一个特定点。 LocalDateTime 不能代表片刻。阅读课堂文档。在 Stack Overflow 上阅读有关该主题的许多其他帖子。 仅供参考,您所说的“ElasticSearch 数据格式”实际上是标准的ISO 8601 格式,用于以文本方式表示日期时间值。在解析/生成字符串时,这些格式默认在 java.time 类中使用。 您的示例 ISO 8601 字符串与您的示例 JSON 不匹配。提供示例数据时,请务必准确。 【参考方案1】:

设法让它与 Spring Boot 2.1.4 和 Spring Data Jest 一起工作。这是我所做的:

    域对象示例:

    @Document(indexName = "datetest")
    public class DateTest 
    
        @Id
        private String id;
    
        @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ", timezone = "UTC")
        private Instant instant = Instant.now();
    
        @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
        private ZonedDateTime zonedDateTime = ZonedDateTime.now();
    
        @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS")
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSS")
        private LocalDateTime localDateTime = LocalDateTime.now();
    
        // getters/setters
    
    

    ElasticSearch/JEST 配置:

    @Configuration
    public class ESConfig 
    
        @Bean
        public EntityMapper getEntityMapper() 
            return new CustomEntityMapper();
        
    
        @Bean
        @Primary
        public ElasticsearchOperations elasticsearchTemplate(final JestClient jestClient,
                final ElasticsearchConverter elasticsearchConverter,
                final SimpleElasticsearchMappingContext simpleElasticsearchMappingContext, EntityMapper mapper) 
            return new JestElasticsearchTemplate(jestClient, elasticsearchConverter,
                    new DefaultJestResultsMapper(simpleElasticsearchMappingContext, mapper));
        
    
        public class CustomEntityMapper implements EntityMapper 
    
            private final ObjectMapper objectMapper;
    
            public CustomEntityMapper() 
                objectMapper = new ObjectMapper();
                objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
                objectMapper.registerModule(new CustomGeoModule());
                objectMapper.registerModule(new JavaTimeModule());
            
    
            @Override
            public String mapToString(Object object) throws IOException 
                return objectMapper.writeValueAsString(object);
            
    
            @Override
            public <T> T mapToObject(String source, Class<T> clazz) throws IOException 
                return objectMapper.readValue(source, clazz);
            
    
        
    
    

    ElasticSearch 中的结果:

希望这会有所帮助。

【讨论】:

【参考方案2】:

那是因为spring-data-jest 使用了DefaultEntityMapper(Spring Data 的一部分),它创建了自己的ObjectMapper,而不使用 Spring boot 提供的那个。这可以在this related question 中看到。

通过定义自己的EntityMapper,例如CustomEntityMapper,您的解决方案就走在了正确的轨道上。但是,spring-data-jest 将此映射器包装到名为 DefaultJestResultsMapper 的类中,然后由名为 JestElasticsearchTemplate 的 bean 使用。

所以,也许你应该这样做:

@Bean
public JestResultsMapper resultMapper(CustomEntityMapper entityMapper) 
    return new DefaultJestResultsMapper(entityMapper);


@Bean
public JestElasticSearchTemplate template(JestClient client, JestResultsMapper resultsMapper) 
    return new JestElasticSearchTemplate(client, resultsMapper);

这应该将您的CustomEntityMapper 注入到JestResultsMapper 中,而JestResultsMapper 又注入到框架使用的JestElasticSearchTemplate 中。

CustomEntityMapper 中,您可以自动连接默认的ObjectMapper(将自动添加JavaTimeModule),也可以自行配置。

【讨论】:

万岁!上面的部分是我所缺少的。有很多答案和不同的配置,所以我将它剥离回一个基本的 Spring Boot 2 测试应用程序并让它工作。我将在上面添加我的示例类和配置。希望对其他人有所帮助,感谢您的支持!【参考方案3】:

根据 Spring Boot 版本 2 中的this answer,它应该可以根据您的需要开箱即用,以便从 java.time 对象生成字符串

如果你有

com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.4.0

在application.properties中作为依赖和下面的行

spring.jackson.serialization.write_dates_as_timestamps=false

所以唯一剩下的就是将时区表示法添加到默认字符串中,而默认字符串不会有它。

如果标准格式化程序不适合您,可以随时编写自己的序列化器/反序列化器并按照here 解释的方式附加它

【讨论】:

这可以配置默认的ObjectMapper,但是spring-data-jest框架不使用这个映射器,所以这个解决方案不起作用。 @g00glen00b 感谢您的澄清,我不知道这一点。我试图帮助从其他线程收集信息。

以上是关于在 Spring Boot 和 ElasticSearch 中使用 Instant、LocalDateTime 和 ZonedDateTime的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot:Spring Boot整合Logback和PageHelper

spring-boot-starter-jta-atomikos 和 spring-boot-starter-batch

spring boot 系列之六:@Conditional和spring boot的自动配置

Spring Boot系列 Spring Boot介绍和基础POM文件

在 Tomcat 上使用 Spring Boot 进行身份验证

Spring Boot集成Spring Cache 和 Redis