SpringBoot使用搜索服务elasticsearch

ElasticSearch介绍

ElasticSearch是一个基于Lucene的搜索服务器,其实就是对Lucene进行封装,提供了 REST API 的操作接口。ElasticSearch作为一个高度可拓展的开源全文搜索和分析引擎,可用于快速地对大数据进行存储,搜索和分析。

ElasticSearch主要特点:分布式、高可用、异步写入、多API、面向文档 。

ElasticSearch核心概念:近实时,集群,节点(保存数据),索引,分片(将索引分片),副本(分片可设置多个副本) 。它可以快速地储存、搜索和分析海量数据。

核心概念介绍

索引(index)

索引是一组相似文档的集合,可以理解一个索引就是一个目录,在这个目录下有众多文档;在新版的ES(6.0以上)中,已经去除了types的概念,所以也可以理解为索引就是一张关系型数据库的表(table);在新建索引时,索引名称必须是小写;es的精髓就是为了提高查询效率。

类型 (type)

在旧版本中,type可以理解为关系型数据库的表,其实在设计初期,是没有types的概念的,设计es的人为了和关系型数据库进行关联,特地加上了types的概念, 但是后来发现没有这个必要,所以在6.*版本进行弱化,也就说在6.*版本以后,一个索引下只能有一个type,而在7.*以后则去除了type的概念。

并且在存储的时候,一个索引下的所有type的数据都会存储在一个文件中,如果一个索引下有多个types,那无疑会降低搜索的效率,所以才会这么急切地要去除type。

document(文档)

代表着es中一条记录,和关系型数据库的rows是一样的, 不同的是,文档的数据是以json的格式进行表示;只要你的计算机磁盘空间足够,在一个索引中,你可以存储无限多的文档。

field(属性)

field 在关系型数据库中被称为字段(column),它俩之间的概念是一样的,每个field都有自己的数据类型,根据数据类型存储不同的数据,区别是关系型数据库必须先定义固定的数据类型和属性长度,而es可以不设定数据类型,在插入数据时会自动生成对应的数据类型;

mapping(映射)

mapping用来配置属性的默认值、数据类型、分析器、是否被索引等等,将一个属性用映射的方式做一些优化,可以提高检索效率和减少空间占用;

shareds (分片)

在es中, 一个索引下的数据是可以有无限大的,并且 它们都存储在一个文件中, 但是这有个问题,文件越大,就意味着搜索效率会降低,就需要将一个文件按照一定的规则拆分成多个文件;而分片就是做拆分用的,比如用户表中有10亿条数据,有各种年龄段的用户,在1 ~ 100岁之间;就可以用分片机制将 这些用户进行划分为5个片区,划分如下:

  • 分片1:1-20岁
  • 分片2:21-40岁
  • 分片3:41-60岁
  • 分片4:61-80岁
  • 分片5:81-100岁

es的分片和mysql的分区类型,不同的是mysql不支持分布式分区,而es支持分布式集群分片,也就是说,es允许将不同的分片划分到不同的集群节点中;

分配(Allocation)

将分片分配给节点的过程,包括分配主分片或者副本,如果是副本,还包含从主分片复制数据的过程,这个过程由master节点完成;

副本分片的分配方式

es的数据分为主分片和副本分片主分片都存储在master节点中,但是副本分片都在其他的node节点上, 这是因为es要保证高可用,万一master节点挂了,其他的子节点还照样可以提供查询数据的服务;

在这里插入图片描述

elasticsearch与JDK的版本对应

https://www.elastic.co/cn/support/matrix#matrix_jvm

SpringBoot集成elasticsearch

ES7.X版本较之前改动比较大,相应的springboot对它的支持改动也特别明显。

在spring-data-elasticsearch官网:https://docs.spring.io/spring-data/elasticsearch/docs/4.0.1.RELEASE/reference/html/#new-features

可以查看到版本匹配信息,如下:

以下演示我们使用的环境是:SpringBoot是2.3.3.RELEASE,es版本是7.10.1。

springboot-data-elasticsearch较之前最大的区别有两点:

1)配置文件

## 旧版本以spring.data.elasticsearch.开头;访问地址配置不用声明访问协议,监听es的tcp端口
spring.data.elasticsearch.cluster-nodes=localhost:9300
 
## 新版本以spring.elasticsearch.rest.开头;访问地址配置需要声明访问协议,直接监听es访问端口
spring.elasticsearch.rest.uris=http://localhost:9200

2)核心访问对象

旧版的核心访问对象是ElasticsearchTemplate;新版的核心访问对象是ElasticsearchRestTemplate;

另外,在新版中,不会对ElasticsearchTemplate做自动装配,如果还需要使用它,需要手动装配,当然,应该禁止这么做。如下:

@Bean(name = "elasticsearchTemplate")
public ElasticsearchTemplate initElasticsearchTemplate(Client client) {
    ElasticsearchTemplate elasticsearchTemplate = new ElasticsearchTemplate(client);
    return elasticsearchTemplate;
}

基本用法

引入相关依赖

<!-- elasticsearch -->
<!-- 2.1.x.RELEASE版本和2.3.3.RELEASE后的版本的自动化配置和API有些不同,我们这里直接使用新版本-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

<!-- fastjson, 仅仅为了方便对象间的转换而已 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.1</version>
</dependency>

application.yml配置es

server:
  port: 9999

# elasticsearch配置
spring:
  elasticsearch:
    rest:
      # es访问地址,多个用英文逗号隔开
      uris: http://192.168.198.155:9201,http://192.168.198.155:9202,http://192.168.198.155:9203
      connection-timeout: 10 #连接超时时间,单位是s
      read-timeout: 5 # 读超时时间,单位是s

核心操作对象ElasticsearchRestTemplate是自动装配的,所以接下来就可以直接访问了。

创建映射对象

和访问数据库一样,统统以对象的形式访问。

/**
 * 更多注解详细说明参考官网:https://docs.spring.io/spring-data/elasticsearch/docs/4.0.1.RELEASE/reference/html/#elasticsearch.mapping.meta-model
 * */
// @Document指定当前类是索引对象。indexName:索引名称;shards:创建索引时的分片数;replicas:创建索引时每个分片的备份数
@Document(indexName = "book", shards = 3, replicas = 2)
public class BookIndexMapping {
 
	// @Id标记数据主键
	@Id
	private String id;
	
	// @Field标记字段。name:映射es中的字段, type:字段类型
	@Field(type = FieldType.Keyword)
	private String title;
	
	@Field(type = FieldType.Text)
	private String content;
	
	@Field(type = FieldType.Double)
	private BigDecimal price;
	
	// 如果java类型使用java.util.Date,反序列化会失败
	@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
	private LocalDateTime publishDate;
 
	public String getId() {
		return id;
	}
 
	public void setId(String id) {
		this.id = id;
	}
 
	public String getTitle() {
		return title;
	}
 
	public void setTitle(String title) {
		this.title = title;
	}
 
	public String getContent() {
		return content;
	}
 
	public void setContent(String content) {
		this.content = content;
	}
 
	public BigDecimal getPrice() {
		return price;
	}
 
	public void setPrice(BigDecimal price) {
		this.price = price;
	}
 
	public LocalDateTime getPublishDate() {
		return publishDate;
	}
 
	public void setPublishDate(LocalDateTime publishDate) {
		this.publishDate = publishDate;
	}
 
	public BookIndexMapping() {
		super();
	}
	
}

创建索引即映射

/**
  * 创建索引
  */
