ElasticSearch基础知识点整理

一、概述

  • 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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值