ElasticSearch


title: ElasticSearch

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiF2jnew-1668231135575)(https://qfedu-1254123199.cos.ap-nanjing.myqcloud.com/img/timg.jfif)]

Author:zed

Version:9.0.1

一、引言


1.1 海量数据

在海量数据中执行搜索功能时,如果使用MySQL,效率太低。

1.2 全文检索

在海量数据中执行搜索功能时,如果使用MySQL,效率太低。

1.3 高亮显示

将搜索关键字,以红色的字体展示。

二、ES概述


2.1 ES的介绍

  • ES是一个使用Java语言并且基于Lucene编写的搜索引擎框架,他提供了分布式的全文搜索功能,提供了一个统一的基于RESTful风格的WEB接口,官方客户端也对多种语言都提供了相应的API。

  • Lucene:Lucene本身就是一个搜索引擎的底层。

  • 分布式:ES主要是为了突出他的横向扩展能力。

  • 全文检索:将一段词语进行分词,并且将分出的单个词语统一的放到一个分词库中,在搜索时,根据关键字去分词库中检索,找到匹配的内容。(倒排索引)

  • RESTful风格的WEB接口:操作ES很简单,只需要发送一个HTTP请求,并且根据请求方式的不同,携带参数的同,执行相应的功能。

  • 应用广泛:Github.com,WIKI,Gold Man用ES每天维护将近10TB的数据。

2.2 ES的由来

ES回忆时光
1587029047688

2.3 ES和Solr

  • Solr在查询死数据时,速度相对ES更快一些。但是数据如果是实时改变的,Solr的查询速度会降低很多,ES的查询的效率基本没有变化。
  • Solr搭建基于需要依赖Zookeeper来帮助管理。ES本身就支持集群的搭建,不需要第三方的介入。
  • 最开始Solr的社区可以说是非常火爆,针对国内的文档并不是很多。在ES出现之后,ES的社区火爆程度直线上升,ES的文档非常健全。
  • ES对现在云计算和大数据支持的特别好。

2.4 倒排索引

将存放的数据,以一定的方式进行分词,并且将分词的内容存放到一个单独的分词库中。

当用户去查询数据时,会将用户的查询关键字进行分词。

然后去分词库中匹配内容,最终得到数据的id标识。

根据id标识去存放数据的位置拉取到指定的数据。

检索的时候 先将检索的内容分词 然后 去分词库匹配 拿到匹配数据的索引 再根据索引去数据存储的位置 拿到匹配的数据

倒排索引
1587278510541

2.5基本概念

2.4.1 Index (索引)

动词 ,相当于Mysql 中的insert

名词, 相当于Mysql 中的 Database

2.4.2 Type (类型)

在Index(索引)中 ,可以定义一个或多个类型。

类似Mysql 中的table ,每一种类型的数据放在一起

2.4.3 Document (文档)

保存在某个索引(index)下,某种类型(type)的一个数据(Document) ,文档是json 格式的,Document 就像是Mysql 中某个table里面的内容。

image-20200913180053166

三、 ElasticSearch安装


3.1 安装ES&Kibana

mkdir -p /opt/docker_elasticsearch/config
mkdir -p /opt/docker_elasticsearch/data
echo "http.host: 0.0.0.0"  >>  /opt/docker_elasticsearch/config/elasticsearch.yml

编写docker-compose.yml文件,192.168.174.128 IP地址换成自己的

version: "3.1"
services:
 elasticsearch:
  image: daocloud.io/library/elasticsearch:7.4.2
  restart: always
  container_name: elasticsearch
  environment:
     - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
  volumes:
     - /opt/docker_elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
     - /opt/docker_elasticsearch/data:/usr/share/elasticsearch/data
     - /opt/docker_elasticsearch/plugins:/usr/share/elasticsearch/plugins
  ports:
     - 9200:9200
     - 9300:9300
 kibana:
   image: daocloud.io/library/kibana:7.4.2
   restart: always
   container_name: kibana
   ports:
     - 5601:5601
   environment:
     - elasticsearch_url=http://192.168.174.128:9200
   depends_on:
     - elasticsearch

9300端口为es集群间组件的通信端口,9200端口为浏览器访问的http协议resetful端口

记得给文件夹授权 chmod 777 config chmod 777 data

如果访问 http://192.168.174.128:9200 失败

查看日志是否vm内存配置过小elasticsearch启动时遇到的错误

问题翻译过来就是:elasticsearch用户拥有的内存权限太小,至少需要262144;

::: details 解决办法 解决办法1:
/etc/sysctl.conf 文件最后添加一行 vm.max_map_count=262144把宿主机内存配大一些

解决办法2:
启动时指定内存,咱们的安装方法就是启动时指定内存
:::
启动完成后,在浏览器输入 http://192.168.174.128:9200/ 看到ES欢迎信息说明安装成功! 在浏览器输入 http://192.168.174.128:5601/ 进入kibana

3.2 安装IK分词器

::: tip ik分词器就是 elasticsearch 的一个插件
由于直接访问Github很慢,下载IK分词器的地址替换为加速器的地址 https://ghproxy.com/https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
:::

进去到ES容器内部,跳转到bin目录下,执行bin目录下的脚本文件:

./elasticsearch-plugin install https://ghproxy.com/https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip

执行 elasticsearch-plugin list 查看插件列表有没有 analysis-ik

四、 ElasticSearch基本操作


4.1 ES的结构 — 操作es 之前 先了解es 的结构

4.1.1 索引Index,分片和备份
  • ES的服务中,可以创建多个索引。

  • 每一个索引默认被分成5片存储。

  • 每一个分片都会存在至少一个备份分片。

  • 备份分片默认不会帮助检索数据,当ES检索压力特别大的时候,备份分片才会帮助检索数据。

  • 备份的分片必须放在不同的服务器中。

    理解: 索引index是es 中最大的数据存储单位 ,和mysql 的区别是 一个索引(index)中可以存海量(几亿条)数据 ,如果我们要在几亿条数据中检索出几条想要的数据 效率会很低 所以 es 提供了 一种对索引进行分片的机制 ,ES 天然支持集群,在集群服务器中 ES 把一个索引进行分片 放在不同的服务器上 如下图 例如 有一亿条数据 分成两个分片 每个分片上有5000万条数据 这样做的好处 一是 提高查询速度 二是 提高数据的存储量,另外 为了保证数据的安全 每个主分片会有备份分片 主分片和备份分片在不同的服务器上 , 比如 主分片2 挂掉了 在 ES服务1 上面 还有 主分片2的备份分片 ,这样在一定程度上保证了数据的安全性 避免数据的丢失。但是 如果 当前集群中 只有一台es服务器 那么 这台服务器上 放的都是主分片,没有备份分片,什么时候扩展了集群中的 另一台服务器 才会存放备份分片。

索引分片备份
1587048753470
4.1.2 类型 Type

一个索引下,可以创建多个类型。

Ps:根据版本不同,类型的创建也不同。

es7 版本 不推荐使用type ,但是还是能用,到后面的es 版本 就不能再使用type 了

类型
1587048924494
4.1.3 文档 Doc

一个类型下,可以有多个文档。这个文档就类似于MySQL表中的多行数据。

文档
1587048972631
4.1.4 属性 Field

一个文档中,可以包含多个属性。类似于MySQL表中的一行数据存在多个列。

属性
1587049031609

4.2 操作ES的RESTful语法

  • GET请求:

/_cat/nodes:查看所有节点 在kibana 中使用 GET _cat/nodes

/_cat/health:查看es 健康状况

/_cat/master:查看主节点

/_cat/indices:查看所有索引 相当于 show databases;

/index:查询索引信息 GET book 相当于查看 数据库表结构

/index/type/doc_id:查询指定的文档信息

注意:咱们用的是es7 直接使用type的话会给出警告信息咱们使用_doc代替type
比如查询指定文档信息GET book/_doc/1 查询 book 索引中 id 为1 的文档信息

GET book/_doc/2
  • POST请求:http://ip:port/index/type/doc_id
# 指定文档id的添加操作             如果索引还未创建 还可以创建索引
POST book/_doc/2
{
 "name":"西游记",   
 "author":"吴承恩"
}
                                                       
 `http://ip:port/index/type/_search`:查询文档,可以在请求体中添加json字符串来代表查询条件
                                    
    
    # 查询操作
    POST book/_search
    {
      "query":{
        "match": {
          "name": "西游记"
        }
      }
    }
    

    

 - `http://ip:port/index/type/doc_id/_update`:修改文档,在请求体中指定json字符串代表修改的具体信息     注意  带 _update 的  修改  json 格式 里需要加 doc  对比 本文档下面的案例说明 
                                                                
    POST book/_update/1      # 修改操作
    { 
      "doc":{
        "name":"大奉打更2人",    
        "author":"xxxxx2"
      }
    }
  • PUT请求:

http://ip:port/index:创建一个索引,也可以在请求体中指定索引的信息,类型,结构

  PUT book2    # 创建一个叫 book2 的索引   执行第二次会报错
                         
                  # 添加或修改文档    第一次是添加(同样 索引不存在 也会创建索引) 后面再执行是修改
  PUT book3/_doc/1
  {
    "name":"java"
  }

http://ip:port/index/type/_mappings:代表创建索引时,指定索引文档存储的属性的信息

  • DELETE请求:

http://ip:port/index:删除索引

DELETE book2 # 删除book2 索引

 - `http://ip:port/index/type/doc_id`:删除指定的文档
                        
   DELETE book/_doc/2      删除索引book中  id 为2 的文档

4.3 索引的操作

4.3.1 创建一个索引

语法如下 先创建一个最简单的 先不指定他的结构化数据

# 创建一个索引
PUT /person
{
  "settings": {
    "number_of_shards": 5,      # 分片 数5   
    "number_of_replicas": 1    # 备份
  }
}
4.3.2 查看索引信息

语法如下 去management 中 查看索引信息

Primaries 意思是 分片

Replicas 意思是备份

Health 健康状态黄色 表示不太健康 因为现在es 集群中只有一台服务器 备份分片没有地方存放 所以是黄色的健康状态, 如果 集群中有多台服务器 备份分片 就可以存储在别的服务器上 避免这台服务器挂掉 数据丢失问题

点索引的名字 可以查看索引的详细信息

# 查看索引信息
GET /person

image-20201206130416212

查看信息
1587090545722
4.3.3 删除索引

语法如下

# 删除索引
DELETE /person

4.4 ES中Field可以指定的类型

  • 字符串类型:
    • text:一把被用于全文检索。 将当前Field进行分词。
  • keyword:当前Field不会被分词。
  • 数值类型:
    • long:取值范围为-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),占用8个字节
  • integer:取值范围为-2147483648~2147483647(-2的31次方到2的31次方-1),占用4个字节
  • short:取值范围为-32768~32767(-2的15次方到2的15次方-1),占用2个字节

  • byte:取值范围为-128~127(-2的7次方到2的7次方-1),占用1个字节

  • double:1.797693e+308~ 4.9000000e-324 (e+308表示是乘以10的308次方,e-324表示乘以10的负324次方)占用8个字节

  • float:3.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的负45次方),占用4个字节

  • half_float:精度比float小一半。

  • scaled_float:根据一个long和scaled来表达一个浮点型,long-345,scaled-100 -> 3.45

  • 时间类型:

    • date类型,针对时间类型指定具体的格式
  • 布尔类型:

    • boolean类型,表达true和false
  • 二进制类型:

    • binary类型暂时支持Base64 encode string
  • 范围类型:

    • long_range:赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,lt,gte,lte
  • integer_range:同上
  • double_range:同上

  • float_range:同上

  • date_range:同上

  • ip_range:同上

  • 经纬度类型:

    • geo_point:用来存储经纬度的
  • ip类型:

    • ip:可以存储IPV4或者IPV6

