2022-05-26  2022-05-26    11088 字   23 分钟

1 认识ElasticSearch

(一)基于数据库查询的问题

需求: 查询title中包含‘手机’ 的信息?

实现:SELECT * FROM tb_item WHERE title LIKE ‘%手机%’;

问题:

(1)性能低:如果是模糊查询,坐标有通配符,不会走索引,会进行全表扫描,性能低。

(2)功能弱:如果以”华为手机“作为条件,查询不出来数据。

解决:使用ES就可以倒排索引就可以解决上述存在的问题

(二)倒排索引

1 倒排索引概念

倒排索引:将文档进行分词,形成词条和id的对应关系即为反向索引。

2 倒排索引案例

以唐诗为例,所处包含“前”的诗句?

正向索引:由《静夜思》–>床前明月光—>“前”字

key value
«静夜思» 窗前明月光,疑是地上霜…
«水调歌头» 明月几时有,把酒问青天…
«春晓» 春眠不觉晓,处处闻啼鸟…

反向索引:

“床前明月光”–> 分词

将一段文本按照一定的规则,拆分为不同的词条(term)

案例:明月几时有,分词。分词效果如下

(三)ElasticSearch存储和查询的原理

index(索引):相当于mysql的库

映射:相当于mysql 的表结构

document(文档):相当于mysql的表中的数据

Es使用倒排索引,对title 进行分词

  1. 使用“手机”作为关键字查询

    生成的倒排索引中,词条会排序,形成一颗树形结构,提升词条的查询速度

  2. 使用“华为手机”作为关键字查询

    华为:1,3

    手机:1,2,3

(四)ElasticSearch概念详解

ElasticSearch是一个基于Lucene的搜索服务器

是一个分布式、高扩展、高实时的搜索与数据分析引擎

基于RESTful web接口

Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎

官网:https://www.elastic.co/

应用场景

搜索:海量数据的查询

日志数据分析

实时数据分析

2 安装ElasticSearch

(一)ElasticSearch安装

1 上传ElasticSearch安装包

2 执行解压操作

 #创建一个安装目录 
 mkdir /usr/local/es
 #将elasticsearch-7.4.0-linux-x86_64.tar.gz解压到/usr/local/es 目录下. -C 大写
 tar -zxvf elasticsearch-7.4.0-linux-x86_64.tar.gz  -C /usr/local/es

3 创建普通用户

因为安全问题,Elasticsearch 不允许root用户直接运行,所以要创建新用户,在root用户中创建新用户,执行如下命令:

useradd offcn  #新增offcn账户
passwd offcn   #为offcn账户设置密码

4 为新用户授权,如下图

chown -R offcn:offcn /usr/local/es/elasticsearch-7.4.0 #文件夹所有者

将 /usr/local/es/elasticsearch-7.4.0文件夹授权给offcn用户,由上图可见,我们的文件夹权限赋给了offcn

5 修改elasticsearch.yml文件

vim /usr/local/es/elasticsearch-7.4.0/config/elasticsearch.yml 
# ======================== Elasticsearch Configuration =========================
cluster.name: my-application
node.name: node-1
network.host: 0.0.0.0
http.port: 9200
cluster.initial_master_nodes: ["node-1"]

cluster.name:配置elasticsearch的集群名称,默认是elasticsearch。建议修改成一个有意义的名称

node.name:节点名,elasticsearch会默认随机指定一个名字,建议指定一个有意义的名称,方便管理

network.host:设置为0.0.0.0允许外网访问

http.port:Elasticsearch的http访问端口

cluster.initial_master_nodes:初始化新的集群时需要此配置来选举master

6 修改虚拟机的配置文件

新创建的offcn用户最大可创建文件数太小,最大虚拟内存太小,切换到root用户,编辑下列配置文件, 添加类似如下内容

# 切换到root用户
su root 

#1. ===最大可创建文件数太小=======
vim /etc/security/limits.conf 

# 在文件末尾中增加下面内容
offcn soft nofile 65536
offcn hard nofile 65536

#===========
vim /etc/security/limits.d/20-nproc.conf
# 在文件末尾中增加下面内容
offcn soft nofile 65536
offcn hard nofile 65536
*  hard    nproc     4096
# 注:* 代表Linux所有用户名称

#2. ===最大虚拟内存太小=======
vim /etc/sysctl.conf
# 在文件中增加下面内容
vm.max_map_count=655360
# 重新加载,输入下面命令:
sysctl -p

7 启动ElasticSearch

su offcn    #切换到offcn 用户启动
cd /usr/local/es/elasticsearch-7.4.0/bin
./elasticsearch #启动

