Elasticsearch 是一款基于 Lucene 库构建的开源、分布式、RESTful 风格的搜索引擎和分析引擎,具有强大的全文搜索、数据分析、机器学习等功能,广泛应用于日志分析、实时数据分析、全文检索等场景。
核心概念
- 索引(Index):类似于传统数据库中的数据库,是存储数据的逻辑容器。一个索引可以包含多个类型的数据,每个索引都有一个唯一的名称用于标识。
- 类型(Type):在早期版本中,一个索引可以有多个类型,用来区分不同类型的数据,但在 7.x 及以上版本中,一个索引只能有一个类型,且默认类型为"_doc"。
- 文档(Document):是 Elasticsearch 中存储和检索的基本数据单元,以 JSON 格式表示。每个文档都有一个唯一的 ID,用于在索引中定位和操作文档。
- 字段(Field):是文档中的一个属性或键值对,用于存储具体的数据内容,如姓名、年龄、价格等。字段有不同的数据类型,如文本、数字、日期等,不同的数据类型决定了字段的存储和检索方式。
主要特点
- 高性能全文搜索:基于 Lucene 的倒排索引技术,能够快速地对文本数据进行全文搜索。它会将文本数据拆分成单词或词汇,建立索引,当进行搜索时,通过查找索引快速定位到包含搜索关键词的文档,大大提高了搜索效率。
- 分布式架构:可以部署在多个节点上,形成一个分布式集群。数据会被自动分片存储在不同的节点上,当一个节点出现问题时,其他节点可以继续提供服务,保证了系统的高可用性和数据的可靠性。同时,分布式架构也使得 Elasticsearch 能够处理海量的数据和高并发的请求。
- 易于扩展:随着数据量和访问量的增加,可以通过简单地增加节点来水平扩展集群。新加入的节点会自动参与数据的存储和查询工作,无需对现有系统进行复杂的改造,能够灵活地应对业务的增长。
- RESTful API:提供了基于 HTTP 协议的 RESTful 风格的 API 接口,方便开发者使用各种编程语言进行集成和开发。通过简单的 HTTP 请求,就可以完成数据的增删改查、索引管理、查询分析等各种操作,降低了开发难度和成本。
- 多租户支持:在 Elasticsearch 集群中,可以创建多个索引,每个索引可以视为一个独立的"租户",不同租户之间的数据相互隔离。这使得 Elasticsearch 能够在一个集群中同时为多个应用程序或用户提供服务,提高了资源利用率。
- 强大的聚合分析功能:除了基本的搜索功能,还提供了丰富的聚合分析功能,如统计、分组、排序、时间序列分析等。可以对搜索结果进行进一步的数据挖掘和分析,帮助用户快速获取数据的统计信息和趋势,为决策提供支持。
常见应用场景
- 日志分析:能够高效地收集、存储和分析各种日志数据,如服务器日志、应用程序日志、网络设备日志等。通过对日志数据的搜索、过滤、聚合分析,可以快速定位系统故障、性能瓶颈、安全问题等,为运维人员提供有力的工具。
- 实时数据分析:适用于需要实时处理和分析大量数据的场景,如金融交易监控、社交媒体数据监测、物联网设备数据处理等。Elasticsearch 可以实时地接收数据,快速地进行查询和分析,及时发现异常情况并做出响应。
- 全文检索:为各种需要全文检索功能的应用程序提供支持,如电子商务网站的商品搜索、知识管理系统的内容检索、新闻网站的新闻搜索等。能够根据用户的输入关键词,快速地在海量文本数据中找到匹配的结果,并按照相关性进行排序,提升用户体验。
- 机器学习:结合 Elasticsearch 的机器学习功能,可以对数据进行自动分析和建模,实现异常检测、预测分析等高级功能。例如,在网络安全领域,通过机器学习算法对网络流量数据进行分析,自动识别潜在的攻击行为;在电商领域,预测用户的购买行为和需求。
架构组件
- 节点(Node):是 Elasticsearch 集群中的一个服务器实例,承担着数据存储、查询处理、集群管理等任务。节点之间通过网络进行通信和协作,共同构成一个完整的 Elasticsearch 集群。
- 集群(Cluster):由一个或多个节点组成,通过配置集群名称来标识。集群负责管理节点、分配分片、处理请求等,保证数据的一致性和高可用性。集群中的主节点(Master Node)负责集群级别的操作,如创建索引、分配分片、节点发现等,数据节点(Data Node)主要负责存储数据和执行查询操作。
- 分片(Shard):为了提高数据的存储能力和查询性能,索引会被分割成多个分片,每个分片是一个独立的 Lucene 索引。分片可以分布在不同的节点上,当进行查询时,查询请求会被分发到各个分片上并行执行,然后将结果汇总返回。分片的数量在索引创建时确定,可以根据数据量和查询需求进行合理配置。
- 副本(Replica):是分片的副本,用于提高数据的可靠性和查询性能。当一个分片出现故障时,副本可以接管其查询请求,保证服务的可用性。同时,副本也可以分担查询负载,提高查询的并发处理能力。副本的数量可以根据集群的节点数量和可靠性要求进行配置。
数据写入与查询流程
- 数据写入流程 :
- 客户端发送写入请求:用户或应用程序通过 Elasticsearch 的 RESTful API 向集群发送数据写入请求,请求中包含要写入的索引名称、文档数据等信息。
- 主节点路由请求:主节点接收到请求后,根据索引的分片策略,将写入请求路由到对应的主分片所在的节点。
- 主分片写入数据:主分片节点接收到请求后,将数据写入本地的分片中,并记录操作日志。写入成功后,会将操作日志发送给对应的副本分片节点。
- 副本分片同步数据:副本分片节点接收到操作日志后,会根据日志内容将数据同步到本地的副本分片中。当所有副本分片都成功同步数据后,主分片节点会向客户端返回写入成功的响应。
- 数据查询流程 :
- 客户端发送查询请求:用户或应用程序通过 RESTful API 向集群发送查询请求,请求中包含查询条件、索引名称等信息。
- 主节点分发查询请求:主节点接收到查询请求后,根据索引的分片分布情况,将查询请求分发到所有相关的分片所在的节点。
- 分片节点执行查询:各个分片节点接收到查询请求后,在本地的分片中执行查询操作,根据查询条件检索匹配的文档,并将查询结果返回给主节点。
- 主节点汇总结果:主节点接收到各个分片节点返回的查询结果后,对结果进行汇总和排序等处理,然后将最终的查询结果返回给客户端。
Elasticsearch基础概念
**Index:**索引(index),就是文档的集合,类似数据库的表(table)
**Document:**文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
**Field:**字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
**Mapping:**Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
**DSL:**DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD
倒排索引
正向索引
- 文档 ID:用于唯一标识每个文档,类似数据库表中的主键,有了它就能精准定位到特定的某一文档。
- 索引项:跟在文档 ID 后面,包含了文档中的各类关键元素,比如文本里的词汇、数据记录的属性值等,有的还会附上这些元素在文档中的具体位置信息。
倒排索引
-
**文档(Document):**用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息。
-
**词条(Term):**对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条。
对比
正向索引的表结构
id(索引) | title | price |
---|---|---|
1 | 小米手机 | 3499 |
2 | 华为手机 | 4999 |
3 | 华为小米充电器 | 49 |
4 | 小米手环 | 49 |
搜索流程:
-
1)如检查到搜索条件为
like '%手机%'
,需要找到title
中包含手机
的数据 -
2)逐条遍历每行数据(每个叶子节点),比如第1次拿到
id
为1的数据 -
3)判断数据中的
title
字段值是否符合条件 -
4)如果符合则放入结果集,不符合则丢弃
-
5)回到步骤1
倒排索引的表结构
词条(索引) | 文档id |
---|---|
小米 | 1,3,4 |
手机 | 1,2 |
华为 | 2,3 |
充电器 | 3 |
手环 | 4 |
搜索流程:
1)如用户输入条件"华为手机"
进行搜索。
2)对用户输入条件分词,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找(由于词条有索引,查询效率很高),即可得到包含词条的文档id:1、2、3
。
4)拿着文档id
到正向索引中查找具体文档即可(由于id
也有索引,查询效率也很高)。
正向索引:
-
优点:
-
可以给多个字段创建索引
-
根据索引字段搜索、排序速度非常快
-
-
缺点:
- 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
倒排索引:
-
优点:
- 根据词条搜索、模糊搜索时,速度非常快
-
缺点:
-
只能给词条创建索引,而不是字段
-
无法根据字段做排序
-
安装Elasticsearch
1.使用docker安装单机版的Elasticsearch
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
--restart=always \
elasticsearch:7.12.1
代码解读:
-d:让容器在后台运行
--name:指定创建的容器名称
-e:设置容器内的环境变量
-v:将容器内的目录挂载到外部的卷上
--privileged:以特权模式运行容器
--network:将容器连接到名为 es-net 的 Docker 网络,在我们搭建服务器的时候可以创建一个docker网络,将要互相通信的放在这一个网络里面。**注意:**这里需要先用docker network create创建一个docker网络这里才能指定
-p:将容器的端口映射到主机的端口
--restart=always:设置容器自启动
elasticsearch:7.12.1:要运行的 Docker 镜像名称和标签,如果我们指定这个镜像本地没有这个版本就会进行下载。**注意:**这里我们采用的是elasticsearch的7.12.1版本,由于8以上版本的JavaAPI变化很大,在企业中应用并不广泛,企业中应用较多的还是8以下的版本。
2.防火墙开放端口
#1.开放es端口
firewall-cmd --zone=public --add-port=9200/tcp --add-port=9300/tcp --permanent
#2.重新加载防火墙配置
firewall-cmd --reload
也可以关闭防火墙
systemctl stop firewalld
3.访问9200端口验证es是否启动成功
地址格式:主机IP:9200
IK分词器
Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词,而分词则需要高效、精准的分词算法,IK分词器就是这样一个中文分词算法。
安装
**方法一:**在线安装
docker exec -it es ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
重启es容器
docker restart es
**方法二:**离线安装
首先,查看之前安装的es容器的plugins数据卷目录:
docker volume inspect es-plugins
结果如下:
进入/var/lib/docker/volumes/es-plugins/_data目录,将我们本地的ik上传到这个目录下
重启es容器
docker restart es
使用
IK分词器包含两种模式:
**ik_smart
:**智能语义切分
示例:
POST /_analyze
{
"analyzer": "ik_smart",
"text": "我是一个程序员"
}
结果:
{
"tokens" : [
{
"token" : "我",
"start_offset" : 0,
"end_offset" : 1,
"type" : "CN_CHAR",
"position" : 0
},
{
"token" : "是",
"start_offset" : 1,
"end_offset" : 2,
"type" : "CN_CHAR",
"position" : 1
},
{
"token" : "一个",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "程序员",
"start_offset" : 4,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 3
}
]
}
**ik_max_word
:**最细粒度切分
示例:
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "我是一个程序员"
}
结果:
{
"tokens" : [
{
"token" : "我",
"start_offset" : 0,
"end_offset" : 1,
"type" : "CN_CHAR",
"position" : 0
},
{
"token" : "是",
"start_offset" : 1,
"end_offset" : 2,
"type" : "CN_CHAR",
"position" : 1
},
{
"token" : "一个",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "一",
"start_offset" : 2,
"end_offset" : 3,
"type" : "TYPE_CNUM",
"position" : 3
},
{
"token" : "个",
"start_offset" : 3,
"end_offset" : 4,
"type" : "COUNT",
"position" : 4
},
{
"token" : "程序员",
"start_offset" : 4,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "程序",
"start_offset" : 4,
"end_offset" : 6,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "员",
"start_offset" : 6,
"end_offset" : 7,
"type" : "CN_CHAR",
"position" : 7
}
]
}
拓展词典
随着互联网的发展,"造词运动"也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在,如:"逆天、依托答辩、我了个豆"
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "逆天、依托答辩、我了个豆"
}
结果:
{
"tokens" : [
{
"token" : "逆",
"start_offset" : 0,
"end_offset" : 1,
"type" : "CN_CHAR",
"position" : 0
},
{
"token" : "天",
"start_offset" : 1,
"end_offset" : 2,
"type" : "CN_CHAR",
"position" : 1
},
{
"token" : "依托",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "答辩",
"start_offset" : 5,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "我",
"start_offset" : 8,
"end_offset" : 9,
"type" : "CN_CHAR",
"position" : 4
},
{
"token" : "了",
"start_offset" : 9,
"end_offset" : 10,
"type" : "CN_CHAR",
"position" : 5
},
{
"token" : "个",
"start_offset" : 10,
"end_offset" : 11,
"type" : "CN_CHAR",
"position" : 6
},
{
"token" : "豆",
"start_offset" : 11,
"end_offset" : 12,
"type" : "CN_CHAR",
"position" : 7
}
]
}
可以看到,逆天和我了个豆都无法正确分词。
所以要想正确分词,IK分词器的词库也需要不断的更新,IK分词器提供了扩展词汇的功能。
1)打开IK分词器config目录:
注意:如果采用在线安装的通过,默认是没有config目录的,可以去网上找一些词库,让后创建一个config文件目录放在config目录下
2)在IKAnalyzer.cfg.xml配置文件内容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext.dic</entry>
</properties>
3)新建ext.dic文件,用记事本或者其他编辑器打开ext.dic文件,然后添加自己想要的词语即可
4)重启elasticsearch
docker restart es
# 查看 日志
docker logs -f elasticsearch
5.重启之后再次执行之前的命令,可以正确分词了
{
"tokens" : [
{
"token" : "逆天",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "依托答辩",
"start_offset" : 3,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "依托",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "答辩",
"start_offset" : 5,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "我了个豆",
"start_offset" : 8,
"end_offset" : 12,
"type" : "CN_WORD",
"position" : 4
}
]
}
还可以在IKAnalyzer.cfg.xml加上如下配置:实现更多功能
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
RESTAPI使用
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。
官方文档地址:
Elasticsearch Clients | Elastic
由于ES目前最新版本是8.8,提供了全新版本的客户端,老版本的客户端已经被标记为过时。而我们采用的是7.12版本,因此只能使用老版本客户端:
然后选择7.12版本,HighLevelRestClient版本:
1.引入es
的RestHighLevelClient
依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.12.1</version>
</dependency>
2.初始化RestHighLevelClient:
public class IndexTest {
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.181.32:9200")//es服务ip地址:端口号
));
}
@Test
void testConnect() {
System.out.println(client);//执行不好错就行了
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
3.索引库相关操作:
public class ElasticIndexTest {
private static final String MAPPING_TEMPLATE = """
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "ik_smart"
},
"price": {
"type": "integer"
},
"image": {
"type": "keyword",
"index": false
},
"category": {
"type": "keyword"
},
"brand": {
"type": "keyword"
},
"sold": {
"type": "integer"
},
"commentCount": {
"type": "integer",
"index": false
},
"isAD": {
"type": "boolean"
},
"updateTime": {
"type": "date"
}
}
}
}
""";//JDK15新特性:三引号
private RestHighLevelClient client;
//创建
@Test
void testCreateIndex() throws IOException {
//准备request对象
CreateIndexRequest request = new CreateIndexRequest("items");
//准备请求参数
request.source(MAPPING_TEMPLATE, XContentType.JSON);
//发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
//判断是否存在
@Test
void testExistsndex() throws IOException {
//准备request对象
GetIndexRequest request = new GetIndexRequest("items");
//发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println("exists = " + exists);
}
//删除
@Test
void testDeleteIndex() throws IOException {
//准备request对象
DeleteIndexRequest request = new DeleteIndexRequest("items");
//发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.181.32:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
4.文档相关操作:
package com.hmall.item.es;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmall.item.domain.po.Item;
import com.hmall.item.domain.po.ItemDoc;
import com.hmall.item.service.IItemService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.List;
@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {
private RestHighLevelClient client;
@Autowired
private IItemService itemService;
@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.181.32:9200")
));
}
//创建文档
@Test
void createDoc() throws IOException {
//准备文档数据
//1.查询数据
Item item = itemService.getById(577967L);
//2.把数据库数据转成文档数据
ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
//准备request对象
IndexRequest request = new IndexRequest("items").id(itemDoc.getId());
//构建请求参数
request.source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON);
//发送请求
client.index(request, RequestOptions.DEFAULT);
}
//获取文档数据
@Test
void testGetDoc() throws IOException {
//准备request对象
GetRequest request = new GetRequest("items", "577967");
//发送请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//获取响应结果中的source
String json = response.getSourceAsString();
//转成java对象
ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);
System.out.println("itemDoc = " + itemDoc);
}
//删除文档
@Test
void testDelDoc() throws IOException {
//准备request对象
DeleteRequest request = new DeleteRequest("items", "577967");
//发送请求
client.delete(request, RequestOptions.DEFAULT);
}
//修改文档数据
@Test
void testUpdateDoc() throws IOException {
//准备request对象
UpdateRequest request = new UpdateRequest("items", "577967");
request.doc("price", 29900);
//发送请求
client.update(request, RequestOptions.DEFAULT);
}
//批量操作文档:新增
@Test
void testBulkDoc() throws IOException {
int pageNo = 1, pageSize = 500;
while (true) {
Page<Item> page = itemService.lambdaQuery().eq(Item::getStatus, 1).page(Page.of(pageNo, pageSize));
List<Item> records = page.getRecords();
if (records == null || records.isEmpty()) {
return;
}
//准备BulkRequest对象
BulkRequest request = new BulkRequest();
for (Item item : records) {
//添加批量提交的请求
request
.add(new IndexRequest("items")
.id(item.getId().toString())
.source(JSONUtil.toJsonStr(BeanUtil.copyProperties(item, ItemDoc.class)), XContentType.JSON));
}
//提交请求
client.bulk(request, RequestOptions.DEFAULT);
pageNo++;
}
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
5.查询数据相关操作:
public class ElasticSearchTest {
private RestHighLevelClient client;
private static void parseResponseResult(SearchResponse response) {
//得到命中的数据
SearchHits hits = response.getHits();
long total = hits.getTotalHits().value;
System.out.println("total = " + total);
for (SearchHit hit : hits) {
//解析数据
String json = hit.getSourceAsString();
//封装itemDoc
ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
//处理高亮字段
Map<String, HighlightField> hfs = hit.getHighlightFields();
if (hfs != null && !hfs.isEmpty()) {
//更具高亮字段名获取高亮结果
HighlightField hf = hfs.get("name");
//用获取到的高亮值覆盖原来非高亮的字段值
String hfName = hf.getFragments()[0].toString();
doc.setName(hfName);
}
System.out.println("doc = " + doc);
}
}
//查询全部
@Test
void testMatchAll() throws IOException {
//创建
SearchRequest request = new SearchRequest("items");
//组织DSL查询参数
request.source().query(QueryBuilders.matchAllQuery());
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析结果
parseResponseResult(response);
}
//根据条件查询:复合查询
@Test
void testSearch() throws IOException {
//创建
SearchRequest request = new SearchRequest("items");
//组织DSL查询参数
request.source().query(
QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "脱脂牛奶"))
.filter(QueryBuilders.termQuery("brand", "德亚"))
.filter(QueryBuilders.rangeQuery("price").lt(30000))
);
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析结果
parseResponseResult(response);
}
//分页排序
@Test
void testPageAndSort() throws IOException {
int pageNo = 1, pageSize = 5;
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
// 2.1.搜索条件参数
request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
// 2.2.排序参数
request.source().sort("price", SortOrder.ASC);
// 2.3.分页参数
request.source().from((pageNo - 1) * pageSize).size(pageSize);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
parseResponseResult(response);
}
//根据条件获取高亮数据
@Test
void testHighlight() throws IOException {
//创建
SearchRequest request = new SearchRequest("items");
//组织DSL查询参数
//query条件
request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
//高亮条件
request.source().highlighter(SearchSourceBuilder.highlight().field("name"));
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析结果
parseResponseResult(response);
}
//数据聚合
@Test
void testAgg() throws IOException {
//创建
SearchRequest request = new SearchRequest("items");
//组织DSL查询参数
//query条件
request.source().size(0);
//聚合条件
String brandAggName = "brandAgg";
request.source().aggregation(
AggregationBuilders.terms(brandAggName).field("brand").size(10)
);
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析结果
Aggregations aggregations = response.getAggregations();
//根据聚合名称获取聚合
Terms aggTerms = aggregations.get(brandAggName);
//获取buckets
List<? extends Terms.Bucket> buckets = aggTerms.getBuckets();
//遍历每一个bucket
for (Terms.Bucket bucket : buckets) {
System.out.println("brand:"+bucket.getKeyAsString());
System.out.println("count:"+bucket.getDocCount());
}
}
@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.181.32:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}