其他的数据类型参考官网:https://www.elastic.co/guide/en/elasticsearch/reference/7.6/mapping-types.html

4.5 创建索引并指定数据结构

语法如下

# 创建索引,指定数据结构
PUT /book
{
  "settings": {
    # 分片数
    "number_of_shards": 5,
    # 备份数
    "number_of_replicas": 1
  },
  # 指定数据结构
  "mappings": {
    # 文档存储的Field
    "properties": {
      # Field属性名
      "name": {
    		# 类型
        "type": "text",
    		# 指定分词器 # 在对这个属性做分词的时候 使用 ik分词器
        "index": true ,
    		# 当前field是否需要额外存储    一般设置为false 即可  不需要额外存储
        "store": false 
      },
      "author": {
          # keyword 也算是字符串类型 
        "type": "keyword"
      },
      "count": {
        "type": "long"
      },
      "on-sale": {
        "type": "date",
         # 时间类型的格式化方式 
        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
      },
      "descr": {
        "type": "text"
      }
    }
  }
}

4.6 文档的操作

文档在ES服务中的唯一标识,_index_type_id三个内容为组合,锁定一个文档,操作是添加还是修改。

4.6.1 新建文档

自动生成_id

# 添加文档,自动生成id      不推荐这种自动生成的id 
POST /book/_doc
{
  "name": "盘龙",
  "author": "我吃西红柿",
  "count": 100000,
  "on-sale": "2000-01-01",
  "descr": "山重水复疑无路,柳暗花明又一村"
}

手动指定_id

# 添加文档,手动指定id     推荐使用
PUT /book/_doc/1
{
  "name": "红楼梦",
  "author": "曹雪芹",
  "count": 10000000,
  "on-sale": "1985-01-01",
  "descr": "一个是阆苑仙葩,一个是美玉无瑕"
}
4.6.2 修改文档

覆盖式修改

# 修改文档   覆盖式修改  如果没有指定某个属性 这个属性会被覆盖掉  覆盖没了
PUT /book/_doc/1
{
  "name": "红楼梦",
  "author": "曹雪芹",
  "count": 4353453,
  "on-sale": "1985-01-01",
  "descr": "一个是阆苑仙葩,一个是美玉无瑕"
}

doc修改方式

# 修改文档,基于doc方式       不会覆盖之前的内容 指定哪一个属性 修改哪一个属性
POST /book/_doc/1/_update      # 7 之前的写法
{
  "doc": {
     # 指定上需要修改的field和对应的值
    "count": "1234565"
  }
}


# 现在 都这样写
POST book/_update/1
{
  "doc":{
    "name": "斗破苍穹"
  }
}
4.6.3 删除文档

根据id删除

# 根据id删除文档
DELETE book/_doc/1      # 删除id 为1 的文档
4.6.4 补充

在kibana 可视化界面中可以看到 创建的索引信息

image-20201026160812996

image-20201026160904473

image-20201026160922091

image-20201026160941223

image-20201026161008465

五、Java操作ElasticSearch【重点


5.1 Java连接ES

创建springboot工程

导入依赖

这里注意  springboot版本 默认了es 的 一些版本,需要咱们自己统一定义  要不然会有版本冲突 所以 在pom 文件中 统一 es 版本

	<properties>
		<java.version>1.8</java.version>
		<elasticsearch.version>7.4.2</elasticsearch.version>
	</properties>



<dependencies>
     <!-- elasticsearch -->
    <dependency>
			<groupId>org.elasticsearch</groupId>
			<artifactId>elasticsearch</artifactId>
			<version>7.4.2</version>
		</dependency>
    
     <!--    elasticsearch 高阶API-->
   <dependency>
            <!--导入 es  的 高阶 api  来 操作 es
                要进行配置
                如果使用spsringdata 操作es  配置会比较简单 只需要在配置文件指定es 的地址就好了
                我们是自己配的 所以自己对es 做配置
            -->
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>

    <!--        3. junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <!--        4. lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.22</version>
    </dependency>
</dependencies>

创建配置类,测试连接ES

package com.glls.esdemo.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author glls

 */
@Configuration
public class GlscElasticSearchConfig {
    //RequestOptions   这个类 主要封装了 访问 ES 的  一些头信息   一些 设置信息

    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();
    }


    @Bean
    public RestHighLevelClient esRestClient(){
        RestClientBuilder builder = null;

        builder = RestClient.builder(new HttpHost("192.168.5.205",9200,"http"));

        RestHighLevelClient client = new RestHighLevelClient(builder);
        //RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.56.10", 9200, "http")));

        return client;
    }
}


// 测试 能不能得到 RestHighLevelClient
    @SpringBootTest
class EsdemoApplicationTests {
    @Resource
    private RestHighLevelClient client;

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

5.2 Java操作索引

5.2.1 创建索引

代码如下

   /**
     * 索引的创建
     *
     * */
	    /**
         * {
         *  "properties":{
         *      "name":{
         *          "type":"text"
         *      },
         *      "age":{
         *          "type":"integer"
         *      },
         *      "birthday":{
         *          "type":"date",
         *          "format":"yyyy-MM-dd"
         *      }
         *  }
         *
         * }
         *
         * */
    @Test
    public void demo1() throws IOException {
        // 创建索引
        String index = "person";
        //1. 准备关于索引的settings
        Settings.Builder settings = Settings.builder()
                .put("number_of_shards", 3)
                .put("number_of_replicas", 1);
        //2. 准备关于索引的结构mappings
        XContentBuilder mappings = JsonXContent.contentBuilder()
                .startObject()        // 和 endObject 成对出现
                .startObject("properties")
                .startObject("name")
                .field("type","text")
                .endObject()
                .startObject("age")
                .field("type","integer")
                .endObject()
                .startObject("birthday")
                .field("type","date")
                .field("format","yyyy-MM-dd")
                .endObject()
                .endObject()
                .endObject();


        //3. 将settings和mappings封装到一个Request对象
        // 不同的操作 准备的request 对象不一样 与下文对比
        CreateIndexRequest request = new CreateIndexRequest(index)
                .settings(settings)
                .mapping(mappings);//  如果是6版本 还需要在这个方法指定type                      //.mapping(type,mappings)

        //4. 通过client对象去连接ES并执行创建索引
        // 通过client 对象 把上面准备的 request 对象 发到es执行  
        CreateIndexResponse resp = client.indices().create(request, RequestOptions.DEFAULT);

        //5. 输出
        System.out.println("resp:" + resp.toString());

    }
5.2.2 检查索引是否存在

代码如下