通过上图我们可以看到elasticsearch已经成功启动

8 查看ElasticSearch是否启动

ps -ef|grep elastic

(二)访问ElasticSearch

1 关闭防火墙

#暂时关闭防火墙
systemctl  stop  firewalld.service

# 或者
#永久设置防火墙状态
systemctl enable firewalld.service  #打开防火墙永久性生效,重启后不会复原 
systemctl disable firewalld.service #关闭防火墙,永久性生效,重启后不会复原 

2 浏览器访问

说明: 此时elasticsearch已成功启动

重点几个关注下即可:
number" : "7.4.0"   表示elasticsearch版本
lucene_version" : "8.2.0"  表示lucene版本
name : 默认启动的时候指定了 ES 实例名称
cluster_name : 默认名为 elasticsearch

3 ElasticSearch辅助工具安装

(一) Postman 介绍和安装

Postman是一个http模拟请求的工具。

官网介绍:“Modern software is built on APIs,Postman helps you develop APIs faster”

看得出来,它是一个专门测试 API 的工具,Postman 提供功能强大的 Web API 和 HTTP 请求的调试,它能够发送任何类型的HTTP 请求 (GET, POST, PUT, DELETE…),并且能附带任何数量的参数和 Headers。不仅如此,它还提供测试数据和环境配置数据的导入导出。

进入官网www.getpostman.com,下载

(二) Kibana介绍&安装

1 什么是Kibana

Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。

Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示Elasticsearch查询动态。

2 Kibana安装

2.1 上传kibana

2.2 解压kibana到安装目录

#创建安装目录
mkdir /usr/local/kibana
#将kibana解压到安装目录
tar -zxvf kibana-7.4.0-linux-x86_64.tar.gz -C /usr/local/kibana

2.3 修改kibana配置

vim /usr/local/kibana/kibana-7.4.0-linux-x86_64/config/kibana.yml
server.port: 5601
server.host: "0.0.0.0"
server.name: "kibana-offcn"
elasticsearch.hosts: ["http://127.0.0.1:9200"]
elasticsearch.requestTimeout: 99999

server.port:http访问端口

server.host:ip地址,0.0.0.0表示可远程访问

server.name:kibana服务名

elasticsearch.hosts:elasticsearch地址

elasticsearch.requestTimeout:请求elasticsearch超时时间,默认为30000,此处可根据情况设置

2.4 启动kibana

由于kibana不建议使用root用户启动,如果用root启动,需要加–allow-root参数

# 切换到kibana的bin目录
cd /usr/local/kibana/kibana-7.4.0-linux-x86_64/bin
# 启动
./kibana --allow-root

启动成功。

2.5 访问kibane

访问地址: http://192.168.126.20:5601/

(三)IK分词器安装

1 环境准备

Elasticsearch 要使用 ik,就要先构建 ik 的 jar包,这里要用到 maven 包管理工具,而 maven 需要java 环境,而 Elasticsearch 内置了jdk, 所以可以将JAVA_HOME设置为Elasticsearch 内置的jdk

1.1 设置JAVA_HOME

vim /etc/profile
# 在profile文件末尾添加
#java environment
export JAVA_HOME=/usr/local/es/elasticsearch-7.4.0/jdk
export PATH=$PATH:${JAVA_HOME}/bin

# 保存退出后,重新加载profile
source /etc/profile

1.2 下载maven安装包

#安装包较小,直接使用wget命令下载,下载的默认位置,当前所在的位置
wget http://mirror.cc.columbia.edu/pub/software/apache/maven/maven-3/3.1.1/binaries/apache-maven-3.1.1-bin.tar.gz 

1.3 解压maven安装包

#创建maven的安装包
mkdir /usr/local/maven/

#解压到指定的目录
tar -zxvf apache-maven-3.1.1-bin.tar.gz -C /usr/local/maven/

1.4 设置软连接

ln -s apache-maven-3.1.1 maven 

1.5 设置path

#打开文件 
vim  /etc/profile.d/maven.sh
#将下面的内容复制到文件,保存 
export MAVEN_HOME=/usr/local/maven/maven  
export PATH=${MAVEN_HOME}/bin:${PATH} 
#设置好Maven的路径之后,需要运行下面的命令使其生效
source /etc/profile.d/maven.sh

1.6 验证maven是否安装成功

mvn -v

1.7 设置maven的阿里云镜像

<mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>
</mirror> 

2 安装IK分词器

2.1 下载IK分词器

wget https://github.com/medcl/elasticsearch-analysis-ik/archive/v7.4.0.zip

2.2 解压IK到指定目录

