springboot整合elasticsearch[高阶API]与语法分析

文章目录


一、说明:

1.1、小编的案例说明

  1. 如果没有安装可以参考小编这篇文章 ==> 文章入口
  2. 小编的elasticsearch的版本是7.4.2
  3. 案例采用的方式(官方提供的):Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)
  4. 使用kibana可视化工具查看数据

1.2、对elasticsearch的操作方式种类

  • 操作 ==> 9300端口:TCP

spring-data-elasticsearch:transport-api.jar

说明:springboot 版本不同, transport-api.jar 不同,不能适配 es 版本
7.x 已经不建议使用,8 以后就要废弃

  • 操作 ==> 9200:HTTP
  1. JestClient:非官方,更新慢
  2. RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦
  3. HttpClient:同上(简单说就是和elasticsearch关联性不高,都要自己写方法)
  4. Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作,API 层次分明,上手简单(推荐)
  5. 使用Elasticsearch-Rest-Client ==>官方文档参考地址
  6. 都在API文档里面,普通的,复杂的检索…
    在这里插入图片描述

二、springboot整合Elasticsearch-Rest-Client

1.1、引入依赖

小编安装的Elasticsearch版本是7.4.2所以版本要对应依赖版本也要是7.4.2

<!--  elasticsearch-java restful client 操作http  -->
<dependency>
     <groupId>org.elasticsearch.client</groupId>
     <artifactId>elasticsearch-rest-high-level-client</artifactId>
     <version>7.4.2</version>
 </dependency>

要注意的一点是springboot默认会引入spring-data-elasticsearch
所以我们也要将其版本改成和我们对应的版本

如图:
在这里插入图片描述
在properties里面修改成我们的版本:

 <properties>
    <java.version>1.8</java.version>

      <!-- 这个是spring data 默认支持的。改成和我们自己配置的版本一致-->
      <elasticsearch.version>7.4.2</elasticsearch.version>
  </properties>

查看maven依赖是否保持一致:
在这里插入图片描述

1.2、设置配置文件

目录:
在这里插入图片描述

package com.atguigu.gulimall.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author suqinyi
 * @Date 2021/5/23
 */

/**
 * 1、导入依赖
 * 2、编写配置
 * 3、参照API文档
 */
@Configuration
public class GulimallElasticSearchConfig {


    public static final RequestOptions COMMON_OPTIONS;

    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }


    //注入client
    @Bean
    public RestHighLevelClient esRestClient() {

        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.56.10", 9200, "http")
                )
        );
        return client;
    }
}

1.3、在测试类中测试 => (添加更新二合一)

说明:如果在对这个users(名称叫索引)添加数据,那么不会报错,他的版本号会加上去(变化),所以是添加更新二合一

package com.atguigu.gulimall.search;

import com.alibaba.fastjson.JSON;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import lombok.Data;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

@RunWith(SpringRunner.class)//指定spring的驱动
@SpringBootTest//小编的springboot版本是2.18  说明:2.x和1.x的版本测试类有着区别
public class GulimallSearchApplicationTests {

    @Autowired
    private RestHighLevelClient client;

    /**
     * 测试存储数据到es
     * 更新也可以
     */
    @Test
    public void indexData() throws IOException {
        IndexRequest indexRequest = new IndexRequest("users");
        //数据的id-不设置就自动生成,类型string,通常是存入数据的主键
        indexRequest.id("1");

        // 方式一:
        //indexRequest.source("username","zhangsan","age",18,"gender","男");

        // 方式二(推荐):
        User user = new User();
        user.setAge(18);
        user.setUsername("zhangsan");
        user.setGender("男");
        String jsonString = JSON.toJSONString(user);
        indexRequest.source(jsonString, XContentType.JSON);

        //调用,执行操作
        IndexResponse index = client.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

        //提取有用的数据
        System.out.println("响应数据:"+"\n"+index);
    }

    @Data//使用lombok插件+依赖
    class User{
        private String username;
        private String gender;
        private Integer age;
    }

    //测试容器是否注入成功
    @Test
    public void testIfOK() {
        System.out.println(client);
    }
}

控制台效果:
在这里插入图片描述

kibana效果:
在这里插入图片描述

1.4、数据检索 => 条件检索

更具体的看官方文档示例

 	/**
     *查询-条件检索
     */
    @Test
    public void searchData() throws IOException {
        //创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        //指定索引
        searchRequest.indices("users");

        //指定DSL,检索条件
        //SearchSourceBuilder sourceBuilder 封装检索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //构造检索条件
        sourceBuilder.query(QueryBuilders.matchQuery("username","zhangsan"));
//        sourceBuilder.from();
//        sourceBuilder.size();
//        sourceBuilder.aggregation();//这个是聚合

        System.out.println("检索条件:"+"\n"+sourceBuilder);

        searchRequest.source(sourceBuilder);

        //执行检索
        SearchResponse searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

        //分析结果
        System.out.println(searchResponse.toString());

        SearchHits hits = searchResponse.getHits();
        SearchHit[] hits1 = hits.getHits();//真正的命中数据--可以进行遍历操作
        for (SearchHit hit : hits1) {
//            System.out.println(hit);
//            System.out.println(hit.xxx);

            //一般要处理的都是source里面的数据 ==> 通常是将其转成string,在和实体类映射起来
            String string = hit.getSourceAsString();
            //利用json工具将string转成实体类:以fastJson为例,jackson也行
            User01 User = JSON.parseObject(string, User01.class);
            System.out.println("user:"+User);
        }
        
        //获取聚合数据--这边小编没准备复杂数据,就没聚合了。这边贴个官方图
    }

    //使用lombok插件+依赖
    @ToString
    @Data
    static class User01{
        private String username;
        private String gender;
        private Integer age;
    }

控制台结果:
在这里插入图片描述
官方的聚合获取操作

官网文档的参考地址==>文档入口
在这里插入图片描述


三、如何采用批量插入?

在项目中通常的操作

  1. 先在elasticsearch创建映射关系(说白点就是创建索引并且构造数据类型)
  2. 在编写java代码

1.1、创建elasticsearch创建映射关系

说明:
索引是user
里面有userId还有个对象student包含着age和name的字段

所以:在java中创建的实体类的属性名和类型要和这个索引里面的字段对应起来,在操作的时候将实体类转成json,插入es才不会有问题