 /**
     * 判断 索引是否存在
     * */
    @Test
    public void demo2() throws IOException {
        //1. 准备request对象
        String index = "person";
        GetIndexRequest request = new GetIndexRequest(index);
        request.indices();

        //2. 通过client去操作
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);


        //3. 输出
        System.out.println(exists);
    }
5.2.3 删除索引

代码如下

@Test
public void delete() throws IOException {
    //1. 准备request对象
    DeleteIndexRequest request = new DeleteIndexRequest();
    request.indices(index);

    //2. 通过client对象执行
    AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);

    //3. 获取返回结果
    System.out.println(delete.isAcknowledged());
}

5.3 Java操作文档

5.3.1 添加文档操作

代码如下

// 添加fastjson 依赖,  jackson 也可以 用法大致一样 
 <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.31</version>
        </dependency>
     
// 创建实体类
 @Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person {

    private Integer id;

    private String name;

    private Integer age;

    @JSONField(format = "yyyy-MM-dd")     // fastjson 转换对象时  对日期类型字段的 格式转换
    private Date birthday;

}
// 测试方法    
 /**
     * 添加文档
     *
     * */
    @Test
    public void demo4() throws IOException {
        String index = "person";

            //1. 准备一个json数据
            Person person = new Person(1,"张三",23,new Date());
            //2. 准备一个request对象(手动指定id)
            IndexRequest request = new IndexRequest(index);
            request.id(person.getId().toString());
            String json = JSON.toJSONString(person);
            System.out.println(json);
            request.source(json, XContentType.JSON);

            //3. 通过client对象执行添加
            IndexResponse resp = client.index(request, RequestOptions.DEFAULT);

            //4. 输出返回结果
            System.out.println(resp.getResult().toString());
    }

//添加doc 方式2
 @Test
    public void testAddDoc2() throws IOException {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id",2);
        map.put("name","李四");
        map.put("age",22);
        map.put("birthday","1999-11-11");


        //构建 request 对象
        IndexRequest request = new IndexRequest(index);
        request.id(map.get("id").toString());
        request.source(map);

        IndexResponse response = client.index(request, RequestOptions.DEFAULT);

        System.out.println(response.getResult().toString());
    }


5.3.2 修改文档

代码如下

/**
     * 修改文档
     * */
    @Test
    public void demo5() throws IOException {
        String index = "person";
        //1. 创建一个Map,指定需要修改的内容
        Map<String,Object> doc = new HashMap<>();
        doc.put("name","张大三");
        String docId = "1";

        //2. 创建request对象,封装数据
        UpdateRequest request = new UpdateRequest(index,docId);

        request.doc(doc);

        //3. 通过client对象执行
        UpdateResponse update = client.update(request, RequestOptions.DEFAULT);

        //4. 输出返回结果
        System.out.println(update.getResult().toString());
    }
5.3.3 删除文档

代码如下

@Test
public void deleteDoc() throws IOException {
    //1. 封装Request对象
    DeleteRequest request = new DeleteRequest(index,type,"1");

    //2. client执行
    DeleteResponse resp = client.delete(request, RequestOptions.DEFAULT);

    //3. 输出结果
    System.out.println(resp.getResult().toString());
}

5.4 Java批量操作文档

5.4.1 批量添加

代码如下

 /**
     * 批量添加
     *
     * */
    @Test
    public void bulkCreateDoc() throws IOException {
        String index="person";
        //1. 准备多个json数据
        Person p1 = new Person(3,"王五",23,new Date());
        Person p2 = new Person(4,"赵六",24,new Date());
        Person p3 = new Person(5,"田七",25,new Date());

        String json1 = JSON.toJSONString(p1);
        String json2 = JSON.toJSONString(p2);
        String json3 = JSON.toJSONString(p3);

        //2. 创建Request,将准备好的数据封装进去
        BulkRequest request = new BulkRequest();
        request.add(new IndexRequest(index).id(p1.getId().toString()).source(json1,XContentType.JSON));
        request.add(new IndexRequest(index).id(p2.getId().toString()).source(json2,XContentType.JSON));
        request.add(new IndexRequest(index).id(p3.getId().toString()).source(json3,XContentType.JSON));

        //3. 用client执行
        BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT);

        //4. 输出结果
        System.out.println(resp.toString());
    }
5.4.2 批量删除

代码如下

@Test
public void bulkDeleteDoc() throws IOException {
    //1. 封装Request对象
    BulkRequest request = new BulkRequest();
    request.add(new DeleteRequest(index,type,"1"));
    request.add(new DeleteRequest(index,type,"2"));
    request.add(new DeleteRequest(index,type,"3"));

    //2. client执行
    BulkResponse resp = client.bulk(request, RequestOptions.DEFAULT);

    //3. 输出
    System.out.println(resp);
}

5.5 ElasticSearch练习

创建索引,指定数据结构

索引名:sms-logs-index

类型名:sms-logs-type

结构如下:

索引结构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2lcdHZGK-1668231136915)(Pictures/1587137696912.png)]
5.5.1代码

实体类

package com.glls.esdemo.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @author glls
 * @email 524840158@qq.com
 * @company xxx
 * @create 2020-10-26 22:55
 *
 *
 * 准备 测试练习数据
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SmsLogs {

    // 唯一ID
    private String id;
    // 创建时间
    private Date createDate;

    private Date sendDate;  // 发送时间

    private String longCode;  //  发送的长号码

    private String mobile;  // 下发手机号

    private String corpName;   // 发送公司名称

    private String smsContent;  // 下发短信内容

    private Integer state;  // 短信下发状态 0 成功  1 失败

    private Integer operatorId;   // 运营商编号  1 移动  2 联通 3 电信

    private String province;  // 省份

    private String ipAddr;  // 下发服务器IP地址

    private Integer replyTotal;  // 短信状态报告返回时长(秒)

    private Integer fee;  // 费用

}

测试数据

package com.glls.esdemo;

import com.alibaba.fastjson.JSON;
import com.glls.esdemo.pojo.SmsLogs;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
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.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Date;

/**
 *
 * 检索操作
 * */

@SpringBootTest
class EsdemoApplicationTests2 {
    @Resource
    private RestHighLevelClient client;

    String index = "sms-logs-index";

    @Test
    public void createSmsLogsIndex() throws IOException {

        //1. settings
        Settings.Builder settings = Settings.builder()
                .put("number_of_shards", 3)
                .put("number_of_replicas", 1);

        //2. mapping.
        XContentBuilder mapping = JsonXContent.contentBuilder()
                .startObject()
                .startObject("properties")
                .startObject("createDate")
                .field("type", "date")
                .endObject()
                .startObject("sendDate")
                .field("type", "date")
                .endObject()
                .startObject("longCode")
                .field("type", "keyword")
                .endObject()
                .startObject("mobile")
                .field("type", "keyword")
                .endObject()
                .startObject("corpName")
                .field("type", "keyword")
                .endObject()
                .startObject("smsContent")
                .field("type", "text")
                .field("analyzer", "ik_max_word")
                .endObject()
                .startObject("state")
                .field("type", "integer")
                .endObject()
                .startObject("operatorId")
                .field("type", "integer")
                .endObject()
                .startObject("province")
                .field("type", "keyword")
                .endObject()
                .startObject("ipAddr")
                .field("type", "ip")
                .endObject()
                .startObject("replyTotal")
                .field("type", "integer")
                .endObject()
                .startObject("fee")
                .field("type", "long")
                .endObject()
                .endObject()
                .endObject();

        //3. 添加索引.
        CreateIndexRequest request = new CreateIndexRequest(index);
        request.settings(settings);
        request.mapping(mapping);
        CreateIndexResponse resp = client.indices().create(request, RequestOptions.DEFAULT);

        System.out.println("resp:" + resp.toString());
        System.out.println("OK!!");
    }