由于这里是zip包不是gz包,所以我们需要使用unzip命令进行解压,如果本机环境没有安装unzip,请执行:

yum install zip 
yum install unzip

解压IK

unzip v7.4.0.zip

2.3 编译jar包

# 切换到 elasticsearch-analysis-ik-7.4.0目录
cd elasticsearch-analysis-ik-7.4.0/
#打包,注意第一次使用maven会比较慢,提前配置好阿里云镜像
mvn package

2.4 jar包移动

#切换目录
cd /usr/local/es/elasticsearch-7.4.0/plugins/
#新建目录
mkdir analysis-ik
cd analysis-ik
#执行拷贝
cp -R /opt/elasticsearch-analysis-ik-7.4.0/target/releases/elasticsearch-analysis-ik-7.4.0.zip      /usr/local/es/elasticsearch-7.4.0/plugins/analysis-ik
#执行解压
unzip  /usr/local/es/elasticsearch-7.4.0/plugins/analysis-ik/elasticsearch-analysis-ik-7.4.0.zip

2.5 拷贝词典

将elasticsearch-analysis-ik-7.4.0目录下的config目录中的所有文件 拷贝到elasticsearch的config目录

cp -R /opt/elasticsearch-analysis-ik-7.4.0/config/*   /usr/local/es/elasticsearch-7.4.0/config

2.6 重新启动

配置成功之后,重新启动ES,重新启动Kibana,使用IK分词器。

(四)使用IK分词器

1 IK分词器使用

IK分词器有两种分词模式:ik_max_word和ik_smart模式。

1.1ik_max_word

会将文本做最细粒度的拆分,比如会将“乒乓球明年总冠军”拆分为“乒乓球、乒乓、球、明年、总冠军、冠军。

#方式一 ik_max_word
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "乒乓球明年总冠军"
}

ik_max_word分词器执行如下:

{
  "tokens" : [
    {
      "token" : "乒乓球",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "乒乓",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "球",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "CN_CHAR",
      "position" : 2
    },
    {
      "token" : "明年",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "总冠军",
      "start_offset" : 5,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "冠军",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 5
    }
  ]
}

1.2 ik_smart

会做最粗粒度的拆分,比如会将“乒乓球明年总冠军”拆分为乒乓球、明年、总冠军。

#方式二ik_smart
GET /_analyze
{
  "analyzer": "ik_smart",
  "text": "乒乓球明年总冠军"
}

ik_smart分词器执行如下:

{
  "tokens" : [
    {
      "token" : "乒乓球",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "明年",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "总冠军",
      "start_offset" : 5,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 2
    }
  ]
}

由此可见 使用ik_smart可以将文本"text": “乒乓球明年总冠军"分成了【乒乓球】【明年】【总冠军】

这样看的话,这样的分词效果达到了我们的要求。

1.3 使用IK分词器查询文档

词条查询:term

​ 词条查询不会分析查询条件,只有当词条和查询字符串完全匹配时才匹配搜索

全文查询:match

​ 全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集

1.创建索引,添加映射,并指定分词器为ik分词器

PUT person2
{
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      },
      "address": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
        "age":{
         "type":"integer"
        }
    }
  }
}

2.添加文档

POST /person2/_doc/1
{
  "name":"张三",
  "age":18,
  "address":"北京海淀区"
}

POST /person2/_doc/2
{
  "name":"李四",
  "age":18,
  "address":"北京朝阳区"
}

POST /person2/_doc/3
{
  "name":"王五",
  "age":18,
  "address":"北京昌平区"
}

POST /person2/_doc/4
{
  "name":"赵六",
  "age":18,
  "address":"昌平区慧聪园"
}

3.查询映射

GET person2

4.查看分词效果

GET _analyze
{
  "analyzer": "ik_max_word",
  "text": "北京海淀"
}

5.词条查询:term

查询person2中匹配到"北京"两字的词条

GET /person2/_search
{
  "query": {
    "term": {
      "address": {
        "value": "北京"
      }
    }
  }
}

6.全文查询:match

全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集

GET /person2/_search
{
  "query": {
    "match": {
      "address":"北京昌平"
    }
  }
}

4 ElasticSearch核心概念

索引(index)

ElasticSearch存储数据的地方,可以理解成关系型数据库中的数据库概念。

映射(mapping)

mapping定义了每个字段的类型、字段所使用的分词器等。相当于关系型数据库中的表结构。

文档(document)

Elasticsearch中的最小数据单元,常以json格式显示。一个document相当于关系型数据库中的一行数据。

倒排索引

一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,对应一个包含它的文档id列表。

类型(type)

一种type就像一类表。如用户表、角色表等。在Elasticsearch7.X默认type为_doc

ES 5.x中一个index可以有多种type。
ES 6.x中一个index只能有一种type。
ES 7.x以后,将逐步移除type这个概念,现在的操作已经不再使用,默认_doc

5 脚本操作ElasticSearch

(一)RESTful风格介绍

Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,可以降低开发的复杂性,提高系统的可伸缩性。

特点:

1.基于http协议

2.使用XML格式定义或JSON格式定义

3.每一个URI代表1种资源。

4.客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:

GET:用来获取资源

POST:用来新建资源(也可以用于更新资源)

PUT:用来更新资源

DELETE:用来删除资源

(二)操作索引

1 创建索引(PUT)

http://ip:端口/索引名称
案例: 
http://192.168.126.20:9200/index_01

2 查询索引(GET)

GET http://ip:端口/索引名称  # 查询单个索引信息
GET http://ip:端口/索引名称1,索引名称2...  # 查询多个索引信息
GET http://ip:端口/_all  # 查询所有索引信息

3 删除索引(DELETE)

DELETE http://ip:端口/索引名称

4 关闭、打开索引(了解)

POST http://ip:端口/索引名称/_close  
POST http://ip:端口/索引名称/_open 

(三)ElasticSearch 数据类型

1 简单数据类型

数据类型 备注
字符串 text :会分词,不支持聚合 相当于mysql 中的sum(求和)
keyword:不会分词,将全部内容作为一个词条,支持聚合
数值 byte, short, integer, long, float, double
布尔 boolean
范围类型 integer_range, float_range, long_range, double_range, date_range
日期 date

2 复杂数据类型

数据类型 备注
数组: [ ] nested (for arrays of JSON objects 数组类型的JSON对象)
对象:{ } object(for single JSON objects 单个JSON对象)

(四)操作映射

1 查询映射

GET /索引的名称/_mapping

2 创建映射

2.1 创建索引后添加映射

#创建一个person 索引
PUT person
#查询person 索引的mapping 是空
GET /person/_mapping
#给person 添加索引
PUT /person/_mapping
{
  "properties":{
    "name":{
      "type":"text"
    },
    "age":{
      "type":"integer"
    }
  }
}

2.2 创建索引并添加映射

#创建索引并且添加映射
PUT person2
{
  "mappings": {
    "properties": {
      "name":{
        "type":"text"
      },
      "age":{
        "type": "integer"
      }
    }
  }
}
#查询映射
GET person2/_mapping

添加字段

#给创建好的索引添加字段
PUT /person2/_mapping
{
  "properties":{
    "address":{
      "type":"text"
    }
  }
}

总结:在实际当中 ,我们很少操作映射,一般都是使用API方式操作文档,所以以上的操作只需要能看懂就可以。

(五) 操作文档

1 查询文档

#根据id查询具体的文档
语法: GET /索引的名称/_doc/索引的id 
案例: GET /person1/_doc/1  

#查询所有的文档
语法:GET /索引名称/_search 
案例:GET /person1/_search

2 添加文档,指定id

#语法:POST /索引的名称/文档的类型/文档的id
POST /person2/_doc/1
{
  "name":"张三",
  "age":18,
  "address":"北京"
}

#查询文档
GET /person1/_doc/1

添加文档,不指定id

#添加文档,不指定id
POST /person1/_doc/
{
  "name":"张三",
  "age":18,
  "address":"北京"
}

#查询所有文档
GET /person1/_search

3 删除文档

#删除指定id文档
DELETE /person1/_doc/1

7 ElasticSearch JavaAPI

(一)SpringBoot整合ElasticSearch环境搭建

1 搭建SpringBoot工程

2 引入ElasticSearch相关坐标

<!--引入es的坐标-->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.4.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.4.0</version>
</dependency>

3 编写核心配置类

编写核心配置文件:

elasticsearch:
  hostname: 192.168.126.20
  port: 9200

编写核心配置类

@Configuration
@ConfigurationProperties(prefix="elasticsearch")
public class ElasticSearchConfig {

    private String host;
    private int port;

    @Bean
    public RestHighLevelClient client(){
        return new RestHighLevelClient(RestClient.builder(
                new HttpHost(host,port,"http")
        ));
    }  
    
    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}

4 测试客户端对象

注意:使用@Autowired注入RestHighLevelClient 如果报红线,则是因为配置类所在的包和测试类所在的包,包名不一致造成的

@SpringBootTest
class SpringElasticsearchApplicationTests {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Test
    void contextLoads() {
        System.out.println(restHighLevelClient);
    }
}

(二)操作ElasticSearch

1 添加索引

/**
 * 添加索引:
 * @throws Exception
 */
