#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));
}
}