PUT user
{
	"mappings": {
		"properties": {
			"skuId": {
				"type": "long"
			},
			"student": {
				"type": "nested",
				"properties": {
					"age": {
						"type": "long"
					},
					"name": {
						"type": "keyword"
					}
				}
			}
		}
	}
}

1.2、java的逻辑代码

    //注入
	@Autowired
    RestHighLevelClient restHighLevelClient;

	//业务的核心代码
    @Override
    public boolean saveToEs(List<SkuEsModel> skuEsModels) throws IOException {
     	/**
         * 说明:
         * 传入的List<SkuEsModel> skuEsModels 是一个list
         * 里面有多个SkuEsModel(实体类)和elasticsearch创建的索引(名称和类型)对应
         * 如果不明白。。在看下小编上面写的那个基础整合案例,里面的User
         */
    	
        //保存到es
        //给es中建立索引,user,建立映射关系
        //SkuEsModel这个实体类的属性名和类型和user索引的数据对应起来

        //给es中保存数据
        //源码里面需要的俩个属性:BulkRequest bulkRequest, RequestOptions optionss
        //批量插入
        BulkRequest bulkRequest = new BulkRequest();

		//遍历
        for (SkuEsModel model : skuEsModels) {
            // 构造保存请求
            IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
            //设置id的话可以保证不会加入相同的数据,数据存在会采用更新
            indexRequest.id(model.getSkuId().toString());
            //将数据转成Json。   这边用的是fastjson
            String jsonString = JSON.toJSONString(model);
            indexRequest.source(jsonString, XContentType.JSON);
            bulkRequest.add(indexRequest);
        }

		//restHighLevelClient.bulk()这个采用是同步,里面也有个异步的方法 restHighLevelClient.bulkAsync();
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

        // TODO 如果批量错误,待处理问题
        
        // 有错误是true,没有是false
        boolean b = bulk.hasFailures();

		//stream流的写法,看不懂的去看下java8新特性
        List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
            return item.getId();
        }).collect(Collectors.toList());

        log.info("数据,{},es返回数据{}",collect,bulk.toString());

        return b;
    }

四、JAVA API 语法示例

1.1、使用的数据

在这里插入图片描述