@Test
public void addIndex() throws Exception{
   //1:使用restHighLevelClient获取操作索引的对象
   IndicesClient indices = restHighLevelClient.indices();
   //2: 具体操作索引,并且获得返回值
   //2.1 创建索引对象,设置索引的名称
   CreateIndexRequest createIndexRequest = new CreateIndexRequest("offcn");
   //2.2 创建索引,获得返回值
   CreateIndexResponse createIndexResponse = indices.create(createIndexRequest, RequestOptions.DEFAULT);
   //3:根据返回值判断返回结果
   System.out.println(createIndexResponse.isAcknowledged());
}

2 添加索引,并添加映射

/**
  * 创建索引并且添加映射
  * @throws IOException
  */
@Test
public void addIndexAndMapping() throws IOException {
    //1.使用client获取操作索引的对象
    IndicesClient indicesClient = restHighLevelClient.indices();
    //2.具体操作,获取返回值
    CreateIndexRequest createRequest = new CreateIndexRequest("offcn");
    //2.1 设置mappings
    String mapping = "{\n" +
        "      \"properties\" : {\n" +
        "        \"address\" : {\n" +
        "          \"type\" : \"text\",\n" +
        "          \"analyzer\" : \"ik_max_word\"\n" +
        "        },\n" +
        "        \"age\" : {\n" +
        "          \"type\" : \"long\"\n" +
        "        },\n" +
        "        \"name\" : {\n" +
        "          \"type\" : \"keyword\"\n" +
        "        }\n" +
        "      }\n" +
        "    }";
    createRequest.mapping(mapping, XContentType.JSON);
    //2.2执行创建,返回对象
    CreateIndexResponse response = indicesClient.create(createRequest, RequestOptions.DEFAULT);
    //3.根据返回值判断结果
    System.out.println(response.isAcknowledged());
}