    @Test
    public void addTestData() throws IOException {
        BulkRequest request = new BulkRequest();

        SmsLogs smsLogs = new SmsLogs();
        smsLogs.setMobile("13800000000");
        smsLogs.setCorpName("途虎养车");
        smsLogs.setCreateDate(new Date());
        smsLogs.setSendDate(new Date());
        smsLogs.setIpAddr("10.126.2.9");
        smsLogs.setLongCode("10690000988");
        smsLogs.setReplyTotal(10);
        smsLogs.setState(0);
        smsLogs.setSmsContent("【途虎养车】亲爱的张三先生/女士,您在途虎购买的货品(单号TH123456)已 到指定安装店多日," + "现需与您确认订单的安装情况,请点击链接按实际情况选择(此链接有效期为72H)。您也可以登录途 虎APP进入" + "“我的-待安装订单”进行预约安装。若您在服务过程中有任何疑问,请致电400-111-8868向途虎咨 询。");
        smsLogs.setProvince("北京");
        smsLogs.setOperatorId(1);
        smsLogs.setFee(3);
        request.add(new IndexRequest(index).id("21").source(JSON.toJSONString(smsLogs), XContentType.JSON));

        smsLogs.setMobile("13700000001");
        smsLogs.setProvince("上海");
        smsLogs.setSmsContent("【途虎养车】亲爱的刘红先生/女士,您在途虎购买的货品(单号TH1234526)已 到指定安装店多日," + "现需与您确认订单的安装情况,请点击链接按实际情况选择(此链接有效期为72H)。您也可以登录途 虎APP进入" + "“我的-待安装订单”进行预约安装。若您在服务过程中有任何疑问,请致电400-111-8868向途虎咨 询。");
        request.add(new IndexRequest(index).id("22").source(JSON.toJSONString(smsLogs), XContentType.JSON));


        // -------------------------------------------------------------------------------------------------------------------

        SmsLogs smsLogs1 = new SmsLogs();
        smsLogs1.setMobile("13100000000");
        smsLogs1.setCorpName("盒马鲜生");
        smsLogs1.setCreateDate(new Date());
        smsLogs1.setSendDate(new Date());
        smsLogs1.setIpAddr("10.126.2.9");
        smsLogs1.setLongCode("10660000988");
        smsLogs1.setReplyTotal(15);
        smsLogs1.setState(0);
        smsLogs1.setSmsContent("【盒马】您尾号12345678的订单已开始配送,请在您指定的时间收货不要走开 哦~配送员:" + "刘三,电话:13800000000");
        smsLogs1.setProvince("北京");
        smsLogs1.setOperatorId(2);
        smsLogs1.setFee(5);
        request.add(new IndexRequest(index).id("23").source(JSON.toJSONString(smsLogs1), XContentType.JSON));

        smsLogs1.setMobile("18600000001");
        smsLogs1.setProvince("上海");
        smsLogs1.setSmsContent("【盒马】您尾号7775678的订单已开始配送,请在您指定的时间收货不要走开 哦~配送员:" + "王五,电话:13800000001");
        request.add(new IndexRequest(index).id("24").source(JSON.toJSONString(smsLogs1), XContentType.JSON));

        // -------------------------------------------------------------------------------------------------------------------

        SmsLogs smsLogs2 = new SmsLogs();
        smsLogs2.setMobile("15300000000");
        smsLogs2.setCorpName("滴滴打车");
        smsLogs2.setCreateDate(new Date());
        smsLogs2.setSendDate(new Date());
        smsLogs2.setIpAddr("10.126.2.8");
        smsLogs2.setLongCode("10660000988");
        smsLogs2.setReplyTotal(50);
        smsLogs2.setState(1);
        smsLogs2.setSmsContent("【滴滴单车平台】专属限时福利!青桔/小蓝月卡立享5折,特惠畅骑30天。" + "戳 https://xxxxxx退订TD");
        smsLogs2.setProvince("上海");
        smsLogs2.setOperatorId(3);
        smsLogs2.setFee(7);
        request.add(new IndexRequest(index).id("25").source(JSON.toJSONString(smsLogs2), XContentType.JSON));

        smsLogs2.setMobile("18000000001");
        smsLogs2.setProvince("武汉");
        smsLogs2.setSmsContent("【滴滴单车平台】专属限时福利!青桔/小蓝月卡立享5折,特惠畅骑30天。" + "戳 https://xxxxxx退订TD");
        request.add(new IndexRequest(index).id("26").source(JSON.toJSONString(smsLogs2), XContentType.JSON));


        // -------------------------------------------------------------------------------------------------------------------

        SmsLogs smsLogs3 = new SmsLogs();
        smsLogs3.setMobile("13900000000");
        smsLogs3.setCorpName("招商银行");
        smsLogs3.setCreateDate(new Date());
        smsLogs3.setSendDate(new Date());
        smsLogs3.setIpAddr("10.126.2.8");
        smsLogs3.setLongCode("10690000988");
        smsLogs3.setReplyTotal(50);
        smsLogs3.setState(0);
        smsLogs3.setSmsContent("【招商银行】尊贵的李四先生,恭喜您获得华为P30 Pro抽奖资格,还可领100 元打" + "车红包,仅限1天");
        smsLogs3.setProvince("上海");
        smsLogs3.setOperatorId(1);
        smsLogs3.setFee(8);
        request.add(new IndexRequest(index).id("27").source(JSON.toJSONString(smsLogs3), XContentType.JSON));

        smsLogs3.setMobile("13990000001");
        smsLogs3.setProvince("武汉");
        smsLogs3.setSmsContent("【招商银行】尊贵的李四先生,恭喜您获得华为P30 Pro抽奖资格,还可领100 元打" + "车红包,仅限1天");
        request.add(new IndexRequest(index).id("28").source(JSON.toJSONString(smsLogs3), XContentType.JSON));

        // -------------------------------------------------------------------------------------------------------------------

        SmsLogs smsLogs4 = new SmsLogs();
        smsLogs4.setMobile("13700000000");
        smsLogs4.setCorpName("中国平安保险有限公司");
        smsLogs4.setCreateDate(new Date());
        smsLogs4.setSendDate(new Date());
        smsLogs4.setIpAddr("10.126.2.8");
        smsLogs4.setLongCode("10690000998");
        smsLogs4.setReplyTotal(18);
        smsLogs4.setState(0);
        smsLogs4.setSmsContent("【中国平安】奋斗的时代,更需要健康的身体。中国平安为您提供多重健康保 障,在奋斗之路上为您保驾护航。退订请回复TD");
        smsLogs4.setProvince("武汉");
        smsLogs4.setOperatorId(1);
        smsLogs4.setFee(5);
        request.add(new IndexRequest(index).id("29").source(JSON.toJSONString(smsLogs4), XContentType.JSON));

        smsLogs4.setMobile("13990000002");
        smsLogs4.setProvince("武汉");
        smsLogs4.setSmsContent("【招商银行】尊贵的王五先生,恭喜您获得iphone 56抽奖资格,还可领5 元打" + "车红包,仅限100天");
        request.add(new IndexRequest(index).id("30").source(JSON.toJSONString(smsLogs4), XContentType.JSON));

        // -------------------------------------------------------------------------------------------------------------------


        SmsLogs smsLogs5 = new SmsLogs();
        smsLogs5.setMobile("13600000000");
        smsLogs5.setCorpName("中国移动");
        smsLogs5.setCreateDate(new Date());
        smsLogs5.setSendDate(new Date());
        smsLogs5.setIpAddr("10.126.2.8");
        smsLogs5.setLongCode("10650000998");
        smsLogs5.setReplyTotal(60);
        smsLogs5.setState(0);
        smsLogs5.setSmsContent("【北京移动】尊敬的客户137****0000,5月话费账单已送达您的139邮箱," + "点击查看账单详情 http://y.10086.cn/; " + " 回Q关闭通知,关注“中国移动139邮箱”微信随时查账单【中国移动 139邮箱】");
        smsLogs5.setProvince("武汉");
        smsLogs5.setOperatorId(1);
        smsLogs5.setFee(4);
        request.add(new IndexRequest(index).id("31").source(JSON.toJSONString(smsLogs5), XContentType.JSON));

        smsLogs5.setMobile("13990001234");
        smsLogs5.setProvince("山西");
        smsLogs5.setSmsContent("【北京移动】尊敬的客户137****1234,8月话费账单已送达您的126邮箱,\" + \"点击查看账单详情 http://y.10086.cn/; \" + \" 回Q关闭通知,关注“中国移动126邮箱”微信随时查账单【中国移动 126邮箱】");
        request.add(new IndexRequest(index).id("32").source(JSON.toJSONString(smsLogs5), XContentType.JSON));
        // -------------------------------------------------------------------------------------------------------------------

        client.bulk(request,RequestOptions.DEFAULT);

        System.out.println("OK!");
    }


}

六、 ElasticSearch的各种查询


6.1 term&terms查询【重点

6.1.1 term查询

term的查询是代表完全匹配,搜索之前不会对你搜索的关键字进行分词,对你的关键字去文档分词库中去匹配内容。

# term查询
POST /sms-logs-index/sms-logs-type/_search
{
  "from": 0,     # limit ?
  "size": 5,	  # limit x,?
  "query": {
    "term": {    # 完整匹配
      "province": {
        "value": "北京"    # 拿这个 北京 去完整匹配   不会分词匹配
      }
    }
  }
}

