接上篇《16、RestClient查询文档-快速入门》
上一篇我们讲解了使用Java程序实现ElasticSearch查询文档的各项功能。本篇我们来继续讲解,使用RestClient进行term、range、bool查询,以及封装公用方法简化查询代码。
一、 从基础到进阶,构建可维护的ES查询
在上一篇博客中,我们成功地搭建了ElasticSearch基于RestHighLevelClient的Java集成环境,并掌握了使用MatchQuery进行基础全文检索的方法。我们通过一个简单的示例,查询了酒店名称中包含“希尔顿”的文档,迈出了使用Java代码操作ES的第一步。
然而,在实际的业务场景中,简单的全文检索远远不够。我们面临的往往是错综复杂的查询需求,如:
●“精确查找”所有品牌为“万豪”的酒店。(而不是名字里带“万豪”的)
●“范围筛选”出价格在500元到1000元之间的高性价比酒店。
●“组合条件”查询:找到位于“上海”、评分“高于45分”、并且品牌“不是速8”的所有酒店。
面对这些需求,如果继续拼接原始的JSON请求体,代码将变得难以编写且极易出错。而直接使用基础的QueryBuilders,虽然类型安全,但构建多层嵌套的Bool查询时,代码会显得臃肿且逻辑分散,可读性和可维护性都会急剧下降。
因此,本篇博客将聚焦三个核心目标:
1.深入核心查询语法:详解如何运用 QueryBuilders构建Term(精确匹配)、Range(范围查询)和功能强大的Bool(组合查询),以解决上述复杂的业务筛选问题。
2.实践与演示:结合hotel索引库中的真实数据(如brand,city,price,score等字段),提供可直接运行的Java代码示例,让大家看到每种查询的具体应用。
3.封装与升华:针对复杂查询代码冗余的问题,我们将封装一个通用的查询工具类。目的是将冗长的构建过程简化为清晰的链式调用,最终实现 “一行代码找到答案” 的效果,极大提升开发效率和代码质量。
通过本篇的学习,大家将掌握如何将ES中最常用的查询类型优雅地集成到Java应用中,写出更专业、更易维护的数据检索代码。
二、核心查询类型实战详解
1.Term查询:精确值匹配
(1)概念:用于匹配确切的值,不会对搜索词进行分词。比如搜索brand为"希尔顿"的酒店,只会匹配字段值精确等于"希尔顿" 的文档。
(2)核心API:QueryBuilders.termQuery(String field, Object value)
(3)示例场景:查询所有品牌brand为 "希尔顿" 的酒店。
(4)代码演示:
package com.example;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class TermQueryExample {
public static void main(String[] args) throws Exception {
// 通过提前写好的工具类,获取 Elasticsearch 客户端
RestHighLevelClient client = ElasticsearchClient.getClient();
// 构建查询请求
// 1.指定索引名
//创建 SearchRequest 对象,并指定要查询的索引名为hotel。
SearchRequest request = new SearchRequest("hotel");
// 2.构建查询条件
// SearchSourceBuilder:用于定义查询的详细参数(如查询内容、分页、排序等)。
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//构建一个匹配查询:
//字段名:name(表示搜索 hotel 索引中的 name 字段)。
//搜索词:"希尔顿"(匹配字段值精确等于"希尔顿" 的文档。)。将查询条件设置到 sourceBuilder 中
sourceBuilder.query(QueryBuilders.termQuery("brand", "希尔顿"));
// 3.将查询条件绑定到请求
request.source(sourceBuilder);
// 执行查询
// 调用 client.search() 方法发送请求:
// 参数1:request(封装了索引名和查询条件)。
// 参数2:RequestOptions.DEFAULT(使用默认请求配置)。
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 处理结果
// response.getHits(): 获取所有匹配的文档(返回 SearchHits 对象)。
SearchHits searchHits = response.getHits();
// 获取查询的总数
long total = searchHits.getTotalHits().value;
// 获取查询的结果数组
SearchHit[] hits = searchHits.getHits();
// 遍历每个匹配的文档(SearchHit 对象)。
for(SearchHit hit : hits){
// hit.getSourceAsString(): 将文档的 JSON 源数据转为字符串并打印。
String hitJson = hit.getSourceAsString();
System.out.println(hitJson);
}
ElasticsearchClient.close();
}
}
(5)结果分析:这条查询会精准命中brand字段值为"希尔顿"的文档,而不会匹配"希尔顿酒店"或"北京希尔顿"。这要求索引映射中brand字段的类型最好是keyword:

2.Range查询:范围匹配
(1)概念:用于匹配字段值在某个范围内的文档,常用于数字、日期等类型的字段。
(2)核心API:QueryBuilders.rangeQuery(String field)
(3)关键方法:
●gte(Object from): 大于等于 (Greater-than or equal to)
●lte(Object to): 小于等于 (Less-than or equal to)
●gt(Object from): 大于 (Greater-than)
●lt(Object to): 小于 (Less-than)
(4)示例场景:查询所有价格price在300到800元之间(包含300和800),且评分score大于45的酒店。
代码演示:
package com.example;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class RangeQueryExample {
public static void main(String[] args) throws Exception {
// 通过提前写好的工具类,获取 Elasticsearch 客户端
RestHighLevelClient client = ElasticsearchClient.getClient();
// 构建查询请求
// 1.指定索引名
//创建 SearchRequest 对象,并指定要查询的索引名为hotel。
SearchRequest request = new SearchRequest("hotel");
// 2.构建查询条件
// SearchSourceBuilder:用于定义查询的详细参数(如查询内容、分页、排序等)。
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 构建一个匹配查询:
// 构建价格范围查询
RangeQueryBuilder priceRangeQuery = QueryBuilders.rangeQuery("price")
.gte(300)
.lte(800);
// 构建评分范围查询
RangeQueryBuilder scoreRangeQuery = QueryBuilders.rangeQuery("score")
.gt(45);
// 组合查询:使用 Bool Query 的 must,表示必须同时满足
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(priceRangeQuery);
boolQuery.must(scoreRangeQuery);
sourceBuilder.query(boolQuery); // 将组合好的布尔查询设置进去
// 3.将查询条件绑定到请求
request.source(sourceBuilder);
// 执行查询
// 调用 client.search() 方法发送请求:
// 参数1:request(封装了索引名和查询条件)。
// 参数2:RequestOptions.DEFAULT(使用默认请求配置)。
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 处理结果
// response.getHits(): 获取所有匹配的文档(返回 SearchHits 对象)。
SearchHits searchHits = response.getHits();
// 获取查询的总数
long total = searchHits.getTotalHits().value;
// 获取查询的结果数组
SearchHit[] hits = searchHits.getHits();
// 遍历每个匹配的文档(SearchHit 对象)。
for(SearchHit hit : hits){
// hit.getSourceAsString(): 将文档的 JSON 源数据转为字符串并打印。
String hitJson = hit.getSourceAsString();
System.out.println(hitJson);
}
ElasticsearchClient.close();
}
}
(5)测试结果:

3. Bool查询:逻辑组合(重中之重)
(1)概念:Bool查询是Elasticsearch中最强大的查询之一,它可以将多个查询子句以逻辑(mustAND,shouldOR,must_notNOT,filter)的方式组合起来,实现非常复杂的查询逻辑。
(2)核心API:QueryBuilders.boolQuery()
(3)四种子句详解:
●must: 查询必须出现在匹配的文档中,并参与相关性算分。相当于逻辑AND。
●should:查询应该出现在匹配的文档中。如果bool查询位于查询上下文且没有must或filter子句,那么至少满足一个should条件。匹配的should子句越多,文档的相关性得分越高。相当于逻辑OR。
●must_not: 查询必须不出现在匹配的文档中。不参与算分,并且在filter上下文执行。相当于逻辑NOT。
●filter:查询必须出现在匹配的文档中。但以不评分、过滤模式进行,这些子句对评分没有影响,但效率极高,因为它会利用缓存。强烈推荐用于过滤(如范围、状态码、精确匹配等)。
综合示例场景:我们来构建一个复杂的业务查询:“搜索所有在上海,品牌不是速8,价格低于500元,并且满足以下条件之一的酒店:【评分高于45分或星级是四钻及以上】。”
(4)代码演示:
package com.example;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.json.JSONObject;
public class BoolQueryExample {
public static void main(String[] args) throws Exception {
// 通过提前写好的工具类,获取 Elasticsearch 客户端
RestHighLevelClient client = ElasticsearchClient.getClient();
// 构建查询请求
// 1.指定索引名
//创建 SearchRequest 对象,并指定要查询的索引名为hotel。
SearchRequest request = new SearchRequest("hotel");
// 2.构建查询条件
// SearchSourceBuilder:用于定义查询的详细参数(如查询内容、分页、排序等)。
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 构建一个匹配查询:
// 3. 首先,构建各个叶子查询
// 城市是上海 (精确匹配,用 Term)
TermQueryBuilder cityQuery = QueryBuilders.termQuery("city", "上海");
// 品牌不是速8 (排除,用 must_not + Term)
TermQueryBuilder brandToExcludeQuery = QueryBuilders.termQuery("brand", "速8");
// 价格低于500 (范围,用 Filter 不参与算分)
RangeQueryBuilder priceQuery = QueryBuilders.rangeQuery("price").lt(500);
// 评分高于45 (范围)
RangeQueryBuilder highScoreQuery = QueryBuilders.rangeQuery("score").gt(45);
// 星级是四钻及以上 (由于star_name是字符串,我们用范围查询其"钻"或"星"级别,这里假设数据规整)
// 例如: "四钻", "五钻", "五星级" 都满足条件。我们可以用 gte "四" 来匹配。
// 注意:这种字符串范围查询需要确保映射正确,且查询结果可能不精确,仅作示例。
RangeQueryBuilder highStarQuery = QueryBuilders.rangeQuery("star_name")
.gte("四");
// 4. 开始组合 Bool 查询
// 主布尔查询:必须满足的条件
BoolQueryBuilder mainBoolQuery = QueryBuilders.boolQuery();
mainBoolQuery.filter(cityQuery); // 城市=上海,过滤
mainBoolQuery.mustNot(brandToExcludeQuery); // 品牌 != 速8,过滤
mainBoolQuery.filter(priceQuery); // 价格<500,过滤
// 子布尔查询:评分>45 OR 星级>=四钻
BoolQueryBuilder shouldBoolQuery = QueryBuilders.boolQuery();
shouldBoolQuery.should(highScoreQuery);
shouldBoolQuery.should(highStarQuery);
// 将这个 OR 条件加入到主查询的 must 子句中
// 这意味着整个查询是: (city=上海 AND brand!=速8 AND price<500) AND (score>45 OR star_name>=四)
mainBoolQuery.must(shouldBoolQuery);
// 5. 将最终组合好的查询设置到请求中
sourceBuilder.query(mainBoolQuery); // 将组合好的布尔查询设置进去
// 6.将查询条件绑定到请求
request.source(sourceBuilder);
// 执行查询
// 调用 client.search() 方法发送请求:
// 参数1:request(封装了索引名和查询条件)。
// 参数2:RequestOptions.DEFAULT(使用默认请求配置)。
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 处理结果
// response.getHits(): 获取所有匹配的文档(返回 SearchHits 对象)。
SearchHits searchHits = response.getHits();
// 获取查询的总数
long total = searchHits.getTotalHits().value;
// 获取查询的结果数组
SearchHit[] hits = searchHits.getHits();
// 遍历每个匹配的文档(SearchHit 对象)。
for(SearchHit hit : hits){
// hit.getSourceAsString(): 将文档的 JSON 源数据转为字符串并打印。
String hitJson = hit.getSourceAsString();
// json工具类,用于格式化json
JSONObject jsonObj = new JSONObject(hitJson);
System.out.println(jsonObj.toString(4));
}
ElasticsearchClient.close();
}
}
(5)测试结果:

这个例子展示了Bool查询的强大之处:它可以无限嵌套,从而构建出极其复杂的查询逻辑。
三、 进阶:封装可复用的查询工具类
看到上面的代码,你是否已经感到有些头晕?每个查询都要写一堆样板代码,SearchRequest, SearchSourceBuilder, 各种 QueryBuilders... 逻辑分散,难以维护。我们需要将他们进一步进行封装,来简化后续的开发流程。
1. 封装的动机
●代码冗余:大量重复的样板代码。
●难以维护:查询逻辑分散在各个 Service 或 Dao 中。
●目标:实现链式调用,如EsQueryWrapper.eq("city", "上海").lt("price", 500).neq("brand", "速8")...。
2. 工具类设计:EsQueryBuilder
我们将创建一个模仿MyBatis-Plus的 QueryWrapper风格的类。
package com.example;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class EsQueryBuilder {
// 核心:内部维护一个 BoolQueryBuilder
private BoolQueryBuilder boolQuery;
public EsQueryBuilder() {
this.boolQuery = QueryBuilders.boolQuery();
}
//------------------ MUST (AND) 相关 ------------------
public EsQueryBuilder mustTerm(String field, Object value) {
this.boolQuery.must(QueryBuilders.termQuery(field, value));
return this;
}
public EsQueryBuilder mustMatch(String field, Object value) {
this.boolQuery.must(QueryBuilders.matchQuery(field, value));
return this;
}
// 可以继续扩展 mustRangeGt, mustRangeLt 等
//------------------ FILTER 相关 (推荐用于精确匹配和范围) ------------------
public EsQueryBuilder filterTerm(String field, Object value) {
this.boolQuery.filter(QueryBuilders.termQuery(field, value));
return this;
}
public EsQueryBuilder filterRangeGt(String field, Object value) {
this.boolQuery.filter(QueryBuilders.rangeQuery(field).gt(value));
return this;
}
public EsQueryBuilder filterRangeLt(String field, Object value) {
this.boolQuery.filter(QueryBuilders.rangeQuery(field).lt(value));
return this;
}
public EsQueryBuilder filterRange(String field, Object from, Object to) {
this.boolQuery.filter(QueryBuilders.rangeQuery(field).gte(from).lte(to));
return this;
}
//------------------ MUST_NOT (NOT) 相关 ------------------
public EsQueryBuilder mustNotTerm(String field, Object value) {
this.boolQuery.mustNot(QueryBuilders.termQuery(field, value));
return this;
}
//------------------ SHOULD (OR) 相关 ------------------
// 注意:直接添加 should 子句到主 boolQuery 需要谨慎使用。
// 更常见的做法是构建一个子 boolQuery 用于 should,然后将其添加到主查询的 must 或 filter 中。
// 这里提供一个获取内部 boolQuery 的方法,用于更复杂的嵌套组合
public BoolQueryBuilder getBoolQuery() {
return this.boolQuery;
}
//------------------ 最终执行方法 ------------------
public SearchRequest buildSearchRequest(String... indices) {
SearchRequest request = new SearchRequest(indices);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(this.boolQuery); // 将构建好的最终查询设置进去
request.source(sourceBuilder);
return request;
}
}
3. 使用封装后的工具类重写查询
现在,让我们用这个全新的EsQueryBuilder来重写上面那个复杂的Bool查询示例:
package com.example;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.json.JSONObject;
public class BoolQueryExample2 {
public static void main(String[] args) throws Exception {
// 通过提前写好的工具类,获取 Elasticsearch 客户端
RestHighLevelClient client = ElasticsearchClient.getClient();
// 初始化工具类
EsQueryBuilder queryBuilder = new EsQueryBuilder();
// 构建查询:链式调用,清晰易懂
queryBuilder.filterTerm("city", "上海") // 城市=上海
.mustNotTerm("brand", "速8") // 品牌 != 速8
.filterRangeLt("price", 500) // 价格<500
.getBoolQuery() // 获取内部的BoolQueryBuilder
.must( // 必须满足以下条件
QueryBuilders.boolQuery() // 构建一个子BoolQuery(OR条件)
.should(QueryBuilders.rangeQuery("score").gt(45)) // 条件1: 评分>45
.should(QueryBuilders.rangeQuery("star_name").gte("四")) // 条件2: 星级>=四钻
);
// 构建请求
SearchRequest request = queryBuilder.buildSearchRequest("hotel");
// 执行查询
// 调用 client.search() 方法发送请求:
// 参数1:request(封装了索引名和查询条件)。
// 参数2:RequestOptions.DEFAULT(使用默认请求配置)。
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 处理结果
// response.getHits(): 获取所有匹配的文档(返回 SearchHits 对象)。
SearchHits searchHits = response.getHits();
// 获取查询的总数
long total = searchHits.getTotalHits().value;
// 获取查询的结果数组
SearchHit[] hits = searchHits.getHits();
// 遍历每个匹配的文档(SearchHit 对象)。
for(SearchHit hit : hits){
// hit.getSourceAsString(): 将文档的 JSON 源数据转为字符串并打印。
String hitJson = hit.getSourceAsString();
// json工具类,用于格式化json
JSONObject jsonObj = new JSONObject(hitJson);
System.out.println(jsonObj.toString(4));
}
ElasticsearchClient.close();
}
}
代码对比与优势总结:
●可读性极大提升:链式调用让查询逻辑一目了然,几乎就像在阅读业务需求本身。
●维护性增强:所有查询构建逻辑通过工具类统一管理,修改查询方式只需改动工具类。
●代码更简洁:省去了大量创建和组装*QueryBuilder对象的样板代码。
四、总结与最佳实践
1.内容回顾:我们深入学习了 HLRC 对 Term(精确匹配)、Range(范围查询)和 Bool(逻辑组合)查询的支持。更重要的是,我们通过封装 EsQueryBuilder工具类,将复杂的 DSL 构建过程简化为了优雅的链式调用。
2.最佳实践建议:
●善用Filter Context:对于不关心相关度分数的条件(如:状态、时间范围、精确匹配标签),务必使用filter子句。它能利用缓存,极大提升查询性能。
●确保索引映射合理:Term查询依赖于keyword类型字段。请确保你的字段映射正确(例如,brand字段应为keyword,或者是一个包含keyword子字段的text类型)。
●理解Bool查询的算分:must和should影响得分,filter和must_not不影响。根据你的业务需求(是搜索排序还是精确过滤)合理选择子句。
●资源管理:记得在使用完毕后,调用client.close()关闭连接,释放资源。
展望:本章介绍的工具类只是一个起点。大家可以继续扩展它,支持聚合(Aggregations)、高亮(Highlighting)、排序(Sort)、分页(From/Size)等功能,使其成为一个真正强大的ES查询助手。
下一篇我们继续来学习ElasticSearch的高亮(Highlighting)、排序(Sort)、分页(From/Size)等功能的Java代码编写。
转载请注明出处:https://blog.csdn.net/acmman/article/details/152282921

被折叠的 条评论
为什么被折叠?