3 查询、删除、判断索引

3.1 查询索引

/**
  * 查询索引
  */
@Test
public void queryIndex() throws IOException {
    //1:使用client获取操作索引的对象
    IndicesClient indices = restHighLevelClient.indices();
    //2:获得对象,执行具体的操作
    //2.1 创建获取索引的请求对象,设置索引名称
    GetIndexRequest getReqeust = new GetIndexRequest("offcn");
    //2.2 执行查询,获得返回值
    GetIndexResponse response = indices.get(getReqeust, RequestOptions.DEFAULT);
    //3:获取结果,遍历
    Map<String, MappingMetaData> mappings = response.getMappings();
    for (String key : mappings.keySet()) {
        System.out.println(key + ":" + mappings.get(key).getSourceAsMap());
    }
}  

3.2 删除索引

/**
  * 删除索引
  */
@Test
public void deleteIndex() throws IOException {
    IndicesClient indices = restHighLevelClient.indices();
    DeleteIndexRequest deleteRequest = new DeleteIndexRequest("offcn");
    AcknowledgedResponse response = indices.delete(deleteRequest, RequestOptions.DEFAULT);
    System.out.println(response.isAcknowledged());
}

3.3 索引是否存在

/**
  * 判断索引是否存在
  */
@Test
public void existIndex() throws IOException {
    IndicesClient indices = restHighLevelClient.indices();
    GetIndexRequest getRequest = new GetIndexRequest("person");
    boolean exists = indices.exists(getRequest, RequestOptions.DEFAULT);
    System.out.println(exists);
}

4 操作文档

4.1 添加文档

添加文档,使用map作为数据

/**
  * 添加文档,使用map作为数据
  */
@Test
public void addDoc() throws IOException {
    //数据对象,map
    Map data = new HashMap();
    data.put("address", "北京昌平");
    data.put("name", "马同志");
    data.put("age", 20);

    //1:获取操作文档的对象
    IndexRequest request = new IndexRequest("offcn").id("1").source(data);
    //2:添加数据,获取结果
    IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
    //3:打印响应结果
    System.out.println(response.getId());
}

添加文档,使用对象作为数据

/**
  * 添加文档,使用对象作为数据
  */
@Test
public void addDoc2() throws IOException {
    //数据对象,javaObject
    Person p = new Person();
    p.setId("2");
    p.setName("小胖2222");
    p.setAge(30);
    p.setAddress("陕西西安");

    //将对象转为json
    String data = JSON.toJSONString(p);
    //1:获取操作文档的对象
    IndexRequest request = new IndexRequest("offcn").id(p.getId()).source(data, XContentType.JSON);
    //2:添加数据,获取结果
    IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
    //3:打印响应结果
    System.out.println(response.getId());
}