CREATE TABLE `student` (
  `id` varchar(50) NOT NULL COMMENT '主键id',
  `name` varchar(50) DEFAULT NULL COMMENT '姓名',
  `age` int(50) DEFAULT NULL COMMENT '年龄',
  `birthday` varchar(50) DEFAULT NULL COMMENT 'yyyyMMdd',
  `address` varchar(50) DEFAULT NULL COMMENT '地址',
  `grade` varchar(50) DEFAULT NULL COMMENT '班级年纪',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  `updateTime` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

# 测试数据
INSERT INTO `student`(`id`, `name`, `age`, `birthday`, `address`, `grade`, `createTime`, `updateTime`) VALUES ('1', '张三', 18, '20001010', '福建省泉州市', '高三1班', '2022-07-29 16:58:04', '2022-07-29 16:58:04');
INSERT INTO `student`(`id`, `name`, `age`, `birthday`, `address`, `grade`, `createTime`, `updateTime`) VALUES ('2', '张三三', 20, '20051211', '福建省厦门市', '高三1班', '2022-07-04 16:58:55', '2022-07-04 16:58:55');
INSERT INTO `student`(`id`, `name`, `age`, `birthday`, `address`, `grade`, `createTime`, `updateTime`) VALUES ('3', '李四', 30, '19900225', '浙江省', '高三1班', '2022-07-04 16:59:37', '2022-07-04 16:59:37');
INSERT INTO `student`(`id`, `name`, `age`, `birthday`, `address`, `grade`, `createTime`, `updateTime`) VALUES ('4', '小王', 30, '19990518', '浙江省', '小王3班', '2022-07-29 17:00:36', '2022-07-19 17:00:38');
INSERT INTO `student`(`id`, `name`, `age`, `birthday`, `address`, `grade`, `createTime`, `updateTime`) VALUES ('5', '老六', 50, '1970', '北京市通州市', '高三1班', '2022-07-04 17:01:38', '2022-07-04 17:01:38');
INSERT INTO `student`(`id`, `name`, `age`, `birthday`, `address`, `grade`, `createTime`, `updateTime`) VALUES ('6', '高三', 70, '20120215', '上海市', '大学', '2022-07-05 15:11:46', '2022-07-05 15:11:46');

1.2、项目结构

在这里插入图片描述

1.3、实体类和配置类和pom

案例版本:boot2.7 + es 7.4.2 web工程

1.3.1、pom.xml

	<properties>
        <java.version>1.8</java.version>
        <!-- 这个是spring data 默认支持的。改成和我们自己配置的版本一致-->
        <elasticsearch.version>7.4.2</elasticsearch.version>
    </properties>


    <dependencies>
        <!--  elasticsearch-java restful client 操作http  -->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>

        <!--mysql依赖85多了ssl-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.1</version>
        </dependency>

        <!--fastjson 一般不使用,这玩意坑比较多-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68.sec10</version>
        </dependency>

        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>

        <!--健康检测-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

1.3.2、配置类

package sqy.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author suqinyi
 * @Date 2022/7/4
 */
@Configuration
public class ESConfig {

    /**
     * @Value(“#{}”) 表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法。当然还有可以表示常量
     * 用 @Value(“${}”)注解从配置文件读取值的用法
     */
    @Value("${spring.elasticsearch.uris}")
    private String url;

    public static final RequestOptions COMMON_OPTIONS;



    static {
        //这边是安全和其他配置可以参照官网
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }


    //注入client
    @Bean
    public RestHighLevelClient esRestClient() {
        System.out.println("初始化RestHighLevelClient");
        //写法一
        RestClientBuilder builder = null;
        //final String hostname, final int port, final String scheme
//       builder = RestClient.builder(new HttpHost("192.168.56.10", 9200, "http"));

        builder = RestClient.builder(HttpHost.create(url));
        RestHighLevelClient client = new RestHighLevelClient(builder);

        //写法二
//        RestHighLevelClient client = new RestHighLevelClient(
//                RestClient.builder(
//                        new HttpHost("192.168.56.10", 9200, "http")
//                )
//        );

        return client;
    }
}

1.3.3、实体类

package sqy.pojo;

import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author suqinyi
 * @Date 2022/7/4
 */
@Component
public class Student {
    private String id;
    private String name;
    private int age;
    private String birthday;//20201010
    private String address;
    private String grade;
    private Date createTime;
    private Date updateTime;

	// get/set/tostring 略....
}

1.4、第一部分:索引创建和删除

package sqy.controller;

import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sqy.config.ESConfig;

import java.io.IOException;
import java.util.Map;

/**
 * @author suqinyi
 * @Date 2022/7/4
 * @desc es检索服务 第一部分 索引和mapping
 * todo 1、添加索引  2、查询索引 3、删除索引
 */
@RequestMapping("/search")
@RestController
public class SearchIndexController {

    @Autowired
    RestHighLevelClient restHighLevelClient;

    //索引名称,相当于mysql的数据库
    private static String INDEX_NAME = "students";


    /**
     * 添加索引
     * localhost:8080/search/addIndex
     */
    @GetMapping("/addIndex")
    public String addIndex() throws IOException {
        //1.使用client获取操作索引对象
        IndicesClient indices = restHighLevelClient.indices();
        //2.1 设置索引名称
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(INDEX_NAME);
        //创建
        CreateIndexResponse createIndexResponse = indices.create(createIndexRequest, ESConfig.COMMON_OPTIONS);
        //3.根据返回值判断结果
        System.out.println(createIndexResponse.index());
        return "创建索引" + INDEX_NAME + "完成";
    }

    /**
     * 查询索引
     * localhost:8080/search/queryIndex
     * <p/>
     * kibana 语法:
     * GET /students/_search
     * { "query": { "match_all": {}} }
     */
    @GetMapping("/queryIndex")
    public String queryIndex() throws IOException {
        IndicesClient indices = restHighLevelClient.indices();
        GetIndexRequest getRequest = new GetIndexRequest(INDEX_NAME);
        GetIndexResponse response = indices.get(getRequest, ESConfig.COMMON_OPTIONS);
        System.out.println(response.getMappings());
        Map mappings = response.getMappings();
        //遍历打印
        for (Object key : mappings.keySet()) {
            System.out.println(key + "===>" + mappings.get(key));
        }
        return "查询索引结束";
    }


    /**
     * 删除索引
     * localhost:8080/search/delIndex
     */
    @GetMapping("/delIndex")
    public String delIndex() throws IOException {
        IndicesClient indices = restHighLevelClient.indices();
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(INDEX_NAME);
        AcknowledgedResponse response = indices.delete(deleteIndexRequest, ESConfig.COMMON_OPTIONS);
        System.out.println(response);
        return "删除索引:" + INDEX_NAME + "成功";
    }
}

效果:
在这里插入图片描述

1.5、第二部分:ES查询语法

1.5.1、所有引入的依赖

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sqy.config.ESConfig;
import sqy.pojo.Student;
import sqy.service.StudentService;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

1.5.2、ES数据初始化新增和修改【含索引名和流程】

/**
 * @author suqinyi
 * @Date 2022/7/4
 * @desc es检索服务 第二部分 基础(查询语法)增删改查.
 * (推荐集成springboot单元测试。这样比较好测)
 * <p>
 * TODO 测试内容
 * 1、数据初始化(新增和修改)和id查询与删除
 * 2、基础检索查询[=、in、between...and、like、通配符、]
 * 3、布尔查询 多条件查询【must(AND)、mustNot(NOT)、should(OR)】-【and和or嵌套】
 * 4、filter查询、filter查询和query的对比(filter是先过滤相当于子查询)
 * 5、匹配查询(match、multiMatchQuery)
 * <p/>
 */
@RequestMapping("/search")
@RestController
public class SearchQueryController {

    @Autowired
    StudentService studentService;

    @Autowired
    RestHighLevelClient restHighLevelClient;

    //索引名称,相当于mysql的数据库
    private static String INDEX_NAME = "students";


    //---------------------------------------------------------------
    //             数据初始化和id查询与删除
    //---------------------------------------------------------------

    /**
     * todo 数据初始化,加载进入es,
     * 这个是添加和更新二合一的方法。主键不存在创建,存在就更新
     *
     * <p>kibana 查询语法:<p/>
     * GET students/_search
     * { "query": { "match_all": {}} }
     * <p>
     * localhost:8080/search/dataInit
     */
    @GetMapping("/dataInit")
    public String dataInit() throws IOException {
        //查询全部数据
        List<Student> studentList = studentService.findAll();
        /**
         * 正常流程:1、查询索引 2、不存在索引就自动创建 3、存在索引就存入数据,
         * 注明:如果不存在索引和mapping,会根据你的【数据(字段)的类型】创建映射
         * restHighLevelClient.exists()
         * 指定索引(库)
         */
        IndexRequest indexRequest = new IndexRequest(INDEX_NAME);
        /**
         * 法一:
         * 并行流+同步调用,执行操作
         */
//        studentList.parallelStream().map(JSON::toJSONString).forEach(jsonString -> {
//            //数据的id-不设置就自动生成,类型string。但是一般都是设置成和主键一致
//            indexRequest.id(String.valueOf(JSON.parseObject(jsonString).get("id")));//设置文档主键
//            indexRequest.source(jsonString, XContentType.JSON);//json
//            //同步
//            try {
//                IndexResponse index = restHighLevelClient.index(indexRequest, ESConfig.COMMON_OPTIONS);
//                System.out.println("数据:"+index);
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//        });

        /**
         * 法二:
         * 普通for循环+异步回调
         */
        for (Student student : studentList) {
            indexRequest.id(student.getId());//设置文档主键
            indexRequest.source(JSON.toJSONString(student), XContentType.JSON);//json
            //异步
            restHighLevelClient.indexAsync(indexRequest, ESConfig.COMMON_OPTIONS, MyActionListener());
        }

        /**
         * 会出现数据覆盖缺失问题【错误写法】
         * 异步+异步模式  ==>不推荐使用stream + 异步
         * 如果使用并行流+异步会出问题 ===> 推测应该是forEach静态变量导致
         */
//        studentList.parallelStream().forEach(student -> {
//            System.out.println("打印阻塞一下:" + student.getId() + "  线程Id:" + Thread.currentThread().getId());
//            indexRequest.id(student.getId());//设置文档主键
//            indexRequest.source(JSON.toJSONString(student), XContentType.JSON);//json
//            //异步
//            restHighLevelClient.indexAsync(indexRequest, ESConfig.COMMON_OPTIONS, MyActionListener());
//        });

        return "数据初始化完成";
    }

 	/**
     * 抽取的监听
     */
    public ActionListener<IndexResponse> MyActionListener() throws IOException {
        ActionListener<IndexResponse> listener = new ActionListener<IndexResponse>() {
            @Override
            public void onResponse(IndexResponse indexResponse) {
                RestStatus status = indexResponse.status();
                if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {
                    System.out.println(status.name() + "创建成功");
                } else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {
                    System.out.println(status.name() + "更新成功");
                }
            }

            @Override
            public void onFailure(Exception e) {
                System.out.println("出异常了");
            }
        };

        return listener;
    }


}

效果:
在这里插入图片描述

1.5.3、主键查询和删除

 /**
     * 根据文档id获取一条数据
     * localhost:8080/search/getDataById/1
     */
    @GetMapping("/getDataById/{id}")
    public Map<String, Object> getDataById(@PathVariable String id) throws Exception {
        System.out.println("查询的文档id为:" + id);
        GetRequest getRequest = new GetRequest(INDEX_NAME, id);
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        System.out.println("getResponse=====>" + JSONObject.toJSON(getResponse));
        System.out.println("Source【实体】内容:" + getResponse.getSource());
        return getResponse.getSource();
    }

    /**
     * 根据文档id删除一条数据
     * 找不到数据删除的时候不会报错,result为 NOT_FOUND
     * 删除成功为DELETE
     * <p><p/>
     * localhost:8080/search/delDataById/1
     */
    @GetMapping("/delDataById/{id}")
    public String delDataById(@PathVariable String id) throws Exception {
        System.out.println("删除的文档id为:" + id);
        DeleteRequest deleteRequest = new DeleteRequest(INDEX_NAME, id);
        DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, ESConfig.COMMON_OPTIONS);
        System.out.println("deleteResponse=====>" + JSONObject.toJSON(deleteResponse));
        String msg = "操作:" + deleteResponse.getResult() + " 数据版本号:" + deleteResponse.getVersion();
        System.out.println(msg);
        return "执行成功。" + msg;
    }

