如何将ElasticSearch 7.0版本与Spring Boot集成?

时间:2019-04-19 11:28:48

标签: spring spring-boot elasticsearch

我正在尝试使用最新版本的Elastic Search库,该库已经在maven存储库中提供。

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

但是不确定如何在导入了6.5的Spring Boot中使用第七版。 我的Maven依赖项:

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

2 个答案:

答案 0 :(得分:7)

因为我们真的不知道何时发布 Spring Data Elastic Search 4.x ,所以我发布了整合当前 Spring的方式数据弹性搜索 4.x 和稳定的 Spring Boot 2.1.7 。如果您想使用Spring Repositories和最新的Elastic Search,它可能是您的临时解决方法。

1)在依赖项中强制使用最新的弹性搜索客户端(在我的情况下为: 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)禁用弹性搜索自动配置和运行状况检查组件,因为它们变得不兼容(您稍后可能要实施自己的运行状况检查)。

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

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

}

3)禁用自动配置后,我们需要自行初始化ElasticsearchRestTemplate。我们还需要这样做以提供自定义MappingElasticsearchConverter以避免类不兼容。

/**
 * 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);
    }

}

答案 1 :(得分:1)

如果有人正在使用 Spring Boot 2.1.2 Kotlin ,则以下代码可能会对您有所帮助。我只是从@Alexander Lech答案中翻译过来的,做了一些小的更改:

首先更改为Alexanders答案:

+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| Id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| TimeStamp | datetime(6) | NO   |     | NULL    |                |
| Poster    | longtext    | YES  |     | NULL    |                |
| Body      | longtext    | YES  |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+

我必须排除@SpringBootApplication(exclude = [ElasticsearchAutoConfiguration::class, ElasticsearchDataAutoConfiguration::class]) 才能使其正常工作。

第二:由于我们使用Kotlin,并且自定义转换器包含很多代码,因此对Kotlin的这种翻译可能会帮助某些人:

ElasticsearchDataAutoConfiguration

此后,解决了一些存储库查询的问题。另请注意,不要使用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) } } ,而要使用spring-boot-starter-data-elasticsearch。 (这花了我一些时间)。

是的,代码很丑陋,但是在发布spring-data-elasticsearch:4.0.0.BUILD-SNAPSHOT之后,您可以将其丢弃。