Проблема с индексацией при миграции Spring Data Elastic с 3.x на 4.x

В нашем монолитном приложении, которое использовало JHIPSTER-6.10.5, мы использовали Spring-Data-Elastic Version: 3.3.1 с Elastic Search Version: 6.8.8. . У нас есть несколько отношений @ManyToOne и @OneToMany с более чем 100 объектами. В некоторых случаях максимум 7 объектов ссылаются друг на друга (я имею в виду взаимосвязанные, а не только от одного к другому). Для эластичного поиска мы использовали

  1. Чтобы игнорировать индексацию: @JsonIgnoreProperities(value = { "unwanted fields" }, allowSetters = true) и @JsonIgnore там, где они не нужны.
  2. Чтобы отобразить отношения: на ManyToOne мы используем @JsonBackReference с соответствующим @JsonManagedReference на соответствующих отношениях OneToMany.

Сейчас мы находимся в процессе перехода на Jhipster-7.0.1 и начали замечать следующие проблемы:

  1. Новая версия Spring-Data-Elastic: 4.1.6 с версией Elastic Search: 7.9.3
  2. Now with Spring data elastic, the Jackson based mapper is not available we are seeing multiple StackOverflow errors. Below is the migration change we did on the annotations:
    1. On the relationships we have added @Field(type = FieldType.Nested, ignoreMalformed = true, ignoreFields = {"unwanted fields"}). This stopped StackOverflow errors at Spring data level but still throw StackOverflow errors at elastic rest-client level internally. So, we are forced to use @Transient to exclude all the OnetoMany relations.
    2. Даже в отношениях ManyToOne с вышеупомянутой аннотацией @Field мы сталкиваемся с исключением elasticsearchException с превышением предела общего количества полей [1000] в индексе [].
    3. Я попытался следовать документации по весенним данным, но не смог его разрешить.
    4. Мы также сохранили аннотации Json(Jackson), созданные Jhipster, но они не действуют.

На данный момент мы застопорились, так как не знаем, как решить эти проблемы; лично было очень удобно и хорошо задокументировано использование аннотаций Json; Мы, будучи новичками как в эластичном поиске, так и в эластичном поиске пружинных данных, начали использовать его только последние 8 месяцев назад, не в состоянии понять, как исправить эти ошибки. Пожалуйста, спросите, если я пропустил какую-либо необходимую информацию. Я поделюсь настолько, насколько это не нарушает политику организации.

Пример репозитория кода по запросу на gitter: https://gitlab.com/thelearner214/spring-data-es-sample

заранее спасибо


person curious learner    schedule 16.05.2021    source источник


Ответы (2)


Взгляните на репозиторий, на который вы ссылаетесь на gitter (вы можете добавить ссылку здесь).

Во-первых: аннотация @Field используется для записи сопоставления индекса, а свойство ignoreFields необходимо для разрыва циклических ссылок при построении сопоставления. Он не используется, когда объект записывается в Elasticsearch.

Что происходит, например, с объектами Address и Customer во время записи в Elasticsearch: документ Customer имеет Addresses, поэтому эти адреса преобразуются как вложенные документы, встроенные в документ Customer. Но у Address есть Customer, поэтому при записи адреса Customer встраивается в этот элемент Address, который уже является вложенным документом заказчика.

Я полагаю, что Customers не должны храниться в Address и наоборот. Поэтому вам нужно пометить эти встроенные документы как @org.springframework.data.annotation.Transient, вам не нужна аннотация @Field, поскольку вы не хотите хранить их как свойства в индексе.

Аннотации Джексона больше не используются Spring Data Elasticsearch.

Основная проблема используемого здесь подхода заключается в том, что моделирование исходит из реляционного мира — связывание и объединение различных таблиц с отношениями (один|многие) к {одному|многим), проявляющееся в графе объектов Java с помощью ORM. mapper — используется в хранилище данных на основе документов, в котором эти концепции не используются.

Раньше это работало в вашей предыдущей версии, потому что более старая версия Spring Data Elasticsearch также использовала Джексона, и поэтому эти поля были пропущены при записи, теперь вам нужно добавить аннотацию @Transient, которая является аннотацией Spring Data.