1.5.4、基础检索查询

1.5.4.0、抽取打印方法
   /**
     * 内容打印
     * 1、检索语句
     * 2、命中内容
     */
    public void contentPrint(SearchSourceBuilder searchSourceBuilder, SearchResponse response) {
        System.out.println("查询条件[检索语法]==>" + searchSourceBuilder);
        System.out.println("查询结果==>" + JSONObject.toJSON(response));
        System.out.println("---------------------------------------");
        System.out.println("命中条数==>" + response.getHits().getTotalHits().value + "  关系==>" + response.getHits().getTotalHits().relation);
        System.out.println("最大分数==>" + response.getHits().getMaxScore());
        System.out.println("---------------------------------------");
        //变量结果
        for (SearchHit hit : response.getHits().getHits()) {
            System.out.println("分数==>" + hit.getScore());
            System.out.println("命中内容[存储数据]==>" + hit.getSourceAsString());
            // ... string 转换json 转实体 ...
        }
    }
1.5.4.1、等值查询termQuery
/**
     * 关键字检索[打分]
     * term等值查询,等同于 = 查询
     * localhost:8080/search/termQuery
     */
    @GetMapping("/termQuery")
    public String termQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //字段名.keyword 不分词
        // 相当于: select * from student where name='老六'
        searchSourceBuilder.query(QueryBuilders.termQuery("name.keyword", "老六"));
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);//打印
        return "term等值查询,等同于 = 查询";
    }

效果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.5.4.2、不打分查询constantScoreQuery
/**
     * 关键字检索[不打分查询] ==>  constantScoreQuery
     * 将不进行score计算,从而提高查询效率
     * localhost:8080/search/constantScoreQuery
     */
    @GetMapping("/constantScoreQuery")
    public String constantScoreQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 这样构造的查询条件,将不进行score计算[核定分数],从而提高查询效率
        searchSourceBuilder.query(QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("name.keyword", "老六")));
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);//打印
        return "term等值查询,不进行score[分数]计算,提高查询效率";
    }
1.5.4.3、多值查询termsQuery
 /**
     * terms多值查询,等同于 in 查询
     * localhost:8080/search/termsQuery
     */
    @GetMapping("/termsQuery")
    public String termsQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //字段名.keyword  相当于: select * from student where name in ('老六','小王')
        searchSourceBuilder.query(QueryBuilders.termsQuery("name.keyword", Arrays.asList("老六", "小王")));
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "terms多值查询,等同于 in 查询";
    }
1.5.4.4、范围查询rangeQuery
/**
     * 范围查询
     * 等同于between...and... 或 (...>= ... <=)
     * localhost:8080/search/rangeQuery
     */
    @GetMapping("/rangeQuery")
    public String rangeQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         *  构建查询语句
         *  类似于 select * from student where age between 10 and 30
         *  类似于 select * from student where age >= 10 and  age <=30
         */
        searchSourceBuilder.query(QueryBuilders.rangeQuery("age").gte(20).lte(30));
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "范围查询[...between...and...]";
    }
1.5.4.5、前缀匹配查询prefixQuery
/**
     * 前缀匹配查询,等同于 like xxx%
     * localhost:8080/search/prefixQuery
     */
    @GetMapping("/prefixQuery")
    public String prefixQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         *  构建查询语句
         *  类似于 select * from student where grade like '高三%'
         */
        searchSourceBuilder.query(QueryBuilders.prefixQuery("grade.keyword", "高三"));
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "前缀匹配查询";
    }