#注意 这里对 term 的理解   即 对 field 类型  text 和 keyword 的理解 ,term 是拿这个查询条件不进行分词  去匹配 文档中的内容, 而文档中的内容 如果是text 类型,会对这个 field 进行分词 ,如果分词后的内容 没有和 term 的 查询条件匹配上 ,那么term 查询 就查不出来结果, 如果文档中的内容 是 keyword ,就不会对文档中的进行分词,此时 就需要term 的完整匹配查询  才能查到数据。 总结  term 不对查询条件进行分词,  field是text或者keyword 类型  分别是 对文档内容分词(text)和不分词(keyword)


# 查询结果
{
  "took" : 2,    # 查询用了2毫秒
  "timed_out" : false,    # 是否超时  没有超时
  "_shards" : {    # 分片信息
    "total" : 3,   # 一共使用三个分片
    "successful" : 3,   # 成功了三个分片
    "skipped" : 0,    # 跳过
    "failed" : 0      # 失败
  },
  "hits" : {           # 查询命中
    "total" : {         # 总命中
      "value" : 2,      # 命中数
      "relation" : "eq"  # 查询关系
    },
    "max_score" : 0.6931472,    # 匹配分数   匹配度越高  分数越高
    "hits" : [
      {
        "_index" : "sms-logs-index",
        "_type" : "_doc",
        "_id" : "21",
        "_score" : 0.6931472,
        "_source" : {
          "corpName" : "途虎养车",
          "createDate" : 1607833538978,
          "fee" : 3,
          "ipAddr" : "10.126.2.9",
          "longCode" : "10690000988",
          "mobile" : "13800000000",
          "operatorId" : 1,
          "province" : "北京",
          "replyTotal" : 10,
          "sendDate" : 1607833538978,
          "smsContent" : "【途虎养车】亲爱的张三先生/女士,您在途虎购买的货品(单号TH123456)已 到指定安装店多日,现需与您确认订单的安装情况,请点击链接按实际情况选择(此链接有效期为72H)。您也可以登录途 虎APP进入“我的-待安装订单”进行预约安装。若您在服务过程中有任何疑问,请致电400-111-8868向途虎咨 询。",
          "state" : 0
        }
      },
      {
        "_index" : "sms-logs-index",
        "_type" : "_doc",
        "_id" : "23",
        "_score" : 0.6931472,
        "_source" : {
          "corpName" : "盒马鲜生",
          "createDate" : 1607833539131,
          "fee" : 5,
          "ipAddr" : "10.126.2.9",
          "longCode" : "10660000988",
          "mobile" : "13100000000",
          "operatorId" : 2,
          "province" : "北京",
          "replyTotal" : 15,
          "sendDate" : 1607833539131,
          "smsContent" : "【盒马】您尾号12345678的订单已开始配送,请在您指定的时间收货不要走开 哦~配送员:刘三,电话:13800000000",
          "state" : 0
        }
      }
    ]
  }
}

代码实现方式

// Java代码实现方式
@Test
public void termQuery() throws IOException {
    //1. 创建Request对象
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.from(0);
    builder.size(5);
    builder.query(QueryBuilders.termQuery("province","北京"));

    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 获取到_source中的数据,并展示
    for (SearchHit hit : resp.getHits().getHits()) {
        Map<String, Object> result = hit.getSourceAsMap();
        System.out.println(result);
    }
}
6.1.2 terms查询

terms和term的查询机制是一样,都不会将指定的查询关键字进行分词,直接去分词库中匹配,找到相应文档内容。

terms是在针对一个字段包含多个值的时候使用。

term:where province = 北京;

terms:where province = 北京 or province = ?or province = ? 一个字段可以等于多个值 有点类似 in

# terms查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "terms": {
      "province": [
        "北京",
        "山西",
        "武汉"
      ]
    }
  }
}


代码实现方式

// Java实现
@Test
public void termsQuery() throws IOException {
    //1. 创建request
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 封装查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.query(QueryBuilders.termsQuery("province","北京","山西"));

    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出_source
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}

6.2 match查询【重点

match查询属于高层查询,他会根据你查询的字段类型不一样,采用不同的查询方式。

  • 查询的是日期或者是数值的话,他会将你基于的字符串查询内容转换为日期或者数值对待。
  • 如果查询的内容是一个不能被分词的内容(keyword),match查询不会对你指定的查询关键字进行分词。
  • 如果查询的内容时一个可以被分词的内容(text),match会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容。

match查询,实际底层就是多个term查询,将多个term查询的结果给你封装到了一起。

6.2.1 match_all查询

查询全部内容,不指定任何查询条件。

# match_all查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match_all": {}
  }
}

代码实现方式

//  java代码实现
@Test
public void matchAllQuery() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.query(QueryBuilders.matchAllQuery());
    builder.size(20);           // ES默认只查询10条数据,如果想查询更多,添加size
    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
    System.out.println(resp.getHits().getHits().length);


}
6.2.2 match查询

指定一个Field作为筛选的条件

# match查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match": {
      "smsContent": "收货安装"     # smsContent 是 text 类型,match 会自动识别  会对查询条件也进行分词 也就是  把收获安装 按照分词器规则 拆分,比如 拆为  收获  和 安装 去和文档进行匹配
      }
  }
}

代码实现方式

@Test
public void matchQuery() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //-----------------------------------------------
    builder.query(QueryBuilders.matchQuery("smsContent","收货安装"));
    //-----------------------------------------------
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.2.3 布尔match查询

基于一个Field匹配的内容,采用and或者or的方式连接

# 布尔match查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match": {
      "smsContent": {
        "query": "中国 健康",
        "operator": "and"      # 内容既包含中国也包含健康
      }
    }
  }
}


# 布尔match查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match": {
      "smsContent": {
        "query": "中国 健康",
        "operator": "or"		# 内容包括健康或者包括中国
      }
    }
  }
}

代码实现方式

// Java代码实现
@Test
public void booleanMatchQuery() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //-----------------------------------------------                               选择AND或者OR
    builder.query(QueryBuilders.matchQuery("smsContent","中国 健康").operator(Operator.OR));
    //-----------------------------------------------
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.2.4 multi_match查询

match针对一个field做检索,multi_match针对多个field进行检索,多个field对应一个text。

# multi_match 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "multi_match": {
      "query": "北京",					# 指定text
      "fields": ["province","smsContent"]    # 指定field们
    }
  }
}

代码实现方式

// java代码实现
@Test
public void multiMatchQuery() throws IOException {
    //1. 创建Request
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //-----------------------------------------------
    builder.query(QueryBuilders.multiMatchQuery("北京","province","smsContent"));
    //-----------------------------------------------
    request.source(builder);
    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}

6.3 其他查询

6.3.1 id查询

根据id查询 where id = ?

# id查询      
GET /sms-logs-index/sms-logs-type/1        # GET /sms-logs-index/_doc/1

代码实现方式

// Java代码实现
@Test
public void findById() throws IOException {
    //1. 创建GetRequest
    GetRequest request = new GetRequest(index,"1");

    //2. 执行查询
    GetResponse resp = client.get(request, RequestOptions.DEFAULT);

    //3. 输出结果
    System.out.println(resp.getSourceAsMap());
}
6.3.2 ids查询

根据多个id查询,类似MySQL中的where id in(id1,id2,id2…)

# ids查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "ids": {
      "values": ["1","2","3"]
    }
  }
}

代码实现方式

// Java代码实现
@Test
public void findByIds() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //----------------------------------------------------------
    builder.query(QueryBuilders.idsQuery().addIds("1","2","3"));
    //----------------------------------------------------------
    request.source(builder);

    //3. 执行
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.3.3 prefix查询

前缀查询,可以通过一个关键字去指定一个Field的前缀,从而查询到指定的文档。

#prefix 查询
POST /sms-logs-index/_search
{
  "query": {
    "prefix": {
      "corpName": {
        "value": "途虎"
      }
    }
  }
}

代码实现方式

// Java实现前缀查询
@Test
public void findByPrefix() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //----------------------------------------------------------
    builder.query(QueryBuilders.prefixQuery("corpName","盒马"));
    //----------------------------------------------------------
    request.source(builder);

    //3. 执行
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.3.4 fuzzy查询

模糊查询,我们输入字符的大概(比如 出现错别字),ES就可以去根据输入的内容大概去匹配一下结果。

# fuzzy查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "fuzzy": {
      "corpName": {
        "value": "盒马先生",
        "prefix_length": 2			# 指定前面几个字符是不允许出现错误的
      }
    }
  }
}

代码实现方式

// Java代码实现Fuzzy查询
@Test
public void findByFuzzy() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //----------------------------------------------------------
    builder.query(QueryBuilders.fuzzyQuery("corpName","盒马先生").prefixLength(2));
    //----------------------------------------------------------
    request.source(builder);

    //3. 执行
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.3.5 wildcard查询

通配查询,和MySQL中的like是一个套路,可以在查询时,在字符串中指定通配符*和占位符?

*号匹配多个字符 ?匹配一个字符

# wildcard 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "wildcard": {
      "corpName": {
        "value": "中国*"    # 可以使用*和?指定通配符和占位符
      }
    }
  }
}

代码实现方式

