【搜索系列】ES查询常用实例演示

文章目录
  1. I. 项目搭建
    1. 1. 项目依赖
    2. 2. 配置信息
  2. II. 实例演示
    1. 0. 准备
    2. 1. 全量查询
    3. 2. 根据Field值精确查询
    4. 3. Field值in查询
    5. 4. 范围查询
    6. 5. Field是否存在查询
    7. 6. 模糊查询
    8. 7. 正则匹配
    9. 8. 前缀查询
    10. 9.小结
  3. III. 不能错过的源码和相关知识点
    1. 0. 项目
    2. 1. 微信公众号: 一灰灰Blog

本文将作为es系列第三篇,结合常见的实例,来演示下如何通过RestHighLevelClient来实现es的各种查询支持

I. 项目搭建

1. 项目依赖

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

开一个web服务用于测试

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
</dependencies>

2. 配置信息

配置文件application.yml,注意下面的配置信息,下面采用的是由我们自己来解析配置的方式

1
2
3
4
5
6
7
8
elasticsearch:
host: localhost
port: 9200
user: elastic
pwd: test123
connTimeout: 3000
socketTimeout: 5000
connectionRequestTimeout: 500

II. 实例演示

0. 准备

在开始之前,先准备插入几条数据,这里会借助上一篇CURD博文中的插入接口

在开始之前就准备两条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
public class TermQueryDemo {
private BasicCurdDemo basicCurdDemo;
@Autowired
private RestHighLevelClient client;
@Autowired
private RequestOptions requestOptions;

private String TEST_ID = "11123-33345-66543-55231";
private String TEST_ID_2 = "11123-33345-66543-55232";

private String index = "term-demo";

public TermQueryDemo(BasicCurdDemo basicCurdDemo) throws IOException {
this.basicCurdDemo = basicCurdDemo;
Map<String, Object> doc = newMap("name", "一灰灰", "age", 10, "skills", Arrays.asList("java", "python"), "site", "blog.hhui.top");
basicCurdDemo.addDoc(index, doc, TEST_ID);
doc = newMap("name", "二灰灰", "age", 16, "skills", Arrays.asList("js", "html"));
basicCurdDemo.addDoc(index, doc, TEST_ID_2);
}

@PreDestroy
public void remove() throws IOException {
basicCurdDemo.delete(index, TEST_ID);
basicCurdDemo.delete(index, TEST_ID_2);
}
}

1. 全量查询

即查询所有的文档,如借助kibanan的控制台,发起的请求形如

1
2
3
4
5
6
GET index/_search
{
"query": {
"match_all": {}
}
}

于此对应的java实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 全量查询
*
* @throws IOException
*/
private void queryAll() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 查询所有的文档
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);

SearchResponse searchResponse = client.search(searchRequest, requestOptions);
System.out.println("mathAll: " + searchResponse.toString());
}

注意上面的实现:

  • 初始化SearchRequest实例,用于构建请求相关数据
  • SearchSourceBuilder 来填充查询条件
  • client.search(searchRequest, requestOptions) 执行查询请求,第二个参数为请求参数,这里主要是设置请求时的权限验证信息

通常来说,实际的业务场景中,不太可能出现上面这种没有任何限制的查全量数据,即便真的有查全量数据的case,更常见的是分页查询,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void queryAll() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
int page = 1;
//每页记录数
int size = 2;
//计算出记录起始下标
int from = (page - 1) * size;
//起始记录下标,从0开始
searchSourceBuilder.from(from);
//每页显示的记录数
searchSourceBuilder.size(size);
// 根据age字段进行倒排
searchSourceBuilder.sort(new FieldSortBuilder("age").order(SortOrder.DESC));
// 查询所有的文档
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);

SearchResponse searchResponse = client.search(searchRequest, requestOptions);
System.out.println("mathAll: " + searchResponse.toString());
}

2. 根据Field值精确查询

即es中常说的term查询,具体实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* term精确查询
*
* @throws IOException
*/
private void term() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