效果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.5.4.6、通配符查询wildcardQuery
 /**
     * todo 这个后面在看下
     * 通配符查询
     * “*”表示0到多个字符
     * 使用“?”表示一个字符
     * 模糊匹配是不走分词的, 直接根据正则到索引中的分词去匹配
     * <p><p/>
     * localhost:8080/search/wildcardQuery
     */
    @GetMapping("/wildcardQuery")
    public String wildcardQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         * todo 注意不建议将通配符作为前缀,否则导致查询很慢
         *  1、*省*  包含省
         *  2、省*   第一个是省开头的
         *  3、*省   最后一个字是省的
         *  4、?建*  第二个字是建的
         */
        String queryContent = "福*省";
        searchSourceBuilder.query(QueryBuilders.wildcardQuery("address.keyword", queryContent));
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "通配符查询";
    }

1.5.5、多条件查询 【and和or多条件嵌套查询】

1.5.5.1、案例1
 /**
     * 多条件查询【and和or嵌套-案例1】
     * must(QueryBuilders)    : AND
     * mustNot(QueryBuilders) : NOT
     * should:                : OR
     * <p><p/>
     * localhost:8080/search/boolQuery01
     */
    @GetMapping("/boolQuery01")
    public String boolQuery() throws Exception {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         *  构建查询语句
         *  等价于: SELECT  * FROM student WHERE grade='高三1班' AND `name`!='张三' AND (age=20 OR address='浙江省')
         */
        searchSourceBuilder.query(
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("grade.keyword", "高三1班"))//表示 AND
                        .mustNot(QueryBuilders.termQuery("name.keyword", "张三"))//表示 NOT
                        .must(QueryBuilders.boolQuery()
                                .should(QueryBuilders.termQuery("age", 20))
                                .should(QueryBuilders.termQuery("address.keyword", "浙江省"))
                        )
        );
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "多条件查询----案例1";
    }

效果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.5.5.2、案例2
 /**
     * and or 多条件嵌套--案例2
     * localhost:8080/search/boolQuery02
     */
    @GetMapping("/boolQuery02")
    public String boolQuery02() throws Exception {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         *  构建查询语句
         *  等价于: SELECT * FROM student WHERE   grade='高三1班'  AND (`name`='张三'  OR(`name`!='张三' AND age='20') )
         */
        searchSourceBuilder.query(
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("grade.keyword", "高三1班"))
                        .must(QueryBuilders.boolQuery()
                                .should(QueryBuilders.termQuery("name.keyword", "张三"))
                                .should(QueryBuilders.boolQuery()
                                        .mustNot(QueryBuilders.termQuery("name.keyword", "张三"))
                                        .must(QueryBuilders.termQuery("age", 20)))
                        )
        );
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "and or 多条件嵌套--案例2";
    }

效果:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.5.5.3、案例3
 /**
     * and or 多条件嵌套--案例3
     * localhost:8080/search/boolQuery03
     */
    @GetMapping("/boolQuery03")
    public String boolQuery03() throws Exception {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         *  构建查询语句
         *  等价于: SELECT * FROM student
         *    WHERE age BETWEEN 18 AND 50
         *    AND (`name`='张三'  OR( address='浙江省' OR (address='福建省厦门市' AND `name`='张三三')  ) )
         *    AND `name` IS NOT NULL
         */
        searchSourceBuilder.query(
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.rangeQuery("age").gte(18).lte(50))
                        .must(QueryBuilders.boolQuery()
                                .should(QueryBuilders.termQuery("name.keyword", "张三"))
                                .should(QueryBuilders.boolQuery()
                                        .should(QueryBuilders.termQuery("address.keyword", "浙江省"))
                                        .should(QueryBuilders.boolQuery()
                                                .must(QueryBuilders.termQuery("address.keyword", "福建省厦门市"))
                                                .must(QueryBuilders.termQuery("name.keyword", "张三三"))
                                        )
                                )
                        )
                        .must(QueryBuilders.existsQuery("name"))//存在字段数据
        );
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "and or 多条件嵌套--案例3";
    }

效果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.5.6、 filter 查询

1.5.6.1、 直接查询
//---------------------------------------------------------------
    //                  filter 查询
    //        一般认为filter的速度快于query的速度
    //        query和filter的区别:
    //        query查询的时候,会先比较查询条件,然后计算分值,最后返回文档结果;
    //        filter是先判断是否满足查询条件,如果不满足会缓存查询结果(记录该文档不满足结果),满足的话,就直接缓存结果
    //        filter不会对结果进行评分,能够提高查询效率
    //        filter不能使用 .minimumShouldMatch()//指定最小匹配度0-100% 不然会找不到数据
    //---------------------------------------------------------------

    /**
     * filter直接包含查询语句
     * filter和query类似。
     * 但是filter不计算得分,效率高
     * <p><p/>
     * localhost:8080/search/filterTest
     */
    @GetMapping("/filterTest01")
    public String filterTest01() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         * 构建查询语句
         * select * from student where name='张三'
         */
        searchSourceBuilder.query(QueryBuilders.boolQuery()
                .filter(QueryBuilders.termQuery("name.keyword", "张三"))
        );
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "filter直接包含查询语句";
    }
1.5.6.2、 filter与must同级使用
 /**
     * filter与must同级使用--相当于子查询
     * .must表示and
     * localhost:8080/search/filterTest02
     */
    @GetMapping("/filterTest02")
    public String filterTest02() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         * 构建查询语句
         * 类似于:(filter先过滤所以sql就相当于子查询)
         *  SELECT a.* from ( select * from student WHERE `name`='张三' ) a
         *   WHERE  a.birthday>='20000101' and  a.birthday<='20061220'
         */
        searchSourceBuilder.query(QueryBuilders.boolQuery()
                .must(QueryBuilders.rangeQuery("birthday.keyword").gte("20000101").lte("20061220"))
                .filter(QueryBuilders.termQuery("name.keyword", "张三"))
        );
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "filter与must同级使用";
    }
1.5.6.3、 filter包含must等
/**
     * filter包含must、mustNot....等...
     * todo 最常用
     * localhost:8080/search/filterTest03
     */
    @GetMapping("/filterTest03")
    public String filterTest03() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         * 构建查询语句【TODO filter和must等同级相当于如下语句:子查询】
         * SELECT a.* FROM ( select * from student where name in ( '李四' ,'小王','老六')  ) a
         *  WHERE a.age>=20 AND a.age <=35 AND  a.address not like '福建省%'
         */
        searchSourceBuilder.query(QueryBuilders.boolQuery()
                .filter(QueryBuilders.boolQuery()
                        .must(QueryBuilders.termsQuery("name.keyword", Arrays.asList("老六", "小王", "李四")))
                )
                .must(QueryBuilders.rangeQuery("age").gte(20).lte(35))
                .mustNot(QueryBuilders.prefixQuery("address.keyword", "福建省"))
        );
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "filter包含must等";
    }

