Как правильно форматировать дату с помощью Spring Data Elasticsearch - PullRequest
0 голосов
/ 03 апреля 2020

Я использую SpringBoot 2.2.5 с Elasticsearch 6.8.6. Я в процессе перехода с Spring Data Jest на использование механизма транспорта REST Spring Data Elasticsearch с ElasticsearchEntityMapper.

У меня есть поле Date со следующим определением:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
private Date date;

Мне бы хотелось, чтобы дата сохранялась в Elasticsearch следующим образом:

"date": "2020-04-02T14:49:05.672+0000"

Когда я запускаю приложение, создается индекс, но при попытке сохранить сущность я получаю следующее исключение:

Caused by: org.elasticsearch.client.ResponseException: method [POST], host [http://localhost:9200], URI [/trends/estrend?timeout=1m], status line [HTTP/1.1 400 Bad Request]
{"error":{"root_cause":[{"type":"mapper_parsing_exception","reason":"failed to parse field [date] of type [date] in document with id 'rS5UP3EB9eKtCTMXW_Ky'"}],"type":"mapper_parsing_exception","reason":"failed to parse field [date] of type [date] in document with id 'rS5UP3EB9eKtCTMXW_Ky'","caused_by":{"type":"illegal_argument_exception","reason":"Invalid format: \"1585905425266\" is malformed at \"5266\""}},"status":400}

Есть какие-нибудь указатели на то, что я делаю неправильно, и что я должен сделать, чтобы это исправить?

Ниже приведены определения конфигурации и сущностей:

@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {

    @Value("${spring.data.elasticsearch.host}")
    private String elasticSearchHost;

    @Value("${spring.data.elasticsearch.port}")
    private String elasticSearchPort;

    @Bean
    public RestHighLevelClient elasticsearchClient() {
        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
        .connectedTo(elasticSearchHost + ":" + elasticSearchPort)
        .usingSsl()
        .build();
        return RestClients.create(clientConfiguration).rest();
    }

    @Bean
    public EntityMapper entityMapper() {
        ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService());
        entityMapper.setConversions(elasticsearchCustomConversions());
        return entityMapper;
    }
}


package com.es.test;

import java.util.Date;
import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Document(indexName = "trends")
public class EsTrend {

    @Id
    private UUID id;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
    private Date date;

    private String entityOrRelationshipId;

    // getter and setters

}

Обновление :

Если я отключу бин ElasticsearchEntityMapper, я не получу исключение, и дата будет записана в правильном формате в Elasticsearch. Что-нибудь еще нужно настроить для ElasticsearchEntityMapper?

1 Ответ

1 голос
/ 04 апреля 2020

Во-первых, пожалуйста, не используйте картограф по умолчанию на основе Джексона. Он удален в следующей основной версии Spring Data Elasticsearch (4.0). Тогда выбора не будет, и внутри будет использоваться ElasticsearchEntityMapper.

Что касается вашей проблемы: ElasticsearchEntityMapper в версии 3.2, которая используется Spring Boot в настоящее время, не использует соответствующую дату информация из атрибута @Field для преобразования сущности, она используется только для создания отображений индекса. Это была отсутствующая функция или ошибка, и она была исправлена ​​в следующей основной версии, там был полностью переработан весь процесс отображения.

Что вы можете сделать в текущей ситуации: вам нужно добавить собственные конвертеры. Вы можете сделать это в своем классе конфигурации следующим образом:

@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {

    private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

    @Value("${spring.data.elasticsearch.host}")
    private String elasticSearchHost;

    @Value("${spring.data.elasticsearch.port}")
    private String elasticSearchPort;

    @Bean
    public RestHighLevelClient elasticsearchClient() {
        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
            .connectedTo(elasticSearchHost + ":" + elasticSearchPort)
            .usingSsl()
            .build();
        return RestClients.create(clientConfiguration).rest();
    }

    @Bean
    public EntityMapper entityMapper() {
        ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), new DefaultConversionService());
        entityMapper.setConversions(elasticsearchCustomConversions());
        return entityMapper;
    }

    @Override
    public ElasticsearchCustomConversions elasticsearchCustomConversions() {
        return new ElasticsearchCustomConversions(Arrays.asList(DateToStringConverter.INSTANCE, StringToDateConverter.INSTANCE));
    }

    @WritingConverter
    enum DateToStringConverter implements Converter<Date, String> {
        INSTANCE;
        @Override
        public String convert(Date date) {
            return formatter.format(date);
        }
    }

    @ReadingConverter
    enum StringToDateConverter implements Converter<String, Date> {
        INSTANCE;
        @Override
        public Date convert(String s) {
            try {
                return formatter.parse(s);
            } catch (ParseException e) {
                return null;
            }
        }
    }
}

Вам все еще нужно иметь формат даты в аннотации @Field, поскольку он необходим для создания правильных отображений индекса.

И вам следует изменить код, чтобы использовать введенные классы времени Java 8, такие как LocalDate или LocalDateTime, Spring Data Elasticsearch поддерживает их "из коробки", тогда как java.util.Date потребуются пользовательские преобразователи.

Изменить 09.04.2020: добавлены необходимые аннотации @WritingConverter и @ReadingConverter.

Редактировать 19.04.2020: Spring Data Elasticsearch 4.0 будет поддерживать java.util.Date класс из коробки с аннотацией @Field.

...