web-dev-qa-db-fra.com

Comment intégrer la version ElasticSearch 7.0 avec Spring Boot?

J'essaie d'utiliser la dernière version de la bibliothèque Elastic Search qui est déjà disponible dans le référentiel maven.

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.0.0</version>
</dependency>

Mais je ne sais pas comment utiliser la 7e version avec Spring Boot qui importe 6.5. Ma dépendance maven:

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  </dependency>
16
Sarvar Nishonboev

Comme nous ne savons pas vraiment quand Spring Data Elastic Search 4.x va sortir, je poste mon chemin d'intégrer la recherche élastique actuelle Spring Data 4.x et stable Spring Boot - 2.1.7. Cela peut fonctionner comme solution de contournement temporaire pour vous si vous souhaitez travailler avec les référentiels Spring et la toute dernière recherche élastique.

1) Forcer le nouveau client de recherche élastique dans vos dépendances (dans mon cas: build.gradle)

dependencies {
    //Force spring-data to use the newest elastic-search client
    //this should removed as soon as spring-data-elasticsearch:4.0.0 is released!
    implementation('org.springframework.data:spring-data-elasticsearch:4.0.0.BUILD-SNAPSHOT') {
        exclude group: 'org.elasticsearch'
        exclude group: 'org.elasticsearch.plugin'
        exclude group: 'org.elasticsearch.client'
    }

    implementation('org.elasticsearch:elasticsearch:7.3.0') { force = true }
    implementation('org.elasticsearch.client:elasticsearch-rest-high-level-client:7.3.0') { force = true }
    implementation('org.elasticsearch.client:elasticsearch-rest-client:7.3.0') { force = true }
}

2) Désactivez les composants de configuration automatique et de vérification de l'état d'Elastic Search lorsqu'ils deviennent incompatibles (vous souhaiterez peut-être implémenter ultérieurement votre propre bilan de santé).

@SpringBootApplication(exclude = {ElasticsearchAutoConfiguration.class, ElasticSearchRestHealthIndicatorAutoConfiguration.class})
@EnableElasticsearchRepositories
public class SpringBootApp {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApp.class, args);
    }

}

3) Comme nous avons désactivé la configuration automatique, nous devons initialiser le ElasticsearchRestTemplate nous-mêmes. Nous devons également le faire pour fournir le MappingElasticsearchConverter personnalisé pour éviter les incompatibilités de classe.

/**
 * Manual configuration to support the newest ElasticSearch that is currently not supported by {@link org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration}.
 *
 * @author aleksanderlech
 */
@Configuration
@EnableConfigurationProperties(ElasticsearchProperties.class)
public class ElasticSearchConfiguration {

    @Primary
    @Bean
    public ElasticsearchRestTemplate elasticsearchTemplate(ElasticsearchProperties configuration) {
        var nodes =  Stream.of(configuration.getClusterNodes().split(",")).map(HttpHost::create).toArray(HttpHost[]::new);
        var client = new RestHighLevelClient(RestClient.builder(nodes));
        var converter = new CustomElasticSearchConverter(new SimpleElasticsearchMappingContext(), createConversionService());
        return new ElasticsearchRestTemplate(client, converter, new DefaultResultMapper(converter));
    }

    private DefaultConversionService createConversionService() {
        var conversionService = new DefaultConversionService();
        conversionService.addConverter(new StringToLocalDateConverter());
        return conversionService;
    }
}

CustomElasticSearchConverter:

/**
 * Custom version of {@link MappingElasticsearchConverter} to support newest Spring Data Elasticsearch integration that supports ElasticSearch 7. Remove when Spring Data Elasticsearch 4.x is released.
 */
class CustomElasticSearchConverter extends MappingElasticsearchConverter {

    private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());

    CustomElasticSearchConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
        super(mappingContext);
        setConversions(conversions);
    }

    CustomElasticSearchConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, GenericConversionService conversionService) {
        super(mappingContext, conversionService);
        setConversions(conversions);
    }

    @Override
    protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property,
                              TypeInformation<R> targetType) {

        if (source == null) {
            return null;
        }

        if (source instanceof List) {
            return readCollectionValue((List) source, property, targetType);
        }

        return super.readValue(source, property, targetType);
    }

    private Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) {

        Class<?> target = targetType.getType();

        if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
            return value;
        }

        if (conversions.hasCustomReadTarget(value.getClass(), target)) {
            return getConversionService().convert(value, target);
        }

        if (Enum.class.isAssignableFrom(target)) {
            return Enum.valueOf((Class<Enum>) target, value.toString());
        }

        return getConversionService().convert(value, target);
    }


    private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
                                      TypeInformation<R> targetType) {

        if (source == null) {
            return null;
        }

        Collection<Object> target = createCollectionForValue(targetType, source.size());

        for (Object value : source) {

            if (isSimpleType(value)) {
                target.add(
                        readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
            } else {

                if (value instanceof List) {
                    target.add(readValue(value, property, property.getTypeInformation().getActualType()));
                } else {
                    target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map) value));
                }
            }
        }

        return (R) target;
    }

    private Collection<Object> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) {

        Class<?> collectionType = collectionTypeInformation.isCollectionLike()//
                ? collectionTypeInformation.getType() //
                : List.class;

        TypeInformation<?> componentType = collectionTypeInformation.getComponentType() != null //
                ? collectionTypeInformation.getComponentType() //
                : ClassTypeInformation.OBJECT;

        return collectionTypeInformation.getType().isArray() //
                ? new ArrayList<>(size) //
                : CollectionFactory.createCollection(collectionType, componentType.getType(), size);
    }

    private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property,
                                                                            Object value) {

        return ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType())
                ? getMappingContext().getRequiredPersistentEntity(value.getClass())
                : getMappingContext().getRequiredPersistentEntity(property.getTypeInformation().getActualType());
    }

    private boolean isSimpleType(Object value) {
        return isSimpleType(value.getClass());
    }

    private boolean isSimpleType(Class<?> type) {
        return conversions.isSimpleType(type);
    }

}
9
Aleksander Lech