1.5.7、 匹配查询match

1.5.7.1、 单条件匹配
/**
     * match:单条件匹配
     * localhost:8080/search/matchQuery
     */
    @GetMapping("/matchQuery")
    public String matchQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         *  构建查询语句
         *  类似于:select * from student name ='老六'
         */
        searchSourceBuilder.query(QueryBuilders.matchQuery("name", "老六"));
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "match单条件匹配查询";
    }
1.5.7.2、 多字段对同一个text匹配
/**
     * multiMatch:多field[字段]对同一个text匹配
     * localhost:8080/search/multiMatchQuery
     */
    @GetMapping("/multiMatchQuery")
    public String multiMatchQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /**
         *  构建查询语句
         *  类似于:select * from student name ='高三' or address='高三' 这个有50%匹配度,所以有部分模糊检索
         *  address里面的高三能匹配50%也能命中数据
         *  operator传入AND或OR,决定了多个匹配条件之间的关系是与还是或
         */
        searchSourceBuilder.query(QueryBuilders.multiMatchQuery("高三", "name", "grade")
                .operator(Operator.valueOf("OR"))//指定为 or的关系
                .minimumShouldMatch("50%"));//最小匹配度
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.contentPrint(searchSourceBuilder, response);
        return "multiMatch=>多field[字段]对同一个text匹配";
    }

1.6、第三部分:ES聚合查询语法

1.6.1、所有的引入

package sqy.controller;

import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.PipelineAggregatorBuilders;
import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ExtendedStats;
import org.elasticsearch.search.aggregations.metrics.ExtendedStatsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedAvg;
import org.elasticsearch.search.aggregations.metrics.ValueCountAggregationBuilder;
import org.elasticsearch.search.aggregations.pipeline.SumBucketPipelineAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sqy.config.ESConfig;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

1.6.2、打印方法(response值获取)

//---------------------------------------------------------------
    //                方法抽取 打印
    //---------------------------------------------------------------

    /**
     * 内容打印
     * 1、检索语句
     * 2、命中内容
     */
    public void aggContentPrint(SearchSourceBuilder searchSourceBuilder, SearchResponse response, String aggName, String aggType) {
        System.out.println("查询条件[检索语法]==>" + searchSourceBuilder);
        if (!StringUtils.isEmpty(aggType)) {
            //ExtendedStats //快捷键 ctrl + alt + u  看继承关系
            ExtendedStats aggregation = response.getAggregations().get(aggName);
            System.out.println("最大值:" + aggregation.getMax());
            System.out.println("最小值:" + aggregation.getMin());
            System.out.println("平均值:" + aggregation.getAvg());
            System.out.println("总和:" + aggregation.getSum());
            System.out.println("总数:" + aggregation.getCount());
        } else {
            Aggregation aggregation = response.getAggregations().get(aggName);
            String value = String.valueOf(JSON.parseObject(JSON.toJSONString(aggregation)).get("value"));
            System.out.println("aggregation结果==>" + JSON.toJSONString(aggregation));
            System.out.println("聚合结果===>聚合名称:" + aggregation.getName() + "  操作类型:" + aggregation.getType() + "  值:" + value);
            System.out.println("------------------------");
            //变量结果
            for (SearchHit hit : response.getHits().getHits()) {
                System.out.println("分数==>" + hit.getScore());
                System.out.println("符合查询条件-命中内容==>" + hit.getSourceAsString());
                // ... string 转换json 转实体 ...
            }
        }
    }

    /**
     * 内容打印 -分组打印
     * 1、检索语句
     * 2、命中内容
     */
    public void groupContentPrint(SearchSourceBuilder searchSourceBuilder, SearchResponse response, String aggName01, String aggName02, String aggName03) {
        /**
         *  https://www.jianshu.com/p/6b6ae4b2d7ef
         *  StringTerms terms =(StringTerms) aggMap.get(aggName);//注明6.x的版本是可以转换的
         *  //报错
         *  java.lang.ClassCastException: org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms
         *  cannot be cast to org.elasticsearch.search.aggregations.bucket.terms.StringTerms
         *  //底层强转源码变了
         *  正确:
         *  ParsedStringTerms parsedStringTerms = (ParsedStringTerms) aggMap.get(aggName);
         *
         *  坑:Terms.Bucket
         *   如果这边 key为string的话序列化会有个小问题
         *   序列化:
         *      1、用fastjson1.2.68.sec10 会报错--类型转换异常不能转成number
         *      2、用gson序列化,不会报错,会将汉字(或string串) 序列化成二进制进行输出
         *
         *   public interface Bucket extends org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket {
         *         Number getKeyAsNumber();
         *         long getDocCountError();
         *   }
         */
        System.out.println("查询条件[检索语法]==>" + searchSourceBuilder);
        Map<String, Aggregation> aggMap = response.getAggregations().asMap();
        ParsedStringTerms teamAgg = (ParsedStringTerms) aggMap.get(aggName01);
        Iterator<? extends Terms.Bucket> iterator = teamAgg.getBuckets().iterator();
        while (iterator.hasNext()) {
            Terms.Bucket bucket = iterator.next();
            Object key = bucket.getKey();//分组字段名
            long count = bucket.getDocCount();//数量
            //打印
            System.out.println("第一重分组---字段值:" + key + "  数量【count】:" + count);
            //得到所有子聚合.....在对子聚合的数据进行操作
            Map subaggmap = bucket.getAggregations().asMap();
            System.out.println("所有子聚合:" + new Gson().toJson(subaggmap));
            if (!StringUtils.isEmpty(aggName02)) {
                //第二个聚合[子聚合].在进行for 、get、...
                ParsedStringTerms subAgg01 = (ParsedStringTerms) bucket.getAggregations().asMap().get(aggName02);
                System.out.println("第一个子聚合:" + new Gson().toJson(subAgg01));
            }
            if (!StringUtils.isEmpty(aggName02)) {
                //ParsedAvg 平均、ParsedMax最大、ParsedMin最小、ParsedSum 总和
                ParsedAvg subAgg02 = (ParsedAvg) bucket.getAggregations().asMap().get(aggName03);
                System.out.println("第二个子聚合:" + new Gson().toJson(subAgg02) + "==>平均值:" + subAgg02.getValue());
            }
            System.out.println("------------------------");
        }
    }