// Java代码实现Wildcard查询
@Test
public void findByWildCard() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //----------------------------------------------------------
    builder.query(QueryBuilders.wildcardQuery("corpName","中国*"));
    //----------------------------------------------------------
    request.source(builder);

    //3. 执行
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.3.6 range查询

范围查询,只针对数值类型,对某一个Field进行大于或者小于的范围指定

# range 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "range": {
      "fee": {
        "gt": 5,
        "lte": 10
         # 可以使用 gt:>      gte:>=     lt:<     lte:<=
      }
    }
  }
}

代码实现方式

// Java实现range范围查询
@Test
public void findByRange() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //----------------------------------------------------------
    builder.query(QueryBuilders.rangeQuery("fee").lte(10).gte(5));
    //----------------------------------------------------------
    request.source(builder);

    //3. 执行
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}

6.3.7 regexp查询

正则查询,通过你编写的正则表达式去匹配内容。

Ps:prefix,fuzzy,wildcard和regexp查询效率相对比较低,要求效率比较高时,避免去使用

# regexp 查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "regexp": {
      "mobile": "180[0-9]{8}"    # 编写正则
    }
  }
}

代码实现方式

// Java代码实现正则查询
@Test
public void findByRegexp() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //----------------------------------------------------------
    builder.query(QueryBuilders.regexpQuery("mobile","139[0-9]{8}"));
    //----------------------------------------------------------
    request.source(builder);

    //3. 执行
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}

6.4 深分页Scroll

ES对from + size是有限制的,from和size二者之和不能超过1W

原理:

  • from+size在ES查询数据的方式:
    • 第一步现将用户指定的关键进行分词。
  • 第二步将词汇去分词库中进行检索,得到多个文档的id。
  • 第三步去各个分片中去拉取指定的全部数据。耗时较长。

  • 第四步将数据根据score进行排序。耗时较长。

  • 第五步根据from的值,将查询到的数据舍弃一部分。

  • 第六步返回结果。

  • scroll+size在ES查询数据的方式:

    • 第一步现将用户指定的关键进行分词。
  • 第二步将词汇去分词库中进行检索,得到多个文档的id。
  • 第三步将文档的id存放在一个ES的上下文中。
  • 第四步根据你指定的size的个数去ES中检索指定个数的数据,拿完数据的文档id,会从上下文中移除。
  • 第五步如果需要下一页数据,直接去ES的上下文中,找后续内容。
  • 第六步循环第四步和第五步

Scroll查询方式,不适合做实时的查询

# 执行scroll查询,返回第一页数据,并且将文档id信息存放在ES上下文中,指定生存时间1m
POST /sms-logs-index/sms-logs-type/_search?scroll=1m
{
  "query": {
    "match_all": {}
  },
  "size": 2,
  "sort": [					# 排序      默认是根据id字段排序
    {
      "fee": {              # 自定义排序字段   也可以指定多个字段排序,比如 fee一样时,按照另一个字段排序
        "order": "desc"    
      }
    }
  ]
}

# 根据scroll查询下一页数据
POST /_search/scroll
{
  "scroll_id": "<根据上面第一步得到的scorll_id去指定>",
  "scroll": "<scorll信息的生存时间>"         # 第二次查询 要重新指定上下文存活时间  要不然第二次查询之后  上下文就没了
}
# 当全部查询完之后 这个 scroll_id 对应的es上下文中的doc id 都被移除干净了


# 删除scroll在ES上下文中的数据
DELETE /_search/scroll/scroll_id

代码实现方式

// Java实现scroll分页
@Test
public void scrollQuery() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    

    //2. 指定scroll信息
    request.scroll(TimeValue.timeValueMinutes(1L));

    //3. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.size(4);
    builder.sort("fee", SortOrder.DESC);
    builder.query(QueryBuilders.matchAllQuery());
    
    request.source(builder);

    //4. 获取返回结果scrollId,source
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    String scrollId = resp.getScrollId();
    System.out.println("----------首页---------");
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }


    while(true) {
        //5. 循环 - 创建SearchScrollRequest
        SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);

        //6. 指定scrollId的生存时间
        scrollRequest.scroll(TimeValue.timeValueMinutes(1L));

        //7. 执行查询获取返回结果
        SearchResponse scrollResp = client.scroll(scrollRequest, RequestOptions.DEFAULT);

        //8. 判断是否查询到了数据,输出
        SearchHit[] hits = scrollResp.getHits().getHits();
        if(hits != null && hits.length > 0) {
            System.out.println("----------下一页---------");
            for (SearchHit hit : hits) {
                System.out.println(hit.getSourceAsMap());
            }
        }else{
            //9. 判断没有查询到数据-退出循环
            System.out.println("----------结束---------");
            break;
        }
    }


    //10. 创建CLearScrollRequest
    ClearScrollRequest clearScrollRequest = new ClearScrollRequest();

    //11. 指定ScrollId
    clearScrollRequest.addScrollId(scrollId);

    //12. 删除ScrollId
    ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);

    //13. 输出结果
    System.out.println("删除scroll:" + clearScrollResponse.isSucceeded());

}

6.5 delete-by-query

根据term,match等查询方式去删除大量的文档

Ps:如果你需要删除的内容,是index下的大部分数据,推荐创建一个全新的index,将保留的文档内容,添加到全新的索引

# delete-by-query
POST /sms-logs-index/sms-logs-type/_delete_by_query
{
  "query": {
    "range": {
      "fee": {
        "lt": 4
      }
    }
  }
}

代码实现方式

// Java代码实现
@Test
public void deleteByQuery() throws IOException {
    //1. 创建DeleteByQueryRequest
    DeleteByQueryRequest request = new DeleteByQueryRequest(index);
    
    //2. 指定检索的条件    和SearchRequest指定Query的方式不一样
    request.setQuery(QueryBuilders.rangeQuery("fee").lt(4));

    //3. 执行删除
    BulkByScrollResponse resp = client.deleteByQuery(request, RequestOptions.DEFAULT);

    //4. 输出返回结果
    System.out.println(resp.toString());

}

6.6 复合查询

6.6.1 bool查询

复合过滤器,将你的多个查询条件,以一定的逻辑组合在一起。

  • must: 所有的条件,用must组合在一起,表示And的意思
  • must_not:将must_not中的条件,全部都不能匹配,标识Not的意思
  • should:所有的条件,用should组合在一起,表示Or的意思
# 查询省份为武汉或者北京
# 运营商不是联通
# smsContent中包含中国和平安
# bool查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "province": {
              "value": "北京"
            }
          }
        },
        {
          "term": {
            "province": {
              "value": "武汉"
            }
          }
        }
      ],
      "must_not": [
        {
          "term": {
            "operatorId": {
              "value": "2"
            }
          }
        }
      ],
      "must": [
        {
          "match": {
            "smsContent": "中国"
          }
        },
        {
          "match": {
            "smsContent": "平安"
          }
        }
      ]
    }
  }
}

代码实现方式

// Java代码实现Bool查询
@Test
public void BoolQuery() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
  
    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // # 查询省份为武汉或者北京
    boolQuery.should(QueryBuilders.termQuery("province","武汉"));
    boolQuery.should(QueryBuilders.termQuery("province","北京"));
    // # 运营商不是联通
    boolQuery.mustNot(QueryBuilders.termQuery("operatorId",2));
    // # smsContent中包含中国和平安
    boolQuery.must(QueryBuilders.matchQuery("smsContent","中国"));
    boolQuery.must(QueryBuilders.matchQuery("smsContent","平安"));

    builder.query(boolQuery);
    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}
6.6.2 boosting查询

boosting查询可以帮助我们去影响查询后的score。

  • positive:只有匹配上positive的查询的内容,才会被放到返回的结果集中。
  • negative:如果匹配上和positive并且也匹配上了negative,就可以降低这样的文档score。
  • negative_boost:指定系数,必须小于1.0

关于查询时,分数是如何计算的:

  • 搜索的关键字在文档中出现的频次越高,分数就越高
  • 指定的文档内容越短,分数就越高
  • 我们在搜索时,指定的关键字也会被分词,这个被分词的内容,被分词库匹配的个数越多,分数越高
# boosting查询  收货安装
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "smsContent": "收货安装"
        }
      },
      "negative": {
        "match": {
          "smsContent": "王五"
        }
      },
      "negative_boost": 0.5
    }
  }
}

代码实现方式

// Java实现Boosting查询
@Test
public void BoostingQuery() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    BoostingQueryBuilder boostingQuery = QueryBuilders.boostingQuery(
            QueryBuilders.matchQuery("smsContent", "收货安装"),
            QueryBuilders.matchQuery("smsContent", "王五")
    ).negativeBoost(0.5f);

    builder.query(boostingQuery);
    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}

6.7 filter查询

query,根据你的查询条件,去计算文档的匹配度得到一个分数,并且根据分数进行排序,不会做缓存的。

filter,根据你的查询条件去查询文档,不去计算分数,而且filter会对经常被过滤的数据进行缓存。