Si quelqu'un utilise Spring Boot 2.1.2 et Kotlin, le code suivant peut vous aider. Je viens de le traduire de la réponse @Alexander Lech, avec quelques petits changements:

Première modification d'Alexanders Réponse:

@SpringBootApplication(exclude = [ElasticsearchAutoConfiguration::class, 
ElasticsearchDataAutoConfiguration::class])

J'ai dû exclure ElasticsearchDataAutoConfiguration, pour le faire fonctionner.

Deuxièmement: puisque nous utilisons Kotlin et que le convertisseur personnalisé est beaucoup de code, cette traduction en Kotlin aidera peut-être quelqu'un:

class CustomElasticSearchConverter(mappingContext: MappingContext<out ElasticsearchPersistentEntity<*>, ElasticsearchPersistentProperty>, customConversionService: GenericConversionService?) : MappingElasticsearchConverter(mappingContext, customConversionService) {

    private val conversionsNew = ElasticsearchCustomConversions(emptyList<Any>())

    init {
        setConversions(conversionsNew)
    }

    override fun <R : Any?> readValue(source: Any?, property: ElasticsearchPersistentProperty, targetType: TypeInformation<R>): R? {
        if (source == null) {
            return null
        }

        if (source is Collection<*>) {
            return readCollectionValue(source, property, targetType) as R?;
        }

        return super.readValue(source, property, targetType);
    }

    private fun readCollectionValue(source: Collection<*>?, property: ElasticsearchPersistentProperty, targetType: TypeInformation<*>): Any? {

        if (source == null) {
            return null
        }

        val target = createCollectionForValue(targetType, source.size)

        for (value in source) {
            require(value != null) { "value must not be null" }

            if (isSimpleType(value)) {
                target.add(readSimpleValue(value, if (targetType.componentType != null) targetType.componentType!! else targetType))
            } else {
                if (value is MutableCollection<*>) {
                    target.add(readValue(value, property, property.typeInformation.actualType as TypeInformation<out Any>))
                } else {
                    @Suppress("UNCHECKED_CAST")
                    target.add(readEntity(computeGenericValueTypeForRead(property, value), value as MutableMap<String, Any>?))
                }
            }
        }

        return target
    }

    private fun readSimpleValue(value: Any?, targetType: TypeInformation<*>): Any? {

        val target = targetType.type

        @Suppress("SENSELESS_COMPARISON")
        if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
            return value
        }

        if (conversionsNew.hasCustomReadTarget(value.javaClass, target)) {
            return conversionService.convert(value, target)
        }

        @Suppress("UNCHECKED_CAST")
        return when {
            Enum::class.Java.isAssignableFrom(target) -> enumByName(target as Class<Enum<*>>, value.toString())
            else -> conversionService.convert(value, target)
        }
    }

    private fun enumByName(target: Class<Enum<*>>, name: String): Enum<*> {
        val enumValue = target.enumConstants.find { it.name == name }
        require(enumValue != null) { "no enum value found for name $name and targetClass $target" }
        return enumValue
    }

    private fun createCollectionForValue(collectionTypeInformation: TypeInformation<*>, size: Int): MutableCollection<Any?> {

        val collectionType = when {
            collectionTypeInformation.isCollectionLike -> collectionTypeInformation.type
            else -> MutableList::class.Java
        }

        val componentType = when {
            collectionTypeInformation.componentType != null -> collectionTypeInformation.componentType
            else -> ClassTypeInformation.OBJECT
        }

        return when {
            collectionTypeInformation.type.isArray -> ArrayList(size)
            else -> CollectionFactory.createCollection(collectionType, componentType!!.type, size)
        }
    }

    private fun computeGenericValueTypeForRead(property: ElasticsearchPersistentProperty, value: Any): ElasticsearchPersistentEntity<*> {

        return when {
            ClassTypeInformation.OBJECT == property.typeInformation.actualType -> mappingContext.getRequiredPersistentEntity(value.javaClass)
            else -> mappingContext.getRequiredPersistentEntity(property.typeInformation.actualType!!)
        }
    }

    private fun isSimpleType(value: Any): Boolean {
        return isSimpleType(value.javaClass)
    }

    private fun isSimpleType(type: Class<*>): Boolean {
        return conversionsNew.isSimpleType(type)
    }

}

Après cela, les problèmes avec certaines requêtes de référentiel ont été résolus. N'oubliez pas non plus d'utiliser spring-boot-starter-data-elasticsearch mais spring-data-elasticsearch:4.0.0.BUILD-SNAPSHOT. (Cela m'a pris du temps).

Oui, le code est moche, mais après spring-data-elasticsearch:4.0.0 est libéré, vous pouvez le jeter.

1
Bernhard Kern