es client api 升级
背景
公司项目从sring-boot2
升级到了spring-boot3
,es的服务端也跟着升级到了es8 ,而es的客户端7和服务端8 是不兼容的,
客户端es 7使用的是: elasticsearch-rest-high-level-client
es 8 升级到: elasticsearch-java
两者之间查询api的变化还是比较大的,也花了不少时间在这个修改上,所以记录下中间的切换姿势,仅供大家参考
升级过程
依赖调整
xml
<!--es7 版本客户端 -->
<!-- <dependency>-->
<!-- <groupId>com.baibu.platform</groupId>-->
<!-- <artifactId>ka-order-server-interface</artifactId>-->
<!-- <version>7.1.0</version>-->
<!-- </dependency>-->
<!--es8 版本客户端 -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.10.4</version>
</dependency>
查询api代码调整
创建client
java
@Bean
public ElasticsearchClient elasticsearchClient(ESProperties esProperties) {
HttpHost[] httpHosts = new HttpHost[esProperties.getNodes().size()];
// 这里配置你的es服务端host
for (int i = 0; i < esProperties.getNodes().size(); i++) {
ESProperties.Node node = esProperties.getNodes().get(i);
HttpHost httpHost = new HttpHost(node.getHost(), node.getPort(), node.getScheme());
httpHosts[i] = httpHost;
}
//
RestClient restClient = RestClient.builder(httpHosts).setHttpClientConfigCallback(httpClientBuilder -> {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
// 这里是设置服务端账户,密码,没有可以不用
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(esProperties.getUsername(), esProperties.getPasswd()));
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
return httpClientBuilder;
}).build();
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
// And create the API client
ElasticsearchClient elasticsearchClient = new ElasticsearchClient(transport);
return elasticsearchClient;
}
查询api
java
// 构建boolquery
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
// must wildcard 模糊查询
boolQueryBuilder.must(query -> query.wildcard(t -> t.field("wildcard").value("*" + "1111")));
// terms 多个值匹配
List<String> list ;
boolQueryBuilder.must(query -> query.terms(t -> t.field("terms").terms(s -> s.value(
.stream().map(FieldValue::of).collect(Collectors.toList())))));
// term 匹配
boolQueryBuilder.must(query -> query.term(t -> t.field("term").value(111)));
// rang 范围 查询
boolQueryBuilder.must(query -> query.range(t -> t.field("range").gte(JsonData.of("格式化的日期".replaceFirst(" ", "T")))));
// nested 嵌套查询
boolQueryBuilder.must(
query -> query.nested(nestedQuery -> nestedQuery.query(wildcardQuery -> wildcardQuery.range(t -> t.field("nested.wildcardQuery").gte(JsonData.of("格式化的日期".replaceFirst(" ", "T"))))).scoreMode(ChildScoreMode.None).path("nested"))
);
SearchRequest searchRequest = SearchRequest.of(s -> s
// 要查询的索引名
.index(vo.getIndex())
// 查询 条件
.query(q -> q
.bool(boolQueryBuilder.build())
// 分页
).from((vo.getPageNum() - 1) * vo.getPageSize())
.size(vo.getPageSize())
// 排序字段
.sort(sorts.stream().map(sort -> SortOptions.of(a -> a.field(f -> f.field(sort.getSortColumn()).order(sort.getSortType())))).collect(Collectors.toList()))
// 查询结果包含哪些字段
.source(source -> source.filter(f -> f.includes(Arrays.stream(vo.getInclude()).toList()).excludes("")))
);
// 这里可以打印es查询 Query DSL ,可以复制到es 控制台验证查询结果
log.info("ES搜索引擎分页请求参数={}", searchRequest.toString());
// 获取查询结果 返回结果是一个map ,id是key
SearchResponse<Map> elasticsearchClient.search(searchRequest, Map.class)
List<Long> id = searchResponse.hits().hits().stream().map(e -> e.source().get("id")).collect(Collectors.toList());
当然你也可以参考官网的方式 一个Query 一个Query 的must,个人觉得不是很方便
java
tring searchText = "bike";
double maxPrice = 200.0;
// Search by product name
Query byName = MatchQuery.of(m -> m
.field("name")
.query(searchText)
)._toQuery();
// Search by max price
Query byMaxPrice = RangeQuery.of(r -> r
.field("price")
.gte(JsonData.of(maxPrice))
)._toQuery();
// Combine name and price queries to search the product index
SearchResponse<Product> response = esClient.search(s -> s
.index("products")
.query(q -> q
.bool(b -> b
.must(byName)
.must(byMaxPrice)
)
),
Product.class
);
// 获取查询结果
List<Hit<Product>> hits = response.hits().hits();
for (Hit<Product> hit: hits) {
Product product = hit.source();
logger.info("Found product " + product.getSku() + ", score " + hit.score());
}
聚合统计
java
// Aggregation 统计 terms 字段每个值和对应值的数量,也可以统计avg 、interval、等
Aggregation aggregation = AggregationBuilders.terms(terms -> terms.field("terms"));
SearchRequest searchRequest = SearchRequest.of(s -> s.index("索引name"))
// 查询条件
.query(q -> q.bool(vo.getBoolQuery()))
// 聚合条件 这里 aggregation 也可以通过lambda 自定义 a -> a.histogram(h -> h.field("price").interval(50.0))
.aggregations("aggregations",aggregation));
SearchResponse searchResponse = elasticsearchClient.search(searchRequest, Map.class);
// 获取统计结果
Aggregate terms = (Aggregate) searchResponse.aggregations().get("aggregations");
searchTypeList.lterms().buckets().array().forEach(e -> {
long quantity = e.docCount();
String key= e.key();
});
注解方式
上面的方式很繁琐,每增加一个条件都需要我们手动设置条件查询语句,我们可以通过在字段上加上自定义注解的方式 去生成对用的查询条件
主要逻辑如下: 源码放在github ,大家自取 https://github.com/Rfruelu/es-search-api-generator
java
/**
* 查询模式
*/
public enum EsQueryMode {
TERM,
TERMS,
WILDCARD,
RANGE,
}
java
/**
* 通用转换
*
* @author LuTshoes
* @version 1.0
*/
public class GeneralConvertHandler implements IConvertHandler {
/**
* 将注解和对象转换为BoolQuery
*
* @param annotation 注解
* @param o 对象
* @return 转换后的BoolQuery
*/
@Override
public BoolQuery convert(Annotation annotation, Object o) {
// 判断注解是否为GeneralConvert类型并且对象不为空
if (annotation instanceof GeneralConvert && Objects.nonNull(o)) {
// 获取注解的key值
String key = ((GeneralConvert) annotation).key();
// 获取注解的查询模式
EsQueryMode mode = ((GeneralConvert) annotation).mode();
// 使用switch语句根据查询模式执行不同的逻辑
switch (mode) {
case TERM:
// 如果查询模式是TERM,则构建BoolQuery对象,添加term查询条件
return QueryBuilders.bool().must(t -> t.term(f -> f.field(key).value(FieldValue.of(JsonData.of(o))))).build();
case TERMS:
// 如果查询模式是TERMS,并且对象是集合类型
if (o instanceof Collection) {
// 将对象转换为集合
Collection<?> collection = (Collection<?>) o;
// 将集合中的每个元素转换为FieldValue对象,并构建成列表
List<FieldValue> fieldValues = collection.stream().map(c -> FieldValue.of(JsonData.of(c))).collect(Collectors.toList());
// 构建BoolQuery对象,添加terms查询条件
return QueryBuilders.bool().must(t -> t.terms(f -> f.field(key).terms(v -> v.value(fieldValues)))).build();
}
break;
case WILDCARD:
// 如果查询模式是WILDCARD,则构建BoolQuery对象,添加wildcard查询条件
return QueryBuilders.bool().must(t -> t.wildcard(f -> f.field(key).value("*" + o + "*"))).build();
case RANGE:
// 如果查询模式是RANGE,并且对象是EsRangeObject类型
if (o instanceof EsRangeObject) {
// 将对象转换为EsRangeObject类型
EsRangeObject rangeObject = (EsRangeObject) o;
// 创建RangeQuery.Builder对象,设置查询的字段
RangeQuery.Builder range = QueryBuilders.range().field(key);
// 如果EsRangeObject的from属性不为空,则添加gte查询条件
Optional.ofNullable(rangeObject.getFrom()).ifPresent(from -> range.gte(JsonData.of(from)));
// 如果EsRangeObject的to属性不为空,则添加lte查询条件
Optional.ofNullable(rangeObject.getTo()).ifPresent(to -> range.lte(JsonData.of(to)));
// 构建BoolQuery对象,添加range查询条件
return QueryBuilders.bool().must(range.build()._toQuery()).build();
}
break;
default:
// 如果查询模式不匹配任何已知模式,则不执行任何操作
break;
}
}
// 如果注解不是GeneralConvert类型或者对象为空,则返回null
return null;
}
}
java
/**
* @description: 通用转换
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface GeneralConvert {
/**
* 获取键值
*
* @return 返回键值
*/
String key();
/**
* 获取当前ES查询模式
*
* @return 返回当前ES查询模式
*/
EsQueryMode mode();
}
java
@Data
@Accessors(chain = true)
public class LuTshoes extends AbstractEsConditionReqDto{
@GeneralConvert(key = "term", mode = EsQueryMode.TERM)
private String term;
@GeneralConvert(key = "terms", mode = EsQueryMode.TERMS)
private List<String> terms;
@GeneralConvert(key = "wildcard", mode = EsQueryMode.WILDCARD)
private String wildcard;
@GeneralConvert(key = "rangeObject", mode = EsQueryMode.RANGE)
private EsRangeObject rangeObject;
public static void main(String[] args) throws IllegalAccessException {
LuTshoes luTshoes = new LuTshoes().setTerm("term").setRangeObject(new
EsRangeObject().setFrom("100").setTo("200")).setWildcard("123456").setTerms(List.of("terms","2"));
System.out.println(luTshoes.build());
}
@Override
public BoolQuery build() throws IllegalAccessException {
// 也可以自己定义实现
return BoolQueryAdapter.convert(this);
}
}