目录
(二)Elastic Stack生态:Beats + Logstash + ElasticSearch + Kibana
干货分享,感谢您的阅读!
一、ES初识
Elasticsearch(后面简写ES)是一个分布式、高扩展、近实时的搜索与数据分析引擎,它能很方便的使大量数据具有搜索、分析和探索的能力,充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更有价值。
(一)检索的基石
底层的检索基石:Lucene(基于Java),Lucene使用倒排索引作为核心检索原理,提供通用的查询语法、摘要、高亮、相关性(BM25等算法)查询等基础库,可以把Lucene当成一个黑盒的系统,这个系统的输入是用户的一些列查询,返回的就是用户需要的记录。
(二)Elastic Stack生态:Beats + Logstash + ElasticSearch + Kibana
当前ES的生态组成了Elastic Stack技术服务栈,囊括了大数据处理领域的方方面面,包括数据收集、写入、检索、监控、处理、分析、安全等。
生态中的四大金刚Elasticsearch、Kibana、Beats 和 Logstash 负责不同领域。
各组件之间的关系可简单概括如下:
(三)搜索术语回顾
以一个检索全国所有的商户的作为背景,此时我们需要把书店做成一个索引(等价于数据库中的一个书店表),每一个连锁书店都有共同的字段(列),例如书店名名,城市名等
搜索术语 | 数据库概念 | 描述 | 用法 |
索引(index) | 表 | 用于描述多个行记录的集合 | 例如把所有的书店放在一个索引里 |
字段(Field) | 列 | 用于表述每一个列的名字,字段是文档的组成单元,包含字段名称、字段属性和字段内容 | 例如书店名,城市名就分别是一个字段 |
字段属性(Attributes) | 类似字段的Varchar(16),int(20),是否index,primary_index等 | 描述字段的属性,例如城市名的属性是一个字符串类型,不需要分词等 | 用来描述字段的,包括是否建倒排、正排、分词、值的类型(数字类型、字符串类型等) |
文档(document) | 记录行 | 文档是可搜索的结构化数据单元,用于描述一整条记录,由多个字段组成 | 例如北京朝阳区彦峰书店等内容就可以作为一个文档 |
正排 | 行记录 | 文档到字段对应关系组成的链表,勾选可过滤后会构建正排链表。doc1->id,type,create_time… | 设置docvalues=true |
倒排 | 类似B+树索引 Mysql不设置索引还是可以进行查询,但是ES不设置,查询结果为空 | 词组到文档的对应关系组成的链表,勾选可搜索后会构建倒排链表。term1->doc1,doc2,doc3;term2->doc1,doc2 | 设置index=true,如果为false,对应的字段域将不能进行检索(即执行各种Query后返回的结果为空,或者直接报错) |
召回 | 查询过程 | 通过用户查询的关键词进行分词,将分词后的词组通过查找倒排链表快速定位到文档,这个过程称为召回。 | |
召回量 | 查询返回的结果数 | 召回得到的文档数为召回量,即totalhits | |
分片(Shard) | 在数据库里没有类似的概念 | 一个索引由多个分片组成,这些分片是索引里面的一部分,每一个分片都具备索引相同的数据结构 | 一个索引分成N个shard,每一个Shard的内容就是这个完整索内容的1/N |
段(Segment) | 在数据库里没有类似的概念 | 分片的组成单元,即多个段构成一个分片,段是检索的基本单元,所有的查询/更新都是基于段来查询的, | |
段合并 | 在数据库里没有类似的概念 | Lucene的删除是标记删除,更新是先删后增,随着数据不断的更新,一个分片中会累积很多段(这些段里存在很多已经删掉的文档),段太多会导致查询性能变慢,因此我们需要一个段合并的过程,将那些没有用的数据清除,减少段的个数 |
(四)应用场景距离
1.搜索引擎
典型应用领域:电商搜索、地图搜索、新闻搜索等各类站内搜索,简单的ES搜索引擎架构如下:
2.推荐系统
ES7.0以上版本高纬向量数据类型引入后可以把推荐模型算法计算的商品和用户向量储存到ES,当实时请求时可加载用户向量并使用ES的Script Score进行查询,使每个文档最终的排序分值等于当前用户向量与当前文档向量的相似度。
3.日志分析
二、简单实践操作
(一)下载与验证操作
Mac下安装ElasticSearch(依赖Java 环境,没有的话按相同方式进行安装操作即可)和Kibana,无需其他配置,如果中间遇到问题,直接“brew reinstall **”即可(**代表对应安装失败的内容)
安装项目 | 安装指令 | 启动指令 | 验证 |
ElasticSearch | brew install elasticsearch | brew services start elasticsearch | http://localhost:9200/ |
Kibana | brew install kibana | brew services start kibana | http://localhost:5601 |
我们可以查看对应的官方文档,对应es集群下的默认用户名和密码分别为:(es默认用户名和密码主要用于后续java客服端配置中使用)
userName: elastic
password: changeme
(二)集群使用
以下相关操作可以直接在http://localhost:5601/app/dev_tools#/console下进行相关的操作,这样简单方便一些,以下集群内容做简单的集群测试演示。
1.创建索引
我们先创建一个简单的书店索引进行分析
PUT /bookstore?pretty
{
"mappings": {
"_source": {
"enabled": true
},
"properties": {
"cityid": {
"type": "integer"
},
"address": {
"type": "keyword"
},
"bookstoreid": {
"type": "integer"
},
"bookstorename": {
"type": "text"
},
"bookstorella": {
"type": "geo_point"
}
}
},
"settings": {
"index": {
"number_of_shards": "2",
"number_of_replicas": "1"
}
}
}
2.添加数据
curl -X POST '127.0.0.1:9200/bookstore/_doc/1000' -H 'content-Type:application/json' -d '{"bookstoreid":1000,"bookstorename":"彦峰书店","cityid":110100,"address":"北京朝阳区望京SOHO一层520号","bookstorella":"31.331241607666016,121.25092315673828"}'
curl -X POST '127.0.0.1:9200/bookstore/_doc/1001' -H 'content-Type:application/json' -d '{"bookstoreid":1001,"bookstorename":"彦峰书店","cityid":350200,"address":"福建浦江镇召楼路1976号","bookstorella":"31.05731773376465,121.5353012084961"}'
curl -X POST '127.0.0.1:9200/bookstore/_doc/1002' -H 'content-Type:application/json' -d '{"bookstoreid":1002,"bookstorename":"彦峰书店","cityid":320100,"address":"南京长宁区北新泾四村5号","bookstorella":"31.21916961669922,121.36328125"}'
curl -X POST '127.0.0.1:9200/bookstore/_doc/1003' -H 'content-Type:application/json' -d '{"bookstoreid":1003,"bookstorename":"彦峰书店","cityid":310100,"address":"上海虹桥站出发层2F-26","bookstorella":"31.197473526000977,121.3229751586914"}'
curl -X POST '127.0.0.1:9200/bookstore/_doc/1004' -H 'content-Type:application/json' -d '{"bookstoreid":1004,"bookstorename":"彦峰书店","cityid":150200,"address":"内蒙古包头市东河区恒殿大厦1356号","bookstorella":"31.23369598388672,121.3904037475586"}'
curl -X POST '127.0.0.1:9200/bookstore/_doc/1005' -H 'content-Type:application/json' -d '{"bookstoreid":1005,"bookstorename":"彦峰书店","cityid":510100,"address":"四川成都中街886号","bookstorella":"31.197004318237305,121.69368743896484"}'
3.查询数据
查询所有的数据
GET /bookstore/_search
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"must_not": [],
"should": [],
"filter": []
}
},
"from": 0,
"size": 10,
"sort": [],
"profile": false
}
查询彦峰书店情况
GET /bookstore/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"bookstorename": "彦"
}
}
],
"must_not": [],
"should": [],
"filter": []
}
},
"from": 0,
"size": 10,
"sort": [],
"profile": false
}
数据返回
{
"took" : 5,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 6,
"relation" : "eq"
},
"max_score" : 0.18232156,
"hits" : [
{
"_index" : "bookstore",
"_type" : "_doc",
"_id" : "1002",
"_score" : 0.18232156,
"_source" : {
"bookstoreid" : 1002,
"bookstorename" : "彦峰书店",
"cityid" : 320100,
"address" : "南京长宁区北新泾四村5号",
"bookstorella" : "31.21916961669922,121.36328125"
}
},
{
"_index" : "bookstore",
"_type" : "_doc",
"_id" : "1004",
"_score" : 0.18232156,
"_source" : {
"bookstoreid" : 1004,
"bookstorename" : "彦峰书店",
"cityid" : 150200,
"address" : "内蒙古包头市东河区恒殿大厦1356号",
"bookstorella" : "31.23369598388672,121.3904037475586"
}
},
{
"_index" : "bookstore",
"_type" : "_doc",
"_id" : "1000",
"_score" : 0.10536051,
"_source" : {
"bookstoreid" : 1000,
"bookstorename" : "彦峰书店",
"cityid" : 110100,
"address" : "北京朝阳区望京SOHO一层520号",
"bookstorella" : "31.331241607666016,121.25092315673828"
}
},
{
"_index" : "bookstore",
"_type" : "_doc",
"_id" : "1001",
"_score" : 0.10536051,
"_source" : {
"bookstoreid" : 1001,
"bookstorename" : "彦峰书店",
"cityid" : 350200,
"address" : "福建浦江镇召楼路1976号",
"bookstorella" : "31.05731773376465,121.5353012084961"
}
},
{
"_index" : "bookstore",
"_type" : "_doc",
"_id" : "1003",
"_score" : 0.10536051,
"_source" : {
"bookstoreid" : 1003,
"bookstorename" : "彦峰书店",
"cityid" : 310100,
"address" : "上海虹桥站出发层2F-26",
"bookstorella" : "31.197473526000977,121.3229751586914"
}
},
{
"_index" : "bookstore",
"_type" : "_doc",
"_id" : "1005",
"_score" : 0.10536051,
"_source" : {
"bookstoreid" : 1005,
"bookstorename" : "彦峰书店",
"cityid" : 510100,
"address" : "四川成都中街886号",
"bookstorella" : "31.197004318237305,121.69368743896484"
}
}
]
}
}
4.返回数据解释
字段 | 含义 | 备注 |
hits.hits[] | 命中的记录数,每一个记录都是满足查询相关的文档 | _index:说明查询的索引名, _type:说明查询的类型,7.x后全部为_doc _id:文档的唯一标示 _score:该条记录(文档)与query的相关分 _source:默认返回soruce数据,用户可以指定该返回哪些字段 |
timed_out | 是否超时,boolean类型,true:超时,false:不超时 | 此处的超时定义为实际查询时间与用户给与的超时时间的差异,如果<用户给定的即为false,否则为true,如果用户未定义timeout参数,则默认为false |
took | 查询操作花费的时间,单位ms | 一般ES查询第一次比较慢,因为数据存储在磁盘中,需要进行一次load |
hits.max_score | 相关性分,分数越高,相关性越高 | max_score:可以取值0,null,以及其他数字值 null:表示对相关分追踪不关注 0:表示不会计算相关分 其他数字:表示相关分 |
hits.total | 查询命中的结果描述,ES在5.x后,将不再确保total的数字准确 | value:命中的数目 relation:有eq,gte两个值,表示此次查询的结果数与真正的结果数之间的关系,eq,表示真正结果数为value的值,gte表示真正的结果数>value的值 |
_shards | 说明此查询遍历的分片个数 | total:查询应该遍历多少个分片, successful:成功返回数据的分片数 failed:查询失败的分片数 skiped:查询跳过的分片数(查询超时可能会跳过部分分片) |
hits | 最外层的,显示命中的结果信息 |
三、连接与简单操作-Java API操作ES
这部分的展示分为三部分展示操作索引相关练习、操作文档相关练习、数据查询练习,因为前面集群演示已经创建过对应的书店索引,以为演示已商品索引来展示。
基本商品数据结果先展示如下:
package org.zyf.javabasic.es.model;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author yanfengzhang
* @description 商品数据模型 简化版
* @date 2022/12/7 23:19
*/
@Data
public class Goods {
/**
* 商品编号
*/
private Long id;
/**
* 商品标题
*/
private String title;
/**
* 商品价格
*/
private BigDecimal price;
/**
* 商品库存
*/
private Integer stock;
/**
* 商品销售数量
*/
private Integer saleNum;
/**
* 商品分类
*/
private String categoryName;
/**
* 商品品牌
*/
private String brandName;
/**
* 上下架状态
*/
private Integer status;
/**
* 说明书
*/
private String spec;
/**
* 商品创建时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
(一)SpringBoot整合ES
1.pom依赖
<!--引入es-high-level-client相关依赖 start-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.8.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>6.8.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.8.2</version>
</dependency>
<!--引入es-high-level-client相关依赖 end-->
2.application.yml配置
elasticsearch:
clusterName: elasticsearch_brew
userName: elastic
password: changeme
hosts: 127.0.0.1:9200
scheme: http
connectTimeOut: 1000
socketTimeOut: 30000
connectionRequestTimeOut: 500
maxConnectNum: 100
maxConnectNumPerRoute: 100
3.客户端配置类
package org.zyf.javabasic.es.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author yanfengzhang
* @description restHighLevelClient 客户端配置类
* @date 2022/12/7 23:34
*/
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticsearchConfig {
/**
* es host ip 地址(集群)
*/
private String hosts;
/**
* es用户名
*/
private String userName;
/**
* es密码
*/
private String password;
/**
* es 请求方式
*/
private String scheme;
/**
* es集群名称
*/
private String clusterName;
/**
* es 连接超时时间
*/
private int connectTimeOut;
/**
* es socket 连接超时时间
*/
private int socketTimeOut;
/**
* es 请求超时时间
*/
private int connectionRequestTimeOut;
/**
* es 最大连接数
*/
private int maxConnectNum;
/**
* es 每个路由的最大连接数
*/
private int maxConnectNumPerRoute;
/**
* 如果@Bean没有指定bean的名称,那么这个bean的名称就是方法名
*/
@Bean(name = "restHighLevelClient")
public RestHighLevelClient restHighLevelClient() {
/** 拆分地址
/** List<HttpHost> hostLists = new ArrayList<>();
/** String[] hostList = hosts.split(",");
/** for (String addr : hostList) {
/** String host = addr.split(":")[0];
/** String port = addr.split(":")[1];
/** hostLists.add(new HttpHost(host, Integer.parseInt(port), scheme));
/** }
/** /** 转换成 HttpHost 数组
/** HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});*/
/*此处为单节点es*/
String host = hosts.split(":")[0];
String port = hosts.split(":")[1];
HttpHost httpHost = new HttpHost(host, Integer.parseInt(port));
/*构建连接对象*/
RestClientBuilder builder = RestClient.builder(httpHost);
/*设置用户名、密码*/
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
/*连接延时配置*/
builder.setRequestConfigCallback(requestConfigBuilder -> {
requestConfigBuilder.setConnectTimeout(connectTimeOut);
requestConfigBuilder.setSocketTimeout(socketTimeOut);
requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeOut);
return requestConfigBuilder;
});
/*连接数配置*/
builder.setHttpClientConfigCallback(httpClientBuilder -> {
httpClientBuilder.setMaxConnTotal(maxConnectNum);
httpClientBuilder.setMaxConnPerRoute(maxConnectNumPerRoute);
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
return httpClientBuilder;
});
return new RestHighLevelClient(builder);
}
}
(二)操作索引相关简单练习
1.索引操作service定义
package org.zyf.javabasic.es.service;
import java.util.Map;
/**
* @author yanfengzhang
* @description 索引操作service
* @date 2022/12/7 23:58
*/
public interface IndexGoodService {
/**
* 创建索引
*
* @param indexName 索引名
* @param mapping 映射结构配置
* @return true-创建成功
* @throws Exception 异常
*/
boolean indexCreate(String indexName, String mapping) throws Exception;
/**
* 获取索引结构
*
* @param indexName 索引名
* @return 索引结构
* @throws Exception 异常
*/
Map<String, Object> getMapping(String indexName) throws Exception;
/**
* 删除索引库
*
* @param indexName 索引名
* @return true-删除成功
* @throws Exception 异常
*/
boolean indexDelete(String indexName) throws Exception;
/**
* 判断索引是否存在
*
* @param indexName 索引名
* @return true-存在
* @throws Exception 异常
*/
boolean indexExists(String indexName) throws Exception;
}
2.对应相关实现
package org.zyf.javabasic.es.service.impl;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RequestOptions;
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.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zyf.javabasic.es.service.IndexGoodService;
import java.util.Map;
/**
* @author yanfengzhang
* @description 索引服务类
* @date 2022/12/7 23:03
*/
@Service
public class IndexGoodServiceImpl implements IndexGoodService {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 创建索引
*
* @param indexName 索引名
* @param mapping 映射结构配置
* @return true-创建成功
* @throws Exception 异常
*/
@Override
public boolean indexCreate(String indexName, String mapping) throws Exception {
CreateIndexRequest indexRequest = new CreateIndexRequest(indexName);
IndicesClient indicesClient = restHighLevelClient.indices();
indexRequest.mapping(mapping, XContentType.JSON);
// 请求服务器
CreateIndexResponse response = indicesClient.create(indexRequest, RequestOptions.DEFAULT);
return response.isAcknowledged();
}
/**
* 获取索引结构
*
* @param indexName 索引名
* @return 索引结构
* @throws Exception 异常
*/
@Override
public Map<String, Object> getMapping(String indexName) throws Exception {
IndicesClient indicesClient = restHighLevelClient.indices();
// 创建get请求
GetIndexRequest request = new GetIndexRequest(indexName);
// 发送get请求
GetIndexResponse response = indicesClient.get(request, RequestOptions.DEFAULT);
// 获取表结构
Map<String, MappingMetaData> mappings = response.getMappings();
Map<String, Object> sourceAsMap = mappings.get(indexName).getSourceAsMap();
return sourceAsMap;
}
/**
* 删除索引库
*
* @param indexName 索引名
* @return true-删除成功
* @throws Exception 异常
*/
@Override
public boolean indexDelete(String indexName) throws Exception {
IndicesClient indicesClient = restHighLevelClient.indices();
// 创建delete请求方式
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
// 发送delete请求
AcknowledgedResponse response = indicesClient.delete(deleteIndexRequest, RequestOptions.DEFAULT);
return response.isAcknowledged();
}
/**
* 判断索引是否存在
*
* @param indexName 索引名
* @return true-存在
* @throws Exception 异常
*/
@Override
public boolean indexExists(String indexName) throws Exception {
IndicesClient indicesClient = restHighLevelClient.indices();
// 创建get请求
GetIndexRequest request = new GetIndexRequest(indexName);
// 判断索引库是否存在
return indicesClient.exists(request, RequestOptions.DEFAULT);
}
}
3.测试数据
package org.zyf.javabasic.es.service.impl;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RequestOptions;
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.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zyf.javabasic.es.service.IndexGoodService;
import java.util.Map;
/**
* @author yanfengzhang
* @description 索引服务类
* @date 2022/12/7 23:03
*/
@Service
public class IndexGoodServiceImpl implements IndexGoodService {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 创建索引
*
* @param indexName 索引名
* @param mapping 映射结构配置
* @return true-创建成功
* @throws Exception 异常
*/
@Override
public boolean indexCreate(String indexName, String mapping) throws Exception {
CreateIndexRequest indexRequest = new CreateIndexRequest(indexName);
IndicesClient indicesClient = restHighLevelClient.indices();
indexRequest.mapping(mapping, XContentType.JSON);
// 请求服务器
CreateIndexResponse response = indicesClient.create(indexRequest, RequestOptions.DEFAULT);
return response.isAcknowledged();
}
/**
* 获取索引结构
*
* @param indexName 索引名
* @return 索引结构
* @throws Exception 异常
*/
@Override
public Map<String, Object> getMapping(String indexName) throws Exception {
IndicesClient indicesClient = restHighLevelClient.indices();
// 创建get请求
GetIndexRequest request = new GetIndexRequest(indexName);
// 发送get请求
GetIndexResponse response = indicesClient.get(request, RequestOptions.DEFAULT);
// 获取表结构
Map<String, MappingMetaData> mappings = response.getMappings();
Map<String, Object> sourceAsMap = mappings.get(indexName).getSourceAsMap();
return sourceAsMap;
}
/**
* 删除索引库
*
* @param indexName 索引名
* @return true-删除成功
* @throws Exception 异常
*/
@Override
public boolean indexDelete(String indexName) throws Exception {
IndicesClient indicesClient = restHighLevelClient.indices();
// 创建delete请求方式
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
// 发送delete请求
AcknowledgedResponse response = indicesClient.delete(deleteIndexRequest, RequestOptions.DEFAULT);
return response.isAcknowledged();
}
/**
* 判断索引是否存在
*
* @param indexName 索引名
* @return true-存在
* @throws Exception 异常
*/
@Override
public boolean indexExists(String indexName) throws Exception {
IndicesClient indicesClient = restHighLevelClient.indices();
// 创建get请求
GetIndexRequest request = new GetIndexRequest(indexName);
// 判断索引库是否存在
return indicesClient.exists(request, RequestOptions.DEFAULT);
}
}
(三)操作文档相关简单练习
1.文档操作service定义
package org.zyf.javabasic.es.service;
import org.elasticsearch.rest.RestStatus;
import org.zyf.javabasic.es.model.Goods;
import java.io.IOException;
import java.util.List;
/**
* @author yanfengzhang
* @description 文档操作service
* @date 2022/12/7 23:45
*/
public interface DocumentGoodService {
/**
* 增加文档信息
*
* @param indexName 索引名
* @param type 文档类型
* @param goods 商品数据
* @return 增加文档信息状态
* @throws IOException 异常信息
*/
RestStatus addDocument(String indexName, String type, Goods goods) throws IOException;
/**
* 获取文档信息
*
* @param indexName 索引名
* @param type 文档类型
* @param id 文档ID
* @return 商品数据
* @throws Exception 异常信息
*/
Goods getDocument(String indexName, String type, String id) throws Exception;
/**
* 更新文档信息
*
* @param indexName 索引名
* @param type 文档类型
* @param goods 商品数据
* @return 更新文档信息状态
* @throws IOException 异常信息
*/
RestStatus updateDocument(String indexName, String type, Goods goods) throws IOException;
/**
* 删除文档信息
*
* @param indexName 索引名
* @param type 文档类型
* @param id 文档ID
* @return 删除文档信息状态
* @throws IOException 异常信息
*/
RestStatus deleteDocument(String indexName, String type, String id) throws IOException;
/**
* 批量导入
*
* @param goodsList 商品列表
* @return 批量导入状态
* @throws IOException 异常信息
*/
RestStatus batchImportGoodsData(List<Goods> goodsList) throws IOException;
}
2.对应相关实现
package org.zyf.javabasic.es.service.impl;
import com.alibaba.fastjson.JSON;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
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.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.RestStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zyf.javabasic.es.model.Goods;
import org.zyf.javabasic.es.service.DocumentGoodService;
import java.io.IOException;
import java.util.List;
/**
* @author yanfengzhang
* @description
* @date 2022/12/7 23:58
*/
@Service
@Log4j2
public class DocumentGoodServiceImpl implements DocumentGoodService {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 增加文档信息
*
* @param indexName 索引名
* @param type 文档类型
* @param goods 商品数据
* @return 增加文档信息状态
* @throws IOException 异常信息
*/
@Override
public RestStatus addDocument(String indexName, String type, Goods goods) throws IOException {
/*1.默认类型为_doc*/
if (StringUtils.isBlank(type)) {
type = "_doc";
}
/*2.将对象转为json*/
String data = JSON.toJSONString(goods);
/*3.创建索引请求对象*/
IndexRequest indexRequest = new IndexRequest(indexName, type).id(goods.getId() + "").source(data, XContentType.JSON);
/*4.执行增加文档*/
IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
return response.status();
}
/**
* 获取文档信息
*
* @param indexName 索引名
* @param type 文档类型
* @param id 文档ID
* @return 商品数据
* @throws Exception 异常信息
*/
@Override
public Goods getDocument(String indexName, String type, String id) throws Exception {
/*1.默认类型为_doc*/
if (StringUtils.isBlank(type)) {
type = "_doc";
}
/*2.创建获取请求对象*/
GetRequest getRequest = new GetRequest(indexName, type, id);
GetResponse response = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
return JSON.parseObject(response.getSourceAsString(), Goods.class);
}
/**
* 更新文档信息
*
* @param indexName 索引名
* @param type 文档类型
* @param goods 商品数据
* @return 更新文档信息状态
* @throws IOException 异常信息
*/
@Override
public RestStatus updateDocument(String indexName, String type, Goods goods) throws IOException {
/*1.默认类型为_doc*/
if (StringUtils.isBlank(type)) {
type = "_doc";
}
/*2.创建索引请求对象*/
UpdateRequest updateRequest = new UpdateRequest(indexName, type, String.valueOf(goods.getId()));
/*3.设置更新文档内容*/
updateRequest.doc(JSON.toJSONString(goods), XContentType.JSON);
/*4.执行更新文档*/
UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
return response.status();
}
/**
* 删除文档信息
*
* @param indexName 索引名
* @param type 文档类型
* @param id 文档ID
* @return 删除文档信息状态
* @throws IOException 异常信息
*/
@Override
public RestStatus deleteDocument(String indexName, String type, String id) throws IOException {
/*1.默认类型为_doc*/
if (StringUtils.isBlank(type)) {
type = "_doc";
}
/*2.创建删除请求对象*/
DeleteRequest deleteRequest = new DeleteRequest(indexName, type, id);
/*3.执行删除文档*/
DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
return response.status();
}
/**
* 批量导入
*
* @param goodsList 商品列表
* @return 批量导入状态
* @throws IOException 异常信息
*/
@Override
public RestStatus batchImportGoodsData(List<Goods> goodsList) throws IOException {
if (CollectionUtils.isEmpty(goodsList)) {
return RestStatus.CREATED;
}
/*bulk导入 循环goodsList,创建IndexRequest添加数据*/
BulkRequest bulkRequest = new BulkRequest();
for (Goods goods : goodsList) {
//将goods对象转换为json字符串
String data = JSON.toJSONString(goods);
IndexRequest indexRequest = new IndexRequest("goods", "_doc");
indexRequest.id(goods.getId() + "").source(data, XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse response = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return response.status();
}
}
3.测试数据
package org.zyf.javabasic.es.test;
import com.google.common.collect.Lists;
import lombok.extern.log4j.Log4j2;
import org.elasticsearch.rest.RestStatus;
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 org.zyf.javabasic.ZYFApplication;
import org.zyf.javabasic.es.model.Goods;
import org.zyf.javabasic.es.service.DocumentGoodService;
import org.zyf.javabasic.es.service.IndexGoodService;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* @author yanfengzhang
* @description
* @date 2022/12/7 23:31
*/
@Log4j2
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DocumentGoodServiceTest {
@Autowired
private IndexGoodService indexGoodService;
@Autowired
private DocumentGoodService documentGoodService;
private final String indexName = "goods";
/**
* 添加文档
*/
@Test
public void addDocument() {
// 创建商品信息
Goods goods = new Goods();
goods.setId(1L);
goods.setTitle("Apple iPhone 13 Pro (A2639) 256GB 远峰蓝色 支持移动联通电信5G 双卡双待手机");
goods.setPrice(new BigDecimal("8799.00"));
goods.setStock(1000);
goods.setSaleNum(599);
goods.setCategoryName("手机");
goods.setBrandName("Apple");
goods.setStatus(0);
goods.setCreateTime(new Date());
// 返回状态
RestStatus restStatus = null;
try {
restStatus = documentGoodService.addDocument(indexName, "_doc", goods);
} catch (Exception e) {
log.error("添加文档失败,错误信息:", e);
}
log.info("添加文档响应状态:{}", restStatus);
}
@Test
public void getDocument() {
Goods goods = null;
try {
goods = documentGoodService.getDocument(indexName, "_doc", "1");
} catch (Exception e) {
log.error("查询文档失败,错误信息:", e);
}
log.info("查询的文档信息:{}", goods);
}
@Test
public void updateDocument() {
// 创建商品信息
Goods goods = new Goods();
goods.setTitle("Apple iPhone 13 Pro Max (A2644) 256GB 远峰蓝色 支持移动联通电信5G 双卡双待手机");
goods.setPrice(new BigDecimal("9999"));
goods.setId(1L);
// 返回状态
RestStatus restStatus = null;
try {
restStatus = documentGoodService.updateDocument(indexName, "_doc", goods);
} catch (Exception e) {
log.error("更新文档失败,错误信息:", e);
}
log.info("更新文档响应状态:{}", restStatus);
}
@Test
public void deleteDocument() {
// 返回状态
RestStatus restStatus = null;
try {
restStatus = documentGoodService.deleteDocument(indexName, "_doc", "1");
} catch (Exception e) {
log.error("删除文档失败,错误信息:", e);
}
log.info("删除文档响应状态:{}", restStatus);
}
/**
* 批量导入测试数据
*/
@Test
public void importDocument() {
List<Goods> goodsList = Lists.newArrayList();
Goods goods1 = new Goods();
goods1.setId(2L);
goods1.setTitle("Apple iPhone 13 Pro (A2639) 512GB 远峰蓝色 支持移动联通电信5G 双卡双待手机");
goods1.setPrice(new BigDecimal("10099.00"));
goods1.setStock(1000);
goods1.setSaleNum(599);
goods1.setCategoryName("手机");
goods1.setBrandName("Apple");
goods1.setStatus(0);
goods1.setCreateTime(new Date());
goodsList.add(goods1);
Goods goods2 = new Goods();
goods2.setId(3L);
goods2.setTitle("Apple iPhone 13 Pro (A2639) 1024GB 远峰蓝色 支持移动联通电信5G 双卡双待手机");
goods2.setPrice(new BigDecimal("11099.00"));
goods2.setStock(1000);
goods2.setSaleNum(599);
goods2.setCategoryName("手机");
goods2.setBrandName("Apple");
goods2.setStatus(0);
goods2.setCreateTime(new Date());
goodsList.add(goods2);
RestStatus restStatus = null;
try {
restStatus = documentGoodService.batchImportGoodsData(goodsList);
} catch (Exception e) {
log.error("批量导入数据失败,错误信息:", e);
}
log.info("批量导入数据响应状态:{}", restStatus);
}
}
(四)数据查询相关简单练习
1.数据查询操作service定义
package org.zyf.javabasic.es.service;
import java.util.List;
/**
* @author yanfengzhang
* @description
* @date 2022/12/8 23:25
*/
public interface QueryDataService {
/**
* 精确查询(termQuery)
*
* @param indexName 索引名
* @param columnName 列名或字段名
* @param value 查询内容
* @param classz 数据结构
* @param <T> 数据结构
* @return 精确查询内容数据
*/
<T> List<T> termQuery(String indexName, String columnName, Object value, Class<T> classz);
/**
* terms:多个查询内容在一个字段中进行查询
*
* @param indexName 索引名
* @param columnName 列名或字段名
* @param dataArgs 查询内容集合
* @param classz 数据结构
* @param <T> 数据结构
* @return 多个查询内容在一个字段中进行查询对应结果
*/
<T> List<T> termsQuery(String indexName, String columnName, Object[] dataArgs, Class<T> classz);
/**
* 匹配查询符合条件的所有数据,并设置分页
*
* @param indexName 索引名
* @param classz 数据结构
* @param startIndex 起始下标
* @param pageSize 页大小
* @param orderList 设置排序
* @param columnName 列名或字段名
* @param value 列名或字段名指定内容
* @param <T> 数据结构
* @return 符合条件的所有数据
*/
<T> List<T> matchAllQuery(String indexName, Class<T> classz, int startIndex, int pageSize, List<String> orderList, String columnName, Object value);
/**
* 词语匹配查询
*
* @param indexName 索引名
* @param classz 数据结构
* @param columnName 列名或字段名
* @param value 指定内容
* @param <T> 数据结构
* @return 词语匹配查询结果
*/
<T> List<T> matchPhraseQuery(String indexName, Class<T> classz, String columnName, Object value);
/**
* 内容在多字段中进行查询
*
* @param indexName 索引名
* @param classz 数据结构
* @param fields 列名或字段名集合
* @param text 指定内容
* @param <T> 数据结构
* @return 查询结果
*/
<T> List<T> matchMultiQuery(String indexName, Class<T> classz, String[] fields, Object text);
/**
* 通配符查询(wildcard):会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
*
* @param indexName 索引名
* @param classz 数据结构
* @param field 列名或字段名集合
* @param text 指定内容
* @param <T> 数据结构
* @return 查询结果
*/
<T> List<T> wildcardQuery(String indexName, Class<T> classz, String field, String text);
/**
* 模糊查询商品信息
*
* @param indexName 索引名
* @param classz 数据结构
* @param field 列名或字段名集合
* @param text 指定内容
* @param <T> 数据结构
* @return 查询结果
*/
<T> List<T> fuzzyQuery(String indexName, Class<T> classz, String field, String text);
/**
* boolQuery 查询
* 高亮展示标题搜索字段
* 设置出参返回字段
* 案例:查询从2018-2022年间标题含 三星 的商品信息
*
* @param indexName 索引名
* @param beanClass 数据结构
* @param <T> 数据结构
* @return 查询结果
*/
<T> List<T> boolQuery(String indexName, Class<T> beanClass);
/**
* 聚合查询 : 聚合查询一定是【先查出结果】,然后对【结果使用聚合函数】做处理.
* Metric 指标聚合分析。常用的操作有:avg:求平均、max:最大值、min:最小值、sum:求和等
* 案例:分别获取最贵的商品和获取最便宜的商品
*
* @param indexName 索引名
*/
void metricQuery(String indexName);
/**
* 聚合查询: 聚合查询一定是【先查出结果】,然后对【结果使用聚合函数】做处理
* Bucket 分桶聚合分析 : 对查询出的数据进行分组group by,再在组上进行游标聚合
* 案例:根据品牌进行聚合查询
*
* @param indexName 索引名
* @param bucketField
* @param bucketFieldAlias
*/
void bucketQuery(String indexName, String bucketField, String bucketFieldAlias);
/**
* 子聚合聚合查询 Bucket 分桶聚合分析
* <p>
* 案例:根据商品分类进行分组查询,并且获取分类商品中的平均价格
*
* @param indexName 索引名
* @param bucketField
* @param bucketFieldAlias
* @param avgFiled
* @param avgFiledAlias
*/
void subBucketQuery(String indexName, String bucketField, String bucketFieldAlias, String avgFiled, String avgFiledAlias);
/**
* 综合聚合查询
* <p>
* 根据商品分类聚合,获取每个商品类的平均价格,并且在商品分类聚合之上子聚合每个品牌的平均价格
*
* @param indexName 索引名
*/
void subSubAgg(String indexName);
}
2.对应相关实现
package org.zyf.javabasic.es.service.impl;
import com.alibaba.fastjson.JSON;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
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.text.Text;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
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.avg.ParsedAvg;
import org.elasticsearch.search.aggregations.metrics.max.ParsedMax;
import org.elasticsearch.search.aggregations.metrics.min.ParsedMin;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zyf.javabasic.es.service.QueryDataService;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @author yanfengzhang
* @description
* @date 2022/12/8 23:37
*/
@Service
@Log4j2
public class QueryDataServiceImpl implements QueryDataService {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 精确查询(termQuery)
*
* @param indexName 索引名
* @param columnName 列名或字段名
* @param value 查询内容
* @param classz 数据结构
* @param <T> 数据结构
* @return 精确查询内容数据
*/
@Override
public <T> List<T> termQuery(String indexName, String columnName, Object value, Class<T> classz) {
/* 查询的数据列表 */
List<T> list = new ArrayList<>();
try {
/*构建查询条件(注意:termQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery(columnName, value));
/*执行查询es数据*/
queryEsData(indexName, classz, list, searchSourceBuilder);
} catch (IOException e) {
log.error("精确查询数据失败,错误信息:", e);
}
return list;
}
/**
* terms:多个查询内容在一个字段中进行查询
*
* @param indexName 索引名
* @param columnName 列名或字段名
* @param dataArgs 查询内容集合
* @param classz 数据结构
* @param <T> 数据结构
* @return 多个查询内容在一个字段中进行查询对应结果
*/
@Override
public <T> List<T> termsQuery(String indexName, String columnName, Object[] dataArgs, Class<T> classz) {
/*查询的数据列表*/
List<T> list = new ArrayList<>();
try {
/* 构建查询条件(注意:termsQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termsQuery(columnName, dataArgs));
/*展示100条,默认只展示10条记录*/
searchSourceBuilder.size(100);
/*执行查询es数据*/
queryEsData(indexName, classz, list, searchSourceBuilder);
} catch (IOException e) {
log.error("单字段多内容查询数据失败,错误信息:", e);
}
return list;
}
/**
* 匹配查询符合条件的所有数据,并设置分页
*
* @param indexName 索引名
* @param classz 数据结构
* @param startIndex 起始下标
* @param pageSize 页大小
* @param orderList 设置排序
* @param columnName 列名或字段名
* @param value 列名或字段名指定内容
* @param <T> 数据结构
* @return 符合条件的所有数据
*/
@Override
public <T> List<T> matchAllQuery(String indexName, Class<T> classz, int startIndex, int pageSize, List<String> orderList, String columnName, Object value) {
/*查询的数据列表*/
List<T> list = new ArrayList<>();
try {
/*创建查询源构造器*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/*构建查询条件*/
if (StringUtils.isNotBlank(columnName) && Objects.nonNull(value)) {
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(columnName, value);
searchSourceBuilder.query(matchQueryBuilder);
} else {
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
searchSourceBuilder.query(matchAllQueryBuilder);
}
/*设置分页*/
searchSourceBuilder.from(startIndex);
searchSourceBuilder.size(pageSize);
/*设置排序*/
if (orderList != null) {
for (String order : orderList) {
/*开头代表:倒序*/
boolean flag = order.startsWith("-");
SortOrder sort = flag ? SortOrder.DESC : SortOrder.ASC;
order = flag ? order.substring(1) : order;
searchSourceBuilder.sort(order, sort);
}
}
/*执行查询es数据*/
queryEsData(indexName, classz, list, searchSourceBuilder);
} catch (IOException e) {
log.error("查询所有数据失败,错误信息:", e);
}
return list;
}
/**
* 词语匹配查询
*
* @param indexName 索引名
* @param classz 数据结构
* @param columnName 列名或字段名
* @param value 指定内容
* @param <T> 数据结构
* @return 词语匹配查询结果
*/
@Override
public <T> List<T> matchPhraseQuery(String indexName, Class<T> classz, String columnName, Object value) {
/*查询的数据列表*/
List<T> list = new ArrayList<>();
try {
/*构建查询条件*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchPhraseQuery(columnName, value));
/*执行查询es数据*/
queryEsData(indexName, classz, list, searchSourceBuilder);
} catch (IOException e) {
log.error("词语匹配查询失败,错误信息:", e);
}
return list;
}
/**
* 内容在多字段中进行查询
*
* @param indexName 索引名
* @param classz 数据结构
* @param fields 列名或字段名集合
* @param text 指定内容
* @param <T> 数据结构
* @return 查询结果
*/
@Override
public <T> List<T> matchMultiQuery(String indexName, Class<T> classz, String[] fields, Object text) {
/*查询的数据列表*/
List<T> list = new ArrayList<>();
try {
/*构建查询条件*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/*设置查询条件*/
searchSourceBuilder.query(QueryBuilders.multiMatchQuery(text, fields));
/*执行查询es数据*/
queryEsData(indexName, classz, list, searchSourceBuilder);
} catch (IOException e) {
log.error("词语匹配查询失败,错误信息:", e);
}
return list;
}
/**
* 通配符查询(wildcard):会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
*
* @param indexName 索引名
* @param classz 数据结构
* @param field 列名或字段名集合
* @param text 指定内容
* @param <T> 数据结构
* @return 查询结果
*/
@Override
public <T> List<T> wildcardQuery(String indexName, Class<T> classz, String field, String text) {
/*查询的数据列表*/
List<T> list = new ArrayList<>();
try {
/*构建查询条件*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.wildcardQuery(field, text));
/*执行查询es数据*/
queryEsData(indexName, classz, list, searchSourceBuilder);
} catch (IOException e) {
log.error("通配符查询失败,错误信息:", e);
}
return list;
}
/**
* 模糊查询商品信息
*
* @param indexName 索引名
* @param classz 数据结构
* @param field 列名或字段名集合
* @param text 指定内容
* @param <T> 数据结构
* @return 查询结果
*/
@Override
public <T> List<T> fuzzyQuery(String indexName, Class<T> classz, String field, String text) {
/*查询的数据列表*/
List<T> list = new ArrayList<>();
try {
/*构建查询条件*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.fuzzyQuery(field, text).fuzziness(Fuzziness.AUTO));
/*执行查询es数据*/
queryEsData(indexName, classz, list, searchSourceBuilder);
} catch (IOException e) {
log.error("通配符查询失败,错误信息:", e);
}
return list;
}
/**
* boolQuery 查询
* 高亮展示标题搜索字段
* 设置出参返回字段
* 案例:查询从2018-2022年间标题含 三星 的商品信息
*
* @param indexName 索引名
* @param beanClass 数据结构
* @param <T> 数据结构
* @return 查询结果
*/
@Override
public <T> List<T> boolQuery(String indexName, Class<T> beanClass) {
/*查询的数据列表*/
List<T> list = new ArrayList<>();
try {
/*创建 Bool 查询构建器*/
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
/*构建查询条件*/
boolQueryBuilder.must(QueryBuilders.matchQuery("title", "三星"));
boolQueryBuilder.must(QueryBuilders.matchQuery("spec", "联通3G"));
boolQueryBuilder.filter().add(QueryBuilders.rangeQuery("createTime").format("yyyy").gte("2018").lte("2022"));
/*构建查询源构建器*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(100);
/*甚至返回字段
如果查询的属性很少,那就使用includes,而excludes设置为空数组
如果排序的属性很少,那就使用excludes,而includes设置为空数组*/
String[] includes = {"title", "categoryName", "price"};
String[] excludes = {};
searchSourceBuilder.fetchSource(includes, excludes);
/*高亮设置
设置高亮三要素: field: 你的高亮字段 , preTags :前缀 , postTags:后缀*/
HighlightBuilder highlightBuilder = new HighlightBuilder().field("title").preTags("<font color='red'>").postTags("</font>");
highlightBuilder.field("spec").preTags("<font color='red'>").postTags("</font>");
searchSourceBuilder.highlighter(highlightBuilder);
/*创建查询请求对象,将查询对象配置到其中*/
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(searchSourceBuilder);
/*执行查询,然后处理响应结果*/
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
/*根据状态和数据条数验证是否返回了数据*/
if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits() > 0) {
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
/* 将 JSON 转换成对象*/
T bean = JSON.parseObject(hit.getSourceAsString(), beanClass);
/*获取高亮的数据*/
HighlightField highlightField = hit.getHighlightFields().get("title");
log.info("高亮名称:{}", highlightField.getFragments()[0].string());
/*替换掉原来的数据*/
Text[] fragments = highlightField.getFragments();
if (fragments != null && fragments.length > 0) {
StringBuilder title = new StringBuilder();
for (Text fragment : fragments) {
title.append(fragment);
}
/* 获取method对象,其中包含方法名称和参数列表*/
Method setTitle = beanClass.getMethod("setTitle", String.class);
if (setTitle != null) {
/*执行method,bean为实例对象,后面是方法参数列表;setTitle没有返回值*/
setTitle.invoke(bean, title.toString());
}
}
list.add(bean);
}
}
} catch (Exception e) {
log.error("布尔查询失败,错误信息:", e);
}
return list;
}
/**
* 聚合查询 : 聚合查询一定是【先查出结果】,然后对【结果使用聚合函数】做处理.
* Metric 指标聚合分析。常用的操作有:avg:求平均、max:最大值、min:最小值、sum:求和等
* 案例:分别获取最贵的商品和获取最便宜的商品
*
* @param indexName 索引名
*/
@Override
public void metricQuery(String indexName) {
try {
/* 构建查询条件*/
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
/*创建查询源构造器*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);
/*获取最贵的商品*/
AggregationBuilder maxPrice = AggregationBuilders.max("maxPrice").field("price");
searchSourceBuilder.aggregation(maxPrice);
/*获取最便宜的商品*/
AggregationBuilder minPrice = AggregationBuilders.min("minPrice").field("price");
searchSourceBuilder.aggregation(minPrice);
/*创建查询请求对象,将查询对象配置到其中*/
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(searchSourceBuilder);
/*执行查询,然后处理响应结果*/
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
ParsedMax max = aggregations.get("maxPrice");
log.info("最贵的价格:" + max.getValue());
ParsedMin min = aggregations.get("minPrice");
log.info("最便宜的价格:" + min.getValue());
} catch (Exception e) {
log.error("指标聚合分析查询失败,错误信息:", e);
}
}
/**
* 聚合查询: 聚合查询一定是【先查出结果】,然后对【结果使用聚合函数】做处理
* Bucket 分桶聚合分析 : 对查询出的数据进行分组group by,再在组上进行游标聚合
* 案例:根据品牌进行聚合查询
*
* @param indexName 索引名
* @param bucketField
* @param bucketFieldAlias
*/
@Override
public void bucketQuery(String indexName, String bucketField, String bucketFieldAlias) {
try {
/*构建查询条件*/
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
/*创建查询源构造器*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);
/*根据bucketField进行分组查询*/
TermsAggregationBuilder aggBrandName = AggregationBuilders.terms(bucketFieldAlias).field(bucketField);
searchSourceBuilder.aggregation(aggBrandName);
/*创建查询请求对象,将查询对象配置到其中*/
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(searchSourceBuilder);
/*执行查询,然后处理响应结果*/
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
/*分组结果数据*/
ParsedStringTerms aggBrandName1 = aggregations.get(bucketFieldAlias);
for (Terms.Bucket bucket : aggBrandName1.getBuckets()) {
log.info(bucket.getKeyAsString() + "====" + bucket.getDocCount());
}
} catch (IOException e) {
log.error("分桶聚合分析查询失败,错误信息:", e);
}
}
/**
* 子聚合聚合查询 Bucket 分桶聚合分析
* <p>
* 案例:根据商品分类进行分组查询,并且获取分类商品中的平均价格
*
* @param indexName 索引名
* @param bucketField
* @param bucketFieldAlias
* @param avgFiled
* @param avgFiledAlias
*/
@Override
public void subBucketQuery(String indexName, String bucketField, String bucketFieldAlias, String avgFiled, String avgFiledAlias) {
try {
/*构建查询条件*/
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
/*创建查询源构造器*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);
/* 根据 bucketField进行分组查询,并且获取分类信息中 指定字段的平均值*/
TermsAggregationBuilder subAggregation = AggregationBuilders.terms(bucketFieldAlias).field(bucketField)
.subAggregation(AggregationBuilders.avg(avgFiledAlias).field(avgFiled));
searchSourceBuilder.aggregation(subAggregation);
/* 创建查询请求对象,将查询对象配置到其中*/
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(searchSourceBuilder);
/*执行查询,然后处理响应结果*/
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
ParsedStringTerms aggBrandName1 = aggregations.get(bucketFieldAlias);
for (Terms.Bucket bucket : aggBrandName1.getBuckets()) {
/*获取聚合后的 组内字段平均值,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象*/
ParsedAvg avgPrice = bucket.getAggregations().get(avgFiledAlias);
log.info(bucket.getKeyAsString() + "====" + avgPrice.getValueAsString());
}
} catch (IOException e) {
log.error("分桶聚合分析查询失败,错误信息:", e);
}
}
/**
* 综合聚合查询
* <p>
* 根据商品分类聚合,获取每个商品类的平均价格,并且在商品分类聚合之上子聚合每个品牌的平均价格
*
* @param indexName 索引名
*/
@Override
public void subSubAgg(String indexName) {
try {
/*构建查询条件*/
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
/*创建查询源构造器*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchAllQueryBuilder);
/*注意这里聚合写的位置不要写错,很容易搞混,错一个括号就不对了*/
TermsAggregationBuilder subAggregation = AggregationBuilders.terms("categoryNameAgg").field("categoryName")
.subAggregation(AggregationBuilders.avg("categoryNameAvgPrice").field("price"))
.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")
.subAggregation(AggregationBuilders.avg("brandNameAvgPrice").field("price")));
searchSourceBuilder.aggregation(subAggregation);
/*创建查询请求对象,将查询对象配置到其中*/
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(searchSourceBuilder);
/*执行查询,然后处理响应结果*/
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
/*获取总记录数*/
log.info("totalHits = " + searchResponse.getHits().getTotalHits());
/*获取聚合信息*/
Aggregations aggregations = searchResponse.getAggregations();
ParsedStringTerms categoryNameAgg = aggregations.get("categoryNameAgg");
/*获取值返回*/
for (Terms.Bucket bucket : categoryNameAgg.getBuckets()) {
/*获取聚合后的分类名称*/
String categoryName = bucket.getKeyAsString();
/*获取聚合命中的文档数量*/
long docCount = bucket.getDocCount();
/*获取聚合后的分类的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象*/
ParsedAvg avgPrice = bucket.getAggregations().get("categoryNameAvgPrice");
log.info(categoryName + "======平均价:" + avgPrice.getValue() + "======数量:" + docCount);
ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");
for (Terms.Bucket brandeNameAggBucket : brandNameAgg.getBuckets()) {
/*获取聚合后的品牌名称*/
String brandName = brandeNameAggBucket.getKeyAsString();
/*获取聚合后的品牌的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象*/
ParsedAvg brandNameAvgPrice = brandeNameAggBucket.getAggregations().get("brandNameAvgPrice");
log.info(" " + brandName + "======" + brandNameAvgPrice.getValue());
}
}
} catch (IOException e) {
log.error("综合聚合查询失败,错误信息:", e);
}
}
/**
* 执行es查询
*
* @param indexName
* @param beanClass
* @param list
* @param searchSourceBuilder
* @param <T>
* @throws IOException
*/
private <T> void queryEsData(String indexName, Class<T> beanClass, List<T> list, SearchSourceBuilder searchSourceBuilder) throws IOException {
/*创建查询请求对象,将查询对象配置到其中*/
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(searchSourceBuilder);
/*执行查询,然后处理响应结果*/
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
/*根据状态和数据条数验证是否返回了数据*/
if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits() > 0) {
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
/*将 JSON 转换成对象*/
T bean = JSON.parseObject(hit.getSourceAsString(), beanClass);
list.add(bean);
}
}
}
}
3.测试数据
package org.zyf.javabasic.es.test;
import com.google.common.collect.Lists;
import lombok.extern.log4j.Log4j2;
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 org.zyf.javabasic.ZYFApplication;
import org.zyf.javabasic.es.model.Goods;
import org.zyf.javabasic.es.service.QueryDataService;
import java.util.List;
/**
* @author yanfengzhang
* @description
* @date 2022/12/8 23:28
*/
@Log4j2
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class QueryDataServiceTest {
@Autowired
private QueryDataService queryDataService;
private final String indexName = "goods";
/**
* 单字段精确查询
*/
@Test
public void termQuery() {
List<Goods> goodsList = null;
try {
goodsList = queryDataService.termQuery(indexName, "title", "华为", Goods.class);
} catch (Exception e) {
log.error("单字段精确查询失败,错误信息:", e);
}
log.info("单字段精确查询结果:{}", goodsList);
}
/**
* 单字段多内容精确查询
*/
@Test
public void termsQuery() {
List<Goods> goodsList = null;
try {
String[] args = {"华为", "OPPO", "TCL"};
goodsList = queryDataService.termsQuery(indexName, "title", args, Goods.class);
} catch (Exception e) {
log.error("单字段多内容精确查询失败,错误信息:", e);
}
log.info("单字段多内容精确查询结果:{}", goodsList);
}
/**
* 单字段匹配分页查询
*/
@Test
public void matchQuery() {
List<Goods> goodsList = null;
try {
List<String> orderList = Lists.newArrayList("-price", "-saleNum");
goodsList = queryDataService.matchAllQuery(indexName, Goods.class, 0, 3, orderList, "title", "华为");
} catch (Exception e) {
log.error("匹配查询失败,错误信息:", e);
}
log.info("匹配查询结果:{}", goodsList);
}
/**
* 单字段多内容精确查询
*/
@Test
public void matchPhraseQuery() {
List<Goods> goodsList = null;
try {
goodsList = queryDataService.matchPhraseQuery(indexName, Goods.class, "title", "华为");
} catch (Exception e) {
log.error("词语匹配查询失败,错误信息:", e);
}
log.info("词语匹配查询结果:{}", goodsList);
}
/**
* 内容在多字段中进行查询
*/
@Test
public void matchMultiQuery() {
List<Goods> goodsList = null;
try {
String[] fields = {"title", "categoryName"};
goodsList = queryDataService.matchMultiQuery(indexName, Goods.class, fields, "手机");
} catch (Exception e) {
log.error("内容在多字段中进行查询失败,错误信息:", e);
}
log.info("内容在多字段中进行查询结果:{}", goodsList);
}
/**
* 通配符查询
* <p>
* 查询所有以 “三” 结尾的商品信息
*/
@Test
public void wildcardQuery() {
List<Goods> goodsList = null;
try {
goodsList = queryDataService.wildcardQuery(indexName, Goods.class, "title", "*三");
} catch (Exception e) {
log.error("通配符查询查询失败,错误信息:", e);
}
log.info("通配符查询结果:{}", goodsList);
}
/**
* 模糊查询
* <p>
* 模糊查询所有以 “三” 结尾的商品信息
*/
@Test
public void fuzzyQuery() {
List<Goods> goodsList = null;
try {
goodsList = queryDataService.fuzzyQuery(indexName, Goods.class, "title", "三");
} catch (Exception e) {
log.error("模糊查询失败,错误信息:", e);
}
log.info("模糊查询结果:{}", goodsList);
}
@Test
public void boolQuery() {
List<Goods> goodsList = null;
try {
goodsList = queryDataService.boolQuery(indexName, Goods.class);
} catch (Exception e) {
log.error("布尔查询失败,错误信息:", e);
}
log.info("布尔查询结果:{}", goodsList);
}
/**
* Metric 指标聚合分析
*/
@Test
public void metricQuery() {
queryDataService.metricQuery(indexName);
}
/**
* Bucket 分桶聚合分析
*/
@Test
public void bucketQuery() {
queryDataService.bucketQuery(indexName, "brandName", "brandNameName");
}
/**
* 子聚合聚合查询
*/
@Test
public void subBucketQuery() {
queryDataService.subBucketQuery(indexName, "brandName", "brandNameName", "price", "avgPrice");
}
/**
* 综合聚合查询
*/
@Test
public void subSubAgg() {
queryDataService.subSubAgg(indexName);
}
}
参考链接:
1.《Elasticsearch搜索引擎构建入门与实践》,高印会,202111,机械工业出版社
2.Mac 安装Es_舟子121的博客-CSDN博客_mac安装es
3.2022-01-20 brew 安装 es kibana nvm zsh - 简书
4.java连接ES的两种方式(5)_Firm陈的博客-CSDN博客_如何接入es
5.02 认知:Elastic Stack生态和场景方案.md
6.https://blog.csdn.net/weixin_40482816/article/details/126955661
7.ES_之_应用场景与基本概念 | HealeJean的梦想博客
8.https://blog.csdn.net/m0_67401920/article/details/126360117