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信息: resources\application.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 = "{\n" +
               "     \"properties\" : {\n" +
               "       \"address\" : {\n" +
               "         \"type\" : \"text\",\n" +
               "         \"analyzer\" : \"ik_max_word\"\n" +
               "       },\n" +
               "       \"age\" : {\n" +
               "         \"type\" : \"long\"\n" +
               "       },\n" +
               "       \"name\" : {\n" +
               "         \"type\" : \"keyword\"\n" +
               "       }\n" +
               "     }\n" +
               "   }";
       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);
      }
    }

     

posted @ 2021-03-28 18:24  java菜鸟儿  阅读(312)  评论(0编辑  收藏  举报