1.what is ElasticStatic
The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。
Elaticsearch,简称为 ES, ES 是一个开源的高扩展的分布式全文搜索引擎, 是整个 ElasticStack 技术栈的核心。
它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据。
一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。
搜索的数据对象是大量的非结构化的文本数据。
文件记录量达到数十万或数百万个甚至更多。
支持大量基于交互式文本的查询。
需求非常灵活的全文搜索查询。
对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全文搜索引擎 。
2.ES倒排索引
2.1倒排索引
也称之为反向索引,建立词和文档id之间的对应关系。即把“文档→单词”的形式变为“单词→文档”的形式。
java编程思想 -----> 分词 -----> java、编程、思想、编程思想、java编程思想
先对数据进行分词,得到一个个的词条,然后将词条与文档的对应关系保存起来,最后在对词条本身做索引排序。
如下数据:
id | name |
---|---|
1 | 小米手机 |
2 | 华为手机 |
3 | 小米电视 |
4 | 三星电视 |
ES主要实现数据的搜索,并且ES还不支持事务,因此要保证数据一致性,那么此时还需要使用到数据库。
2.2.ES环境搭建
ElasticSearch本质就是一个搜索引擎,用来实现海量数据的搜索。
Kibana是一个可视化的工具,配合ES进行使用,可以ES中所存储的数据进行可视化的展示(柱状图、饼状图、散点图、折线图...)。并且在Kibana中还提供了开发者工具
# 启动容器(如果不存在容器就创建、存在则修改)
docker compose -f docker-compose.yml up -d# 删除所有容器
docker compose -f docker-compose.yml down# 停止所有容器
docker compose -f docker-compose.yml stop# 启动所有容器
docker compose -f docker-compose.yml start# 重启所有容器
docker compose -f docker-compose.yml restart
2.3ik分词器
在IK分词器中提供了两种分词器算法:
1、ik_smart:粗粒度分词,分出的词比较少
2、ik_max_word:细粒度分词,分出的词比较多
2.4自定义词典
1、在plugins/ik/config目录下创建一个ext.dic文件
touch ext.dic
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>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
3.重启ES
docker restart es
3 ES核心概念
为了方便理解ES(8.5.0)中的相关概念,我们可以对比着MySQL进行学习,如下所示:
MySQL | Elasticsearch | 说明 |
---|---|---|
Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) |
Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式,比如下面这条用户数据;
{
"name" : "John",
"sex" : "Make",
"age" : 25,
"birthDate": "2024/05/01",
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
mapping是处理数据的方式和规则方面做一些限制,如:某个字段的数据类型、默认值、分析器、是否被索引等等。这些都是映射里面可以设置的,其它就是处理ES里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。
4 ES基本操作(DSL)
4.2.1 创建索引库
PUT /my_index
结果:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "my_index"
}
4.2.2 查看所有索引库
语法: GET /_cat/indices?v
4.2.3 查看单个索引库
语法: GET /{索引名称}
GET /my_index
结果:
{
"my_index" : {
"aliases" : { },
"mappings" : { },
"settings" : {
"index" : {
"creation_date" : "1633499968211",
"number_of_shards" : "1",
"number_of_replicas" : "1",
"uuid" : "bclHUdHrS4W80qxnj3NP0A",
"version" : {
"created" : "7080099"
},
"provided_name" : "my_index"
}
}
}
}
4.2.4 删除索引库
DELETE /my_index
结果:
{
"acknowledged" : true
}
4.3 文档操作
PUT /my_index/_doc/1
{
"title": "小米手机",
"category": "小米",
"images": "/xm.jpg",
"price": 3999
}
返回结果:
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
4.3.2 查看文档
语法: GET /{索引名称}/{类型}/{id}
GET /my_index/_doc/1
结果:
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "小米手机",
"category" : "小米",
"images" : "/xm.jpg",
"price" : 3999
}
}
4.3.3 修改文档
PUT /my_index/_doc/1
{
"title": "华为手机",
"category": "华为",
"images": "/hw.jpg"
}
0注意:上述的修改是先根据id把文档删除掉,然后重新添加文档
4.3.4 指定字段修改
POST /my_index/_update/1
{
"doc": {
"price": 4500
}
}
4.3.5 删除文档
DELETE /my_index/_doc/1
结果:
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "1",
"_version" : 12,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 11,
"_primary_term" : 1
}
4.4 映射mapping
语法: GET /{索引名称}/_mapping
Mapping的常见属性:
type:字段数据类型,常见的简单类型有:
字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
数值:long、integer、short、byte、double、float、
布尔:boolean
日期:date
对象:object
index:是否创建倒排索引,默认为true
analyzer:指定在创建索引的时候所使用的分词器
search_analyzer:指定在搜索的时候所使用的分词器(如果没有指定该分词器,那么搜索的时候使用analyzer所指定的分词器)
4.4.3 创建索引指定映射
#删除原创建的索引
DELETE /my_index
#创建索引,并同时指定映射关系和分词器等。
PUT /my_index
{
"mappings": {
"properties": {
"id":{
"type": "long",
"index": true
},
"title": {
"type": "text",
"index": true,
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"category": {
"type": "keyword",
"index": true
},
"images": {
"type": "keyword",
"index": false
},
"price": {
"type": "integer",
"index": true
}
}
}
}
结果:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "my_index"
}
创建映射的两种方式:
1、使用ES自动映射推断机制
2、在创建索引库的时候指定映射
上述两种方式如何进行选择?
1、优先选择"自动映射推断机制", 如果这种方式创建出来的映射中的某一个字段相关属性和我们所期望的有点不太一样,删除索引库
2、此时选择第二种方式创建映射(把有问题的字段属性设置一下即可)
5 ES高级查询(DSL)
5.1 查询所有(match_all)
GET /my_index/_search
{
"query": { # query 关键字就是设置搜索方式以及一些其他的搜索参数
"match_all": {}
}
}
5.2 匹配查询(match)
GET /my_index/_search
{
"query": {
"match": {
"title": "华为智能手机" # 会对搜索的关键字进行分词,然后使用各个词条从对应的倒排索引表中进行搜索
}
}
}
5.3 多字段匹配(multi_match)
GET /my_index/_search
{
"query": {
"multi_match": {
"query": "华为智能手机",
"fields": ["title","category"]
}
}
}
5.4 关键字精确查询(term)
GET /my_index/_search
{
"query": {
"term": {
"title": {
"value": "华为手机"
}
}
}
}
5.5 范围查询(range)
GET /my_index/_search
{
"query": {
"range": {
"price": {
"gte": 3000,
"lte": 5000
}
}
}
}
范围查询使用range。
-
gte: 大于等于
-
lte: 小于等于
-
gt: 大于
-
lt: 小于
5.6 组合查询(bool)
bool各条件之间有and,or或not的关系
-
must: 各个条件都必须满足,所有条件是and的关系
-
should: 各个条件有一个满足即可,即各条件是or的关系
-
must_not: 不满足所有条件,即各条件是not的关系
-
filter: 与must效果等同,但是它不计算得分,效率更高点。
ES会根据用户所输入的关键字和文档的匹配程度去计算文档的得分,后期会使用文档的得分对文档数据进行排序,返回的时候就会返回排序的结果
5.6.1 must
GET /my_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "华为"
}
},
{
"range": {
"price": {
"gte": 3000,
"lte": 5000
}
}
}
]
}
}
}
5.6.2 should
GET /my_index/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "华为"
}
},
{
"range": {
"price": {
"gte": 3000,
"lte": 5000
}
}
}
]
}
}
}
5.6.3 must_not
GET /my_index/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"title": "华为"
}
},
{
"range": {
"price": {
"gte": 3000,
"lte": 5000
}
}
}
]
}
}
}
5.7 排序(sort)
GET /my_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "华为"
}
}
]
}
},
"sort": [
{
"price": {
"order": "asc"
}
}
]
}
5.8 分页查询(from、size)
分页的两个关键属性:from、size
-
from: 当前页的起始索引,默认从0开始。 from = (pageNo - 1) * pageize
-
size: 每页显示多少条
POST /my_index/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 2
}
5.9 高亮查询(highlight)
高亮显示的实现分为两步:
1)给文档中的所有关键字都添加一个标签,例如<em>
标签
2)页面给<em>
标签编写CSS样式
POST /my_index/_search
{
"query": {
"match": {
"title": "华为"
}
},
"highlight": {
"fields": { // 指定要高亮的字段
"FIELD": {
"pre_tags": "<em>", // 用来标记高亮字段的前置标签
"post_tags": "</em>" // 用来标记高亮字段的后置标签
}
}
}
}
6 Spring Data ES
Spring Data是一个用于简化数据库、非关系型数据库、索引库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce
框架和云计算数据服务。 Spring Data可以极大的简化JPA(Elasticsearch…)的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。
Spring Data Elasticsearch 基于Spring data API 简化 Elasticsearch操作(基本的增删改查),将原始操作Elasticsearch的客户端API 进行封装 。
Spring Data为Elasticsearch项目提供集成搜索引擎。Spring Data Elasticsearch也是基于ORM思想进行设计。
思考问题:怎么使用呢?需要建立实体类与索引库之间的对应关系,以及实体类中的属性和索引库字段之间的对应关系【使用相关的注解完成】。
6.4 环境搭建
6.4.1 创建项目
创建一个spring boot的项目(spring-data-es),在pom.xml文件中加入如下的依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
6.4.2 yml文件
# es连接地址配置
spring:
elasticsearch:
uris: 192.168.136.147:9200
6.4.3 启动类
package com.es;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringDataEsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataEsApplication.class , args) ;
}
}
6.4.4 实体类
// 商品实体类
@Data
@Document(indexName = "product") // 建立实体类和es索引库中的对应关系
public class Product {
@Id
private Long id ; // 主键
@Field(name = "productName" , type = FieldType.Text , analyzer = "ik_max_word" )
private String productName ; // 商品的名称,普通的属性
@Field(type = FieldType.Integer)
private Integer store ; // 产品库存
@Field(type = FieldType.Double)
private Double price ; // 产品价格
@Field(type = FieldType.Long)
private Long brandId ; // 品牌id
@Field(type = FieldType.Keyword)
private String brandName ; // 品牌名称
@Field(type = FieldType.Nested) // 针对List集合类型的属性需要将其类型设置为Nested, TODO: 为什么需要设置为Nested
private List<Attr> attrList ; // 产品属性
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Attr { // 产品属性实体类
@Id
private Long attrId ; // 属性id
@Field(type = FieldType.Keyword)
private String attrName ; // 属性名称
@Field(type = FieldType.Keyword)
private String attrValue ; // 属性值
}
注解含义:
@Document 作用在类,标记实体类为文档对象。indexName属性:对应索引库名称
@Id 作用在成员变量,标记一个字段作为id主键
@Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
1、type:字段类型,取值是枚举:FieldType
2、index:是否索引,布尔类型,默认是true
3、store:是否存储,布尔类型,默认是false
4、analyzer:分词器名称:ik_max_word
6.5 使用方式一
借助:ElasticsearchRepository接口
定义持久层操作接口:
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product , Long> {
}
6.5.1 添加文档
@Test
public void saveProduct() {
List<Attr> attrList = Arrays.asList(new Attr(1L, "内存", "8G"), new Attr(2L, "硬盘", "256G"));
Product prod = Product.builder()
.id(4L).productName("vivo手机")
.price(2999.0).store(50)
.brandId(3L)
.brandName("vivo")
.attrList(attrList).build();
productRepository.save(prod) ;
}
6.5.2 根据id查询
@SpringBootTest(classes = SpringDataEsApplication.class)
public class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository ;
@Test
public void findById() {
Optional<Product> product = productRepository.findById(1L);
Product prod = product.get();
System.out.println(prod);
}
}
6.5.4 分页查询
@Test
public void queryPage() {
Pageable pageable = PageRequest.of(0, 1);
Page<Product> products = productRepository.findAll(pageable);
products.forEach( s -> System.out.println(s) );
}
6.6 使用方式二
借助:ElasticsearchClient
ElasticsearchClient是Elasticsearch中所提供的一个操作ES的新的核心类,并且已经被Spring Boot实现了自动化配置。
6.6.1 基本操作
@SpringBootTest(classes = SpringDataEsApplication.class)
//classes = SpringDataEsApplication.class 表示加载应用的主配置类
public class ElasticsearchClientTest {
@Autowired
private ElasticsearchClient elasticsearchClient ;
@Test
public void saveProduct() throws IOException {
List<Attr> attrList = Arrays.asList(new Attr(1L, "内存", "6G"), new Attr(2L, "硬盘", "128G"));
Product prod = Product.builder()
.id(2L).productName("小米手机")
.price(3999.0).store(200)
.brandId(2L)
.brandName("小米")
.attrList(attrList).build();
//IndexRequest 是 Elasticsearch 的请求对象,表示向 Elasticsearch 发送一个索引请求,将一个文档存入指定的索引库。
IndexRequest indexRequest = new IndexRequest.Builder<Product>()
.index("product") // 指定索引库的名称
.id("2") // 指定文档id
.document(prod)
.build() ;
IndexResponse indexResponse = elasticsearchClient.index(indexRequest) ;
System.out.println(indexResponse);
}
}
根据id查询
@Test
public void getById() throws IOException {
GetRequest getRequest = new GetRequest.Builder()
.index("product")
.id("2")
.build() ;
GetResponse<Product> response = elasticsearchClient.get(getRequest, Product.class);
Product product = response.source();
System.out.println(product);
}
@Test
public void updateById() throws IOException {
Product prod = Product.builder().id(1L).store(150).build();
// Builder类上的两个泛型:TDocument表示完整的文档类型,而TPartialDocument表示部分更新的文档类型。
UpdateRequest updateRequest = new UpdateRequest.Builder<Product , Product>()
.index("product")
.id("1")
.doc(prod)
.build() ;
UpdateResponse updateResponse = elasticsearchClient.update(updateRequest, Product.class);
System.out.println(updateResponse);
}
@Test
public void deleteById() throws IOException {
DeleteRequest deleteRequest = new DeleteRequest.Builder().index("product").id("3").build() ;
DeleteResponse deleteResponse = elasticsearchClient.delete(deleteRequest);
System.out.println(deleteResponse);
}
6.6.2 高级操作
查询所有
@Test
public void search() throws IOException {
// 创建搜索条件对象
Query query = new Query.Builder().matchAll(QueryBuilders.matchAll().build()).build();
// 创建搜索请求对象
SearchRequest searchRequest = new SearchRequest.Builder()
.index("product") // 设置索引库的名称
.query(query)
.build() ;
// 发送搜索请求
SearchResponse<Product> searchResponse = elasticsearchClient.search(searchRequest, Product.class);
// 解析结果
HitsMetadata<Product> hitsMetadata = searchResponse.hits();
long total = hitsMetadata.total().value() ;
System.out.println("满足条件的总记录数:" + total);
List<Hit<Product>> hits = hitsMetadata.hits();
for(Hit<Product> hit : hits) {
Product product = hit.source();
System.out.println(product);
}
}
// 范围查询
Query query = new Query.Builder().range(QueryBuilders.range().field("store").gt(JsonData.of(180)).build()).build() ;
// 匹配查询
Query query = new Query.Builder().match(QueryBuilders.match().field("productName").query("小米").build()).build();
// 多字段匹配查询
Query query = new Query.Builder().multiMatch(QueryBuilders.multiMatch().fields("productName" , "brandName").query("手机").build()).build();
// 词条查询
Query query = new Query.Builder().term(QueryBuilders.term().field("brandName").value("小米").build()).build() ;
@Test
public void updataRequest() throws IOException {
Product prod = Product.builder().id(1L).price(2000D).build();
// Builder类上的两个泛型:TDocument表示完整的文档类型,而TPartialDocument表示部分更新的文档类型。
UpdateRequest updateRequest = new UpdateRequest.Builder<Product , Product>()
.index("product")
.id("1")
.doc(prod)
.build() ;
UpdateResponse updateResponse = elasticsearchClient.update(updateRequest, Product.class);
System.out.println(updateResponse);
// //属性数据
// List<Attr> attrList = List.of(new Attr(1L,"内存","32GB"),
// new Attr(2L,"内存","64GB"));
// //商品数据
// Product product = new
// Product(3L,"华为手机",999,6599.99,5L,"华为",attrList);
//
// UpdateRequest<Object, Object> build = new UpdateRequest.Builder<>()
// .index("product")
// .id("2")
// .doc(product)
// .build();
// elasticsearchClient.update(build,Product.class);
}
Product prod = Product.builder().id(1L).price(2000D).build();//构造器模式
@Test
public void jingqueSearch() throws IOException {
// TermQuery termQuery = new TermQuery.Builder()
// .field("brandName")
// .value("苹果")
// .build();
// Query query = new Query.Builder()
// .term(termQuery->termQuery.field("brandName").value("苹果"))
// .build();
SearchRequest build = new SearchRequest.Builder()
.query(query1->query1.term(termQuery->termQuery.field("brandName").value("苹果")))
.build();
SearchResponse searchResponse= elasticsearchClient.search(build,Product.class);
System.out.println(searchResponse);
}
}
@Test
public void jingqueSearch() throws IOException {
// TermQuery termQuery = new TermQuery.Builder()
// .field("brandName")
// .value("苹果")
// .build();
// Query query = new Query.Builder()
// .term(termQuery->termQuery.field("brandName").value("苹果"))
// .build();
SearchRequest build = new SearchRequest.Builder()
.query(query1->query1.term(termQuery->termQuery.field("brandName").value("苹果")))
.build();
SearchResponse searchResponse= elasticsearchClient.search(build,Product.class);
System.out.println(searchResponse);
}
@Test
public void highLight() throws IOException {
MatchQuery matchQuery = new MatchQuery.Builder()
.query("华为")
.field("brandName")
.build();
HighlightField highlightField = new HighlightField.Builder()
.preTags("<font color='red'>")
.postTags("</font>")
.build();
Highlight highlight = new Highlight.Builder()
.fields("brandName",highlightField)
.build();
Query query =new Query.Builder()
.match(matchQuery)
.build();
SearchRequest searchRequest=new SearchRequest.Builder()
.highlight(highlight)
.query(query)
.build();
SearchResponse<Product> searchResponse=elasticsearchClient.search(searchRequest,Product.class);
HitsMetadata<Product> hitsMetadata = searchResponse.hits();
long total = hitsMetadata.total().value() ;
System.out.println("满足条件的总记录数:" + total);
List<Hit<Product>> hits = hitsMetadata.hits();
for (Hit<Product> hit : hits){
// 获取原始文档数据
Product product = hit.source();
// 解析高亮结果
Map<String, List<String>> highlightFielMap = hit.highlight();
if(highlightFielMap != null && highlightFielMap.size() > 0) {
List<String> productNameHighlightList = highlightFielMap.get("productName");
if(productNameHighlightList != null && productNameHighlightList.size() > 0) {
String productNameHighlightValue = productNameHighlightList.get(0);
product.setProductName(productNameHighlightValue);
}
}
// 输出结果
System.out.println(product);
}
}
}
@Test
public void highLight() throws IOException {
Highlight highlight = new Highlight.Builder()
.fields("brandName",highlightField->highlightField
.preTags("<font color='red'>").postTags("</font>"))
.build();
Query query =new Query.Builder()
.match(matchQuery->matchQuery.field("brandName").query("华为"))
.build();
SearchRequest searchRequest=new SearchRequest.Builder()
.highlight(highlight)
.query(query)
.build();
SearchResponse<Product> searchResponse=elasticsearchClient.search(searchRequest,Product.class);
HitsMetadata<Product> hitsMetadata = searchResponse.hits();
long total = hitsMetadata.total().value() ;
System.out.println("满足条件的总记录数:" + total);
List<Hit<Product>> hits = hitsMetadata.hits();
for (Hit<Product> hit : hits){
// 获取原始文档数据
Product product = hit.source();
// 解析高亮结果
Map<String, List<String>> highlightFielMap = hit.highlight();
if(highlightFielMap != null && highlightFielMap.size() > 0) {
List<String> productNameHighlightList = highlightFielMap.get("productName");
if(productNameHighlightList != null && productNameHighlightList.size() > 0) {
String productNameHighlightValue = productNameHighlightList.get(0);
product.setProductName(productNameHighlightValue);
}
}
// 输出结果
System.out.println(product);
}
}
}
7.Nested类型
8.聚合查询
ES聚合查询是一种统计、分组和过滤数据的方式,通过对文档中的字段进行聚合操作,实现对数据的统计分析。类似于MySQL分组操作(group by)以及聚合函数(max、min、avg 、sum、 count)。
ES提供的聚合分析功能有指标聚合(metrics aggregations)、桶聚合(bucket aggregations)、管道聚合(pipeline aggregations)三大类。
8.1 桶型聚合
桶型聚合是将文档按照某个字段进行分组,把相同值的文档放入同一个桶中,然后对桶中的文档进行统计、计算等操作(类似于MySQL中的Group by)。ES中的桶型
8.1.1 桶型聚合简介
8.1.2 terms聚合演示
@Test
public void bucketAggreation() throws IOException {
// MatchAll 查询
MatchAllQuery matchAllQuery = new MatchAllQuery.Builder()
.build();
Query querybuid = new Query.Builder()
.matchAll(matchAllQuery)
.build();
// Terms 聚合,按 price 字段聚合,获取前 2 个桶
TermsAggregation termsAggregation = new TermsAggregation.Builder()
.field("price")
.size(2)
.build();
// 将聚合添加到请求中
Aggregation aggregation = new Aggregation.Builder()
.terms(termsAggregation)
.build();
// 创建搜索请求
SearchRequest searchRequest = new SearchRequest.Builder()
.index("product")
.query(querybuid)
.aggregations("price", aggregation) // 设置聚合名称为 "price"
.build();
// 执行搜索请求
SearchResponse searchResponse = elasticsearchClient.search(searchRequest,Product.class);
// 获取聚合结果
Map<String, Aggregate> aggregations = searchResponse.aggregations();
// 根据聚合名称 "price" 获取聚合结果
Aggregate aggregate = aggregations.get("price");
System.out.println("aggregate = " + aggregate);
// 转换为 DoubleTermsAggregate(假设 price 是 double 类型)
DoubleTermsAggregate o = (DoubleTermsAggregate) aggregate._get();
// 获取聚合结果中的桶
Buckets<DoubleTermsBucket> buckets = o.buckets();
// 打印桶的信息
List<DoubleTermsBucket> array = buckets.array();
for (DoubleTermsBucket bucket : array) {
System.out.println("bucket.key() = " + bucket.key());
System.out.println("bucket.docCount() = " + bucket.docCount());
}
}
@Test
public void statsSearch() throws IOException {
StatsAggregation statsAggregation = new StatsAggregation.Builder()
.field("price")
.build();
Aggregation aggregation = new Aggregation.Builder()
.stats(statsAggregation)
.build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index("product")
.aggregations("stats",aggregation)
.build();
// 执行搜索请求
SearchResponse searchResponse = elasticsearchClient.search(searchRequest,Product.class);
Map<String, Aggregate> aggregateMap = searchResponse.aggregations();
Aggregate aggregate = aggregateMap.get("stats");
StatsAggregate statsAggregate = (StatsAggregate) aggregate._get();
double sum = statsAggregate.sum();
System.out.println("sum = " + sum);
double min = statsAggregate.min();
System.out.println("min = " + min);
double max = statsAggregate.max();
System.out.println("max = " + max);
double avg = statsAggregate.avg();
System.out.println("avg = " + avg);
long count = statsAggregate.count();
System.out.println("count = " + count);
System.out.println();
}
9 .自动补全功能
9.1 自动补全说明
自动补全功能就是当用户输入搜索关键字以后,自动从ES索引库中搜索出来和当前关键字相匹配的数据,如下所示:
Completion Suggester实现自动补全功能,需要自动补全的字段类型一定是completion,参与补全查询的字段必须是completion类型。字段的内容可以是用
来补全的多个词条形成的数组。
GET /test/_search
{
"suggest": {
"suggestValue": {
"prefix":"fo",
"completion":{
"skip_duplicates":true,
"field":"suggest",
"fuzzy":{
"fuzziness":"auto"
}
}
}
}
}
suggest
: 表示这是一个建议(suggestion)查询,用于提供基于用户输入的自动补全建议。
suggestValue
: 是自定义的建议查询名称,你可以根据需要随意命名。
prefix
: 查询前缀,用于匹配建议的起始字符串。在这个例子中,任何以"fos"开头的建议都会被返回。
completion
: 表示这是一个完成建议查询,它是Elasticsearch中用于实现自动补全功能的一种查询类型。
skip_duplicates
: 当设置为true
时,会跳过结果中的重复项,确保每个建议都是唯一的。
field
: 指定在哪个字段上执行完成建议查询。在这个例子中,字段名为"suggest"。
fuzzy
: 启用模糊匹配,允许返回与用户输入相近但不完全匹配的建议。
fuzziness
: 指定模糊匹配的程度。"auto"
表示Elasticsearch会根据术语的长度自动选择一个合适的模糊值。例如,对于短的术语,它可能会允许更多的编辑距离。总的来说,这段代码的目的是在Elasticsearch的
test
索引中,查找字段suggest
包含以"fos"为前缀的项,并且返回唯一的、可能模糊匹配的建议列表。这通常用于实现搜索框的自动补全功能,帮助用户在输入查询时快速找到他们可能想要搜索的完整术语。
@Test
public void suggestReserach() throws IOException {
CompletionSuggester completionSuggester = new CompletionSuggester.Builder()
.field("suggest")
.skipDuplicates(true)
.fuzzy(fuzzy1->fuzzy1.fuzziness("auto"))
.build();
FieldSuggester fieldSuggester = new FieldSuggester.Builder()
.prefix("fo")
.completion(completionSuggester)
.build();
Suggester suggester = new Suggester.Builder()
.suggesters("Suggester",fieldSuggester)
.build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index("test")
.suggest(suggester)
.build();
// 执行搜索请求
SearchResponse searchResponse = elasticsearchClient.search(searchRequest,Object.class);
Map suggest = searchResponse.suggest();
}
}