4.2 修改文档:添加文档时,如果id存在则修改,id不存在则添加

/**
  * 修改文档:添加文档时,如果id存在则修改,id不存在则添加
  */
@Test
public void updateDoc() throws IOException {
    //数据对象,map
    Map data = new HashMap();
    data.put("address", "北京昌平");
    data.put("name", "朱同志");
    data.put("age", 20);

    //1:获取操作文档的对象
    IndexRequest request = new IndexRequest("offcn").id("1").source(data);
    //2:添加数据,获取结果
    IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
    //3:打印响应结果
    System.out.println(response.getId());
}

4.3 根据id查询文档

/**
  * 根据id查询文档
  */
@Test
public void findDocById() throws IOException {
    GetRequest getReqeust = new GetRequest("offcn", "1");
    //getReqeust.id("1");
    GetResponse response = restHighLevelClient.get(getReqeust, RequestOptions.DEFAULT);
    //获取数据对应的json
    System.out.println(response.getSourceAsString());
}

4.4 根据id删除文档

/**
  * 根据id删除文档
  */
@Test
public void delDoc() throws IOException {
    DeleteRequest deleteRequest = new DeleteRequest("offcn", "1");
    DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
    System.out.println(response.getId());
}

(三)ElasticSearch的批量操作

1 bulk 批量操作脚本实现

需求:同时完成删除,更新,添加操作

POST _bulk
{"delete":{"_index":"person1","_id":"4"}}
{"create":{"_index":"person1","_id":"5"}}
{"name":"八号","age":18,"address":"北京"}
{"update":{"_index":"person1","_id":"2"}}
{"doc":{"name":"2号"}}

查询运行结果~ 

2 bulk批量操作JavaAPI 实现

/**
     *  Bulk 批量操作
     */
    @Test
    public void test2() throws IOException {

        //创建bulkrequest对象,整合所有操作
        BulkRequest bulkRequest =new BulkRequest();

           /*
        # 1. 删除5号记录
        # 2. 添加6号记录
        # 3. 修改3号记录 名称为 “三号”
         */
        //添加对应操作
        //1. 删除5号记录
        DeleteRequest deleteRequest=new DeleteRequest("person1","5");
        bulkRequest.add(deleteRequest);

        //2. 添加6号记录
        Map<String, Object> map=new HashMap<>();
        map.put("name","六号");
        IndexRequest indexRequest=new IndexRequest("person1").id("6").source(map);
        bulkRequest.add(indexRequest);
        //3. 修改3号记录 名称为 “三号”
        Map<String, Object> mapUpdate=new HashMap<>();
        mapUpdate.put("name","三号");
        UpdateRequest updateRequest=new UpdateRequest("person1","3").doc(mapUpdate);

        bulkRequest.add(updateRequest);
        //执行批量操作

        BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(response.status());
    }

3 导入数据分析&创建索引

需求: 将数据库中Good表的数据导入到ElasticSearch当中

实现步骤:

分析goods表结构

创建goods索引

查询Goods表数据

批量添加到ElasticSearch中

PUT goods
{
	"mappings": {
		"properties": {
			"title": {
				"type": "text",
				"analyzer": "ik_smart"
			},
			"price": { 
				"type": "double"
			},
			"createTime": {
				"type": "date"
			},
			"categoryName": {	
				"type": "keyword"
			},
			"brandName": {	
				"type": "keyword"
			},
	
			"spec": {		
				"type": "object"
			},
			"saleNum": {	
				"type": "integer"
			},
			
			"stock": {	
				"type": "integer"
			}
		}
	}
}

title:商品标题

price:商品价格

createTime:创建时间

categoryName:分类名称。如:家电,手机

brandName:品牌名称。如:华为,小米

spec: 商品规格。如: spec:{“屏幕尺寸”,“5寸”,“内存大小”,“128G”}

saleNum:销量

stock:库存量

插入一条测试数据

POST goods/_doc/1
{
  "title":"小米手机",
  "price":1000,
  "createTime":"2021-12-01",
  "categoryName":"手机",
  "brandName":"小米",
  "saleNum":3000,
  "stock":10000,
  "spec":{
    "网络制式":"移动4G",
    "屏幕尺寸":"4.5"
  }
}