# filter查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "corpName": "盒马鲜生"
          }
        },
        {
          "range": {
            "fee": {
              "lte": 4
            }
          }
        }
      ]
    }
  }
}

代码实现方式

// Java实现filter操作
@Test
public void filter() throws IOException {
    //1. SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 查询条件
    SearchSourceBuilder builder = new SearchSourceBuilder();
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    boolQuery.filter(QueryBuilders.termQuery("corpName","盒马鲜生"));
    boolQuery.filter(QueryBuilders.rangeQuery("fee").lte(5));

    builder.query(boolQuery);
    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }


}

6.8 高亮查询【重点

高亮查询就是你用户输入的关键字,以一定的特殊样式展示给用户,让用户知道为什么这个结果被检索出来。

高亮展示的数据,本身就是文档中的一个Field,单独将Field以highlight的形式返回给你。

ES提供了一个highlight属性,和query同级别的。

  • fragment_size:指定高亮数据展示多少个字符回来。默认100个
  • pre_tags:指定前缀标签,举个栗子< font color=“red” >
  • post_tags:指定后缀标签,举个栗子< /font >
  • fields:指定哪几个Field以高亮形式返回
效果图
image-20201208195749344

RESTful实现

# highlight查询
POST /sms-logs-index/sms-logs-type/_search
{
  "query": {
    "match": {
      "smsContent": "盒马"
    }
  },
  "highlight": {
    "fields": {
      "smsContent": {}
    },
    "pre_tags": "<font color='red'>",
    "post_tags": "</font>",
    "fragment_size": 10
  }
}

代码实现方式

// Java实现高亮查询
@Test
public void highLightQuery() throws IOException {
    //1. SearchRequest
    SearchRequest request = new SearchRequest(index);
    
    //2. 指定查询条件(高亮)
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //2.1 指定查询条件
    builder.query(QueryBuilders.matchQuery("smsContent","盒马"));
    //2.2 指定高亮
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("smsContent",10)
            .preTags("<font color='red'>")
            .postTags("</font>");
    builder.highlighter(highlightBuilder);

    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 获取高亮数据,输出
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getHighlightFields().get("smsContent"));
    }
}

6.9 聚合查询【重点

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

ES的聚合查询和MySQL的聚合查询类似,ES的聚合查询相比MySQL要强大的多,ES提供的统计数据的方式多种多样。

# ES聚合查询的RESTful语法
POST /index/type/_search
{
    "aggs": {
        "名字(agg)": {   # 名字 自定义 只会影响返回结果的名字
            "agg_type": {    # es 给咱们提供的聚合类型   咱们直接使用即可 
                "属性": "值"
            }
        }
    }
}
6.9.1 去重计数查询

去重计数,即Cardinality,第一步先将返回的文档中的一个指定的field进行去重,统计一共有多少条

# 去重计数查询 北京 上海 武汉 山西
# 这些记录中 一共出现了几个省份
POST /sms-logs-index/sms-logs-type/_search
{
  "aggs": {
    "agg": {
      "cardinality": {
        "field": "province"     # 按照 field 进行去重
      }
    }
  }
}

代码实现方式

//  Java代码实现去重计数查询
@Test
public void cardinality() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定使用的聚合查询方式
    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.aggregation(AggregationBuilders.cardinality("agg").field("province"));

    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 获取返回结果
    Cardinality agg = resp.getAggregations().get("agg");
    long value = agg.getValue();
    System.out.println(value);
}
6.9.2 范围统计

统计一定范围内出现的文档个数,比如,针对某一个Field的值在 0100,100200,200~300之间文档出现的个数分别是多少。

范围统计可以针对普通的数值,针对时间类型,针对ip类型都可以做相应的统计。

range,date_range,ip_range

数值统计

# 数值方式范围统计
POST /sms-logs-index/_search
{
  "aggs": {
    "agg": {
      "range": {
        "field": "fee",
        "ranges": [
          {
            "to": 5        # 没有等于的效果
          },
          {
            "from": 5,    # from有包含当前值的意思    有等于的效果 
            "to": 10
          },
          {
            "from": 10
          }
        ]
      }
    }
  }
}

时间范围统计

# 时间方式范围统计
POST /sms-logs-index/sms-logs-type/_search
{
  "aggs": {
    "agg": {
      "date_range": {
        "field": "createDate",
        "format": "yyyy", 
        "ranges": [
          {
            "to": 2000
          },
          {
            "from": 2000
          }
        ]
      }
    }
  }
}

ip统计方式

# ip方式 范围统计
POST /sms-logs-index/sms-logs-type/_search
{
  "aggs": {
    "agg": {
      "ip_range": {
        "field": "ipAddr",
        "ranges": [
          {
            "to": "10.126.2.9"
          },
          {
            "from": "10.126.2.9"
          }
        ]
      }
    }
  }
}

代码实现方式

// Java实现数值 范围统计
@Test
public void range() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
   

    //2. 指定使用的聚合查询方式
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //---------------------------------------------
    builder.aggregation(AggregationBuilders.range("agg").field("fee")
                                        .addUnboundedTo(5)
                                        .addRange(5,10)
                                        .addUnboundedFrom(10));
    //---------------------------------------------
    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 获取返回结果
    Range agg = resp.getAggregations().get("agg");
    for (Range.Bucket bucket : agg.getBuckets()) {
        String key = bucket.getKeyAsString();
        Object from = bucket.getFrom();
        Object to = bucket.getTo();
        long docCount = bucket.getDocCount();
        System.out.println(String.format("key:%s,from:%s,to:%s,docCount:%s",key,from,to,docCount));
    }
}
6.9.3 统计聚合查询

他可以帮你查询指定Field的最大值,最小值,平均值,平方和等

使用:extended_stats

# 统计聚合查询
POST /sms-logs-index/sms-logs-type/_search
{
  "aggs": {
    "agg": {
      "extended_stats": {
        "field": "fee"
      }
    }
  }
}

代码实现方式

// Java实现统计聚合查询
@Test
public void extendedStats() throws IOException {
    //1. 创建SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定使用的聚合查询方式
    SearchSourceBuilder builder = new SearchSourceBuilder();
    //---------------------------------------------
    builder.aggregation(AggregationBuilders.extendedStats("agg").field("fee"));
    //---------------------------------------------
    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 获取返回结果
    ExtendedStats agg = resp.getAggregations().get("agg");
    double max = agg.getMax();
    double min = agg.getMin();
    System.out.println("fee的最大值为:" + max + ",最小值为:" + min);
}

其他的聚合查询方式查看官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/index.html

6.10 地图经纬度搜索

ES中提供了一个数据类型 geo_point,这个类型就是用来存储经纬度的。

创建一个带geo_point类型的索引,并添加测试数据

# 创建一个索引,指定一个name,locaiton
PUT /map
{
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  },
  "mappings": {
    "map": {
      "properties": {
        "name": {
          "type": "text"
        },
        "location": {
          "type": "geo_point"
        }
      }
    }
  }
}


# 添加测试数据
PUT /map/map/1
{
  "name": "天安门",
  "location": {
    "lon": 116.403981,
    "lat": 39.914492 
  }
}


PUT /map/map/2
{
  "name": "海淀公园",
  "location": {
    "lon": 116.302509,
    "lat": 39.991152 
  }
}

PUT /map/map/3
{
  "name": "北京动物园",
  "location": {
    "lon": 116.343184,
    "lat": 39.947468 
  }
}
6.10.1 ES的地图检索方式
语法说明
geo_distance直线距离检索方式
geo_bounding_box以两个点确定一个矩形,获取在矩形内的全部数据
geo_polygon以多个点,确定一个多边形,获取多边形内的全部数据
6.10.2 基于RESTful实现地图检索

geo_distance

# geo_distance
POST /map/map/_search
{
  "query": {
    "geo_distance": {
      "location": {				# 确定一个点
        "lon": 116.433733,
        "lat": 39.908404
      },
      "distance": 3000,			 # 确定半径    默认为   米    ,可以通过  unit 来指定
      "distance_type": "arc"     # 指定形状为圆形
    }
  }
}

geo_bounding_box

# geo_bounding_box
POST /map/map/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {				# 左上角的坐标点
          "lon": 116.326943,
          "lat": 39.95499
        },
        "bottom_right": {			 # 右下角的坐标点
          "lon": 116.433446,
          "lat": 39.908737
        }
      }
    }
  }
}

geo_polygon