Но я не знаю, как @Transient может мешать Spring Data JPA — еще один момент, показывающий, что не стоит использовать один и тот же класс Java для разных хранилищ.

person P.J.Meisch    schedule 16.05.2021
comment
Спасибо за быстрый и подробный ответ @P.J.Meish. Насколько я понимаю: Поскольку Customer уже имеет Address вложенных документов, нам нужно отметить Customer в документе Address, который должен быть помечен @Transient. Это может нарушить отображение ORM, как вы упомянули. Еще один быстрый вопрос: вы рекомендуете разделить классы Entity и Document и сопоставить/заполнить их отдельно перед сохранением? - person curious learner; 16.05.2021
comment
Кроме того, есть ли какие-либо другие аннотации, кроме @Transient, доступные или планируемые только для Spring Data Elastic без влияния на JPA. Спрашиваю, потому что изменение структуры проекта для обновления версии приведет к значительным временным и бюджетным затратам для существующих проектов. - person curious learner; 16.05.2021
comment
Я всегда буду использовать разные классы для хранения данных в разных хранилищах. Если вы это сделаете, нет необходимости в дополнительных аннотациях. Что также делают многие люди: они возвращают сущности магазина напрямую через REST API. И там Джексон снова будет интерпретировать эти аннотации. Их разделение означает больше работы, но вы получаете более чистую рабочую настройку с меньшей опасностью несовместимых настроек. - person P.J.Meisch; 17.05.2021

Вот подход, который мы используем в качестве временной меры, пока мы не перепишем/не найдем лучшее решение. Нельзя использовать отдельные классы для ES, например @P.J.Meisch, так как нам нужно поддерживать большое количество объектов, а программа миграции микросервисов уже выполняется.

Пишу сюда, так как это может быть полезно для кого-то еще с похожей проблемой.

Создал утилиту для сериализации и десериализации объекта, чтобы воспользоваться преимуществами аннотаций Джексона в классе. Пример: @JsonIgnoreProperities, @JsonIgnore и т. д. Таким образом, мы можем сократить использование аннотации @Transient и по-прежнему получать идентификаторы связанных объектов.

package com.sample.shop.service.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;

import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;

public class ESUtils {

private static final Logger log = LoggerFactory.getLogger(ESUtils.class);

public static <T> Optional<T> mapForES(Class<T> type, T input) {
    ObjectMapper mapper = getObjectMapper();
    try {
        return Optional.ofNullable(mapper.readValue(mapper.writeValueAsString(input), type));
    } catch (JsonProcessingException e) {
        log.error("Parsing exception {} {}", e.getMessage());
        return Optional.empty();
    } 
}

public static <T> List<T> mapListForES(Class<T> type, List<T> input) {
    ObjectMapper mapper = getObjectMapper();
    try {
        JavaType javaType = mapper.getTypeFactory().constructCollectionType(List.class, type);
        String serialText = mapper.writeValueAsString(input);
        return mapper.readValue(serialText, javaType);
    } catch (JsonProcessingException e) {
        log.error("Parsing exception {} {}", e.getMessage());
    } 
}

@NotNull
private static ObjectMapper getObjectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    mapper.configure(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL, true);
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    Hibernate5Module module = new Hibernate5Module();
    module.disable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);
    module.enable(Hibernate5Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
    module.enable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
    module.enable(Hibernate5Module.Feature.REPLACE_PERSISTENT_COLLECTIONS);
    return mapper;
    }
}

Затем, чтобы сохранить одну запись, скорректируйте логику сохранения, чтобы использовать указанную выше утилиту, например:

//    categorySearchRepository.save(result); instead of the Jhipster generated code let's use the ESUtils
ESUtils.mapForES(Category.class,category).map(res -> categorySearchRepository.save(res));

И сохранить список для массовой переиндексации с помощью второй утилиты:

Page<T> categoryPage = jpaRepository.findAll(page);
List<T> categoryList = ESUtils.mapListForES(Category.class, categoryPage.getContent());
elasticsearchRepository.saveAll(categoryList);

Возможно, это не лучшее решение, но мы выполнили работу по миграции.

person curious learner    schedule 05.07.2021