// termQuery: 精确查询
// SpanTermQuery: 词距查询
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("site", "blog.hhui.top"));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("term: " + response.toString());
}

从上面的实现也可以看出,查询的套路没啥区别,无非就是SearchSourceBuilder中的参数构造不一样;上面主要通过

  • QueryBuilders.termQuery("site", "blog.hhui.top") 来构建 term的查询条件,表明查询 site=blog.hhui.top 的文档

中文查询不到问题

在我们实际使用过程中,如果value为中文,在查询时,可能会遇到命名有对应的数据,但是就查不到,主要原因就在于分词,如对于中文的查询,可以考虑下面这种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void term2() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 对于中文查询,需要注意分词的场景, 如果直接使用 "name : 一灰灰" 的方式进行查询,则啥也不会返回
// elasticsearch 里默认的IK分词器是会将每一个中文都进行了分词的切割,所以你直接想查一整个词,或者一整句话是无返回结果的。
// 在此种情况下,我们可以通过指定 keyword 的方式来处理, 设置关键词搜索(不进行分词)
searchSourceBuilder.query(QueryBuilders.termQuery("name.keyword", "一灰灰"));

searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("term2: " + response.toString());
}

3. Field值in查询

另外一个常见的就是多值查询,也就是我们常说的 field in (val1, val2...),这个对应的就是es中的terms查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 相当于in查询
* {"terms": { "name": ["一灰灰", "二灰灰] }}
*
* @throws IOException
*/
private void multTerm() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termsQuery("name.keyword", "一灰灰", "二灰灰"));

searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("term: " + response.toString());
}

4. 范围查询

对于数值类型的Field,同样是支持比较、范围查询的,对应的是es中 range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 范围查询
* { "range": { "age": { "gt":8, "lt": 12 } }}
*
* @throws IOException
*/
private void range() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.rangeQuery("age").gt(8).lt(12));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("range: " + response.toString());
}

注意上面的查询有条件

  • QueryBuilders.rangeQuery("age").gt(8).lt(12)
  • 表示查询 age > 8 && age < 12
  • gte: 表示 >=
  • lte: 表示 <=

5. Field是否存在查询

es不同于mysql的在于它的field可以动态新增,当我们希望查询包含某个字段的文档时,可以考虑 exists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 根据字段是否存在查询
*
* @throws IOException
*/
private void exists() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.existsQuery("site"));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("exists: " + response.toString());
}

6. 模糊查询

es作为搜索引擎,更常见的是模糊匹配,比如match查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 根据字段匹配查询
*
* @throws IOException
*/
private void match() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("name", "灰"));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("matchQuery: " + response.toString());
}

多Field中进行查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 多字段中查询
*
* @throws IOException
*/
private void multiMatch() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.multiMatchQuery("灰", "name", "site"));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("multiMatchQuery: " + response.toString());
}

在es的语法支持中,除了match,还有一个wildcard,可以使用?来代指单字符,*来代指0..n字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 模糊查询 ? 单字符 * 0..n字符
*
* @throws IOException
*/
private void wild() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.wildcardQuery("site", "*top"));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("wildcard: " + response.toString());
}

7. 正则匹配

1
2
3
4
5
6
7
8
9
10
private void regexp() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.regexpQuery("site", ".*hhui.*"));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("regexpQuery: " + response.toString());
}

8. 前缀查询

1
2
3
4
5
6
7
8
9
10
private void prefix() throws IOException {
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.types("_doc");

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.prefixQuery("site", "blog"));
searchRequest.source(searchSourceBuilder);
SearchResponse response = client.search(searchRequest, requestOptions);
System.out.println("prefixQuery: " + response.toString());
}

9.小结

本文虽然介绍了一些常见的查询case,但注意并不仅仅只有这些,比如

  • 查询指定Feild的内容
  • 排序
  • 分组聚合
  • 多查询条件组合:and/or
  • 高亮

更多的使用实例,敬请期待…,欢迎感兴趣的小伙伴,点赞收藏评论一波😝

III. 不能错过的源码和相关知识点

0. 项目

系列博文

源码

1. 微信公众号: 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到