@Test
public void testCreateIndex() {
    boolean exists = elasticsearchRestTemplate.indexOps(BookIndexMapping.class).exists();
    // 如果索引已存在,删除索引
    if (exists) {
        // 删除索引
        elasticsearchRestTemplate.indexOps(BookIndexMapping.class).delete();
    }
    // 创建索引
    elasticsearchRestTemplate.indexOps(BookIndexMapping.class).create();

    // 创建映射
    Document mappings = elasticsearchRestTemplate.indexOps(BookIndexMapping.class).createMapping();
    elasticsearchRestTemplate.indexOps(BookIndexMapping.class).putMapping(mappings);

    System.out.println("---执行成功---");
}

新增文档数据

/**
  * 新增文档数据
  */
@Test
public void testSaveDocument() {
    BookIndexMapping entity = new BookIndexMapping();
    entity.setTitle("高一英语(上册 人教版)");
    entity.setContent("how are you! how old are you!");
    entity.setPrice(new BigDecimal(23.9));
    entity.setPublishDate(LocalDateTime.now());
    elasticsearchRestTemplate.save(entity);
    System.out.println("---插入成功---");
}

/**
  * 新增文档数据:指定id
  */
@Test
public void testSaveDocumentById() {
    BookIndexMapping entity = new BookIndexMapping();
    entity.setId(UUID.randomUUID().toString());
    entity.setTitle("大一英语(上册 人教版)");
    entity.setContent("are you ok?");
    entity.setPrice(new BigDecimal(13.92));
    entity.setPublishDate(LocalDateTime.now());
    elasticsearchRestTemplate.save(entity);
    System.out.println("---插入成功---");
}

删除索引

/**
  * 删除索引
  */
@Test
public void testDeleteIndex() {
    boolean deleted = elasticsearchRestTemplate.indexOps(BookIndexMapping.class).delete();
    System.out.println("是否删除成功 : " + deleted);
}

删除文档数据

/**
  * 删除文档数据
  */
@Test
public void deleteDoc() {
    // 返回被删除的数据id
    String result = elasticsearchRestTemplate.delete("AEMtZoABXMkR2EYpy2Nv", BookIndexMapping.class);
    System.out.println(result);
}

查询文档操作

/**
 * 根据id获取文档数据
 */
@Test
public void getByIdTest() {
    BookIndexMapping entity = elasticsearchRestTemplate.get("1vItZoAB7iqN_CIwy89J", BookIndexMapping.class);
    System.out.println(JSON.toJSONString(entity));
}

/**
 * query查询
 */
@Test
public void queryTest() {
    // match查询,匹配content_字段
    NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchQuery("content", "are")).build();
    SearchHits<BookIndexMapping> list = elasticsearchRestTemplate.search(query, BookIndexMapping.class);
    System.out.println(JSON.toJSONString(list));
}

/**
 * filter查询
 */
@Test
public void filterTest() {
    // match查询,匹配content_字段
    NativeSearchQuery query = new NativeSearchQueryBuilder().withFilter(QueryBuilders.matchQuery("content_", "are")).build();
    SearchHits<BookIndexMapping> list = elasticsearchRestTemplate.search(query, BookIndexMapping.class);
    System.out.println(JSON.toJSONString(list));
}
  • type:字段类型
  • index:是否分词,默认情况下分词,一般默认分词就好,除非这个字段你确定查询时不会用到
  • format:时间类型的格式化,可选的类型由DateFormat定义。
  • store:默认情况下不存储原文
  • analyzer:指定字段建立索引分词时指定的分词器,比如对索引库中的中国人进行分词。
  • searchAnalyzer:指定字段搜索时使用的分词器,比如输入框中写中国人,然后服务器对输入框中的中国人进行分词
  • ignoreFields:如果某个字段需要被忽略
  • includeInParent:

type的类型是FieldType,常用的有如下可选值:

//会被分词器解析,生成倒排索引,支持模糊、精确查询,不用于排序,很少用于聚合
Text, 
//不进行分词,直接索引,支持模糊、精确查询,支持聚合
Keyword, 
//64位有符号整数
Long, 
//32位有符号整数
Integer, 
//16位有符号整数
Short, 
//8位有符号整数
Byte, 
//64位双精度浮点型
Double, 
//32位单进度浮点型
Float, 
//16位半精度浮点类型
Half_Float, 
//带有缩放银子的浮点数,可以当整型看待
Scaled_Float, 
//表示该字段是一个文本,日期类型
Date, 
//纳秒
Date_Nanos, 
//true、false
Boolean,
//会把值当做经过 base64 编码的字符串,默认不存储,且不可搜索
Binary, 
//Long类型的范围
Integer_Range, 
//Float的类型
Float_Range, 
//Long类型的范围
Long_Range, 
//Double范围
Double_Range,
//64位整数,毫秒计时
Date_Range,
//IP的范围
Ip_Range,
//一个对象中可以嵌套对象
Object, 
//是object中的一个特例,可以让array类型的Object独立索引和查询。
Nested, 
//ip类型的字段用于存储IPV4或者IPV6的地址
Ip, 
//用于统计词频
TokenCount, 
//抽取类型
Percolator, 
Flattened, 
Search_As_You_Type

ES内置分析器(Analyzer )

es在索引文档时,会通过各种类型 Analyzer 对text类型字段做分析,不同的 Analyzer 会有不同的分词结果,内置的分词器有以下几种,基本上内置的 Analyzer 包括 Language Analyzers 在内,对中文的分词都不够友好。中文分词需要安装其它 Analyzer。

standard

标准分析器是默认的分析器,如果没有指定,则使用该分析器。它提供了基于文法的标记化(基于 Unicode 文本分割算法) ,并且对大多数语言都有效。

simple

简单分析器将文本分解为任何非字母字符的标记,如数字、空格、连字符和撇号、放弃非字母字符,并将大写字母更改为小写字母。

whitespace

空格分析器在遇到空白字符时将文本分解为术语

stop

停止分析器与简单分析器相同,但增加了删除停止字的支持。默认使用的是 _english_ 停止词。

keyword

不分词,把整个字段当做一个整体返回

pattern

模式分析器使用正则表达式将文本拆分为术语。正则表达式应该匹配令牌分隔符,而不是令牌本身。正则表达式默认为 w+ (或所有非单词字符)。

中文分词器

1)ik_smart

ik分词器中的简单分词器,支持自定义字典,远程字典

2)ik_max_word

ik_分词器的全量分词器,支持自定义字典,远程字典

ElasticsearchRepository接口

把数据存储到es中,有两种方式:一种是 ElasticsearchRepository 接口,另一种是ElasticsearchRestTemplate类,上面基础用法都是使用ElasticsearchRestTemplate。我们接下来看看ElasticsearchRepository 接口。

我们可以通过继承 ElasticsearchRepository 来完成基本的CRUD及分页操作的,和普通的JPA没有什么区别。

1、创建映射对象和普通的pojo

@Document(indexName = "article", shards = 3, replicas = 2)
public class Article {
    @Id
    private String id;

    @Field(type = FieldType.Text)
    private String title;

    @Field(type = FieldType.Nested, includeInParent = true)
    private List<Author> authors;

    public Article(String title) {
        this.title = title;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<Author> getAuthors() {
        return authors;
    }

    public void setAuthors(List<Author> authors) {
        this.authors = authors;
    }
}
public class Author {
    private String name;