# geo_polygon
POST /map/map/_search
{
  "query": {
    "geo_polygon": {
      "location": {
        "points": [					# 指定多个点确定一个多边形
          {
            "lon": 116.298916,
            "lat": 39.99878
          },
          {
            "lon": 116.29561,
            "lat": 39.972576
          },
          {
            "lon": 116.327661,
            "lat": 39.984739
          }
        ]
      }
    }
  }
}
6.10.3 Java实现geo_polygon
// 基于Java实现geo_polygon查询
@Test
public void geoPolygon() throws IOException {
    //1. SearchRequest
    SearchRequest request = new SearchRequest(index);
    request.types(type);

    //2. 指定检索方式
    SearchSourceBuilder builder = new SearchSourceBuilder();
    List<GeoPoint> points = new ArrayList<>();
    points.add(new GeoPoint(39.99878,116.298916));
    points.add(new GeoPoint(39.972576,116.29561));
    points.add(new GeoPoint(39.984739,116.327661));
    builder.query(QueryBuilders.geoPolygonQuery("location",points));

    request.source(builder);

    //3. 执行查询
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);

    //4. 输出结果
    for (SearchHit hit : resp.getHits().getHits()) {
        System.out.println(hit.getSourceAsMap());
    }
}

七、springboot 整合elasticsearch

1.添加依赖

  <dependency>
           <!--导入 es  的 高阶 api  来 操作 es
               要进行配置
               如果使用spsringdata 操作es  配置会比较简单 只需要在配置文件指定es 的地址就好了
               我们是自己配的 所以自己对es 做配置
           -->
           <groupId>org.elasticsearch.client</groupId>
           <artifactId>elasticsearch-rest-high-level-client</artifactId>
           <version>7.4.2</version>
       </dependency>
引入这个依赖后 发现 elasticsearch 的版本是6.8.7 ,这是因为 springboot 对es 的默认支持  如下

image-20201025101510446

所以 咱们需要手动把这个版本改了 在 搜索模块中 直接指定es 的 版本,把父工程中的版本号覆盖掉

image-20201025101626675

2.编写配置 给容器注入 RestHighLevelClient

# 创建配置类
@Configuration
public class GlscElasticSearchConfig {
	   
       
       //RequestOptions   这个类 主要封装了 访问 ES 的  一些头信息   一些 设置信息
    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();
    }
	
	
    @Bean
    public RestHighLevelClient esRestClient(){
        RestClientBuilder builder = null;

        builder = RestClient.builder(new HttpHost("192.168.56.10",9200,"http"));

        RestHighLevelClient client = new RestHighLevelClient(builder);
        //RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.56.10", 9200, "http")));

        return client;
    }
}
# 测试  获取RestHighLevelClient 这个bean
@SpringBootTest
public class GlscSearchApplicationTests {

    @Resource
    private RestHighLevelClient client;
    
    @Test
    public void contextLoads() {
        System.out.println(client);
    }
}    
7.6.2 测试保存

参考 https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-document-index.html

 /**
     * 测试存储数据到es
     *
     * 如果没内容  就是添加  有内容 就是更新操作
     * */
    @Test
    public void indexData() throws IOException {
        // users  是 索引 名字
        IndexRequest indexRequest = new IndexRequest("users");

        indexRequest.id("2");  // 数据的id

        // 第一种方式
        //indexRequest.source("userName","zhangsan","age",18,"gender","男");

        // 第二种方式    推荐
        User user = new User();
        user.setUserName("lisi");
        user.setAge(18);
        user.setGender("男");
        String s = JSON.toJSONString(user);
        indexRequest.source(s, XContentType.JSON);

        // 网络操作 都会有异常
        // 执行保存操作   返回响应
        IndexResponse index = client.index(indexRequest, GlscElasticSearchConfig.COMMON_OPTIONS);

        //输出响应
        System.out.println(index);
    }
7.6.3 测试检索

参考 https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html

/**
     * 测试复杂检索
     *
     * */
    @Test
    public void searchData() throws IOException {
        //1.创建检索请求
        SearchRequest searchRequest = new SearchRequest();

        //2. 指定从哪里开始检索   指定索引
        searchRequest.indices("bank");
        //3.指定 DSL  检索条件
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //3.1 构造检索条件
//        builder.query();
//        builder.from();
//        builder.size();
//        builder.aggregation();
        builder.query(QueryBuilders.matchQuery("address","mill"));
        // 按照年龄的值分布 进行聚合
        TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
        builder.aggregation(ageAgg);
        // 按照平均薪资 聚合
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        builder.aggregation(balanceAvg);
        // 检索条件
        System.out.println("检索条件"+builder.toString());

        searchRequest.source(builder);


        //4. 执行 检索
        SearchResponse response = client.search(searchRequest, GlscElasticSearchConfig.COMMON_OPTIONS);
        System.out.println(response.toString());

        //5. 分析结果   结果封装在  response 对象中

        //RestStatus status = response.status();  得到响应状态码
        //TimeValue took = response.getTook();   花费了多长时间
        //boolean timedOut = response.isTimedOut();  是否超时


        // 外层的hits
        SearchHits hits = response.getHits();   // 得到所有命中的记录

        //TotalHits totalHits = hits.getTotalHits();  // 得到 总记录数
        //long value = totalHits.value;   // 总记录数
        //TotalHits.Relation relation = totalHits.relation;   // 相关性得分

        //float maxScore = hits.getMaxScore();   //最大得分

        // 获取 所有记录  遍历     内层的hits
        SearchHit[] searchHits = hits.getHits();
        for(SearchHit hit : searchHits){
//            String index = hit.getIndex();
//            String id = hit.getId();
//            float score = hit.getScore();
//
//            String sourceAsString = hit.getSourceAsString();
//            Map<String, Object> sourceAsMap = hit.getSourceAsMap();  // 将返回的数据 转为map

            String sourceAsString = hit.getSourceAsString();
            Account account = JSON.parseObject(sourceAsString, Account.class);

            System.out.println("account:"+account);
        }

        //获取这次检索到的分析信息
        Aggregations aggregations = response.getAggregations();
//        List<Aggregation> aggregations1 = aggregations.asList();
//        for(Aggregation aggregation : aggregations1){
//            System.out.println("当前聚合的名字:"+aggregation.getName());
//        }

        Terms ageAgg1 = aggregations.get("ageAgg");
        for(Terms.Bucket bucket : ageAgg1.getBuckets()){
            String keyAsString = bucket.getKeyAsString();
            System.out.println("年龄:"+keyAsString);
        }

        Avg balanceAvg1 = aggregations.get("balanceAvg");

        System.out.println("平均薪资:"+balanceAvg1.getValue());


    }

    @Data
    @ToString
    static class Account{
        private int account_number;
        private int balance;
        private String firstname;
        private String lastname;
        private int age;
        private String gender;
        private String address;
        private String employer;
        private String email;
        private String city;
        private String state;

    }

 builder.from();
//        builder.size();
//        builder.aggregation();
        builder.query(QueryBuilders.matchQuery("address","mill"));
        // 按照年龄的值分布 进行聚合
        TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
        builder.aggregation(ageAgg);
        // 按照平均薪资 聚合
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        builder.aggregation(balanceAvg);
        // 检索条件
        System.out.println("检索条件"+builder.toString());

        searchRequest.source(builder);


        //4. 执行 检索
        SearchResponse response = client.search(searchRequest, GlscElasticSearchConfig.COMMON_OPTIONS);
        System.out.println(response.toString());

        //5. 分析结果   结果封装在  response 对象中

        //RestStatus status = response.status();  得到响应状态码
        //TimeValue took = response.getTook();   花费了多长时间
        //boolean timedOut = response.isTimedOut();  是否超时


        // 外层的hits
        SearchHits hits = response.getHits();   // 得到所有命中的记录

        //TotalHits totalHits = hits.getTotalHits();  // 得到 总记录数
        //long value = totalHits.value;   // 总记录数
        //TotalHits.Relation relation = totalHits.relation;   // 相关性得分

        //float maxScore = hits.getMaxScore();   //最大得分

        // 获取 所有记录  遍历     内层的hits
        SearchHit[] searchHits = hits.getHits();
        for(SearchHit hit : searchHits){
//            String index = hit.getIndex();
//            String id = hit.getId();
//            float score = hit.getScore();
//
//            String sourceAsString = hit.getSourceAsString();
//            Map<String, Object> sourceAsMap = hit.getSourceAsMap();  // 将返回的数据 转为map

            String sourceAsString = hit.getSourceAsString();
            Account account = JSON.parseObject(sourceAsString, Account.class);

            System.out.println("account:"+account);
        }

        //获取这次检索到的分析信息
        Aggregations aggregations = response.getAggregations();
//        List<Aggregation> aggregations1 = aggregations.asList();
//        for(Aggregation aggregation : aggregations1){
//            System.out.println("当前聚合的名字:"+aggregation.getName());
//        }

        Terms ageAgg1 = aggregations.get("ageAgg");
        for(Terms.Bucket bucket : ageAgg1.getBuckets()){
            String keyAsString = bucket.getKeyAsString();
            System.out.println("年龄:"+keyAsString);
        }

        Avg balanceAvg1 = aggregations.get("balanceAvg");

        System.out.println("平均薪资:"+balanceAvg1.getValue());


    }

    @Data
    @ToString
    static class Account{
        private int account_number;
        private int balance;
        private String firstname;
        private String lastname;
        private int age;
        private String gender;
        private String address;
        private String employer;
        private String email;
        private String city;
        private String state;

    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值