一、概述
-
ElasticSearch是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。
-
Elasticsearch 8最低jdk版本要求jdk17
-
ElasticSearch vs Solr 总结
-
es基本是开箱即用,非常简单。Solr安装略微复杂。
-
Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能。
-
Solr 支持更多格式的数据,比如JSON、XML、CSV,而 Elasticsearch 仅支持json文件格式。
-
Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用。
现在很多互联网应用都是要求实时搜索的,所以我们选择了elasticsearch。
-
二、倒排索引(*)
-
ElasticSearch 实现搜索过程底层使用倒排索引
-
第一个层面:--分词--匹配--排序--
第一步 分词
第二步 匹配
第三步 排序
-
第二个层面
底层从文档内容里面抽取很多关键词条,根据词条创建索引 ,结构:词条 + 对应文档
三、核心概念
ES(mysql)--索引库(数据库)--Type类型 默认_doc(表)--文档json格式(记录)--字段
在mysql数据库里面
创建数据库,在数据库创建表(表里面包含字段和记录..)
在es里面操作
创建索引库,在索引库创建类型(相当于mysql里面表),在每个类型里面有很多文档,每个文档里面有字段
-
索引库
操作es首先创建索引库,相当于mysql里面数据库
-
Type 类型
在es索引库操作类型,类型相当于mysql数据库里面表
目前使用es是8.x,在es8.x版本里面不支持自己创建类型,只能使用默认的类型,名称固定的: _doc
通俗描述:在es的索引库里面只有一个类型(表)
-
文档
在索引库的类型里面有很多文档,文档格式是json数据格式
-
字段(Field)
在每个文档里面有很多字段,字段对应具体值
-
映射(Mapping)
mapping是处理数据的方式和规则方面做一些限制,如:某个字段的数据类型、默认值
四、ES基础操作
1、分词器基本使用
-
es官方提供分词器,但是对于中文没有很好支持,所以为了中文分词,使用第三方分词工具,目前使用工具 IK分词器
-
es官方提供的分词器,不支持中文,单字分割
IK分词器:ik_smart 和 ik_max_word
2.索引操作
#创建索引库 PUT /my_index #查看所有索引 GET /_cat/indices?v #查看单个索引 get /my_test #删除索引库 DELETE /my_test
3.文档基本操作
# 添加创建文档
PUT /my_test/_doc/1
{
"title": "小米手机",
"category": "小米",
"images": "http://www.gulixueyuan.com/xm.jpg",
"price": 3999
}
# 查看文档
GET /my_test/_doc/1
# 查看索引文档
GET /my_test/_search
#修改文档
PUT /my_test/_doc/1
{
"title": "小米手机",
"category": "小米",
"images": "http://www.gulixueyuan.com/xm.jpg",
"price": 4500
}
#修改局部属性
POST /my_test/_update/1
{
"doc": {
"price": 4500
}
}
# 删除
DELETE /my_test/_doc/1
4.映射mapping
-
创建数据库表需要设置字段名称,类型,长度,约束等;
-
es 的索引库也一样,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)。
-
es 中能分词的字段,这个字段数据类型必须是 text!keyword 不分词!
-
查看映射 GET /my_test/_mapping
(1)动态映射
-- 根据添加文档数据,自动识别类型,创建映射
(2)静态映射
-- 在创建索引库时候,就设置好字段类型或者约束等信息
PUT /my_index
{
"mappings": {
"properties": {
"title": {
"type": "text",
"index": true,
"store": true,
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart" # 利用ik分词器对字段进行分词
},
"category": {
"type": "keyword", # keyword表示不进行分词
"index": true,
"store": true
},
"images": {
"type": "keyword",
"index": true,
"store": true
},
"price": {
"type": "integer",
"index": true,
"store": true
}
}
}
}
五、DSL高级查询
-
Query DSL概述: Domain Specific Language(领域专用语言),Elasticsearch提供了基于JSON的DSL来定义查询。
-
DSL作用:专门用在es里面,查询es里面json格式文档
创建索引库时,应该使用静态映射(可以指定分词规则);因为动态映射使用的是默认分词器,不支持ik分词器
若term关键字精确查询的字段,还进行了分词,会导致查询的结果为空,因为term和分词器会有冲突。
1.查询所有文档 match_all
# 查询所有文档
POST /my_index/_search
{
"query": {
"match_all": {}
}
}
2.匹配查询 match
# 匹配查询
POST /my_index/_search
{
"query": {
"match": {
"title": "华为智能手机"
}
}
}
3.多字段匹配 multi_match
# 多字段匹配
POST /my_index/_search
{
"query": {
"multi_match": {
"query": "华为智能手机",
"fields": ["title","category"]
}
}
}
4.精确查询 term
-
如果使用term不能进行分词查询,查询字段不能设置分词器规则,如果设置字段分词器,term和分词器冲突了,结果不正确,为空
-
term关键字和分词器有冲突的,不能一起使用
# 关键字精确查询
## 不进行分词查询
POST /my_index/_search
{
"query": {
"term": {
"title": {
"value": "华为手机"
}
}
}
}
5.范围查询 range
范围查询使用range。
-
gte: 大于等于
-
lte: 小于等于
-
gt: 大于
-
lt: 小于
# 范围查询
POST /my_index/_search
{
"query": {
"range": {
"price": {
"gte": 5500,
"lte": 6000
}
}
}
}
6.指定返回字段 _source
# 指定返回字段
POST /my_index/_search
{
"query": {
"range": {
"price": {
"gte": 5500,
"lte": 6000
}
}
},
"_source": ["title","category"]
}
7.组合查询 bool
es里面实现组合查询,使用关键字 bool
bool 各个条件之间有and,or或not的关系
-
must: 各个条件bool都必须满足,所有条件是and的关系
-
should: 各个条件有一个满足即可,即各条件是or的关系
-
must_not: 不满足所有条件,即各条件是not的关系
-
filter: 与must效果等同,但是它不计算得分,效率更高点。
must (and关系)
## must and关系
POST /my_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "华为"
}
},
{
"range": {
"price": {
"gte": 5000,
"lte": 6000
}
}
}
]
}
}
}
should (or关系)
# should or关系
POST /my_index/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "华为"
}
},
{
"range": {
"price": {
"gte": 2000,
"lte": 6000
}
}
}
]
}
}
}
must not (条件都不满足)
POST /my_index/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"title": "华为"
}
},
{
"range": {
"price": {
"gte": 3000,
"lte": 5000
}
}
}
]
}
}
}
filter (效率高,不计得分)
-
filter和must效果相同的,也是and关系
-
但是filter和must区别是:filter查询过程中不计算得分(匹配度),效率更高
POST /my_index/_search
{
"query": {
"bool": {
"filter": [
{
"match": {
"title": "华为"
}
}
]
}
}
}
8.聚合操作
max
# 聚合 max
POST /my_index/_search
{
"query": {
"match_all": {}
},
"size": 0,
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
min
POST /my_index/_search
{
"query": {
"match_all": {}
},
"size": 0,
"aggs": {
"min_price": {
"min": {
"field": "price"
}
}
}
}
avg
POST /my_index/_search
{
"query": {
"match_all": {}
},
"size": 0,
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
sum
POST /my_index/_search
{
"query": {
"match_all": {}
},
"size": 0,
"aggs": {
"sum_price": {
"sum": {
"field": "price"
}
}
}
}
stats
POST /my_index/_search
{
"query": {
"match_all": {}
},
"size": 0,
"aggs": {
"stats_price": {
"stats": {
"field": "price"
}
}
}
}
9.分组 -- terms
# 聚合 分组
POST /my_index/_search
{
"query": {
"match_all": {}
},
"size": 0,
"aggs": {
"groupby_category": {
"terms": {
"field": "category",
"size": 10
}
}
}
}
10.排序 -- sort
# 排序
POST /my_index/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "asc"
}
}
]
}
11.分页 -- from+size
# limit后面第一个参数:数据开始位置 (当前页-1)*每页显示记录数 # limit后面第二个参数:每页显示记录数 select * from users limit 0,3
分页的两个关键属性:from、size。
-
from: 当前页的起始索引,默认从0开始。 from = (pageNum - 1) * size
-
size: 每页显示多少条
# 分页
POST /my_index/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 2
}
12.高亮查询 -- highlight
# 高亮显示
POST /my_index/_search
{
"query": {
"match": {
"title": "华为手机"
}
},
"highlight": {
"fields": {
"title": {}
},
"pre_tags": ["<font color:#e4393c>"],
"post_tags": ["</font>"]
}
}
六、nested 介绍
-
在es创建映射时候,字段支持很多种类型,比如text、long等等,除了这些常见类型之后,有特殊数据类型:nested
-
如果数据有复杂数据,默认使用Object类型
-
比如复杂数据 [{...},{....}]
-
使用特殊类型nested,将数组中的每个对象索引为单独的隐藏文档
PUT my_comment_index
{
"mappings": {
"properties": {
"comments": {
"type": "nested" // 因为comments包含复杂数据,故设置字段的特殊数据类型映射
}
}
}
}
PUT my_comment_index/_doc/1
{
"title": "狂人日记",
"body": "《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国国民精神的麻木愚昧颇感痛切。",
"comments": [
{
"name": "张三",
"age": 34,
"rating": 8,
"comment": "非常棒的文章",
"commented_on": "30 Nov 2023"
},
{
"name": "李四",
"age": 38,
"rating": 9,
"comment": "文章非常好",
"commented_on": "25 Nov 2022"
},
{
"name": "王五",
"age": 33,
"rating": 7,
"comment": "手动点赞",
"commented_on": "20 Nov 2021"
}
]
}
GET /my_comment_index/_search
{
"query": {
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{
"match": {
"comments.name": "李四"
}
},
{
"match": {
"comments.age": 34
}
}
]
}
}
}
}
}
查询结果:居然正常的响应结果了
原因分析:comments 字段默认的数据类型是Object,故我们的文档内部存储为: { "title": [ 狂人日记], "body": [ 《狂人日记》是一篇象征性和寓意很强的小说,当时... ], "comments.name": [ 张三, 李四, 王五 ], "comments.comment": [ 非常棒的文章,文章非常好,王五,... ], "comments.age": [ 33, 34, 38 ], "comments.rating": [ 7, 8, 9 ] }
我们可以清楚地看到,comments.name和comments.age之间的关系已丢失。这就是为什么我们的文档匹配李四和34的查询。
结果发现没有返回任何的文档,这是何故?
当将字段设置为nested 嵌套对象将数组中的每个对象索引为单独的隐藏文档,这意味着可以独立于其他对象查询每个嵌套对象。文档的内部表示:
{ { "comments.name": [ 张三], "comments.comment": [ 非常棒的文章 ], "comments.age": [ 34 ], "comments.rating": [ 9 ] }, { "comments.name": [ 李四], "comments.comment": [ 文章非常好 ], "comments.age": [ 38 ], "comments.rating": [ 8 ] }, { "comments.name": [ 王五], "comments.comment": [手动点赞], "comments.age": [ 33 ], "comments.rating": [ 7 ] }, { "title": [ 狂人日记 ], "body": [ 《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国... ] } }
每个内部对象都在内部存储为单独的隐藏文档。 这保持了他们的领域之间的关系。
七、Java Api操作ES
-
Java操作es有两种方式
第一种方式:使用es自带原生api进行操作
第二种方式:SpringBoot整合es操作(SpringData)
-
使用原生api操作适合复杂查询操作
-
使用SpringData适合简单es操作
(一)es原生api操作
第一步 创建springboot,引入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>co.elastic.clients</groupId> <artifactId>elasticsearch-java</artifactId> <version>8.5.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
第二步 创建启动类,初始化ElasticsearchClient对象
@SpringBootApplication
public class EsApplication {
public static void main(String[] args) {
SpringApplication.run(EsApplication.class,args);
}
@Bean
public ElasticsearchClient buildElasticsearchClient() {
BasicCredentialsProvider credsProv = new BasicCredentialsProvider();
credsProv.setCredentials(
AuthScope.ANY, new UsernamePasswordCredentials("elastic", "111111")
);
RestClient restClient = RestClient
.builder(HttpHost.create("http://192.168.6.128:9200"))
.setHttpClientConfigCallback(hc -> hc
.setDefaultCredentialsProvider(credsProv)
)
.build();
// Create the transport with a Jackson mapper
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
// And create the API client
ElasticsearchClient esClient = new ElasticsearchClient(transport);
return esClient;
}
}
第三步 编写测试方法
-
同一级别点方法,下一级别lamba表达式
@SpringBootTest
public class ElasticsearchDemoApplicationTests {
//注入ElasticsearchClient
@Autowired
private ElasticsearchClient elasticsearchClient;
@Test
public void find() throws IOException {
//创建SearchRequest对象
SearchRequest.Builder request = new SearchRequest.Builder();
request.index("my_index").query(
f->f.match(
f1->f1.field("title").query("华为")));
SearchResponse<Object> response =
elasticsearchClient.search(request.build(), Object.class);
// elasticsearchClient.search(dsl语句,返回结果类型)
// POST /my_index/_search
// {
// "query": {
// "match": {
// "title": "华为智能手机"
// }
// }
// }
// SearchResponse<Object> response = elasticsearchClient.search(
// s -> s.index("my_index").query(
// f -> f.match(
// f1 -> f1.field("title").query("华为智能手机")
// )),
// Object.class);
//List<Hit<Object>> hits = response.hits().hits();/
for (Hit<Object> hit : response.hits().hits()) {
System.out.println(hit.source());
}
}
}
(二) SpringData整合ES
-
在Spring里面有模块 SpringData,使用SpringData可以操作数据库,包含mysql,Redis,ES.......
第一步 创建SpringBoot项目,引入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
第二步 创建启动类
@SpringBootApplication
public class EsDataApplication {
public static void main(String[] args) {
SpringApplication.run(EsDataApplication.class,args);
}
}
第三步 创建配置文件
server: port: 8502 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志 feign: sentinel: enabled: true spring: main: allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册 cloud: sentinel: transport: dashboard: localhost:8080 openfeign: lazy-attributes-resolution: true client: config: default: connectTimeout: 30000 readTimeout: 30000 loggerLevel: basic elasticsearch: uris: http://192.168.6.128:9200 username: elastic password: 111111 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8
第四步 创建实体类
@Data
@Document(indexName = "user") //索引库名
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements Serializable {
@Id
private Long id;
// es 中能分词的字段,这个字段数据类型必须是 text!keyword 不分词! analyzer = "ik_max_word"
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String username;
@Field(type = FieldType.Keyword)
private String address;
@Field(type = FieldType.Long)
private Long age;
@Field(type = FieldType.Date,format = DateFormat.date_time, pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
第五步 创建interface继承ElasticsearchRepository
// ElasticsearchRepository<实体类,id类型>
public interface UserRepository extends ElasticsearchRepository<User,Long> {
}
第六步 测试方法
@SpringBootTest
public class EsApplicationTest {
@Autowired
private UserRepository userRepository;
@Autowired
private ElasticsearchClient elasticsearchClient;
//添加 / 修改
@Test
public void add() {
User user = new User();
user.setId(1l);
user.setUsername("lucyatguigu");
user.setAddress("China");
user.setAge(20l);
user.setCreateTime(new Date());
userRepository.save(user);
}
//删除
@Test
public void delete() {
User user = new User();
user.setId(1l);
userRepository.delete(user);
}
}
171万+

被折叠的 条评论
为什么被折叠?



