es client api 升级
背景
公司项目从sring-boot2
升级到了spring-boot3
,es的服务端也跟着升级到了es8 ,而es的客户端7和服务端8 是不兼容的,
客户端es 7使用的是: elasticsearch-rest-high-level-client
es 8 升级到: elasticsearch-java
两者之间查询api的变化还是比较大的,也花了不少时间在这个修改上,所以记录下中间的切换姿势,仅供大家参考
升级过程
依赖调整
<!--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代码调整
参考官方文档: https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/getting-started-java.html
创建client
@Beanpublic ElasticsearchClient elasticsearchClient(ESProperties esProperties) {HttpHost[] httpHosts = new HttpHost[esProperties.getNodes().size()];// 这里配置你的es服务端hostfor (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 clientElasticsearchClient elasticsearchClient = new ElasticsearchClient(transport);return elasticsearchClient;}
查询api
// 构建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,个人觉得不是很方便
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());
}
聚合统计
// 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
/*** 查询模式*/
public enum EsQueryMode {TERM,TERMS,WILDCARD,RANGE,
}
/*** 通用转换** @author LuTshoes* @version 1.0*/
public class GeneralConvertHandler implements IConvertHandler {/*** 将注解和对象转换为BoolQuery** @param annotation 注解* @param o 对象* @return 转换后的BoolQuery*/@Overridepublic 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类型或者对象为空,则返回nullreturn null;}}
/*** @description: 通用转换*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface GeneralConvert {/*** 获取键值** @return 返回键值*/String key();/*** 获取当前ES查询模式** @return 返回当前ES查询模式*/EsQueryMode mode();}
@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(newEsRangeObject().setFrom("100").setTo("200")).setWildcard("123456").setTerms(List.of("terms","2"));System.out.println(luTshoes.build());}@Overridepublic BoolQuery build() throws IllegalAccessException {// 也可以自己定义实现return BoolQueryAdapter.convert(this);}
}