    public Author(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2、继承ElasticsearchRepository

@Repository
public interface ArticleRepository extends ElasticsearchRepository<Article,String> {

}

3、测试验证(先创建索引)

@RunWith(SpringRunner.class)
@SpringBootTest(classes = EsApp.class)
public class RepositoryEsTest {

    @Autowired
    private ArticleRepository articleRepository;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 创建索引
     */
    @Test
    public void testCreateIndex() {
        boolean exists = elasticsearchRestTemplate.indexOps(Article.class).exists();
        // 如果索引已存在,删除索引
        if (exists) {
            // 删除索引
            elasticsearchRestTemplate.indexOps(Article.class).delete();
        }
        // 创建索引
        elasticsearchRestTemplate.indexOps(Article.class).create();

        // 创建映射
        Document mappings = elasticsearchRestTemplate.indexOps(Article.class).createMapping();
        elasticsearchRestTemplate.indexOps(Article.class).putMapping(mappings);

        System.out.println("---执行成功Article Index---");
    }

    /**
     * 保存数据
     */
    @Test
    public void testSaveEsRepository() {
        Article article1 = new Article("1234");
        article1.setAuthors(Arrays.asList(new Author("12"), new Author("34")));
        articleRepository.save(article1);

        Article article2 = new Article("5678");
        article2.setAuthors(Arrays.asList(new Author("56"), new Author("78")));
        articleRepository.save(article2);

        Article article3 = new Article("90");
        article3.setAuthors(Arrays.asList(new Author("9"), new Author("9")));
        articleRepository.save(article3);
    }

    /**
     * 查询数据
     */
    @Test
    public void testQueryRepository(){
        Iterable<Article> articles = articleRepository.findAll();
        Iterator<Article> iterator = articles.iterator();
        while (iterator.hasNext()){
            Article article = iterator.next();
            System.out.println("article:" + JSON.toJSONString(article));
        }
    }
}

注:ElasticsearchRepository的search方法过期了,可以使用ElasticsearchRestTemplate的search来替代。

ElasticsearchRestTemplate API

映射对象

@Document(indexName = "staff", shards = 3, replicas = 2)
public class Staff {

    // @Id标记数据主键
    @Id
    private String id;

    // @Field标记字段。name:映射es中的字段, type:字段类型
    @Field(type = FieldType.Text)
    private String name;

    @Field(type = FieldType.Integer)
    private Integer age;

    @Field(type = FieldType.Double)
    private BigDecimal balance;

    @Field(type = FieldType.Keyword)
    private String jobName;


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

    public String getJobName() {
        return jobName;
    }

    public void setJobName(String jobName) {
        this.jobName = jobName;
    }
}

创建索引、新增测试数据

@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;

/**
  * 创建索引
  */
@Test
public void createIndex() {
    boolean exists = elasticsearchRestTemplate.indexOps(Staff.class).exists();
    // 如果索引已存在,删除索引
    if (exists) {
        // 删除索引
        elasticsearchRestTemplate.indexOps(Staff.class).delete();
    }
    // 创建索引
    elasticsearchRestTemplate.indexOps(Staff.class).create();

    // 创建映射
    Document mappings = elasticsearchRestTemplate.indexOps(Staff.class).createMapping();
    elasticsearchRestTemplate.indexOps(Staff.class).putMapping(mappings);

    System.out.println("---执行成功 Staff ---");
}

/**
  * 新增数据
  */
@Test
public void testSaveDocument() throws InterruptedException {
    //随机之内的整数
    Random rand = new Random();
    //职位
    List<String> jobList = Arrays.asList("主任", "高级专员", "中级专员", "初级专员");
    List<Staff> staffList = new ArrayList();
    for (int i = 14000; i < 15000; i++) {
        Staff staff = new Staff();
        staff.setId(String.valueOf(i));
        staff.setName("小泽" + i);
        staff.setAge(rand.nextInt(100));
        staff.setJobName(jobList.get(rand.nextInt(4)));
        staff.setBalance(new BigDecimal((Math.random() + 5d) * 1000).setScale(2, RoundingMode.HALF_DOWN));
        staffList.add(staff);
        if(staffList.size() >= 10){
            elasticsearchRestTemplate.save(staffList);
            TimeUnit.MILLISECONDS.sleep(500);
        }
    }
    elasticsearchRestTemplate.save(staffList);
}

@Test
public void deleteIndex(){
    boolean deleted = elasticsearchRestTemplate.indexOps(Staff.class).delete();
    System.out.println("是否删除成功 : " + deleted);
}

分页查询

/**
  * 搜索全部数据 , 分页显示 , 按 balance字段降序 排序
  */
@Test
public void testAllSearch() {
    // 构建查询条件(搜索全部)
    MatchAllQueryBuilder queryBuilder1 = QueryBuilders.matchAllQuery();
    // 分页
    Pageable pageable = PageRequest.of(0, 5);
    // 排序
    FieldSortBuilder balance = new FieldSortBuilder("balance").order(SortOrder.DESC);
    // 执行查询
    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .withQuery(queryBuilder1)
        .withPageable(pageable)
        .withSort(balance)
        .build();
    SearchHits<Staff> searchHits = elasticsearchRestTemplate.search(query, Staff.class);

    //封装page对象
    List<Staff> accounts = new ArrayList();
    for (SearchHit<Staff> hit : searchHits) {
        accounts.add(hit.getContent());
    }
    Page<Staff> page = new PageImpl(accounts, pageable, searchHits.getTotalHits());

    //输出分页对象
    System.out.println(page.getTotalPages());
    System.out.println(page.getTotalElements());
}

条件搜索

/**
  * 条件搜索
  */
@Test
public void testConditionSearch() {
    // 搜索出 jobName 为 '主任' 的文档
    TermQueryBuilder builder = QueryBuilders.termQuery("jobName", "主任");

    // 搜索jobName包含 '专员' 模糊匹配, 因为在Staff中该字段是keyword,不会分词,所以查询不到
    FuzzyQueryBuilder builder1 = QueryBuilders.fuzzyQuery("jobName", "专员");

    // 搜索jobName字段为 '初级专员' 的文档
    TermQueryBuilder builder2 = QueryBuilders.termQuery("jobName", "初级专员");

    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .withQuery(builder)
        .build();

    SearchHits<Staff> searchHits = elasticsearchRestTemplate.search(query, Staff.class);

    for (SearchHit<Staff> hit : searchHits) {
        System.out.println(JSON.toJSONString(hit.getContent()));
    }
}

QueryBuilders还有很多查询方法。

组合查询

/**
  * 组合查询
  */
@Test
public void testAssociation() {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    // must表示同时满足,should满足其中一个,must_not表示同时不满足
    boolQueryBuilder.must(QueryBuilders.matchQuery("jobName", "初级专员"));
    boolQueryBuilder.must(QueryBuilders.rangeQuery("balance").gte(5800d)); //大于等于5800

    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .withQuery(boolQueryBuilder)
        .build();

    SearchHits<Staff> searchHits = elasticsearchRestTemplate.search(query, Staff.class);
    for (SearchHit<Staff> hit : searchHits) {
        System.out.println(JSON.toJSON(hit.getContent()));
    }
}

过滤搜索

/**
  * 过滤搜索
  */
@Test
public void testFilterQuery() {
    // 构建条件
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    //大于等于20000,小于等于30000
    RangeQueryBuilder balance = QueryBuilders.rangeQuery("balance").gte(5700).lte(5800);
    boolQueryBuilder.filter(balance);

    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .withQuery(boolQueryBuilder)
        .build();

    SearchHits<Staff> searchHits = elasticsearchRestTemplate.search(query, Staff.class);

    for (SearchHit<Staff> hit : searchHits) {
        System.out.println(JSON.toJSONString(hit.getContent()));
    }
}

聚合查询

聚合搜索,aggs,类似于group by,对jobName字段进行聚合

/**
  * 聚合搜索, 对jobName字段进行聚合
  */
@Test
public void testAggregation() {

    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .addAggregation(AggregationBuilders.terms("count").field("jobName"))
        .build();

    SearchHits<Staff> searchHits = elasticsearchRestTemplate.search(query, Staff.class);

    //取出聚合结果
    Aggregations aggregations = searchHits.getAggregations();
    Terms terms = (Terms) aggregations.asMap().get("count");

    for (Terms.Bucket bucket : terms.getBuckets()) {
        String keyAsString = bucket.getKeyAsString();   // 聚合字段列的值
        long docCount = bucket.getDocCount();           // 聚合字段对应的数量
        System.out.println(keyAsString + " " + docCount); //高级专员 576
    }
}

嵌套聚合

统计出相同jobName的文档数量,再统计出balance的平均值,降序排序

/**
  * 嵌套聚合,统计出相同jobName的文档数量,再统计出balance的平均值,降序排序
  */
@Test
public void testAggregation2() {
    // 创建聚合查询条件
    TermsAggregationBuilder jobNameAgg = AggregationBuilders.terms("count").field("jobName");
    AvgAggregationBuilder balanceAgg = AggregationBuilders.avg("avg_balance").field("balance");
    // 嵌套
    jobNameAgg.subAggregation(balanceAgg);
    // 按balance的平均值降序排序
    jobNameAgg.order(BucketOrder.aggregation("avg_balance", false));

    NativeSearchQuery build = new NativeSearchQueryBuilder()
        .addAggregation(jobNameAgg)
        .build();
    //执行查询
    SearchHits<Staff> searchHits = elasticsearchRestTemplate.search(build, Staff.class);
    // 取出聚合结果
    Aggregations aggregations = searchHits.getAggregations();
    Terms terms = (Terms) aggregations.asMap().get("count");

    for (Terms.Bucket bucket : terms.getBuckets()) {
        // jobName : count : avg
        ParsedAvg avg = bucket.getAggregations().get("avg_balance");
        System.out.println(bucket.getKeyAsString() + " " + bucket.getDocCount() + " " + avg.getValueAsString());
    }
}

范围聚合

按字段的范围进行分段聚合,按age字段[20,30],[30,40],[40,50],之后按gender统计文档个数和balance的平均值。

/**
  * 按字段的范围进行分段聚合,按age字段[20,30],[30,40],[40,50],之后按jobName统计文档个数和balance的平均值
  */
@Test
public void testRangeQuery() {
    // 创建聚合查询条件
    RangeAggregationBuilder group_by_age =
        AggregationBuilders.range("group_by_age").field("age")
        .addRange(20, 30).addRange(30, 40).addRange(40, 50);
    TermsAggregationBuilder count = AggregationBuilders.terms("count").field("jobName");
    AvgAggregationBuilder balanceAgg = AggregationBuilders.avg("avg_balance").field("balance");

    //嵌套
    group_by_age.subAggregation(count);
    count.subAggregation(balanceAgg);

    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .addAggregation(group_by_age)
        .build();

    SearchHits<Staff> searchHits = elasticsearchRestTemplate.search(query, Staff.class);

    ParsedRange parsedRange = searchHits.getAggregations().get("group_by_age");

    for (Range.Bucket bucket : parsedRange.getBuckets()) {
        // "key" : "20.0-30.0",  "doc_count" : 451,
        System.out.println(bucket.getKeyAsString() + " : " + bucket.getDocCount());

        Terms group_by_gender = bucket.getAggregations().get("count");
        for (Terms.Bucket genderBucket : group_by_gender.getBuckets()) {
            //  "key" : "M", "doc_count" : 232, "key" : "F", "doc_count" : 219,
            System.out.println(genderBucket.getKeyAsString() + " : " + genderBucket.getDocCount());
            ParsedAvg balanceAvg = genderBucket.getAggregations().get("avg_balance");
            System.out.println(balanceAvg.getValueAsString());
        }
        System.out.println("-----------\n");
    }
}

elasticsearch-rest-high-level-client

下面介绍下 SpringBoot 如何通过 elasticsearch-rest-high-level-client 工具操作 ElasticSearch。

为什么没有使用 Spring 家族封装的 spring-data-elasticsearch?

  • 主要原因是灵活性和更新速度,Spring 将 ElasticSearch 过度封装,让开发者很难跟 ES 的 DSL 查询语句进行关联。再者就是更新速度,ES 的更新速度是非常快,但是 spring-data-elasticsearch 更新速度比较缓慢。
  • 由于上面两点,所以选择了官方推出的 Java 客户端 elasticsearch-rest-high-level-client,它的代码写法跟 DSL 语句很相似,懂 ES 查询的使用其上手很快。
<!--es客户端,不使用springboot封装的客户端-->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.14.2</version>
</dependency>
<!-- fastjson, 仅仅为了方便对象间的转换而已 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.1</version>
</dependency>

版本号对不上,为什么呢?

原来是SpringBoot对其版本也进行了管理,需要重新覆盖管理。

在spring-boot-dependencies中我们可以看到:

这里我们采用排除旧版本,重新导入对应版本号的依赖的方式解决:

<!--es客户端,不使用springboot封装的客户端-->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.14.2</version>
    <!--剔除,原因是spring-boot-dependencies中对elasticsearch的版本进行管理了, 引入的是旧版本-->
    <exclusions>
        <exclusion>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.14.2</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.14.2</version>
</dependency>
<!-- fastjson, 仅仅为了方便对象间的转换而已 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.1</version>
</dependency>

说明:要保证elasticsearch三个依赖的版本号一致,否则启动会失败,出现各种异常,如找不到class、IGNORE_DEPRECATIONS等。

索引操作

mapping json
{
  "mappings": {
    "doc": {
      "dynamic": true,
      "properties": {
        "name": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          }
        },
        "address": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          }
        },
        "remark": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          }
        },
        "age": {
          "type": "integer"
        },
        "salary": {
          "type": "float"
        },
        "birthDate": {
          "type": "date",
          "format": "yyyy-MM-dd"
        },
        "createTime": {
          "type": "date"
        }
      }
    }
  }
}