1.6.3、聚合函数max、min、sum、count、avg

/**
 * @author suqinyi
 * @Date 2022/7/5
 * @desc es检索服务 第三部分 聚合分析(统计查询)
 * (推荐集成springboot单元测试。这样比较好测)
 */
@RequestMapping("/search")
@RestController
public class SearchAggregationController {

    @Autowired
    RestHighLevelClient restHighLevelClient;

    //索引名称,相当于mysql的数据库
    private static String INDEX_NAME = "students";


    /**
     * Extended Stats Aggregation
     * 一并求max、min、sum、count、avg
     * 追加方差、标准差等
     * localhost:8080/search/extendedStatsAgg
     */
    @GetMapping("/extendedStatsAgg")
    public String extendedStatsAgg() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);//创建检索请求,指定索引库
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//创建检索源
        String aggName = "extend_agg";
        ExtendedStatsAggregationBuilder aggBuilder = AggregationBuilders.extendedStats(aggName).field("age");
        searchSourceBuilder.size(10);//设置返回的条数
        searchSourceBuilder.aggregation(aggBuilder);//放入聚合的内容
        searchRequest.source(searchSourceBuilder); //放入检索源
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS); //执行
        this.aggContentPrint(searchSourceBuilder, response, aggName, "Extended");
        restHighLevelClient.close();//关闭es。
        return "拓展聚合分析";
    }


    /**
     * 聚合分析:max 最大值
     * localhost:8080/search/maxQuery
     */
    @GetMapping("/maxQuery")
    public String maxQuery() throws IOException {
        //创建检索请求,指定索引库
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        //创建检索源
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //自己指定个聚合名称
        String aggName = "max_age_agg";
        //设置聚合的方式,字段和内容. TODO 这个字段必须是整数类型,不然会报错
        AggregationBuilder maxBuilder = AggregationBuilders.max(aggName).field("age");
        //设置返回的条数
        searchSourceBuilder.size(10);
        //放入聚合的内容
        searchSourceBuilder.aggregation(maxBuilder);
        //放入检索源
        searchRequest.source(searchSourceBuilder);
        //执行
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        //打印
        this.aggContentPrint(searchSourceBuilder, response, aggName, null);
        //关闭es。没有关闭的话,在一次进行搜索的时候,使用的还是同一个ESClient,这样就会导致重复的聚合名称出现
        restHighLevelClient.close();
        return "聚合分析:max 最大值";
    }


    /**
     * 聚合分析:min 最小值
     * localhost:8080/search/minQuery
     */
    @GetMapping("/minQuery")
    public String minQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME); //创建检索请求,指定索引库
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //创建检索源
        String aggName = "min_age_agg";
        //获取最小年龄 min
        AggregationBuilder maxBuilder = AggregationBuilders.min(aggName).field("age");
        searchSourceBuilder.aggregation(maxBuilder);//放入聚合的内容
        searchRequest.source(searchSourceBuilder); //放入检索源
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS); //执行
        this.aggContentPrint(searchSourceBuilder, response, aggName, null);
        restHighLevelClient.close();//关闭es。
        return "聚合分析:min 最小值";
    }


    /**
     * 聚合分析:sum 总和
     * localhost:8080/search/sumQuery
     */
    @GetMapping("/sumQuery")
    public String sumQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME); //创建检索请求,指定索引库
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //创建检索源
        String aggName = "sum_age_agg";
        //年龄总和
        AggregationBuilder maxBuilder = AggregationBuilders.sum(aggName).field("age");
        searchSourceBuilder.aggregation(maxBuilder);//放入聚合的内容
        searchRequest.source(searchSourceBuilder); //放入检索源
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS); //执行
        this.aggContentPrint(searchSourceBuilder, response, aggName, null);
        restHighLevelClient.close();//关闭es。
        return "聚合分析:sum 总和";
    }

    /**
     * 聚合分析: avg 平均值
     * localhost:8080/search/sumQuery
     */
    @GetMapping("/avgQuery")
    public String avgQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME); //创建检索请求,指定索引库
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //创建检索源
        String aggName = "avg_age_agg";
        //年龄总和
        AggregationBuilder maxBuilder = AggregationBuilders.avg(aggName).field("age");
        searchSourceBuilder.aggregation(maxBuilder);//放入聚合的内容
        searchRequest.source(searchSourceBuilder); //放入检索源
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS); //执行
        this.aggContentPrint(searchSourceBuilder, response, aggName, null);
        restHighLevelClient.close();//关闭es。
        return "聚合分析:sum 总和";
    }
    
}

1.6.4、 查询+聚合

   /**
     * 先查询,再统计
     * localhost:8080/search/queryAndAggQuery
     */
    @GetMapping("/queryAndAggQuery")
    public String queryAndAggQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME); //创建检索请求,指定索引库
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //创建检索源
        String aggName = "query_max_age_agg";
        /**
         *  max 统计,获取最大值
         *  sql: SELECT max( a.age ) FROM ( SELECT * FROM student WHERE grade = '高三1班' ) a
         */
        //查询
        searchSourceBuilder.query(
                QueryBuilders.termQuery("grade.keyword", "高三1班")
        );
        AggregationBuilder maxBuilder = AggregationBuilders.max(aggName).field("age");
        searchSourceBuilder.aggregation(maxBuilder);//聚合条件
        searchRequest.source(searchSourceBuilder);//放入检索源
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);//检索
        this.aggContentPrint(searchSourceBuilder, response, aggName, null);
        //这边不能关闭es,不然会报错【单纯聚合才需要关闭】
        //restHighLevelClient.close();
        return "先查询,再统计";
    }

1.6.5、 去重查询cardinality

 /**
     * 去重查询
     * localhost:8080/search/cardinalityQuery
     */
    @GetMapping("/cardinalityQuery")
    public String cardinalityQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        String aggName = "address_count";
        /**
         * cardinality 去重
         * 类似sql : select count(distinct address) from student;
         */
        AggregationBuilder aggBuilder = AggregationBuilders.cardinality(aggName).field("address.keyword");
        searchSourceBuilder.aggregation(aggBuilder);
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.aggContentPrint(searchSourceBuilder, response, aggName, null);
        return "去重查询";
    }

