3.ES查询常用实例演示
本文将作为es系列第三篇,结合常见的实例,来演示下如何通过RestHighLevelClient
来实现es的各种查询支持
I. 项目搭建
1. 项目依赖
本项目借助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
进行开发
开一个web服务用于测试
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
</dependencies>
2. 配置信息
配置文件application.yml,注意下面的配置信息,下面采用的是由我们自己来解析配置的方式
elasticsearch:
host: localhost
port: 9200
user: elastic
pwd: test123
connTimeout: 3000
socketTimeout: 5000
connectionRequestTimeout: 500
II. 实例演示
0. 准备
在开始之前,先准备插入几条数据,这里会借助上一篇CURD博文中的插入接口
在开始之前就准备两条数据
@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的控制台,发起的请求形如
GET index/_search
{
"query": {
"match_all": {}
}
}
于此对应的java实现如下
/**
* 全量查询
*
* @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,更常见的是分页查询,如下
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查询,具体实现如下
/**
* 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为中文,在查询时,可能会遇到命名有对应的数据,但是就查不到,主要原因就在于分词,如对于中文的查询,可以考虑下面这种方式
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
查询
/**
* 相当于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
/**
* 范围查询
* { "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
/**
* 根据字段是否存在查询
*
* @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查询
/**
* 根据字段匹配查询
*
* @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中进行查询
/**
* 多字段中查询
*
* @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字符
/**
* 模糊查询 ? 单字符 * 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. 正则匹配
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. 前缀查询
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. 项目
系列博文
源码
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 源码:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/142-search-es
1. 微信公众号: 一灰灰Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰Blog个人博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top