Как указать тип поля для хэш-карты временных средств доступа в Spring Data Elasticsearch

#java #elasticsearch #spring-data-elasticsearch

Вопрос:

Учитывая

 private Map<String, ZonedDateTime> timePoints = new HashMap<>();
 

Как указать spring-data тип поля?

При размещении непосредственно в словаре конвертер пытается проанализировать ключ и значение вместе, если они были строкой даты.

 @Field(FieldType.Date)
private Map<String, ZonedDateTime> timePoints = new HashMap<>();
 

Когда тип поля не указан, появляется следующая ошибка:

 Type .. of property .. is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for writing! It will be mapped to a complex object in Elasticsearch!
 

Ответ №1:

Добавление аннотации к свойству, например

 @Field(FieldType.Date)
private Map<String, ZonedDateTime> timePoints = new HashMap<>();
 

не может работать, потому что a Map не является временным типом и, следовательно, не может быть преобразован как таковой.

Если вы оставите аннотацию, Map<String, ZonedDateTime> она будет интерпретироваться как объект. Если у вас, например, есть

 Map<String, ZonedDateTime> map = new Map();
map.put("utc", ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("UTC")));
map.put("paris", ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Europe/Paris")));
 

а затем при сохранении этого объекта с помощью Spring Data Elasticsearch будет предпринята попытка создать объект для отправки в Elasticsearch (представление в формате JSON), который выглядит следующим образом:

 {
  "utc": {
    
  },
  "paris": {
    
  }
}
 

Внутренние объекты, которые должны представлять временные значения, хранятся как вложенные объекты, а не как какое-то преобразованное значение, поскольку невозможно добавить тип поля к значениям карты — вы видите предупреждения об этом в журналах.

Но использование a Map в качестве свойства в Elasticsearch в любом случае проблематично. Ключи интерпретируются как свойства подобъекта. Невозможно определить отображение типов в индексе раньше, потому что неизвестно, какие возможные имена могут иметь эти свойства. В моем примере это были «utc» и «paris», но это могла быть любая строка. Каждое из этих значений будет добавлено Elasticsearch в качестве динамически отображаемого поля в индекс. Это может привести к так называемому взрыву сопоставления, поэтому Elasticsearch ограничивает количество полей в индексе значением по умолчанию 1000. Вы могли бы переосмыслить способ хранения данных в Elasticsearch.

Если вы хотите придерживаться a Map , вам нужно будет написать пользовательский конвертер, который сможет преобразовать ваш Map<String, ZonedDateTime> в a Map<String, String> и обратно.

Ответ №2:

Не знаю, понял ли я ваш вопрос на 100%, но вам следует попробовать реализовать постоянные и добавить аннотированные свойства, эти значения будут автоматически поддерживаться для объектов, хранящихся в Elasticsearch, как в этом примере:

 @Document(indexName = "person")
public class Person implements Persistable<Long> {

    @Nullable @Id
    private Long id;

    @CreatedDate
    @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
    private Instant created;

    @Nullable @CreatedBy
    private String createdBy;

    @LastModifiedDate
    @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
    private Instant lastModified;

    @Nullable @LastModifiedBy
    private String lastModifiedBy;

    @Nullable
    public Long getId() { return id; }

    @Override
    public boolean isNew() { return id == null || (createdBy == null amp;amp; created == null); }

    // other properties, getter, setter...
}
 

Ответ №3:

Из документации похоже, что вам, возможно, придется создавать пользовательские конвертеры и добавлять их в пользовательский ElasticsearchCustomConversions компонент. Кажется, что приведенные ниже преобразователи работают:

     @Bean
    public ElasticsearchCustomConversions elasticsearchCustomConversions() {
        return new ElasticsearchCustomConversions(
                Arrays.asList(new ZonedDateTimeWriteConverter(), new ZonedDateTimeReadConverter()));
    }
    
    @WritingConverter
    static class ZonedDateTimeWriteConverter implements Converter<ZonedDateTime, String> {

        @Override
        public String convert(ZonedDateTime source) {
            return DateTimeFormatter.ISO_ZONED_DATE_TIME.format(source);
        }
    }

    @ReadingConverter
    static class ZonedDateTimeReadConverter implements Converter<String, ZonedDateTime> {

        @Override
        public ZonedDateTime convert(String source) {
            return ZonedDateTime.from(DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(source));
        }
    }