Java客户端

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * 创建索引
  */
@Test
public void testCreateIndex() {
    try {
        // 创建 Mapping
        XContentBuilder mapping = XContentFactory.jsonBuilder()
            .startObject()
            .field("dynamic", true)
            .startObject("properties")
            .startObject("name")
            .field("type", "text")
            .startObject("fields")
            .startObject("keyword")
            .field("type", "keyword")
            .endObject()
            .endObject()
            .endObject()
            .startObject("address")
            .field("type", "text")
            .startObject("fields")
            .startObject("keyword")
            .field("type", "keyword")
            .endObject()
            .endObject()
            .endObject()
            .startObject("remark")
            .field("type", "text")
            .startObject("fields")
            .startObject("keyword")
            .field("type", "keyword")
            .endObject()
            .endObject()
            .endObject()
            .startObject("age")
            .field("type", "integer")
            .endObject()
            .startObject("salary")
            .field("type", "float")
            .endObject()
            .startObject("birthDate")
            .field("type", "date")
            .field("format", "yyyy-MM-dd")
            .endObject()
            .startObject("createTime")
            .field("type", "date")
            .endObject()
            .endObject()
            .endObject();
        // 创建索引配置信息,配置
        Settings settings = Settings.builder()
            .put("index.number_of_shards", 3)
            .put("index.number_of_replicas", 2)
            .build();
        // 新建创建索引请求对象,然后设置索引类型(ES 7.0 将不存在索引类型)和 mapping 与 index 配置
        CreateIndexRequest request = new CreateIndexRequest("dc_user", settings);
        request.mapping("doc", mapping);
        // RestHighLevelClient 执行创建索引
        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        // 判断是否创建成功
        boolean isCreated = createIndexResponse.isAcknowledged();
        System.out.println("是否创建成功:" + isCreated);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 删除索引
  */
public void deleteIndex() {
    try {
        // 新建删除索引请求对象
        DeleteIndexRequest request = new DeleteIndexRequest("dc_user");
        // 执行删除索引
        AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        // 判断是否删除成功
        boolean siDeleted = acknowledgedResponse.isAcknowledged();
        System.out.println("是否删除成功:" + siDeleted);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

mapping对应的实体对象:

public class UserInfo {

    //姓名
    private String name;
    //年龄
    private Integer age;
    //地址
    private String address;
    //薪水
    private Float salary;
    //备注
    private String remark;
    //创建时间
    private Date createTime;
    //出生日期
    private String birthDate;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(String birthDate) {
        this.birthDate = birthDate;
    }
}

文档操作

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * 批量添加文档(用于测试演练)
  */
@Test
public void batchAddDocument() {
    try {
        Random random = new Random();
        List<String> addressList = Arrays.asList("北京市丰台区", "北京市昌平区", "北京市大兴区", "北京市通州区", "北京市朝阳区");
        List<String> birthDateList = Arrays.asList("1990-01-10", "1970-06-10", "1994-10-10", "1990-01-10", "1998-03-10");
        for (int i = 0; i < 100; i++) {
            // 创建索引请求对象
            // es7废除了type, type的值都是doc, 原有的一些方法也被标记为过期了
            //IndexRequest indexRequest = new IndexRequest("dc_user", "doc");
            IndexRequest indexRequest = new IndexRequest("dc_user");
            indexRequest.id(i + "00");
            // 创建员工信息
            UserInfo userInfo = new UserInfo();
            userInfo.setName("张三" + i);
            userInfo.setAge(random.nextInt(80) + 10);
            userInfo.setSalary(random.nextFloat() + 1000f);
            userInfo.setAddress(addressList.get(random.nextInt(5)));
            userInfo.setRemark("来自" + userInfo.getAddress() + "的张先生");
            userInfo.setCreateTime(new Date());
            userInfo.setBirthDate(birthDateList.get(random.nextInt(5)));
            // 将对象转换为 byte 数组
            byte[] json = JSON.toJSONBytes(userInfo);
            // 设置文档内容
            indexRequest.source(json, XContentType.JSON);
            // 执行增加文档
            IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
            System.out.println("创建状态:" + response.status());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 增加文档信息
  */
@Test
public void addDocument() {
    try {
        // 创建索引请求对象
        // es7废除了type, type的值都是doc, 原有的一些方法也被标记为过期了
        //IndexRequest indexRequest = new IndexRequest("dc_user", "doc");
        IndexRequest indexRequest = new IndexRequest("dc_user");
        indexRequest.id("1");
        // 创建员工信息
        UserInfo userInfo = new UserInfo();
        userInfo.setName("张三");
        userInfo.setAge(29);
        userInfo.setSalary(100.00f);
        userInfo.setAddress("北京市");
        userInfo.setRemark("来自北京市的张先生");
        userInfo.setCreateTime(new Date());
        userInfo.setBirthDate("1990-01-10");
        // 将对象转换为 byte 数组
        byte[] json = JSON.toJSONBytes(userInfo);
        // 设置文档内容
        indexRequest.source(json, XContentType.JSON);
        // 执行增加文档
        IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        System.out.println("创建状态:" + response.status());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 获取文档信息
  */
@Test
public void getDocument() {
    try {
        // 获取请求对象
        GetRequest getRequest = new GetRequest("dc_user", "1");
        // 获取文档信息
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        // 将 JSON 转换成对象
        if (getResponse.isExists()) {
            UserInfo userInfo = JSON.parseObject(getResponse.getSourceAsBytes(), UserInfo.class);

            System.out.println("员工信息:" + JSON.toJSONString(userInfo));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 更新文档信息
  */
@Test
public void updateDocument() {
    try {
        // 创建索引请求对象
        UpdateRequest updateRequest = new UpdateRequest("dc_user", "1");
        // 设置员工更新信息
        UserInfo userInfo = new UserInfo();
        userInfo.setSalary(200.00f);
        userInfo.setAddress("北京市海淀区");
        // 将对象转换为 byte 数组
        byte[] json = JSON.toJSONBytes(userInfo);
        // 设置更新文档内容
        updateRequest.doc(json, XContentType.JSON);
        // 执行更新文档
        UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        System.out.println("创建状态:" + response.status());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 删除文档信息
  */
@Test
public void deleteDocument() {
    try {
        // 创建删除请求对象
        DeleteRequest deleteRequest = new DeleteRequest("dc_user", "1");
        // 执行删除文档
        DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        System.out.println("删除状态:" + response.status());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 根据条件删除文档
  *
  * @throws IOException
  */
@Test
public void deleteByQueryRequest() throws IOException {
    //参数为索引名,可以不指定,可以一个,可以多个
    DeleteByQueryRequest request = new DeleteByQueryRequest("dc_user");
    // 设置版本冲突时继续
    request.setConflicts("proceed");
    // 设置查询条件,第一个参数是字段名,第二个参数是字段的值
    // 我们在创建索引的时候的name是text类型,是会进行分词的,不一定存在张三这个文本
    // 所以我们又定义了keyword字段类型,type=keyword,来存储张三这个完整文本
    request.setQuery(new TermQueryBuilder("name.keyword", "张三"));
    // 更新最大文档数
    request.setMaxDocs(1);
    // 单批次大小
    request.setBatchSize(1);
    //并行, max_docs必须大于等于slices
    request.setSlices(1);
    // 使用滚动参数来控制“搜索上下文”保持多长时间
    request.setScroll(TimeValue.timeValueMinutes(10));
    // 超时
    request.setTimeout(TimeValue.timeValueMinutes(2));
    // 刷新索引
    request.setRefresh(true);
    BulkByScrollResponse response = restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);
    System.out.println("isDeleted : " + response.getStatus().getUpdated()); //0 删除成功
}

/**
  * 查询所有的文档, 并逐一迭代进行删除
  *
  * @throws IOException
  */
@Test
public void findAll() throws IOException {
    SearchRequest searchRequest = new SearchRequest();
    //指定索引
    searchRequest.indices("dc_user");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 查询所有
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    //设置查询数量大小, 不设置默认是10
    searchSourceBuilder.size(100);
    searchRequest.source(searchSourceBuilder);
    //获取到结果
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    SearchHits hits = searchResponse.getHits();
    System.out.println("查询到的数据总数:" + hits.getTotalHits().value);
    List<String> idList = new ArrayList();
    Iterator<SearchHit> iterator = hits.iterator();
    while (iterator.hasNext()) {
        //es中的一条完整数据,包括_index、_type、_id、_score以及实际的业务数据
        SearchHit hit = iterator.next();
        System.out.println("实际的业务数据:" + hit.getSourceAsString());
        //删除文档, 根据拿到的_id:hit.getId()
        System.out.println("待删除的_id = " + hit.getId());
        DeleteRequest deleteRequest = new DeleteRequest("dc_user", hit.getId());
        DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        System.out.println("删除状态:" + response.status());
    }
}

精确查询(term)

@Autowired
private RestHighLevelClient restHighLevelClient;


/**
  * 精确查询(查询条件不会进行分词,但是查询内容可能会分词,导致查询不到)
  */
@Test
public void termQuery() {
    try {
        // 构建查询条件(注意:termQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.termQuery("address.keyword", "北京市通州区"));
        //不设置查询大小, 默认是查询10条
        searchSourceBuilder.size(20);
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 多个内容在一个字段中进行查询
  */
@Test
public void termsQuery() {
    try {
        // 构建查询条件(注意:termsQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.termsQuery("address.keyword", "北京市丰台区", "北京市昌平区", "北京市大兴区"));
        //不设置查询大小, 默认是查询10条
        searchSourceBuilder.size(20);
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

匹配查询(match)

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * 匹配查询符合条件的所有数据,并设置分页
  */
@Test
public void matchAllQuery() {
    try {
        // 构建查询条件
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        // 创建查询源构造器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(matchAllQueryBuilder);
        // 设置分页
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(3);
        // 设置排序
        searchSourceBuilder.sort("salary", SortOrder.ASC);
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 匹配查询数据
  */
@Test
public void matchQuery() {
    try {
        // 构建查询条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("address", "*通州区"));
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 词语匹配查询
  */
@Test
public void matchPhraseQuery() {
    try {
        // 构建查询条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchPhraseQuery("address", "北京市通州区"));
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 内容在多字段中进行查询
  */
@Test
public void matchMultiQuery() {
    try {
        // 构建查询条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.multiMatchQuery("北京市", "address", "remark"));
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

模糊查询(fuzzy)

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * 模糊查询所有以 “三” 结尾的姓名
  */
@Test
public void fuzzyQuery() {
    try {
        // 构建查询条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.fuzzyQuery("name", "三").fuzziness(Fuzziness.AUTO));
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

范围查询(range)

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * 查询岁数 ≥ 30 岁的员工数据
  */
@Test
public void rangeQuery() {
    try {
        // 构建查询条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.rangeQuery("age").gte(30));
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 查询距离现在 30 年间的员工数据
  * [年(y)、月(M)、星期(w)、天(d)、小时(h)、分钟(m)、秒(s)]
  * 例如:
  * now-1h 查询一小时内范围
  * now-1d 查询一天内时间范围
  * now-1y 查询最近一年内的时间范围
  */
@Test
public void dateRangeQuery() {
    try {
        // 构建查询条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // includeLower(是否包含下边界)、includeUpper(是否包含上边界)
        searchSourceBuilder.query(QueryBuilders.rangeQuery("birthDate")
                                  .gte("now-30y").includeLower(true).includeUpper(true));
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

通配符查询(wildcard)

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * 查询所有以 “三” 结尾的姓名
  * <p>
  * *:表示多个字符(0个或多个字符)
  * ?:表示单个字符
  */
@Test
public void wildcardQuery() {
    try {
        // 构建查询条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.wildcardQuery("name.keyword", "*三?"));
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

布尔查询(bool)

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * 布尔查询
  */
@Test
public void boolQuery() {
    try {
        // 创建 Bool 查询构建器
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 构建查询条件
        boolQueryBuilder.must(QueryBuilders.termsQuery("address.keyword", "北京市昌平区", "北京市大兴区", "北京市房山区"))
            .filter().add(QueryBuilders.rangeQuery("birthDate").format("yyyy").gte("1990").lte("1995"));
        // 构建查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(boolQueryBuilder);
        // 创建查询请求对象,将查询对象配置到其中
        SearchRequest searchRequest = new SearchRequest("dc_user");
        searchRequest.source(searchSourceBuilder);
        // 执行查询,然后处理响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 根据状态和数据条数验证是否返回了数据
        if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                // 将 JSON 转换成对象
                UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
                // 输出查询信息
                System.out.println(JSON.toJSONString(userInfo));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Metric 聚合分析

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * stats 统计员工总数、员工工资最高值、员工工资最低值、员工平均工资、员工工资总和
  */
@Test
public void aggregationStats() {
    try {
        // 设置聚合条件
        AggregationBuilder aggr = AggregationBuilders.stats("salary_stats").field("salary");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(aggr);
        // 设置查询结果不返回,只返回聚合结果
        searchSourceBuilder.size(0);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status()) || aggregations != null) {
            // 转换为 Stats 对象
            ParsedStats aggregation = aggregations.get("salary_stats");
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            System.out.println("count:" + aggregation.getCount());
            System.out.println("avg:" + aggregation.getAvg());
            System.out.println("max:" + aggregation.getMax());
            System.out.println("min:" + aggregation.getMin());
            System.out.println("sum:" + aggregation.getSum());
            System.out.println("-------------------------------------------");
        }
        // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
        String responseResult = response.toString();
        System.out.println("responseResult : " + responseResult);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * min 统计员工工资最低值
  */
@Test
public void aggregationMin() {
    try {
        // 设置聚合条件
        AggregationBuilder aggr = AggregationBuilders.min("salary_min").field("salary");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(aggr);
        searchSourceBuilder.size(0);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status()) || aggregations != null) {
            // 转换为 Min 对象
            ParsedMin aggregation = aggregations.get("salary_min");
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            System.out.println("min:" + aggregation.getValue());
            System.out.println("-------------------------------------------");
        }
        // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
        String responseResult = response.toString();
        System.out.println("responseResult : " + responseResult);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * max 统计员工工资最高值
  */
@Test
public void aggregationMax() {
    try {
        // 设置聚合条件
        AggregationBuilder aggr = AggregationBuilders.max("salary_max").field("salary");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(aggr);
        searchSourceBuilder.size(0);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status()) || aggregations != null) {
            // 转换为 Max 对象
            ParsedMax aggregation = aggregations.get("salary_max");
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            System.out.println("max:" + aggregation.getValue());
            System.out.println("-------------------------------------------");
        }
        // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
        String responseResult = response.toString();
        System.out.println("responseResult : " + responseResult);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * avg 统计员工工资平均值
  */
@Test
public void aggregationAvg() {
    try {
        // 设置聚合条件
        AggregationBuilder aggr = AggregationBuilders.avg("salary_avg").field("salary");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(aggr);
        searchSourceBuilder.size(0);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status()) || aggregations != null) {
            // 转换为 Avg 对象
            ParsedAvg aggregation = aggregations.get("salary_avg");
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            System.out.println("avg:" + aggregation.getValue());
            System.out.println("-------------------------------------------");
        }
        // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
        String responseResult = response.toString();
        System.out.println("responseResult : " + responseResult);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
  * sum 统计员工工资总值
  */
@Test
public void aggregationSum() {
    try {
        // 设置聚合条件
        SumAggregationBuilder aggr = AggregationBuilders.sum("salary_sum").field("salary");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(aggr);
        searchSourceBuilder.size(0);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status()) || aggregations != null) {
            // 转换为 Sum 对象
            ParsedSum aggregation = aggregations.get("salary_sum");
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            System.out.println("sum:" + aggregation.getValue());
            System.out.println("-------------------------------------------");
        }
        // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
        String responseResult = response.toString();
        System.out.println("responseResult : " + responseResult);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
  * count 统计员工总数
  */
@Test
public void aggregationCount() {
    try {
        // 设置聚合条件
        AggregationBuilder aggr = AggregationBuilders.count("employee_count").field("salary");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(aggr);
        searchSourceBuilder.size(0);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status()) || aggregations != null) {
            // 转换为 ValueCount 对象
            ParsedValueCount aggregation = aggregations.get("employee_count");
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            System.out.println("count:" + aggregation.getValue());
            System.out.println("-------------------------------------------");
        }
        // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
        String responseResult = response.toString();
        System.out.println("responseResult : " + responseResult);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
  * percentiles 统计员工工资百分位
  */
@Test
public void aggregationPercentiles() {
    try {
        // 设置聚合条件
        AggregationBuilder aggr = AggregationBuilders.percentiles("salary_percentiles").field("salary");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(aggr);
        searchSourceBuilder.size(0);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status()) || aggregations != null) {
            // 转换为 Percentiles 对象
            ParsedPercentiles aggregation = aggregations.get("salary_percentiles");
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            for (Percentile percentile : aggregation) {
                System.out.println(String.format("百分位:%s:%s", percentile.getPercent(), percentile.getValue()));
            }
            System.out.println("-------------------------------------------");
        }
        // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
        String responseResult = response.toString();
        System.out.println("responseResult : " + responseResult);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Bucket 聚合分析

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * 按岁数进行聚合分桶
  */
@Test
public void aggrBucketTerms() {
    try {
        AggregationBuilder aggr = AggregationBuilders.terms("age_bucket").field("age");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(10);
        searchSourceBuilder.aggregation(aggr);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status())) {
            // 分桶
            Terms byCompanyAggregation = aggregations.get("age_bucket");
            List<? extends Terms.Bucket> buckets = byCompanyAggregation.getBuckets();
            // 输出各个桶的内容
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            for (Terms.Bucket bucket : buckets) {
                System.out.println(String.format("桶名:%s | 总数:%s", bucket.getKeyAsString(), bucket.getDocCount()));
            }
            System.out.println("-------------------------------------------");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 按工资范围进行聚合分桶
  */
@Test
public void aggrBucketRange() {
    try {
        AggregationBuilder aggr = AggregationBuilders.range("salary_range_bucket")
            .field("salary")
            .addUnboundedTo("低级员工", 3000)
            .addRange("中级员工", 5000, 9000)
            .addUnboundedFrom("高级员工", 9000);
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);
        searchSourceBuilder.aggregation(aggr);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status())) {
            // 分桶
            Range byCompanyAggregation = aggregations.get("salary_range_bucket");
            List<? extends Range.Bucket> buckets = byCompanyAggregation.getBuckets();
            // 输出各个桶的内容
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            for (Range.Bucket bucket : buckets) {
                System.out.println(String.format("桶名:%s | 总数:%s", bucket.getKeyAsString(), bucket.getDocCount()));
            }
            System.out.println("-------------------------------------------");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 按照时间范围进行分桶
  */
@Test
public void aggrBucketDateRange() {
    try {
        AggregationBuilder aggr = AggregationBuilders.dateRange("date_range_bucket")
            .field("birthDate")
            .format("yyyy")
            .addRange("1985-1990", "1985", "1990")
            .addRange("1990-1995", "1990", "1995");
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);
        searchSourceBuilder.aggregation(aggr);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status())) {
            // 分桶
            Range byCompanyAggregation = aggregations.get("date_range_bucket");
            List<? extends Range.Bucket> buckets = byCompanyAggregation.getBuckets();
            // 输出各个桶的内容
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            for (Range.Bucket bucket : buckets) {
                System.out.println(String.format("桶名:%s | 总数:%s", bucket.getKeyAsString(), bucket.getDocCount()));
            }
            System.out.println("-------------------------------------------");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 按工资多少进行聚合分桶
  */
@Test
public void aggrBucketHistogram() {
    try {
        AggregationBuilder aggr = AggregationBuilders.histogram("salary_histogram")
            .field("salary")
            .extendedBounds(0, 12000)
            .interval(3000);
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);
        searchSourceBuilder.aggregation(aggr);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status())) {
            // 分桶
            Histogram byCompanyAggregation = aggregations.get("salary_histogram");
            List<? extends Histogram.Bucket> buckets = byCompanyAggregation.getBuckets();
            // 输出各个桶的内容
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            for (Histogram.Bucket bucket : buckets) {
                System.out.println(String.format("桶名:%s | 总数:%s", bucket.getKeyAsString(), bucket.getDocCount()));
            }
            System.out.println("-------------------------------------------");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
  * 按出生日期进行分桶
  */
@Test
public void aggrBucketDateHistogram() {
    try {
        DateHistogramAggregationBuilder aggr = AggregationBuilders.dateHistogram("birthday_histogram").field("birthDate");
        aggr.calendarInterval(DateHistogramInterval.YEAR);
        //field.calendarInterval(DateHistogramInterval.days(10));
        aggr.format("yyyy");
        aggr.minDocCount(0);//强制返回空 buckets,既空的月份也返回

        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);
        searchSourceBuilder.aggregation(aggr);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status())) {
            // 分桶
            Histogram byCompanyAggregation = aggregations.get("birthday_histogram");

            List<? extends Histogram.Bucket> buckets = byCompanyAggregation.getBuckets();
            // 输出各个桶的内容
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            for (Histogram.Bucket bucket : buckets) {
                System.out.println(String.format("桶名:%s | 总数:%s", bucket.getKeyAsString(), bucket.getDocCount()));
            }
            System.out.println("-------------------------------------------");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}

Metric 与 Bucket 聚合分析

@Autowired
private RestHighLevelClient restHighLevelClient;

/**
  * topHits 按岁数分桶、然后统计每个员工工资最高值
  */
@Test
public void aggregationTopHits() {
    try {
        AggregationBuilder testTop = AggregationBuilders.topHits("salary_max_user")
            .size(1)
            .sort("salary", SortOrder.DESC);
        AggregationBuilder salaryBucket = AggregationBuilders.terms("salary_bucket")
            .field("age")
            .size(10);
        salaryBucket.subAggregation(testTop);
        // 查询源构建器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);
        searchSourceBuilder.aggregation(salaryBucket);
        // 创建查询请求对象,将查询条件配置到其中
        SearchRequest request = new SearchRequest("dc_user");
        request.source(searchSourceBuilder);
        // 执行请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 获取响应中的聚合信息
        Aggregations aggregations = response.getAggregations();
        // 输出内容
        if (RestStatus.OK.equals(response.status())) {
            // 分桶
            Terms byCompanyAggregation = aggregations.get("salary_bucket");
            List<? extends Terms.Bucket> buckets = byCompanyAggregation.getBuckets();
            // 输出各个桶的内容
            System.out.println("-------------------------------------------");
            System.out.println("聚合信息:");
            for (Terms.Bucket bucket : buckets) {
                System.out.println("桶名:" + bucket.getKeyAsString());
                ParsedTopHits topHits = bucket.getAggregations().get("salary_max_user");
                for (SearchHit hit : topHits.getHits()) {
                    System.out.println(hit.getSourceAsString());
                }
            }
            System.out.println("-------------------------------------------");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

QueryBuilders使用API

public class EsQueryBuildersDSL {

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * match query 单个匹配
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder matchQuery() {
        return QueryBuilders.matchQuery("name", "葫芦4032娃");
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * multimatch  query
     * 创建一个匹配查询的布尔型提供字段名称和文本。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder multiMatchQuery() {
        //现住址和家乡在【山西省太原市7429街道】的人
        return QueryBuilders.multiMatchQuery(
                "山西省太原市7429街道",     // Text you are looking for
                "home", "now_home"       // Fields you query on
        );
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * boolean query and 条件组合查询
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder booleanQuery() {
        return QueryBuilders
                .boolQuery()
                .must(QueryBuilders.termQuery("name", "葫芦3033娃"))
                .must(QueryBuilders.termQuery("home", "山西省太原市7967街道"))
                .mustNot(QueryBuilders.termQuery("isRealMen", false))
                .should(QueryBuilders.termQuery("now_home", "山西省太原市"));
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * ids query
     * 构造一个只会匹配的特定数据 id 的查询。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder idsQuery() {
        return QueryBuilders.idsQuery().addIds("CHszwWRURyK08j01p0Mmug", "ojGrYKMEQCCPvh75lHJm3A");
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * constant score query
     * 另一个查询和查询,包裹查询只返回一个常数分数等于提高每个文档的查询。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder constantScoreQuery() {
        /*return // Using with Filters
            QueryBuilders.constantScoreQuery(FilterBuilders.termFilter("name", "kimchy"))
                    .boost(2.0f);*/

        // With Queries
        return QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("name", "葫芦3033娃"))
                .boost(2.0f);
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * disjunction max query
     * 一个生成的子查询文件产生的联合查询,
     * 而且每个分数的文件具有最高得分文件的任何子查询产生的,
     * 再加上打破平手的增加任何额外的匹配的子查询。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder disMaxQuery() {
        return QueryBuilders.disMaxQuery()
                .add(QueryBuilders.termQuery("name", "kimchy"))          // Your queries
                .add(QueryBuilders.termQuery("name", "elasticsearch"))   // Your queries
                .boost(1.2f)
                .tieBreaker(0.7f);
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * fuzzy query
     * 使用模糊查询匹配文档查询。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder fuzzyQuery() {
        return QueryBuilders.fuzzyQuery("name", "葫芦3582");
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * matchall query
     * 查询匹配所有文件。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder matchAllQuery() {
        return QueryBuilders.matchAllQuery();
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * prefix query
     * 包含与查询相匹配的文档指定的前缀。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder prefixQuery() {
        return QueryBuilders.prefixQuery("name", "葫芦31");
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * querystring query
     *   查询解析查询字符串,并运行它。有两种模式,这种经营。
     * 第一,当没有添加字段(使用{ @link QueryStringQueryBuilder #字段(String)},将运行查询一次,非字段前缀
     *   将使用{ @link QueryStringQueryBuilder # defaultField(字符串)}。
     * 第二,当一个或多个字段
     *   (使用{ @link QueryStringQueryBuilder #字段(字符串)}),将运行提供的解析查询字段,并结合
     *   他们使用DisMax或者一个普通的布尔查询(参见{ @link QueryStringQueryBuilder # useDisMax(布尔)})。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder queryString() {
        return QueryBuilders.queryStringQuery("+kimchy -elasticsearch");
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * range query
     * 查询相匹配的文档在一个范围。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder rangeQuery() {
        return QueryBuilders
                .rangeQuery("name")
                .from("葫芦1000娃")
                .to("葫芦3000娃")
                .includeLower(true)     //包括下界
                .includeUpper(false); //包括上界
    }


    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * term query
     * 一个查询相匹配的文件包含一个术语。。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder termQuery() {
        return QueryBuilders.termQuery("name", "葫芦580娃");
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * wildcard query
     *   实现了通配符搜索查询。支持通配符* < /tt>,<tt>
     *   匹配任何字符序列(包括空),<tt> ? < /tt>,
     *   匹配任何单个的字符。注意该查询可以缓慢,因为它
     *   许多方面需要遍历。为了防止WildcardQueries极其缓慢。
     *   一个通配符词不应该从一个通配符* < /tt>或<tt>
     *   < /tt> <tt> ?。
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder wildcardQuery() {
        return QueryBuilders.wildcardQuery("name", "葫芦*2娃");
    }


    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * more like this (field) query (mlt and mlt_field)
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder moreLikeThisQuery() {

        // mlt Query
        return QueryBuilders.moreLikeThisQuery(new String[]{"jobName", "name"}, new String[]{"高级专员"}, null) 
                .minTermFreq(1)                                 // Ignore Threshold
                .maxQueryTerms(12);                             // Max num of Terms
        // in generated queries
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * span queries (first, near, not, or, term)
     * 跨度查询
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder spanQueries() {
        QueryBuilder queryBuilder1 = QueryBuilders.spanFirstQuery(QueryBuilders.spanTermQuery("name", "葫芦580娃"), 30000);     // Max查询范围的结束位置

        QueryBuilder queryBuilder2 = QueryBuilders.spanNearQuery(QueryBuilders.spanTermQuery("name", "葫芦3812娃"), 30000)
                .addClause(QueryBuilders.spanNearQuery(QueryBuilders.spanTermQuery("name", "葫芦7139娃"), 10000)) // Span Term Queries
                .inOrder(false);

        // Span Not
        QueryBuilder queryBuilder3 = QueryBuilders.spanNotQuery(
                QueryBuilders.spanTermQuery("name", "葫芦580娃"),
                QueryBuilders.spanTermQuery("home", "山西省太原市2552街道")
        );

        // Span Or
        QueryBuilder queryBuilder4 = QueryBuilders.spanOrQuery(QueryBuilders.spanTermQuery("name", "葫芦580娃"))
                .addClause(QueryBuilders.spanTermQuery("name", "葫芦3812娃"))
                .addClause(QueryBuilders.spanTermQuery("name", "葫芦7139娃"));

        // Span Term
        QueryBuilder queryBuilder5 = QueryBuilders.spanTermQuery("name", "葫芦580娃");
        return queryBuilder5;
    }

    /**
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * nested query
     * 嵌套查询
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    public static QueryBuilder nestedQuery() {
        return QueryBuilders.nestedQuery("location",               // Path
                QueryBuilders.boolQuery()                      // Your query
                        .must(QueryBuilders.matchQuery("location.lat", 0.962590433140581))
                        .must(QueryBuilders.rangeQuery("location.lon").lt(0.00000000000000000003)),
                ScoreMode.Total); // max, total, avg or none
    }
}

范围查询

1)数字范围

//闭区间查询
QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").from(${fieldValue1}).to(${fieldValue2}); 
 
//开区间查询
QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").from(${fieldValue1}, false).to(${fieldValue2}, false);
 
//大于
QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").gt(${fieldValue});
 
 //大于等于
QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").gte(${fieldValue}); 
 
//小于
QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").lt(${fieldValue}); 
 
//小于等于
QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").lte(${fieldValue});

常见示例:

// 增加价格区间的检索条件
if (searchRequest.getMinPrice() != null) {
    queryBuilder.must(QueryBuilders.rangeQuery(avagePriceStr)
		.from(searchRequest.getMinPrice().toString()).includeLower(true)
		.includeUpper(true));
}
if (searchRequest.getMaxPrice() != null) {
    queryBuilder.must(QueryBuilders.rangeQuery(avagePriceStr)
		.to(searchRequest.getMaxPrice().add(new BigDecimal(1)).toString())
		.includeUpper(false));
}

2)时间范围

public SearchResponse getApiResponseByDetail(SearchRequestBuilder responseBuilder, String condition) {
    String time1 = "2020-01-02T00:00:00.000Z";
    String time2 = "2020-01-02T15:59:59.000Z";
    RangeQueryBuilder rangequerybuilder = QueryBuilders
        //传入时间,目标格式2020-01-02T03:17:37.638Z
        .rangeQuery("@timestamp")
        .from(time1).to(time2);

    SearchResponse searchResponse = responseBuilder.setQuery(
        QueryBuilders.boolQuery()
        //must表示and
        .must(rangequerybuilder) //根据时间范围查询
        .must(QueryBuilders.existsQuery("api_id"))
        .must(QueryBuilders.matchPhraseQuery("detail", condition))
    ).setExplain(true).execute().actionGet();
    return searchResponse;
}

多条件查询

QueryBuilder qb1 = QueryBuilders.moreLikeThisQuery(new String[]{"${fieldName1}"}, new String[]{"${fieldValue1}"}, null);
QueryBuilder qb2 = QueryBuilders.rangeQuery("${fieldName2}").gt("${fieldValue2}");
QueryBuilder qb3 = QueryBuilders.boolQuery().must(qb1).must(qb2);

 

参考:

 

posted @ 2022-04-24 14:39  残城碎梦  阅读(1922)  评论(0编辑  收藏  举报