更新
Spring Boot 2.3 正在集成 spring-data-elasticsearch 4,因此它将支持 ElasticSearch 7.x 开箱即用。它很快就会发布,但你已经可以尝试了:
plugins {
id 'org.springframework.boot' version '2.3.0.RC1'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}
我已经对它进行了积极的测试,并且我的所有测试场景都通过了,所以我肯定会推荐这种方式。对于由于某些原因无法升级到 2.3 的人,我将在下面保留答案。
OLD WORKAROUND(以前版本的原始答案)
由于我们真的不知道 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) 禁用 Elastic Search 自动配置和健康检查组件,因为它们变得不兼容(您以后可能想要实施自己的健康检查)。
@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);
}
}