4 导入数据代码实现

    /**
     * 从Mysql 批量导入 elasticSearch
     */
    @Test
    public void test3() throws IOException {
        //1.查询所有数据,mysql
        List<Goods> goodsList = goodsMapper.findAll();
        //2.bulk导入
        BulkRequest bulkRequest=new BulkRequest();
        //2.1 循环goodsList,创建IndexRequest添加数据
        for (Goods goods : goodsList) {
            //2.2 设置spec规格信息 Map的数据   specStr:{}
            String specStr = goods.getSpecStr();
            //将json格式字符串转为Map集合
            Map map = JSON.parseObject(specStr, Map.class);
            //设置spec map
            goods.setSpec(map);
            //将goods对象转换为json字符串
            String data = JSON.toJSONString(goods);
            IndexRequest indexRequest=new IndexRequest("goods").source(data,XContentType.JSON);
            bulkRequest.add(indexRequest);

        }
        BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(response.status());
    }

(四)ElasticSearch高级查询

1 matchAll脚本实现

# 默认情况下,es一次展示10条数据,通过from和size来控制分页
# 查询结果详解

GET goods/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 100
}
GET goods

2 matchAll-JavaAPI

/**
     * 查询所有
     *  1. matchAll
     *  2. 将查询结果封装为Goods对象,装载到List中
     *  3. 分页。默认显示10条
     */
    @Test
    public void matchAll() throws IOException {

        //2. 构建查询请求对象,指定查询的索引名称
        SearchRequest searchRequest=new SearchRequest("goods");

        //4. 创建查询条件构建器SearchSourceBuilder
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();

        //6. 查询条件
        QueryBuilder queryBuilder= QueryBuilders.matchAllQuery();
        //5. 指定查询条件
        sourceBuilder.query(queryBuilder);

        //3. 添加查询条件构建器 SearchSourceBuilder
        searchRequest.source(sourceBuilder);
        // 8 . 添加分页信息  不设置 默认10条
//        sourceBuilder.from(0);
//        sourceBuilder.size(100);
        //1. 查询,获取查询结果

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        //7. 获取命中对象 SearchHits
        SearchHits hits = searchResponse.getHits();

        //7.1 获取总记录数
      Long total= hits.getTotalHits().value;
        System.out.println("总数:"+total);
        //7.2 获取Hits数据  数组
        SearchHit[] hits1 = hits.getHits();
            //获取json字符串格式的数据
        List<Goods> goodsList = new ArrayList<>();
        for (SearchHit searchHit : hits1) {
            String sourceAsString = searchHit.getSourceAsString();
            //转为java对象
            Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            goodsList.add(goods);
        }

        for (Goods goods : goodsList) {
            System.out.println(goods);
        }
    }

3 termQuery 词条查询

term查询和字段类型有关系,首先回顾一下ElasticSearch两个数据类型

ElasticSearch两个数据类型:

text:会分词,不支持聚合
keyword:不会分词,将全部内容作为一个词条,支持聚合

脚本实现:

#term查询: 查询title当中包含  华为
GET /goods/_search
{
  "query": {
    "term": {
      "title": {
        "value": "华为"
      }
    }
  }
}

javaAPI代码实现:

/**
     * termQuery:词条查询
     */
    @Test
    public void testTermQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest("goods");
        SearchSourceBuilder sourceBulider = new SearchSourceBuilder();
        QueryBuilder query = QueryBuilders.termQuery("title","华为");//term词条查询
        sourceBulider.query(query);
        searchRequest.source(sourceBulider);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits searchHits = searchResponse.getHits();
        //获取记录数
        long value = searchHits.getTotalHits().value;
        System.out.println("总记录数:"+value);
        List<Goods> goodsList = new ArrayList<>();
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            //转为java
            Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            goodsList.add(goods);
        }
        for (Goods goods : goodsList) {
            System.out.println(goods);
        }
    }

4 matchQuery

match查询,会对查询条件分词,然后将分词后的查询条件和词条进行等值匹配,默认取并集(OR)

# match查询
GET goods/_search
{
  "query": {
    "match": {
      "title": "华为手机"
    }
  },
  "size": 500
}

match 的默认搜索(or 并集)

例如:华为手机,会分词为 “华为”,“手机” 只要出现其中一个词条都会搜索到

match的 and(交集) 搜索

例如:例如:华为手机,会分词为 “华为”,“手机” 但要求“华为”,和“手机”同时出现在词条

总结:

term query会去倒排索引中寻找确切的term,它并不知道分词器的存在。这种查询适合keywordnumericdate

match query知道分词器的存在。并且理解是如何被分词的

5 模糊查询脚本实现

