• Elasticsearch (三)进阶检索及整合 spring boot


    五、进阶检索

    * 批量导入es官方提供的数据

    es官方批量数据地址,导入数据:
    POST /bank/account/_bulk

    {"index":{"_id":"1"}}
    {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
    {"index":{"_id":"6"}}
    {"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"}
    {"index":{"_id":"13"}}
    .....全部的数据导入
    

    进阶搜索,主要参考 官方文档

    5.1、SearchAPI

    ES支持两种基本方式的检索:

    • 通过使用 REST request URI 发送搜索参数( uri + 检索参数 )
    • 通过使用 REST request body 发送搜索参数( uri + 请求体 )

    5.1.1 检索信息

    • 一切检索从 _search 开始

      GET /bank/account/_search 检索 bank 下所有信息,包括type和docs

    • uri + 请求参数进行检索

      GET /bank/account/_search?q=*&account_number:asc

      请求参数解析:

      请求参数 含义
      q=* 查询所有
      account_number:asc 查询到的所有数据,根据账号进行升序排列

      返回值参数解析: 参考API文档

      返回参数 含义
      took Elasticsearch 执行搜索的时间(ms)
      time_out 搜索是否超时
      _shards 多少分片被搜索了,以及统计 成功 / 失败 的搜索分片
      hits 搜索结果
      hits.total 搜索结果的总数
      hits.hits 实际的搜索结果数组(默认为前10个文档)
      sort 结果的排序 key(键) 没有则按 score 排序
      scoremax_score 相关性得分和最高得分(全文检索用)
    • uri + 请求体进行检索 Query DSL

      GET /bank/_search

      {
        # 查询条件
        "query": {	
          "match_all": {}		# 请求全部
        },
        # 排序规则 排序规则为一个数组,如果有多个,则依次添加上即可
        "sort": [		
          {		
            "account_number": "asc"
          }
        ]
      }
      

      image-20201208165805055

    5.1.2 Query DSL

    5.1.2.1 基本语法

    Elasticsearch 提供提供了一个可以执行查询的 JSON 风格的 DSL (domain-specific language 领域特定语言)。被称为 Query DSL。该查询语言非常全面,并且刚开始的时候有点复杂,可以通过几个简单示例去理解他

    • 一个查询语句的典型结构

      {
          QUERY_NAME: {		# 如: query,sort, from, fitter。。。。
              ARGUMENT: VALUE,
              ARGUMENT: VALUE, ....
          }
      }
      
    • 如果是针对某个字段的查询,可以是这样的

      {
          QUERY_NAME: {		# 如: query,sort, from, fitter。。。。
              FIELD_NAME: {	# 字段名
      			ARGUMENT: VALUE,
              	ARGUMENT: VALUE, ....
              }
          }
      }
      
    • 示例:按金额字段降序,查询 bank 索引下的 5-10 条数据

      POST /bank/_search

      {
        "query": {	
          "match_all": {}
        },
        "sort": [
          # 这里可以简写为 "balance": "desc"
        	{
          	"balance" : {
          		"order": "desc"
      		}  
      	}
        ],
        "from": 5,	# 从第5个文档开始
        "size": 5		# 取5个文档
      }
      
    • 详细可参考 官方API文档

    5.1.2.2 返回部分字段 _source

    Elasticsearch 默认返回的是文档的全部字段,但是可以通过 _source 去指定返回的字段,

    如:按金额字段降序,查询 bank 索引下的 5-10 条数据,只返回账号和金额字段
    POST /bank/_search

     {
        "query": {	
          "match_all": {}
        },
        "sort": [
        	{ "balance" : "desc" }  
        ],
        "from": 5,	
        "size": 5,
        "_source": ["balance","account_number"]
    }
    

    image-20201208173256541

    5.1.2.3 全文检索 match

    match 对非字符串字段为精准查询,对字符串字段为 全文检索 , 返回的结果,按照评分从大到小进行排序。全文检索会对检索条件进行分词匹配

    如: 查询金额为 20 的文档
    GET /bank/_search

    {
      "query": {
        "match": {
          "account_number": 20
        }
      }
    }
    

    image-20201221210120016

    此时的返回结果中多 了一个字段 max_score 即为返回的所有文档的最大得分

    又如: 在address 中全文检索 mill road
    GET /bank/_search

    {
      "query": {
        "match": {
          "address": "mill road"
        }
      }
    }
    

    image-20201221211340641

    最终可以查询出32条记录,即 address 中包含 mill 或者 road 或者 mill road 的所有记录,并按相关性得分从大到小排序

    5.1.2.4 短语匹配 match_phrase

    根据5.1.2.3可知,使用 match 进行全文检索的时候,会将短语进行分词,然后进行匹配,最终按评分的大小进行排序返回。但是有时候,我们并不想让他进行分词搜索,即需要将匹配的值当成一个整体的单词。

    如:查询地址中 包含 mill road 的文档
    GET /bank/_search

    {
      "query": {
        "match_phrase": {
          "address": "mill road"
        }
      }
    }
    

    image-20201221211508419

    跟5.1.2.3 的第二个例子比较可以看出,使用 match_phrase 查询,不会进行分词匹配。当然 在使用 match 进行查询的时候,也可以不进行分词,因为,每个文本字段都有一个 .keyword ,当使用 match 进行查询的时候, 就可以使用 .keyword 进行匹配。如

    {
      "query": {
        "match": {
          "address.keyword": "mill road"
        }
      }
    }
    

    不过,该方法与 match_phrase 进行匹配的有所不同,当使用这个方法进行查询的时候,就要求 address 字段的值 必须跟 后面的查询的值是完全一样的才会被查询出来,即为精确查询

    5.1.2.5 多字段匹配 multi_match

    如同字面意思,有时候的查询,我们可能不止匹配一个字段,需要进行多个字段的查询,故使用 multi_match 可以对字段进行查询。且在查询的过程中,会进行分词。

    如:查询 addresscit 包含 mill 的文档
    GET /bank/_search

    {
      "query": {
        "multi_match": {
          "query": "mill",
          "fields": ["address","city"]
        }
      }
    }
    

    image-20201221212502190

    5.1.2.6 复合查询 bool

    bool 是用来做复合查询,复合查询可以合并 任何 其他查询语句,包括复合语句,这就意味着,复合语句之间可以相互的嵌套,可以表达非常复杂的逻辑。

    • must :必须达到 must 列举的所有条件
      如: 查询 address 中包含 millgenderF 的文档
      GET /bank/_search

      {
        "query": {
          "bool": {
            "must": [
              {
                "match": {
                  "gender": "F"
                }
              },
              {
                "match": {
                  "address": "mill"
                }
              }
            ]
          }
        }
      }
      

      image-20201221213739320

    • must_notmust_not 里包含的条件必须都不满足

      如: 查询 address 中包含 millgenderM 的文档, 且 age 不为 38 的文档

      GET /bank/_search

      {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "gender": "M"
              }
            },
            {
              "match": {
                "address": "mill"
              }
            }
          ],
          "must_not": [
            {
              "match": {
                "age": 38
              }
            }
          ]
        }
      }
      }
      

      image-20201221214142958

    • should : 应该达到 should 列举的条件,如果达到会增加相关文档的评分,并不会改变查询的结果。 如果 query 中只有 should 且只有一种匹配规则,那么should 的条件就会被作为默认匹配条件而去改变查询结果

      如:查询 address 中包含 millgenderM 的文档, 且 age 不为 28,且 lastname 最好是 Holland 的文档

      GET /bank/_search

      {
        "query": {
          "bool": {
            "must": [
              {
                "match": {
                  "gender": "M"
                }
              },
              {
                "match": {
                  "address": "mill"
                }
              }
            ],
            "must_not": [
              {
                "match": {
                  "age": 28
                }
              }
            ],
            "should": [
              {
                "match": {
                  "lastname": "Holland"
                }
              }
            ]
          }
        }
      }
      

      image-20201221214646546

      通过查询的结果可以看出,lastname 不为 Holland 的也可以查询出来。

    5.1.2.7 结果过滤 filter

    官方文档中,有如下一段话:

    ​ Each must, should, and must not element in a Boolean query is referred to as a query clause. How well a document meets the criteria in each must or should clause contributes to the document’s relevance score。The ciriteria in a must_not clause is treated as a filter. It affects whether or not the document is included in the results, but does not contribute to how documents are scored.

    即 在使用 must, should, must not,特别是 must, should的时候,如果满足条件的文档,那么就会提升相关文档的相关性得分。而当使用 must_not 的时候,它会被当做一个 filter ,从而不会贡献文档的得分。

    filter : 结果过滤。 在查询中,并不是所有的查询都需要产生分数,特别是那些仅用filtering(过滤)的文档,为了不计算分数, Elasticsearch 会自动检查场景并且优化查询的执行

    如:查询 age 在 18 到 30 之间 的文档

    • 方法一 : 使用普通查询, 此时会增加出相关性得分
      GET /bank/_search

      {
        "query": {
          "bool": {
            "must": [
              {
                "range": {
                  "age": {
                    "gte": 18,
                    "lte": 30
                  }
                }
              }
            ]
          }
        }
      }
      

      image-20201221220845585

    • 方法二: 使用 filter 过滤,对相关性得分不影响
      GET /bank/_search

      {
        "query": {
          "bool": {
            "filter": {
              "range": {
                "age": {
                  "gte": 10,
                  "lte": 20
                }
              }
            }
          }
        }
      }
      

      image-20201221221058215

    5.1.2.8 非text匹配 term

    match 一样,用于匹配某个属性的值。全文检索字段用 match 其他非 text 字段匹配用 term

    如:查询 age 为 28 的文档
    GET /bank/_search

    {
      "query": {
        "term": {
          "age": 28
        }
      }
    }
    

    image-20201221222231664

    5.1.2.9 聚合 aggregations

    聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于 MySQL中的 GROUP BYSQL 聚合函数。在 Elasticsearch中,您有执行搜索返回 hits (命中结果),并且同时返回聚合结果,把一一个响应中的所有hits (命中结果)分隔开的能力。这是非常强大且有效的,您可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的API来避免网络往返。 官方文档

    • 搜索 address 中包含 mill 的所有人的年龄分布。
      GET /bank/_search

      {
        "query": {
          "match": {
            "address": "mill"
          }
        },
        "aggs": {
          "ageAgg": {
            "terms": {	# 看聚合的字段有多少中可能
              "field": "age",
              "size": 10		# 只取出前10种可能
            }
          }
        }
      }
      

      image-20201221223934900

      通过返回的结果可以看出,除了返回结果的 hits,还多返回了一个字段 aggregations, 里面就是聚合的结果

    • 搜索 address 中包含 mill 的所有人的年龄分布及平均年龄。
      GET /bank/_search

      {
      "query": {
        "match": {
          "address": "mill"
        }
      },
      "aggs": {
        "ageAgg": {
          "terms": {
            "field": "age",
            "size": 10
          }
        },
        "ageAvgAgg": {
          "avg": {
            "field": "age"
          }
        }
      }
      }
      

      image-20201221224649919

    • 复杂聚合:聚合的聚合 如: 按照年龄聚合,并且查询出每个年龄段的平均薪资。
      GET /bank/_search

      {
        "query": {
          "match": {
            "address": "mill"
          }
        },
        "aggs": {
          "ageAgg": {
            "terms": {
              "field": "age",
              "size": 10
            },
            "aggs": {
              "avuAgg": {
                "avg": {
                  "field": "balance"
                }
              }
            }
          }
        }
      }
      

      image-20201221225033354

      可以看出,在 ageAgg 的聚合里面,还多了一个 avuAgg ,即在原来的聚合的结果中,再次进行数据的分析聚合。同理,Elaticsearch 可以进行多层的子聚合。

    5.1.3 映射 mapping

    官方文档
    映射是用来定义一个文档如何被处理的,即,每一个属性字段是如何进行存储和将被如何索引的。如,可以使用映射来定义:

    • 哪些String字段应视为全文字段。
    • 哪些字段包含数字,日期或地理位置。
    • 日期值的格式。
    • 自定义规则来控制动态添加字段的映射。
    5.1.3.1 字段类型

    Elaticsearch 中,有非常多的映射类型:

    • 核心类型

      • 字符串(String)
        text, keyword

      • 数字类型(Numeric)

        long, integer, short, byte, double, float,half_float,scaled_ float

      • 日期类型(Date)
        date

      • 布尔类型(Boolean)

        boolean

      • 二进制类型 (binary)

        binary

    • 聚合类型

      • 数组类型 (array)
        Array 支持不针对特性的类型
      • 对象类型 (Object)
        object 用于单 JSON 的对象
      • 嵌套类型 (Nested)
        nested 用于JSON对象数组
    • 地理类型 (Geo)

      • 地理坐标(Geo-points)
        geo_poiint 用于描述 经纬度坐标

      • 地理图形(Geo-Shape)

        geo_shape 用于描述复杂性质, 如多边形

    • 特定类型

      • IP类型

        ip 用于描述 ipv4ipv6 地址

      • 补全类型 (Completion)
        completion 提供自动完成提示

      • 令牌计数类型 (Token count)
        token_count 用于统计字符串中词条数量

      • 附件类型 (attachment)
        参考 mapper-attachements 插件, 支持将附件如 Microsoft Office 格式, ePubHTML 等等索引为 attachment 数据类型

      • 抽取类型 (Percolator)
        抽取特定领域查询语言(query-dsl)的查询

    • 多字段

      • 通常用于为 不同目的用不同方法索引同一个字段。 例如, string 字段可以映射为一个 test 字段用于全文检索,同样可以映射为一个 keyword 字段用于排序和聚合。另外, 你可以使用 standard analyzer, english analyzer, french analyzer 来索引一个 text 字段
      • 这就是 muti-fields 的目的。大多数的数据类型通过 fields 参数来支持 muti-fields

    Elaticsearch 会在第一次存储数据的时候,自动猜测数据的类型。

    5.1.3.2 新版本的改变

    Elaticsearch7 及以上移除了type的概念。

    • 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用,但ES中不是这样的。elasticsearch 是基于Lucene开发的搜索引擎,而ES中不同type下名称相同的filed最终在Lucene中的处理方式是一样的。
      • 两个不同 type下的两个user_ name, 在ES同一个索引下其实被认为是同一个filed,你必须在两个不同的type中定义相同的filed射。否则,不同type中的相同字段名称就会在处理中出现冲突的情况,导致Lucene处理效率下降。
      • 去掉type就是为了提高ES处理数据的效率。

    Elasticsearch 7.x

    • URL 中的type参数为可选。比如,索引一个文档不再要求提供文档类型。

    Elasticsearch 8.x

    • 不再支持URL中的type参数。

    解决:
    1)、将索引从多类型迁移到单类型,每种类型文档一个独立索引
    2)、将已存在的索引下的类型数据,全部迁移到指定位置即可。详见数据迁移

    5.1.3.3 创建映射

    PUT /my_index // 由于取消了 type ,所以就不再用 type了

    {
      "mappings": {
        "properties": {
          "age": {"type": "integer"},
          "email": {"type": "keyword"},
          "name": {"type": "text"}
        }
      }
    }
    

    image-20201222142833179

    5.1.3.4 添加新的字段

    PUT /my_index/_mapping

    {
      "properties": {
        "employee_id": {
          "type": "keyword",
          "index": false    # 设置为不被索引,默认为 true
        }
      }
    }
    

    image-20201222144030263

    5.1.3.5 更新映射

    对于已经存在的映射字段,不能够更新。 更新必须创建新的索引进行数据迁移

    5.1.3.6 数据迁移

    先创建新的索引,创建新的映射规则,然后使用如下方法进行数据迁移 官方文档

    PUT _reindex 【固定写法】

    {
        "source": {
            "index": "bank"   # 旧索引
        },
        "dest": {
            "index": "new_bank"  # 新索引
        }
    }
    

    将旧索引的 type 下的数据进行迁移, 新旧版本的替换

    PUT _reindex 【固定写法】

    {
        "source": {
            "index": "bank",   # 旧索引
            "type": "accownt"  # 旧索引的类型
        },
        "dest": {
            "index": "new_bank"  # 新索引
        }
    }
    

    5.2 分词

    一个tokenizer (分词器)接收一个字符流,将之分割为独立的tokens (词元,通常是独立的单词),然后输出tokens流。例如,whitespace tokenizer遇到空白字符时分割文本。它会将文本"Quick brown fox!"分割为[Quick, brown, fox!]。该tokenizer (分词器)还负责记录各个term (词条)的顺序或position位置(用于phrase短语和word proximity词近邻查询),以及term (词条)所代表的原始word (单词)的start(起始)和end (结束)的characteroffsets (字符偏移量) (用于 高亮显示搜索的内容)。Elasticsearch提供了很多内置的分词器,可以用来构建custom analyzers (自定义分词器)。 官方文档

    5.2.1 标准分词器 standard

    POST _analyze

    {
      "analyzer": "standard",
      "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
    }
    
    [ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog's, bone ]
    

    不过,Elasticsearch 自带的分词器只能完成英文的分词,如果需要中文的分词,就需要安装额外的分词器,下面就安装一个常见的中文分词器 ik 分词器

    5.2.1 安装 IK 分词器

    注意: 不能用默认 elaticsearch-plugin install xxx.zip 进行安装

    5.2.1.1 下载 IK 分词器

    IK分词器 GitHub 地址 IK分词器 releases 版本下载地址

    进入 IK分词器 releases 版本 下载地址,选择对应的 Elasticsearch 版本,复制下载链接地址。
    image-20201222214206359

    使用 docker 的交互命令,进入容器的内部:

    [root@localhost ~]# docker exec -it elasticsearch /bin/bash
    [root@7a0299da8e79 elasticsearch]# 
    

    进入 plugins 路径,使用 wget 命令进行下载

    [root@7a0299da8e79 elasticsearch]# cd plugins
    [root@7a0299da8e79 plugins]# wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
    

    不过一般情况下, docker容器的内部是不会安装wget 命令,但是由于我们在启动的时候,已经将这个 plugins 目录映射到了我们本地路径下,所以在我们下载到我们本地的 plugins 路径也是一样的。

    [root@7a0299da8e79 elasticsearch]# exit
    [root@localhost ~]# cd /mydata/elasticsearch/plugins
    [root@localhost plugins]# wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
    

    如果本地环境也没有 wget 命令的话,可以使用如下命令进行安装

    yum install wget
    
    5.2.1.2 安裝

    下载完成后, 可以在 plugins 路径下得到一个压缩包 elasticsearch-analysis-ik-7.4.2.zip, 但是 linux 环境下,默认是无法解压 zip 格式的压缩包的,所以需要先安装一个工具 unzip

    yum install -y unzip zip
    

    安装完后,使用命令,解压文件, 并删除原有安装包

    unzip -oq elasticsearch-analysis-ik-7.4.2.zip -d ik
    rm -rf elasticsearch-analysis-ik-7.4.2.zip
    

    重启 elasticsearch 即可

    docker restart elasticsearch
    

    如果虚拟机系统无法联网,或者嫌下载解压麻烦的话,也可以在本机上下载完成后,解压好,然后使用类似于 xftp 的软件将解压好的文件夹,放入对应的目录下即可

    5.2.1.3 使用 ik 分词器

    IK 分词器有两个常用的分词:

    • ik_smart

      GET _analyze
      {
        "analyzer": "ik_smart",
        "text": "我是中国人"
      }
      
    • ik_max_word

      GET _analyze
      {
        "analyzer": "ik_max_word",
        "text": "我是中国人"
      }
      

    通过查看结果,可见,不同的分词器,分词的结果是不同的,并且,只能识别一些常见的词语,一些新的词汇,网络用语,人名等等,都是无法识别的,所以还需要一个自定义的词库。

    5.2.1.4 自定义拓展词库

    只需要修改 ik 分词器的配置文件 /usr/share/elasticsearch/plugins/ik/config 目录下的 IKAnalyzer.cfg.xml 文件即可,在配置文件中,指定一个远程的词库,让 ik 分词器向远程词库发送请求,获取新的词汇。解决方案:

    • 创建一个项目,让 ik 分词器向该项目发起请求,获取新的词汇
    • 安装 Nginx ,并将词库放于 Nginx 中,让 ik 分词器向 Nginx 发送请求,获取新的词汇

    这里介绍第二种解决方案:

    Docker安装 Nginx

    • 随便启动一个Nginx 实例,只是为了复制出原有的配置。

      docker run -p 80:80 --name nginx -d nginx:1.10
      
    • 将容器内的配置文件拷贝到当前目录(注意结尾有个 点)

      docker container cp nginx:/etc/nginx .
      
    • 修改文件夹的名称为 conf,并移动到 /mydata/nginx

      mv nginx conf
      mkdir /mydata/nginx
      mv conf nginx/
      
    • 停止并删除原有 Nginx 容器

      docker stop nginx
      docker rm nginx
      
    • 创建新的 Nginx 容器

      docker run -p 80:80 --name nginx 
      -v /mydata/nginx/html:/usr/share/nginx/html 
      -v /mydata/nginx/logs:/var/log/nginx 
      -v /mydata/nginx/conf:/etc/nginx 
      -d nginx
      
    • 访问 http:// ip:80 即可访问到 nginx 的欢迎页面

    • 此时,在nginxhtml 目录下面存放的所有资源都是可以直接访问的

    Nginxhtml 目录下,新建一个 es 目录,并创建 fenci.txt 文件

    # 在 /mydata/nginx 下
    cd /html
    mkdir es
    cd es
    vi fenci.txt
    

    fenci.txt文件里面写入新的词汇,一行一个,如:乔碧萝欧力给,保存后,即可通过链接 http:// ip:80/es/fenci.txt进行访问。词库创建好后,需要配置 ik 分词器的配置文件

    cd /mydata/elasticsearch/plugins/ik/config
    vi IKAnalyzer.cfg.xml
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties>
    	<comment>IK Analyzer 扩展配置</comment>
    	<!--用户可以在这里配置自己的扩展字典 -->
    	<entry key="ext_dict"></entry>
    	 <!--用户可以在这里配置自己的扩展停止词字典-->
    	<entry key="ext_stopwords"></entry>
    	<!--用户可以在这里配置远程扩展字典 -->
    	<entry key="remote_ext_dict">http://ip:80/es/fenci.txt</entry>   # 在这里填入刚刚的分词地址
    	<!--用户可以在这里配置远程扩展停止词字典-->
    	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
    </properties>
    

    保存之后,重启 es 即可

    GET _analyze
    {
      "analyzer": "ik_smart",
      "text": "乔碧萝殿下,欧力给"
    }
    
    GET _analyze
    {
      "analyzer": "ik_max_word",
      "text": "乔碧萝殿下,欧力给"
    }
    

    image-20201222223527413

    六、Spring Boot 整合 Elasticsearch —— Elasticsearch-Rest-Client

    虽然说,前端也可以直接调用 es 的接口,不过,一般不会这么做,正常来说,都会由前端请求后端的一个接口,再让 java 去请求 es,获取数据,那么,java 操作 es 有两种方法:

    1. 请求 9300 端口 , TCP
      • spring-data-elasticsearch:transport-api.jar
        • springboot 版本不同, transport-api.jar 不同,不能适配 es 版本
        • 7.x 的版本已经不建议使用, 8 以后的版本就要废弃了
    2. 请求 9200 端口,HTTP
      • JestClient : 非官方,更新慢
      • RestTemplate:模拟发送HTTP请求,ES很多操作需要自己封装,麻烦
      • HttpClient:模拟发送HTTP请求,ES很多操作需要自己封装,麻烦
      • Elasticsearch-Rest-Client : 官方 RestClient, 封装了 ES 操作, API 层次分明,上手简单

    所以,一般情况下,会选择使用 Elasticsearch-Rest-Client 来进行整合。Elasticsearch-Rest-Client 又分为两个版本,Java Low Level REST ClientJava High Level REST Client ,后者是对前者的进一步封装,就犹如 jdbcMybatis 的关系一样,所以这里选择 elasticsearch-rest-high-level-client 高阶版本 高阶官方文档

    6.1 导入maven 依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.8.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <properties>
            <java.version>1.8</java.version>
            # 由于spring boot 也管理了 elasticsearch ,那么就会导致下面导入的elasticsearch与 api 版本不一致,所以需要在这里接管 elasticsearch 版本号
            <elasticsearch.version>7.4.2</elasticsearch.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
                <version>7.4.2</version>
            </dependency>
        </dependencies>
    </project>
    

    6.2 配置 Elasticsearch

    官方文档

    package com.search.config;
    
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * 给容器中注入一个  RestHighLevelClient
     * @description: elasticsearch 配置类
     */
    @Configuration
    public class ElasticsearchConfig {
    
        /**
        * 请求设置项,配置统一的请求设置,详细配置参考官方文档
        * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/java-rest-high-getting-started-request-options.html
        */
        public static final RequestOptions COMMON_OPTIONS;
    
        static {
            RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
            //builder.addHeader("Authorization", "Bearer " + TOKEN);
            //builder.setHttpAsyncResponseConsumerFactory(
            //        new HttpAsyncResponseConsumerFactory
            //                .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
            COMMON_OPTIONS = builder.build();
        }
    
        /**
         * 参加官方文档
         * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/java-rest-high-getting-started-initialization.html
         */
        @Bean
        public RestHighLevelClient esRestClient() {
            RestHighLevelClient client = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("192.168.10.3", 9200, "http"))); // 集群的情况下,只需要在这里写入多个HttpHost即可,对应上IP,端口号,协议名
            return client;
        }
    }
    

    6.3 测试

    package com.search;
    
    import com.alibaba.fastjson.JSON;
    import com.search.config.ElasticsearchConfig;
    import lombok.Data;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.aggregations.AggregationBuilders;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    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 java.io.IOException;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class GulimallSearchApplicationTests {
    
        @Autowired
        private RestHighLevelClient client;
    
        @Test
        public void contextLoads() {
            System.out.println(client);
        }
    
        /**
         * 测试加载数据到es
         */
        @Test
        public void indexData() throws IOException {
            IndexRequest indexRequest = new IndexRequest("users");
            indexRequest.id("1");
            User user = new User();
            user.setUserName("张三");
            user.setAge(1);
            user.setGender("女");
            String s = JSON.toJSONString(user);
            indexRequest.source(s, XContentType.JSON);
            // 执行操作
            IndexResponse index = client.index(indexRequest, ElasticsearchConfig.COMMON_OPTIONS);
    
            // 提取有用的响应数据
            System.out.println(index);
        }
    
        /**
        * 测试复杂检索
        */
        @Test
        public void searchData() throws Exception {
            // 检索请求
            SearchRequest searchRequest = new SearchRequest();
            // 指定索引
            searchRequest.indices("bank");
            // 指定SDL,检索条件 SearchSourceBuilder
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    
            searchSourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
            searchSourceBuilder.from(0);
            searchSourceBuilder.size(20);
            searchSourceBuilder.aggregation(AggregationBuilders.terms("ageAgg").field("age").size(10));
    
            System.out.println(searchSourceBuilder.toString());
            searchRequest.source(searchSourceBuilder);
    
            //执行检索
            SearchResponse search = client.search(searchRequest, ElasticsearchConfig.COMMON_OPTIONS);
    
            // 分析结果
            System.out.println(search.toString());
    
        }
    
        @Data
        class User{
            private String userName;
            private String gender;
            private Integer age;
        }
    }
    
  • 相关阅读:
    1、一条sql查询语句的执行过程
    go 内存分配
    GO Json
    gorm CRUD:读写数据
    go 基于切片的队列实现
    go的错误处理
    grpc
    sqlalchemy 判断字段是否存在
    定时函数
    用Python获取Linux资源信息的三种方法
  • 原文地址:https://www.cnblogs.com/lanxiang/p/14179066.html
Copyright © 2020-2023  润新知