文章目录
一、说明:
1.1、小编的案例说明
- 如果没有安装可以参考小编这篇文章 ==> 文章入口
- 小编的elasticsearch的版本是7.4.2
- 案例采用的方式(官方提供的):Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)
- 使用kibana可视化工具查看数据
1.2、对elasticsearch的操作方式种类
- 操作 ==> 9300端口:TCP
spring-data-elasticsearch:transport-api.jar
说明:springboot 版本不同, transport-api.jar 不同,不能适配 es 版本
7.x 已经不建议使用,8 以后就要废弃
- 操作 ==> 9200:HTTP
- JestClient:非官方,更新慢
- RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦
- HttpClient:同上(简单说就是和elasticsearch关联性不高,都要自己写方法)
Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作,API 层次分明,上手简单(推荐)
- 使用Elasticsearch-Rest-Client ==>官方文档参考地址
- 都在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;
}
控制台结果:
官方的聚合获取操作
官网文档的参考地址==>文档入口
三、如何采用批量插入?
在项目中通常的操作
- 先在elasticsearch创建映射关系(说白点就是创建索引并且构造数据类型)
- 在编写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依赖8比5多了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版本的升级要慎重
码字不易,转载请注明出处