zoukankan      html  css  js  c++  java
  • ElasticSearch

    ElasticSearch

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

    数据库查询存在的问题:

    1. 性能低:使用模糊查询,左边有通配符,不会走索引,会全表扫描,性能低

    2. 功能弱:

      • 对于如下的数据如果以”华为手机“作为条件,查询不出来数据

        select * from goods where title like '%华为手机%'
      • 华为手机需要拆分成华为和手机两个词然后分别查询,但是MySQL等关系型数据库没有拆分词语的功能

        select * from goods where title like '%华为%' or title like '%手机%'

    Es通过倒排索引解决这些问题,比如京东的商品信息就保存在ElasticSearch中,可以很快速的得到搜索结果

     

    倒排索引(重点)

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

    分词:将华为手机拆分成华为和手机两个词,这个动作简称为分词

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

    1. 先对“床前明月光”--> 分词

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

    2. 所有的分词结果都记录对应的诗句内容

    反向索引的实现就是对诗句进行分词,分成单个的词或字,由词推句,即为反向索引

    2.3-ES存储和查询的原理(理解)

    需要解决数据库查询存在的问题:

    1. 性能低:使用模糊查询,左边有通配符,不会走索引,会全表扫描,性能低

    2. 功能弱:对于如下的数据如果以”华为手机“作为条件,查询不出来数据

    存储和查询原理:

    • 存储

      对存储数据中的title进行分词,记录每个词语和数据id的对应关系(倒排索引)

    • 搜索:使用倒排索引,自定将对title进行分词(“华为”,“手机”),找到所有的匹配:1,2,3

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

      华为:1,3

      手机:1,2,3

    ES和MySQL的区别

    •MySQL有事务性,而ElasticSearch没有事务性,所以你删了的数据是无法恢复的。

    •ElasticSearch没有物理外键这个特性,,如果你的数据强一致性要求比较高,还是建议慎用

    •ElasticSearch和MySql分工不同,MySQL负责存储(增删改)数据,ElasticSearch负责搜索数据

    MySQL同步数据到ES常用工具:

    • 通过JavaAPI写入ES

    • logstash, es官方推荐的

    • canal, 阿里开源的

    3 映射(mapping)

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

    4 文档(document)

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

    5 倒排索引

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

    对比MySQL

    脚本操作ES(重点)

    复习RESTful风格

    1.REST(Representational State Transfer),表述性状态转移,是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。就是一种定义接口的规范。

    2.基于HTTP。

    3.使用XML格式定义或JSON格式定义。

    4.每一个URI代表一种资源。

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

    GET:用来获取资源(查询)

    POST:用来新建资源(新增)

    PUT:用来更新资源(修改)

    DELETE:用来删除资源(删除)

     

    操作索引

    使用Kibana操作ES:http://192.168.52.128:5601/app/kibana#/dev_tools/console?_g=()

    kibana是操作ES的WEB客户端,相当于操作MySQL数据库的sqlyog

    # 创建索引 
    PUT person
    # 查看索引
    GET person
    # 删除索引(同时会删除其所有数据,相当于mysql的drop database)
    DELETE person
    # 查询所有索引
    GET _all

    delete /c*   (通配符删除c 开头的索引)

    ES数据类型

    1. 简单数据类型

    • 字符串

      text:会分词,不支持聚合
      keyword:不会分词,将全部内容作为一个词条,支持聚合
    • 数值:long.inteter,double等

      image-20201104190520738

    • 布尔:boolean

    • 日期:date

    • 二进制:binary

    • 范围类型

      integer_range, float_range, long_range, double_range, date_range 
    1. 复杂数据类型

    • 数组:[ ] Nested: nested (for arrays of JSON objects 数组类型的JSON对象)

    • 对象:{ } Object: object(for single JSON objects 单个JSON对象)

    注意: 字段类型没有修改功能

    操作映射

    添加

    # 删除索引(同时会删除其所有数据,相当于mysql的drop database)
    DELETE person

    # 创建索引
    PUT person

    # 查看索引
    GET person

    # 添加映射(相当于添加表字段)
    PUT /person/_mapping
    {
       "properties":{
           "name":{
               "type":"text"
          },
           "age":{
               "type":"integer"
          }
      }
    }

    查看

    # 仅查看映射(查看表结构)
    GET person/_mapping
    # 仅查看索引,会自动显示表结构(查看表结构)
    GET person

     

    索引+ 映射一起创建

    # 创建索引并添加映射(相当于建立数据库时,(因为只有一张表type=_doc)同时制定表字段)
    PUT /person
    {
     "mappings": {
       "properties": {
         "name": {
           "type": "text"
        },
         "age": {
           "type": "integer"
        }
      }
    }
    }

    操作文档

    添加/更新文档

    # 指定id,如果id=1数据不存在,则添加(insert)数据;否则是修改(update)
    PUT /person/_doc/1
    {
     "name":"张三",
     "age":18,
     "address":"北京海淀区"
    }

    # 添加文档,不指定id
    POST /person/_doc/
    {
     "name":"王五",
     "age":18,
     "address":"北京"
    }

    查看文档(简单查看)

    # 根据id 查看
    GET /person1/_doc/1
    # 查看所有(无条件查询)
    GET /person1/_search

    删除

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

    ik分词器

    中文分词器

    •IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包

    •是一个基于Maven构建的项目

    •具有60万字/秒的高速处理能力

    •支持用户词典扩展定义

    •下载地址:https://github.com/medcl/elasticsearch-analysis-ik/archive/v7.4.0.zip

    参见 ik分词器安装.md

     

    ik分词器使用**

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

    1、ik_max_word

    # 方式一ik_max_word
    # 会将文本做最细粒度的拆分,比如会将“乒乓球明年总冠军”拆分为“乒乓球、乒乓、球、明年、总冠军、冠军。
    GET /_analyze
    {
     "analyzer": "ik_max_word",
     "text": "乒乓球明年总冠军"
    }

    ik_max_word分词器执行如下:

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

    2、ik_smart

    # 方式二ik_smart
    # 会做最粗粒度的拆分,比如会将“乒乓球明年总冠军”拆分为乒乓球、明年、总冠军。
    GET /_analyze
    {
     "analyzer": "ik_smart",
     "text": "乒乓球明年总冠军"
    }

    ik_smart分词器执行如下:

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

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

    这样看的话,这样的分词效果更智能一些,达到了我们的要求。

     

    使用IK分词器-查询文档(重点)

    准备测试数据

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

    # 如果有删除
    DELETE person

    # 添加映射_指定分词器(相当于添加表字段)
    PUT person
    {
     "mappings": {
       "properties": {
         "name": {
           "type": "keyword"   // keyword 类型 不会分词
        },
         "address": {
           "type": "text",  // text 类型 会分词, 但不能进行聚合查询(类似SQL group by/sum函数)
           "analyzer": "ik_max_word"
        }
      }
    }
    }

    GET person

    2.添加文档

    # 添加几条数据备用
    # 指定id
    POST /person/_doc/1
    {
     "name":"张三",
     "age":18,
     "address":"北京海淀区"
    }

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

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

    POST /person/_doc/4
    {
     "name":"李雷",
     "age":18,
     "address":"华为5G手机"
    }

    3.查询映射数据

    GET /person/_search

    4.查看分词效果

    GET _analyze
    {
     "analyzer": "ik_max_word",
     "text": "北京昌平区"
    }

     

    term查询-关键词

    词条查询:term,不会将查询条件拆分

    使用“北京”做为查询条件:

    # 可以查询到3条数据
    GET /person/_search
    {
     "query": {
       "term": {
         "address": {
           "value": "北京"
        }
      }
    }
    }

     

    使用“北京昌平”做为查询条件:

    # 查询不到数据,因为没有一个叫“北京昌平”的词条
    GET /person/_search
    {
     "query": {
       "term": {
         "address": {
           "value": "北京昌平"
        }
      }
    }
    }

    match查询-全文

    全文查询:match

    match查询会对查询条件进行分词,先将查询条件进行分词,然后查询,求并集(or)

    # 1.对查询条件“北京昌平”进行分词: 北京,昌平
    # 2.根据分词结果逐个查询

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

    结果会把所有address中有北京和昌平的都查询出来:

     

     

    term VS match

    • 词条查询:term(关键词查询)

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

    • 全文查询:match(全文查询)

    match查询会对查询条件进行分词,先将查询条件进行分词,然后查询,求并集(or)

     

    JavaAPI(重点)

    SpringBoot整合ES

    ①搭建SpringBoot工程

    ②引入ElasticSearch相关坐标:7.4.0

    <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.1.8.RELEASE</version>
       <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </dependency>

       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>

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

       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.4</version>
       </dependency>

    </dependencies>

    ③测试

    • 编写配置类ElasticSearchConfig

      package com.itheima.elasticsearchdemo.config;

      import lombok.Data;
      import org.apache.http.HttpHost;
      import org.elasticsearch.client.RestClient;
      import org.elasticsearch.client.RestHighLevelClient;
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;

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

         private String host;

         private int port;

         //添加get,set方法
         
         @Bean
         public RestHighLevelClient client(){
             return new RestHighLevelClient(RestClient.builder(
                     new HttpHost(host,port,"http")
            ));
        }
      }
    • 配置es信息: resourcesapplication.yml

      elasticsearch:
      host: 192.168.52.128
      port: 9200
    • 编写单元测试类:ElasticsearchDay01ApplicationTests

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

      package com.itheima.elasticsearchdemo;

      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;

      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = ElasticsearchDemoApplication.class)
      public class ElasticsearchDemoApplicationTests {

         @Autowired
         private RestHighLevelClient client;

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

     

    创建索引

    在ElasticsearchDemoApplicationTests单元测试类中进行如下练习:

    1.添加索引

    # 注意导包:org.elasticsearch.client.indices.CreateIndexRequest
    /**
    * 添加索引
    */
    @Test
    public void addIndex() throws IOException {
       //1.使用client获取操作索引的对象
       IndicesClient indicesClient = client.indices();
       //2.具体操作,获取返回值
       CreateIndexRequest createRequest = new CreateIndexRequest("itheima");
       CreateIndexResponse response = indicesClient.create(createRequest,
               RequestOptions.DEFAULT);

       //3.根据返回值判断结果
       System.out.println(response.isAcknowledged());
    }

    添加索引,并添加映射

     /**
        * 添加索引,并添加映射
        */
       @Test
       public void addIndexAndMapping() throws IOException {
          //1.使用client获取操作索引对象
           IndicesClient indices = client.indices();
           //2.具体操作获取返回值
           //2.具体操作,获取返回值
           CreateIndexRequest createIndexRequest = new CreateIndexRequest("itcast");
           //2.1 设置mappings
           String mapping = "{ " +
                   "     "properties" : { " +
                   "       "address" : { " +
                   "         "type" : "text", " +
                   "         "analyzer" : "ik_max_word" " +
                   "       }, " +
                   "       "age" : { " +
                   "         "type" : "long" " +
                   "       }, " +
                   "       "name" : { " +
                   "         "type" : "keyword" " +
                   "       } " +
                   "     } " +
                   "   }";
           createIndexRequest.mapping(mapping,XContentType.JSON);

           CreateIndexResponse createIndexResponse = indices.create(createIndexRequest, RequestOptions.DEFAULT);
           //3.根据返回值判断结果
           System.out.println(createIndexResponse.isAcknowledged());
      }

     

    查询、删除、判断索引

    1. 查询索引

        /**
        * 查询索引
        */
       @Test
       public void queryIndex() throws IOException {
           IndicesClient indices = client.indices();

           GetIndexRequest getRequest=new GetIndexRequest("itcast");
           GetIndexResponse response = indices.get(getRequest, RequestOptions.DEFAULT);
           Map<String, MappingMetaData> mappings = response.getMappings();
           //iter 提示foreach
           for (String key : mappings.keySet()) {
               System.out.println(key+"==="+mappings.get(key).getSourceAsMap());
          }
      }
    1. 删除索引

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

      }
    1. 索引是否存在

     /**
        * 索引是否存在
        */
       @Test
       public void existIndex() throws IOException {
           IndicesClient indices = client.indices();

           GetIndexRequest getIndexRequest=new GetIndexRequest("itheima");
           boolean exists = indices.exists(getIndexRequest, RequestOptions.DEFAULT);

           System.out.println(exists);
      }

     

    添加文档

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

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

       //1.获取操作文档的对象
       IndexRequest request = new IndexRequest("itcast").id("1").source(data);
       //添加数据,获取结果
       IndexResponse response = client.index(request, RequestOptions.DEFAULT);

       //打印响应结果
       System.out.println(response.getId());
    }

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

    检查是否添加fastjson和lombok的依赖:

    <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
    </dependency>

    <!--fastjson依赖-->
    <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>fastjson</artifactId>
       <version>1.2.4</version>
    </dependency>

    创建Person类:使用lombok提供的@Data注解来完成setter, getter,toString等方法

    package com.itheima.domain;

    import lombok.Data;

    @Data
    public class Person {
       private String id;
       private String name;
       private int age;
       private String address;

    }

    通过fastjson将Java对象转换成JSON字符串:

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

       //将对象转为json
       String data = JSON.toJSONString(p);

       //1.获取操作文档的对象
       IndexRequest request = new IndexRequest("itcast").id(p.getId()).source(data,
               XContentType.JSON);
       //添加数据,获取结果
       IndexResponse response = client.index(request, RequestOptions.DEFAULT);

       //打印响应结果
       System.out.println(response.getId());
    }

     

    修改、查询、删除文档

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

    /**
        * 修改文档:添加文档时,如果id存在则修改,id不存在则添加
        */
    @Test
    public void UpdateDoc() throws IOException {
       Person person=new Person();
       person.setId("2");
       person.setName("李四");
       person.setAge(20);
       person.setAddress("北京三环车王");

       String data = JSON.toJSONString(person);

       IndexRequest request=new IndexRequest("itcast").id(person.getId()).source(data,XContentType.JSON);
       IndexResponse response = client.index(request, RequestOptions.DEFAULT);
       System.out.println(response.getId());
    }

    2.根据id查询文档

    /**
      * 根据id查询文档
      */
    @Test
    public void getDoc() throws IOException {

       //设置查询的索引、文档
       GetRequest indexRequest=new GetRequest("itcast","2");

       GetResponse response = client.get(indexRequest, RequestOptions.DEFAULT);
       System.out.println(response.getSourceAsString());
    }

    3.根据id删除文档

    /**
        * 根据id删除文档
        */
       @Test
       public void delDoc() throws IOException {

           //设置要删除的索引、文档
           DeleteRequest deleteRequest=new DeleteRequest("itcast","1");

           DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT);
           System.out.println(response.getId());
      }
    • 新增或修改:

      IndexRequest request = new IndexRequest("index_name").id("")
      client.index()
    • 查询

      GetRequest request = new GetRequest("index_name").id("")
      client.get()
    • 删除

      DeleteRequest request = new DeleteRequest("index_name").id("")
      client.delete()
      • ElasticSearch 高级操作

      bulk批量操作-JavaAPI

      1. 配置es连接地址:application.yml

        elasticsearch:
        host: 192.168.52.128
        port: 9200
      2. 在单元测试类中编写批量操作代码

        @RunWith(SpringRunner.class)
        @SpringBootTest
        class ElasticsearchDemo2ApplicationTests {

           @Autowired
           private RestHighLevelClient client;

           /**
            * 1. 批量操作 bulk
            */
           @Test
           public void testBulk() throws IOException {
               //创建bulkrequest对象,整合所有操作
               BulkRequest bulkRequest = new BulkRequest();

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

               //2. 添加6号记录
               Map map = new HashMap();
               map.put("name", "六号");
               IndexRequest indexRequest = new IndexRequest("person").id("6").source(map);
               bulkRequest.add(indexRequest);


               Map map2 = new HashMap();
               map2.put("name", "三号");
               //3. 修改3号记录 名称为 “三号”
               UpdateRequest updateReqeust = new UpdateRequest("person", "3").doc(map2);
               bulkRequest.add(updateReqeust);

               //执行批量操作
               BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
               RestStatus status = response.status();
               System.out.println(status);
          }
        }

      3.验证执行结果

      GET person/_doc/1
      GET person/_doc/3
      GET person/_doc/6

      导入数据-创建索引

      应用场景:

      公司之前做的电子商城项目,查询使用的是MySQL数据库;后期发现太慢,需要用ES进行搜索,所以要将原来存储在MySQL的数据全部拿到ES中。

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

      "stock": {
      "type": "integer"
      }
      }
      }
      }
      • title:商品标题

      • price:商品价格

      • createTime:创建时间

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

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

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

      • saleNum:销量

      • stock:库存量

      导入数据-代码实现

      1. 检查数据库配置和表是否创建

      2. 添加mybatis,mysql,fastjson依赖

        <!--mybatis-->
        <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.1.0</version>
        </dependency>

        <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.4</version>
        </dependency>
      3. 执行代码

        /**
        * 批量导入
        */
        @Test
        public void importData() throws IOException {
           //1.查询所有数据,mysql
           List<Goods> goodsList = goodsMapper.findAll();

           //2.bulk导入
           BulkRequest bulkRequest = new BulkRequest();

           //2.1 循环goodsList,创建IndexRequest添加数据
           for (Goods goods : goodsList) {
               //2.2 设置spec规格信息 Map的数据   specStr:{}
               //goods.setSpec(JSON.parseObject(goods.getSpecStr(),Map.class));

               String specStr = goods.getSpecStr();
               //将json格式字符串转为Map集合
               Map map = JSON.parseObject(specStr, Map.class);
               //设置spec map
               goods.setSpec(map);
               //将goods对象转换为json字符串
               String data = JSON.toJSONString(goods);//map --> {}
               IndexRequest indexRequest = new IndexRequest("goods");
               indexRequest.id(goods.getId() + "").source(data, XContentType.JSON);
               bulkRequest.add(indexRequest);
          }

           BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
           System.out.println(response.status());
        }
      4. 验证执行结果:

        GET goods/_search

         

      ElasticSearch查询

      matchAll-脚本

      查询所有商品信息

      # 发现只能查询出10条数据
      GET goods/_search

      ES中分页查询:分页的参数 和MySQL中的 limit 一致

      SELECT * FROM user limit 0, 10;
      # 默认情况下,es一次展示10条数据,通过from和size来控制分页
      GET goods/_search
      {
       "query": {
         "match_all": {}
      },
       "from": 0, // 从第几条开始,索引从 0 开始
       "size": 100 // 查询多少条
      }

       

      matchAll-JavaAPI

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

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

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

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

             //3. 添加查询条件构建器 SearchSourceBuilder
             searchRequest.source(sourceBuilder);

             // 8 . 添加分页信息
             sourceBuilder.from(0);
             sourceBuilder.size(100);
             
             //1. 查询,获取查询结果
             SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

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

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

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

        }

      termQuery关键词查询(不会分词)

      term Query为精确查询,在搜索时会整体匹配关键字,不再将关键字分词

      termQuery-脚本

      GET goods/_search
      {
       "query": {
         "term": {
           "title": {
             "value": "华为手机"  // 根据"华为手机"这个词 去ES库 中 的title 的倒排索引中找(完全匹配)
          }
        }
      }
      }

      term查询,查询text类型字段时,只有其中的单词相匹配都会查到,text字段会对数据进行分词

      termQuery-JavaAPI

      QueryBuilder query = QueryBuilders.termQuery("title", "华为");//term词条查询

      //keyword:完全匹配
      //QueryBuilder query = QueryBuilders.termQuery("categoryName", "电视");//term词条查询
      /**
      * termQuery:词条查询
      */
      @Test
      public void testTermQuery() throws IOException {
         SearchRequest searchRequest = new SearchRequest("goods");

         //构建查询条件
         SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
         QueryBuilder query = QueryBuilders.termQuery("title", "华为");//term词条查询

         //keyword:完全匹配
         //QueryBuilder query = QueryBuilders.termQuery("categoryName", "电视");//term词条查询

         sourceBuilder.query(query);
         searchRequest.source(sourceBuilder);

         //执行查询
         SearchResponse searchResponse = client.search(searchRequest,
                 RequestOptions.DEFAULT);

         //获取记录数
         SearchHits searchHits = searchResponse.getHits();
         long value = searchHits.getTotalHits().value;
         System.out.println("总记录数:" + value);

         List<Goods> goodsList = new ArrayList<>();
         SearchHit[] hits = searchHits.getHits();
         for (SearchHit hit : hits) {
             String sourceAsString = hit.getSourceAsString();
             //转为java
             Goods goods = JSON.parseObject(sourceAsString, Goods.class);
             goodsList.add(goods);
        }

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

      matchQuery全局搜索(进行分词)

      matchQuery-脚本

      match Query即全文检索,它的搜索方式是先将搜索字符串分词,再使用各各词条从索引中搜索。

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

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

      # match查询 
      GET goods/_search
      {
       "query": {
         "match": {
           "title": "华为手机" // 先把 华为手机 分词, 然后再去倒排索引中找
        }
      },
       "size": 500
      }
      • match的 and(交集) 搜索

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

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

       

      matchQuery-JavaAPI

      MatchQueryBuilder query = QueryBuilders.matchQuery("title", "华为手机");
      query.operator(Operator.AND);//求交集
      /**
      * matchQuery:词条分词查询
      */
      @Test
      public void testMatchQuery() throws IOException {
         //指定索引名称
         SearchRequest searchRequest = new SearchRequest("goods");

         //构建查询条件
         SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
         MatchQueryBuilder query = QueryBuilders.matchQuery("title", "华为手机");
         query.operator(Operator.AND);//求交集
         sourceBuilder.query(query);
         searchRequest.source(sourceBuilder);

         //执行查询
         SearchResponse searchResponse = client.search(searchRequest,
                 RequestOptions.DEFAULT);

         SearchHits searchHits = searchResponse.getHits();
         //获取记录数
         long value = searchHits.getTotalHits().value;
         System.out.println("总记录数:" + value);

         List<Goods> goodsList = new ArrayList<>();
         SearchHit[] hits = searchHits.getHits();
         for (SearchHit hit : hits) {
             String sourceAsString = hit.getSourceAsString();

             //转为java
             Goods goods = JSON.parseObject(sourceAsString, Goods.class);
             goodsList.add(goods);
        }

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

      termQuery和matchQuery的区别

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

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

      模糊查询-JavaAPI

      //通配符查询
      WildcardQueryBuilder query = QueryBuilders.wildcardQuery("title", "华*");//华后多个字符
      //正则查询
      RegexpQueryBuilder query = QueryBuilders.regexpQuery("title", "\w+(.)*");
      //前缀查询,针对类型为keyword的字符串
      PrefixQueryBuilder query = QueryBuilders.prefixQuery("brandName", "三");

      完整代码:

      /**
      * 通配符查询:WildcardQuery
      */
      @Test
      public void testWildcardQuery() throws IOException {
         SearchRequest searchRequest = new SearchRequest("goods");

         SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
         //通配符查询
         //WildcardQueryBuilder query = QueryBuilders.wildcardQuery("title", "华*");

         //正则查询
         //RegexpQueryBuilder query = QueryBuilders.regexpQuery("title", "\w+(.)*");

         //前缀查询
         PrefixQueryBuilder query = QueryBuilders.prefixQuery("brandName", "三");

         sourceBuilder.query(query);
         searchRequest.source(sourceBuilder);


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

         SearchHits searchHits = searchResponse.getHits();
         //获取记录数
         long value = searchHits.getTotalHits().value;
         System.out.println("总记录数:" + value);

         List<Goods> goodsList = new ArrayList<>();
         SearchHit[] hits = searchHits.getHits();
         for (SearchHit hit : hits) {
             String sourceAsString = hit.getSourceAsString();

             //转为java
             Goods goods = JSON.parseObject(sourceAsString, Goods.class);
             goodsList.add(goods);
             System.out.println(goods);
        }
      }

      范围&排序查询

      范围查询:查找指定字段在指定范围内包含值:gte大于等于,lte 小于等于

      gte: greater than or equal 大于等于
      lte: less than or equal 小于等于

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

       

      JavaAPI:

      //范围查询 以price 价格为条件
      RangeQueryBuilder query = QueryBuilders.rangeQuery("price");

      //指定下限
      query.gte(2000);
      //指定上限
      query.lte(3000);

      sourceBuilder.query(query);

      //排序 价格 降序排列
      sourceBuilder.sort("price",SortOrder.DESC);

       

      queryString多字段查询

      SELECT
      *
      FROM
      goods
      WHERE
      title LIKE '%华为%手机'
      OR brandName LIKE '%华为%手机'
      OR categoryName LIKE '%华为%手机'

      queryString 多条件查询,可以让字符串中包含关键词

      •会对查询条件进行分词。
      •然后将分词后的查询条件和词条进行等值匹配
      •默认取并集(OR)
      •可以指定多个查询字段

      query_string:识别query中的连接符(or 、and),可以使用有default_operator连接符的脚本

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

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

      GET goods/_search
      {
       "query": {
         "simple_query_string": {
           "fields": ["title","categoryName","brandName"],
           "query": "华为 AND 手机" //不识别or, and
           // , "default_operator": "AND"
        }
      }
      }

      Java代码

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

      bool布尔查询-脚本(重点)

      SELECT
      *
      FROM
      goods
      WHERE
      title LIKE '%手机%'
      OR brandName LIKE '%华为%'

      boolQuery:对多个查询条件连接。

      must(and):条件必须成立
      must_not(not):条件必须不成立
      should(or):条件可以成立


      filter:条件必须成立,性能比must高。不会计算得分
            得分:即条件匹配度,匹配度越高,得分越高
      # boolquery
      # must和filter配合使用时,max_score(得分)是显示的
      # must 默认数组形式
      GET goods/_search
      {
       "query": {
         "bool": {
           "must": [
            {
               "term": {
                 "brandName": {
                   "value": "华为"
                }
              }
            }
          ],
           "filter":[
            {
             "term": {
               "title": "手机"
            }
            },
            {
              "range":{
               "price": {
                 "gte": 2000,
                 "lte": 3000
              }
              }
            }
           
          ]
        }
      }
      }

      布尔查询-JavaAPI

      布尔查询:boolQuery

      1. 查询品牌名称为:华为

      2. 查询标题包含:手机

      3. 查询价格在:2000-3000

      其中must 、filter为连接方式;term、match为不同的查询方式

      //1.构建boolQuery
      BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

      //2.构建各个查询条件
      //2.1 查询品牌名称为:华为
      TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName", "华为");
      boolQuery.must(termQueryBuilder);

      //2.2. 查询标题包含:手机
      MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", "手机");
      boolQuery.filter(matchQuery);

      //2.3 查询价格在:2000-3000
      RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
      rangeQuery.gte(2000);
      rangeQuery.lte(3000);
      boolQuery.filter(rangeQuery);

      sourceBuilder.query(boolQuery);

      聚合查询-JavaAPI**

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

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

      2. 查询品牌列表

      /**
          * 聚合查询:桶聚合,分组查询
          * 1. 查询title包含手机的数据
          * 2. 查询品牌列表
          */
      @Test
      public void testAggQuery() throws IOException {

         SearchRequest searchRequest=new SearchRequest("goods");

         SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
         //1. 查询title包含手机的数据

         MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "手机");

         sourceBuilder.query(queryBuilder);
         //2. 查询品牌列表 只展示前100条
         AggregationBuilder aggregation=AggregationBuilders.terms("goods_brands").field("brandName").size(100);
         sourceBuilder.aggregation(aggregation);


         searchRequest.source(sourceBuilder);

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

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

         //7.1 获取总记录数
         Long total= hits.getTotalHits().value;
         System.out.println("总数:"+total);

         // aggregations 对象
         Aggregations aggregations = searchResponse.getAggregations();
         //将aggregations 转化为map
         Map<String, Aggregation> aggregationMap = aggregations.asMap();

         //通过key获取goods_brands 对象 使用Aggregation的子类接收 buckets属性在Terms接口中体现

         //Aggregation goods_brands1 = aggregationMap.get("goods_brands");
         Terms goods_brands =(Terms) aggregationMap.get("goods_brands");

         //获取buckets 数组集合
         List<? extends Terms.Bucket> buckets = goods_brands.getBuckets();

         Map<String,Object>map=new HashMap<>();
         //遍历buckets   key 属性名,doc_count 统计聚合数
         for (Terms.Bucket bucket : buckets) {
             System.out.println(bucket.getKey());
             map.put(bucket.getKeyAsString(),bucket.getDocCount());
        }

         System.out.println(map);
      }

      高亮查询-脚本(重点)

      高亮显示可以将搜索结果一个或多个字突出显示,以便向用户展示匹配关键字的位置。

      高亮三要素:

      •高亮字段

      •前缀

      •后缀

      默认前后缀 :

      <em>手机</em>

      替换默认高亮前后缀为:更改字体颜色为red

      GET goods/_search
      {
       "query": {
         "match": {
           "title": "电视"
        }
      },
       "highlight": {
         "fields": {
           "title": {
             "pre_tags": "<font color='red'>",
             "post_tags": "</font>"
          }
        }
      }
      }

       

      高亮查询-JavaAPI

      1. 设置高亮

        • 高亮字段

        • 前缀

        • 后缀

      2. 将高亮后的字段数据,替换原有数据

      /**
          *
          * 高亮查询:
          * 1. 设置高亮
          *     * 高亮字段
          *     * 前缀
          *     * 后缀
          * 2. 将高亮了的字段数据,替换原有数据
          */
      @Test
      public void testHighLightQuery() throws IOException {
         SearchRequest searchRequest = new SearchRequest("goods");

         SearchSourceBuilder sourceBulider = new SearchSourceBuilder();

         // 1. 查询title包含手机的数据
         MatchQueryBuilder query = QueryBuilders.matchQuery("title", "手机");

         sourceBulider.query(query);

         //设置高亮
         HighlightBuilder highlighter = new HighlightBuilder();
         //设置三要素
         highlighter.field("title");
         //设置前后缀标签
         highlighter.preTags("<font color='red'>");
         highlighter.postTags("</font>");

         //加载已经设置好的高亮配置
         sourceBulider.highlighter(highlighter);

         searchRequest.source(sourceBulider);

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


         SearchHits searchHits = searchResponse.getHits();
         //获取记录数
         long value = searchHits.getTotalHits().value;
         System.out.println("总记录数:"+value);

         List<Goods> goodsList = new ArrayList<>();
         SearchHit[] hits = searchHits.getHits();
         for (SearchHit hit : hits) {
             String sourceAsString = hit.getSourceAsString();

             //转为java
             Goods goods = JSON.parseObject(sourceAsString, Goods.class);

             // 获取高亮结果,替换goods中的title
             Map<String, HighlightField> highlightFields = hit.getHighlightFields();
             HighlightField HighlightField = highlightFields.get("title");
             Text[] fragments = HighlightField.fragments();
             
             //highlight title替换 替换goods中的title
             goods.setTitle(fragments[0].toString());
             goodsList.add(goods);
        }

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

       

  • 相关阅读:
    shiro3
    shiro2
    shiro1
    Android设计模式之策略模式
    Notify通知
    Menu菜单
    Windows下配置cygwin和ndk编译环境
    Android组件化和插件化开发
    android MVP模式介绍与实战
    实现LoaderCallbacks接口动态循环加载网上图片并展示在手机屏幕上 ...
  • 原文地址:https://www.cnblogs.com/sunhao410526/p/14587038.html
Copyright © 2011-2022 走看看