1.6.6、 分组查询

1.6.6.1、 单字段分组
/**
     * 分组查询
     * localhost:8080/search/groupQuery
     */
    @GetMapping("/groupQuery")
    public String groupQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //searchSourceBuilder.size(0);//指定查询条数
        // 按address[地址]分组
        String aggName = "address_count";
        /**
         * 分组
         * 类似sql : select address,count(1) from student group by address;
         */
        TermsAggregationBuilder aggBuilder = AggregationBuilders.terms(aggName).field("address.keyword");
        searchSourceBuilder.aggregation(aggBuilder);
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        this.groupContentPrint(searchSourceBuilder, response, aggName, "", "");
        return "分组查询";
    }
1.6.6.2、 多字段分组查询
/**
     * 多字段分组查询
     * localhost:8080/search/multiGroupQuery
     */
    @GetMapping("/multiGroupQuery")
    public void multiGroupQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);
        /**
         * sql:
         *  SELECT 	a.grade,group_concat(a.address),AVG(age)  FROM 	student a WHERE	a.age BETWEEN 10 AND 100 GROUP BY a.grade
         */
        //先查询在聚合
        searchSourceBuilder.query(
                QueryBuilders.boolQuery().filter(QueryBuilders.rangeQuery("age").gte(10).lte(100))
        );
        //自定义聚合名称
        String aggName01 = "grade_count_first";
        String aggName02 = "address_count_second";
        String aggName03 = "age_avg_third";
        // 1、先 grade【班级】分组
        AggregationBuilder gradeAggBuilder = AggregationBuilders.terms(aggName01).field("grade.keyword");
        // 2、再按address【地址】分组
        AggregationBuilder addressAggBuilder = AggregationBuilders.terms(aggName02).field("address.keyword");
        // 3、在求平均年龄
        AggregationBuilder ageAggBuilder = AggregationBuilders.avg(aggName03).field("age");
        //todo 按顺序放入子聚合条件
        gradeAggBuilder.subAggregation(addressAggBuilder);
        gradeAggBuilder.subAggregation(ageAggBuilder);
        //放入检索条件
        searchSourceBuilder.aggregation(gradeAggBuilder);
        //放入检索源
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, ESConfig.COMMON_OPTIONS);
        //response打印
        this.groupContentPrint(searchSourceBuilder, response, aggName01, aggName02, aggName03);
    }



//    public static void main(String[] args) {
//        //桥套分组子查询
//        SumBucketPipelineAggregationBuilder houseSum = PipelineAggregatorBuilders.sumBucket("houseSum", "buildId.count");
//        ValueCountAggregationBuilder countByFloor = AggregationBuilders.count("count").field("buildId.keyword");
//        TermsAggregationBuilder gourpByFloorId = AggregationBuilders.terms("buildId").field("buildId.keyword")
//                .subAggregation(countByFloor);
//        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("cata").field("useAndClassName.keyword")
//                .subAggregation(houseSum)
//                .subAggregation(gourpByFloorId);
//    }

效果:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.6.7 范围分组range查询

  /**
     * 范围分组range查询
     * localhost:8080/search/multiGroupQuery01
     */
    @GetMapping("/multiGroupQuery01")
    public void multiGroupQuery01() throws IOException {
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);
        /**
         * 10-40岁 有多少人,50-100岁有多少人
         */
        String aggName = "age_count_agg";
        AggregationBuilder ageAggBuilder = AggregationBuilders.range(aggName).field("age")
                .addRange(10, 40)
                .addRange(50, 100);
        searchSourceBuilder.aggregation(ageAggBuilder);
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println("查询条件:" + searchSourceBuilder);
        System.out.println("结果:" + JSON.toJSONString(response));
        ParsedRange parsedRange = (ParsedRange) response.getAggregations().asMap().get(aggName);
        System.out.println("结果:" + JSON.toJSONString(parsedRange));
    }

五、小结

更加详细的操作看官网文档的示例啦

其实如果安装的是elasticsearch6.x版本
使用spring-data-elasticsearch:transport-api
.jar也非常便捷
对于elasticsearch版本的升级要慎重

码字不易,转载请注明出处

SpringBoot整合Elasticsearch常用API主要包括以下几个方面: 1. 配置Elasticsearch信息 首先需要在application.yml中配置Elasticsearch的连接信息: ``` spring: data: elasticsearch: cluster-name: elasticsearch cluster-nodes: 127.0.0.1:9300 ``` 2. 创建ElasticsearchRepository 在SpringBoot中,可以通过ElasticsearchRepository来访问Elasticsearch,只需要继承该接口即可。 ``` public interface UserRepository extends ElasticsearchRepository<User, Long> { } ``` 其中,User是实体类,Long是主键类型。 3. 创建实体类 创建实体类,使用注解来映射Elasticsearch中的索引和字段。 ``` @Document(indexName = "user", type = "_doc") public class User { @Id private Long id; @Field(type = FieldType.Keyword) private String name; @Field(type = FieldType.Integer) private Integer age; // getter and setter } ``` 4. 增删改查 通过ElasticsearchRepository提供的方法,可以实现增删改查的操作。如下: ``` @Autowired UserRepository userRepository; // 新增 userRepository.save(user); // 删除 userRepository.deleteById(id); // 修改 userRepository.save(user); // 查询 Optional<User> optional = userRepository.findById(id); ``` 5. 搜索 Elasticsearch提供了丰富的搜索API,可以通过QueryBuilder来构建查询条件,通过SearchRequest来执行搜索操作。如下: ``` @Autowired RestHighLevelClient restHighLevelClient; // 构建查询条件 QueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "张三"); // 构建SearchRequest SearchRequest searchRequest = new SearchRequest("user"); searchRequest.types("_doc"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(queryBuilder); searchRequest.source(searchSourceBuilder); // 执行搜索 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); // 处理搜索结果 SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); User user = JSON.parseObject(sourceAsString, User.class); System.out.println(user); } ``` 以上就是SpringBoot整合Elasticsearch常用API的介绍。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

suqinyi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值