通配符使用,?代表任意单个字符,*代表0个或者是多个字符
案例: 
"*华*"  包含华字的
"华*"   华字后边多个字符
"华?"  华字后边单个字符
"*华"或"?华" 会引发全表(全索引)扫描 注意效率问题
# wildcard 查询。查询条件分词,模糊查询
GET goods/_search
{
  "query": {
    "wildcard": {
      "title": {
        "value": "华*"
      }
    }
  }
}

6 模糊查询javaAPI

//模糊查询
WildcardQueryBuilder query = QueryBuilders.wildcardQuery("title", "华*");//华后多个字符

7 范围查询脚本实现

# 范围查询
GET goods/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 2000,
        "lte": 3000
      }
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}

8 范围查询API实现

//范围查询 以price 价格为条件
RangeQueryBuilder query = QueryBuilders.rangeQuery("price");
//指定下限
query.gte(2000);
//指定上限
query.lte(3000);
sourceBuilder.query(query);
//排序  价格 降序排列
sourceBuilder.sort("price",SortOrder.DESC);

9 queryString 查询

queryString 多条件查询

会对查询条件进行分词。

然后将分词后的查询条件和词条进行等值匹配

默认取并集(OR)

可以指定多个查询字段

query_string:识别query中的连接符(or 、and)

# queryString
GET goods/_search
{
  "query": {
    "query_string": {
      "fields": ["title","categoryName","brandName"], 
      "query": "华为 AND 手机"
    }
  }
}

simple_query_string:不识别query中的连接符(or 、and),查询时会将 “华为”、“and”、“手机”分别进行查询

GET goods/_search
{
  "query": {
    "simple_query_string": {
      "fields": ["title","categoryName","brandName"], 
      "query": "华为 AND 手机"
    }
  }
}

10 queryString javaAPI代码实现:

QueryStringQueryBuilder query = QueryBuilders.queryStringQuery("华为手机").field("title").field("categoryName")
.field("brandName").defaultOperator(Operator.AND);

11 聚合查询脚本实现

指标聚合:相当于MySQL的聚合函数。max、min、avg、sum等

桶聚合:相当于MySQL的 group by 操作。不要对text类型的数据进行分组,会失败

# 聚合查询
# 指标聚合 聚合函数
GET goods/_search
{
  "query": {
    "match": {
      "title": "手机"
    }
  },
  "aggs": {
    "max_price": {
      "max": {
        "field": "price"
      }
    }
  }
}

# 桶聚合  分组
GET goods/_search
{
  "query": {
    "match": {
      "title": "手机"
    }
  },
  "aggs": {
    "goods_brands": {
      "terms": {
        "field": "brandName",
        "size": 100
      }
    }
  }
}

12 聚合查询JavaAPI

聚合查询:桶聚合,分组查询

  1. 查询title包含手机的数据

  2. 查询品牌列表

    /**
         * 聚合查询:桶聚合,分组查询
         * 1. 查询title包含手机的数据
         * 2. 查询品牌列表
         */
    @Test
    public void testAggQuery() throws IOException {
    
        SearchRequest searchRequest=new SearchRequest("goods");
    
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        //1. 查询title包含手机的数据
    
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "手机");
    
        sourceBuilder.query(queryBuilder);
        //2. 查询品牌列表  只展示前100条
        AggregationBuilder aggregation=AggregationBuilders.terms("goods_brands").field("brandName").size(100);
        sourceBuilder.aggregation(aggregation);
    
    
        searchRequest.source(sourceBuilder);
    
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    
        //7. 获取命中对象 SearchHits
        SearchHits hits = searchResponse.getHits();
    
        //7.1 获取总记录数
        Long total= hits.getTotalHits().value;
        System.out.println("总数:"+total);
    
        // aggregations 对象
        Aggregations aggregations = searchResponse.getAggregations();
        //将aggregations 转化为map
        Map<String, Aggregation> aggregationMap = aggregations.asMap();
    
    
        //通过key获取goods_brands 对象 使用Aggregation的子类接收  buckets属性在Terms接口中体现
    
        //        Aggregation goods_brands1 = aggregationMap.get("goods_brands");
        Terms goods_brands =(Terms) aggregationMap.get("goods_brands");
    
        //获取buckets 数组集合
        List<? extends Terms.Bucket> buckets = goods_brands.getBuckets();
    
        Map<String,Object>map=new HashMap<>();
        //遍历buckets   key 属性名,doc_count 统计聚合数
        for (Terms.Bucket bucket : buckets) {
    
            System.out.println(bucket.getKey());
            map.put(bucket.getKeyAsString(),bucket.getDocCount());
        }
    
        System.out.println(map);
    
    }
    

avatar
青山
悟已往之不谏 知来者之可追
一言
今日诗词
站点信息
本站访客数 :
本站总访问量 :