3.ES查询常用实例演示

一灰灰blogSpringBoot搜索系列ElasticSearchElasticSearch约 1980 字大约 7 分钟

本文将作为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. 项目

系列博文

源码

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

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

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

一灰灰blog
一灰灰blog
Loading...