Elasticsearch总结

目录

1、正排索引和倒排索引

2、什么是Elasticsearch

3、es核心概念

        索引:

        文档:

        域:

4、安装es和可视化工具Kibana

5、原生操作es

        索引操作

                新增索引

                删除索引

        文档操作

                新增文档

                修改文档

                删除文档

                查询文档

         查询所有文档

        分词器

                默认分词器

                IK分词器

                IK拼音分词器

                自定义分词器

        复杂查询

                条件查询

                排序查询

               分页查询

                复合搜索

                高亮显示

                自动补全

6、SpringDataEs操作es

        项目搭建

        创建实体类

        Repository接口方法

        DSL查询文档

        按照规则命名查询

        分页查询

        结果排序

        template操作:

                操作索引

                        创建索引

                        删除文档

                增删改文档

                查询文档

                复杂查询

                分页排序

                结果排序

7、实战案例

        实现功能

        项目搭建

        编写Repository层

        编写Service层

        编写Controller层

        编写前端页面


部分图片来自百战程序员

1、正排索引和倒排索引

索引:索引是将数据中的一部分信息提取出来,重新组织成一定的数据结构,我们可以根据该结构进行快速搜索,这样的就够称之为索引。索引即目录,例如词典会将字的拼音提取出来做目录,通过目录可以快速找到字的位置,索引分为正排索引倒排索引 

正排索引:将文档id建立为索引,通过id快速查找数据,就像数据库中的主键就会建立正排索引

倒排索引:倒排索引就不是通过id建立索引了,而是通过提取数据中的关键字,然后将关键字建立为索引,通过匹配的关键字去查询数据

 

2、什么是Elasticsearch

  1. Elasticsearch 是一个免费且开放的分布式搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据
  2. Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 RESTFUL 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;
  3. Elastic Stack 是一套适用于数据采集、扩充、存储、分析和可视化的免费开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据

3、es核心概念

        索引:

索引对应的就是数据库中的表

        文档:

文档对应的就是表中的一条数据

        域:

域名对应的就是字段

4、安装es和可视化工具Kibana

es下载路径:

https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.8.2-linux-x86_64.tar.gz

kibana下载路径:

https://artifacts.elastic.co/downloads/kibana/kibana-8.8.2-linux-x86_64.tar.gz

大家自行下载上传到虚拟机中或者直接使用wget拉取(这里要提醒的是es和kibana的版本必须一致否则会有错误) 

Elasticsearch启动:

1、关闭防火墙

systemctl stop firewalld.service

2、配置最大可创建文件数大小

#打开系统文件:

vim /etc/sysctl.conf

#添加以下配置:

vm.max_map_count=655360

#配置生效:

sysctl -p

3、由于es不能以root运行,我们需要创建一个非root用户

#创建es用户

useradd es

4、解压es

#解压es

tar -zxvf 文件目录名 

#修改文件属主

chown -R es:es 文件目录路径

5、启动es

#切换用户

su es

#启动elasticsearch,进入到elasticsearch的bin目录下

./elasticsearch

 Kibana启动:

1、解压文件

tar -zxvf 文件目录名

2、配置kibana.yml

#进入kibana的config目录的kibana.yml文件,添加如下配置

#es的默认端口号是9200

server.host=虚拟机ip

elasticsearch.hosts=["http://运行es的虚拟机ip:端口号"]

3、运行kibana

#因为kibana不能以root用户运行,所以需要使用非root用户,我之前创建了一个es用户,

#所以我们直接使用es用户

#将文件所属改为es

chown -R es:es 文件目录路径

#切换用户

su es

#切换到kibana的bin目录,运行kibana

./kibana

4、运行之后访问http://kibana虚拟机ip:端口号

#kibana的默认端口是5601

5、访问 http://虚拟机ip:5601

访问成功过后我们就可以对es进行操作了

这个页面是通过restful风格api对es进行操作的 

 这个页面里的Index Management可以查看索引

5、原生操作es

        索引操作

                新增索引

1、建立没有结构的索引

PUT /索引名

示例:

PUT /student

2、建立有结构的索引

PUT /索引名

{

        "mappings":{

                "properties":{

                       "域名":{

                                "type":"字段类型",

                                "index":"是否创建索引",

                                "store":"是否存储",

                                "analyzer":"分词器"

                        },

                        "域名":{

                                ......

                        }

                }

        }

}

示例:

PUT /student1
{
  "mappings": {
    "properties": {
      "id":{
        "type": "integer"
      },
      "name":{
        "type":"text"
      }
    }
  }
}

                删除索引

DELETE /索引名

示例:

DELETE /student

        文档操作

文档存储数据类似于set集合其存储的id不可重复

如果id重复则直接覆盖id里面所对应的数据,如果不写id则自动生成

                新增文档

POST /索引名/_doc/[id]

示例:

POST /student1/_doc/1
{
  "id":"1",
  "name":"zhangsan"
}

                修改文档

POST /索引名/_update/id值

示例:

POST /student1/_update/1
{
  "doc":{
    "name":"lisi"
  }
}
 

                删除文档

DELETE /索引名/_doc/id值

                查询文档

GET /索引名/_doc/id值

示例:

GET /student1/_doc/1

         查询所有文档

GET /索引名/_search

{

        "query":{

                "match_all":{}

        }

}

        分词器

                默认分词器

ES文档的数据拆分成一个个有完整含义的关键词,并将关键词与文档对应,这样就可以通过关键词查询文档。要想正确的分词,需要选择合适的分词器。

standard analyzer:Elasticsearch默认分词器,根据空格和标点符号对英文进行分词,会进行单词的大小写转换。

默认分词器是英文分词器,对中文的分词是一字一词。

 GET /_analyze

{

    "text":测试语句,

    "analyzer":分词器

}

                IK分词器

IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。提供了两种分词算法:

  • ik_smart:最少切分
  • ik_max_word:最细粒度划分

安装IK分词器

关闭es服务

使用rz命令将ik分词器上传至虚拟机
注:ik分词器的版本要和es版本保持一致。

解压ik分词器到elasticsearch的plugins目录下
unzip elasticsearch-analysis-ik-7.17.0.zip -d /usr/local/elasticsearch1/plugins/analysis-ik

#切换用户es

su es

#进入ES安装文件夹:

cd /usr/local/elasticsearch1/bin/

#启动ES服务:

./elasticsearch -d

GET /_analyze

{

    "text":"测试语句",

    "analyzer":"ik_smart/ik_max_word"

}

IK分词器词典

IK分词器根据词典进行分词,词典文件在IK分词器的config目录中。

  • main.dic:IK中内置的词典。记录了IK统计的所有中文单词。
  • IKAnalyzer.cfg.xml:用于配置自定义词库。

<properties>

    <comment>IK Analyzer 扩展配置</comment>

    <!--用户可以在这里配置自己的扩展字典 -->

    <entry key="ext_dict">ext_dict.dic</entry>

     <!--用户可以在这里配置自己的扩展停止词字典-->

    <entry key="ext_stopwords">ext_stopwords.dic</entry>

</properties>

1、编辑ext_dict.dic文件(只需要在里面添加想要的关键词就好了)

2、编辑ext_stopwords.dic文件(只需要在里面添加不想要的关键词就好了)

3、测试分词效果

GET /_analyze

{

  "text":"我爱英雄联盟",

  "analyzer":"ik_max_word"

}

没有自定义词典的分词效果:

添加了自定义词典的分词效果:

                IK拼音分词器

拼音分词器可以将中文分成对应的全拼,全拼首字母等。

 

安装拼音分词器

关闭es服务使用rz命令将拼音分词器上传至虚拟机

注:拼音分词器的版本要和es版本保持一致。

解压ik分词器到elasticsearch的plugins目录下

unzip elasticsearch-analysis-pinyin-7.17.0.zip -d /usr/local/elasticsearch1/plugins/analysis-pinyin

 

启动ES服务

su es

#进入ES安装文件夹:

cd /usr/local/elasticsearch1/bin/

#启动ES服务:

./elasticsearch

GET /_analyze

{

    "text":测试语句,

    "analyzer":pinyin

}

                自定义分词器

真实开发中我们往往需要对一段内容既进行文字分词,又进行拼音分词,此时我们需要自定义ik+pinyin分词器。

 

注意:两个分词器不是叠加的,而是各干各的,也就是说先对文档进行ik分词,然后再对文档进行pinyin分词。而不是对文档先ik分词,然后再对已经被ik分词的文档进行pinyin分词,也就是说倒排索引中会有两种不同的关键词一种是通过ik分词器分词的,一种是通过pinyin分词器分词的

 PUT /索引名
{
  "settings" : {
    "analysis" : {
      "analyzer" : {
        "ik_pinyin" : { //自定义分词器名
          "tokenizer":"ik_max_word", // 基本分词器
          "filter":"pinyin_filter" // 配置分词器过滤
         }
       },
      "filter" : { // 分词器过滤时配置另一个分词器,相当于同时使用两个分词器
        "pinyin_filter" : { 
          "type" : "pinyin", // 另一个分词器
          // 拼音分词器的配置
          "keep_separate_first_letter" : false, // 是否分词每个字的首字母
          "keep_full_pinyin" : true, // 是否分词全拼
          "keep_original" : true, // 是否保留原始输入
          "remove_duplicated_term" : true // 是否删除重复项
         }
       }
     }
   },
  "mappings":{
    "properties":{
      "域名1":{
        "type":域的类型,
        "store":是否单独存储,
        "index":是否创建索引,
             "analyzer":分词器
       },
      "域名2":{ 
        ...
       }
     }
   }
}

示例:

PUT /product2

{
  "settings": {
    "analysis": {
      "analyzer": {
        "ik_pinyin":{
          "tokenizer":"ik_max_word",
          "filter":"pinyin_filter"
        }
      },
      "filter": {
        "pinyin_filter":{
          "type":"pinyin",
          "keep_separate_first_letter" : false, 
          "keep_full_pinyin" : true, 
          "keep_original" : true, 
          "remove_duplicated_term" : true 
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{
        "type": "integer",
        "store": true
      },
      "name":{
        "type": "text",
        "store": true,
        "index": true,
        "analyzer": "ik_pinyin"
      }
    }
  }
}

测试自定义分词器

GET /索引名/_analyze

{

 "text": "你好百战程序员",

 "analyzer": "ik_pinyin"

}

        复杂查询

通过前面的操作我们学会了基本的es操作,但是在查询的时候我们的查询操作不可能会这么简单,我们肯定需要对于查询添加一些条件,所以我们这里就来学一下复杂查询

                条件查询

 1、match_all查询所有文档

GET /索引名/_search

{

        "query":{

                "match_all":{}

        }

}

2、match全文检索(想要全文检索需要再创建索引的时候域的index设置为true)

这里需要注意的是如果使用match方式查询的话会先对关键字进行分词,然后最用分词后的数据进行查询

GET /索引名/_search

{

        "query":{

                "match":{

                        "域名":"关键字"

                }

        }

}

示例:

3、range范围查询

GET /索引名/_search

{

        "query":{

                "range":{

                        "域名":{

                                //大于等于1,小于等于2

                               "gte":1,

                                "lte":2

                        }

                }

        }

}

GET /索引名/_search

{

        "query":{

                "range":{

                        "域名":{

                                //大于1,小于3

                               "gt":1,

                                "lt":3

                        }

                }

        }

}

示例:

4、fuzziness纠错(最多只能纠错2位)

GET /索引名/_search

{

        "query":{

                "match":{

                        "域名":{

                                "query":"关键词",

                                "fuzziness":纠错个数

                        }

                }

        }

}

示例: 

5、match_phrase短语检索,关键字不做任何分词,在搜索字段对应的倒排索引中精准匹配

GET /索引名/_search

{

        "query":{

                "match_phrase":{

                        "域名":"关键词"

                }

        }

}

示例:

6、terms词组检索,关键词不做任何分词,在搜索字段对应的倒排索引中精准匹配

GET /索引名/_search
{

        "query":{

                "terms":{

                        "域名":[

                               "关键词",

                                "关键词",

                                ...

                        ]

                }

        }

}

                排序查询

GET /索引名/_search

{

        "query":{

                "match":{

                        "域名":"关键词"

                }

        },

        "sort":[

                "域名":{

                        "order":"排序方式"

                }

        ]

示例:

               分页查询

 GET /索引名/_search

{

        "query":{

                "match":{

                        "域名":"关键词"

                }

        },

        "from":起始下标,

        "size":每页个数

}

示例:

                复合搜索

GET /索引名/_search

{

        "query":{

                "bool":{

                         // 必须满足的条件

                        "must":{

                                {

                                        "搜索方式":{

                                               "域名":"关键字"

                                        }

                                },

                                {...}

                        },

                        //任意一个条件满足即可

                        "should":{    

                                {

                                        "搜索方式":{

                                               "域名":"关键字"

                                        }

                                },

                                {...}

                        },

                        //必须不满足的条件

                        "must_not":{

                                {

                                        "搜索方式":{

                                               "域名":"关键字"

                                        }

                                },

                                {...}

                        }

                }

        }

}

示例:

                高亮显示

GET /索引名/_search

{

        "query":{

                "match":{

                        "域名":"关键词"

                }

        },

        "highlight":{

               "fields":{

                        "高亮字段名":{

                              // 返回高亮数据的最大长度

                             "fragment_size":100,

                              // 返回结果最多可以包含几段不连续的文字

                             "number_of_fragments":5

                        }

                },

               "pre_tags":["前缀"],

               "post_tags":["后缀"]

        }

}

 示例:

                自动补全

自动补全的字段的类型必须是completion,所以我们需要新建一个索引,将对应的域的类型设置为completion

PUT /product
{
  "mappings": {
    "properties": {
      "id":{
        "type": "integer",
        "index": true,
        "store": true
      },
      "productName":{
        "type": "completion"
      },
      "productDesc":{
        "type": "text",
        "index": true,
        "store": true
      }
    }
  }
}

然后我们需要新增几条数据

POST /product/_doc
{
  "id":1,
  "productName":"elasticsearch1",
  "productDesc":"elasticsearch1 is a good search engine"
}


POST /product/_doc
{
  "id":2,
  "productName":"elasticsearch2",
  "productDesc":"elasticsearch2 is a good search engine"
}


POST /product/_doc
{
  "id":3,
  "productName":"elasticsearch3",
  "productDesc":"elasticsearch3 is a good search engine"
}

自动补全

GET /索引名/_search

{

        "suggest":{

                "自定义名字":{

                        "prefix":"被补全的关键字",

                        "completion":{

                                "fields":"补全字段",

                                "skip_duplicates": true, // 忽略重复结果

                                "size": 10 //最多查询到的结果数

                        }

                }

        }

}

6、SpringDataEs操作es

        项目搭建

创建SpringBoot项目,引入SpringDataEs依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置yml文件

spring:
  elasticsearch:
    uris: http://运行es的虚拟机IP:9200

        创建实体类

一个实体类的所有对象都会存入ES的一个索引中,所以我们在创建实体类时关联ES索引。

//索引名叫product,启动SpringBoot的时候是否自动创建索引
@Document(indexName = "product",createIndex = true)

@Data

public class Product {

  @Id 
  @Field(type = FieldType.Integer,store = true,index = true)

  private Integer id;

  @Field(type = FieldType.Text,store = true,index = true,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")

  private String productName;

  @Field(type = FieldType.Text,store = true,index = true,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")

  private String productDesc;

}

 

@Document:标记在类上,标记实体类为文档对象,一般有如下属性:

indexName:对应索引的名称

createIndex:是否自动创建索引

 

@Id:标记在成员变量上,标记一个字段为主键,该字段的值会同步到ES该文档的id值。

 

@Field:标记在成员变量上x`标记为文档中的域,一般有如下属性:

type:域的类型

index:是否创建索引,默认是 true

store:是否单独存储,默认是 false

analyzer:分词器

searchAnalyzer:搜索时的分词器

        Repository接口方法

创建Repository接口继承ElasticsearchRepository,接口提供了增删改查方法

(该接口有两个泛型,第一个泛型是实体类类型,第二个泛型是实体类的主键的类型)

import com.itbaizhan.esblog.pojo.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface ProductRepository extends ElasticsearchRepository<Product,Integer> {
}

测试接口:



import com.itbaizhan.esblog.pojo.Product;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

@SpringBootTest
public class ProductRepositoryTest {
    @Autowired
    private ProductRepository repository;

    @Test
    public void t1(){
        //新增
        Product product = new Product(1,"HUAWEI MATE 30","照亮你的美");
        repository.save(product);
    }

    @Test
    public void t2(){
        //修改
        Product product = new Product(1,"OPPO RENO 6 PRO","充电两分钟通话两小时");
        repository.save(product);//因为id相同所以是修改,就像map集合一样
    }

    @Test
    public void t3(){
        //根据id获取文档
        Optional<Product> optional = repository.findById(1);
        Product product = optional.get();
        System.out.println(product);
    }


    @Test
    public void t4(){
        //查询所有
        Iterable<Product> products = repository.findAll();
        products.forEach(System.out::println);
    }

    @Test
    public void t5(){
        //删除
        Product product = new Product(1);
        repository.delete(product);
    }

}

        DSL查询文档

接下来我们讲解SpringDataES支持的查询方式,首先准备一些文档数据:

// 添加一些数据

repository.save(new Product(2, "三体1", "三体1是优秀的科幻小说"));

repository.save(new Product(3, "三体2", "三体2是优秀的科幻小说"));

repository.save(new Product(4, "三体3", "三体3是优秀的科幻小说"));

repository.save(new Product(5, "elasticsearch", "elasticsearch是基于lucene开发的优秀的搜索引擎"));

使用Repository继承的方法查询文档

该方式我们之前已经讲解过了

 

使用DSL语句查询文档

ES通过json类型的请求体查询文档,方法如下:

GET /索引/_search

{

  "query":{

    搜索方式:搜索参数

   }

}

 

query后的json对象称为DSL语句,我们可以在接口方法上使用@Query注解自定义DSL语句查询。

?0代表占位符,一个?0匹配一个方法形式参数

@Query("{" +

    "   \"match\": {" +

    "    \"productDesc\": \"?0\"" +

    "   }" +

    "  }")

List<Product> findByProductDescMatch(String keyword);

示例:

@Query("{" +

    " \"match\": {" +

    "  \"productDesc\": {" +

    "   \"query\": \"?0\"," +

    "   \"fuzziness\": 1" +

    "  }" +

    " }" +

    "}")

List<Product> findByProductDescFuzzy(String keyword);

        按照规则命名查询

按照规则命名方法进行查询

关键字

命名规则

解释

示例

and

FindByField1AndField2

根据Field1和Field2 获得数据

FindByTitleAndContent

or

FindByField1OrField2

根据Field1或Field2 获得数据

FindByTitleOrContent

is

FindByField

根据Field获得数据

FindByTitle

not

FindByFieldNot

根据Field获得补集数据

FindByTitleNot

between

FindByFieldBetween

获得指定范围的数据

FindByPriceBetween

List<Product> findByProductName(String productName);

List<Product> findByProductNameOrProductDesc(String productName,String productDesc);

List<Product> findByIdBetween(Integer startId,Integer endId);

  • 只需在Repository接口中按照SpringDataES的规则命名方法,该方法就能完成相应的查询。
  • 规则:查询方法以findBy开头,涉及查询条件时,条件的属性用条件关键字连接。

        分页查询

使用继承或自定义的方法时,在方法中添加Pageable类型的参数

返回值为Page类型即可进行分页查询。

// 测试继承的方法:

@Test

public void testFindPage(){

  // 参数1:页数,

  //  参数2:每页条数

  Pageable pageable = PageRequest.of(1, 3);

  Page<Product> page = repository.findAll(pageable);

  System.out.println("总条数"+page.getTotalElements());

  System.out.println("总页数"+page.getTotalPages());

  System.out.println("数据"+page.getContent());

}


// 自定义方法

Page<Product> findByProductDesc(String productDesc, Pageable pageable);





// 测试自定义方法

@Test

public void testFindPage2(){

  Pageable pageable = PageRequest.of(1, 2);

  Page<Product> page = repository.findByProductDescMatch("我喜欢三体", pageable);

  System.out.println("总条数"+page.getTotalElements());

  System.out.println("总页数"+page.getTotalPages());

  System.out.println("数据"+page.getContent());

}

        结果排序

使用继承或自定义的方法时,在方法中添加Sort类型的参数即可进行结果排序

// 结果排序

@Test

public void testFindSort(){

//第一个参数:排序的类型    第二个参数:根据哪个字段进行排序

  Sort sort = Sort.by(Sort.Direction.DESC, "id");

  Iterable<Product> all = repository.findAll(sort);

  for (Product product : all) {

    System.out.println(product);

   }

}





// 测试分页加排序

@Test

public void testFindPage2(){

  Sort sort = Sort.by(Sort.Direction.DESC,"id");

  Pageable pageable = PageRequest.of(0, 2,sort);

  Page<Product> page = repository.findByProductDescMatch("我喜欢三体", pageable);

  System.out.println("总条数"+page.getTotalElements());

  System.out.println("总页数"+page.getTotalPages());

  System.out.println("数据"+page.getContent());

}

        template操作:

通过继承ElasticsearchRepository类我们可以很方便的进行增删改查,但是使用这种方式查询文档,无法复杂查询,也就是说只能通过id查询或者查询所有,无法通过关键字匹配查询,这就使得查询收到了很大的局限性,而使用SpringDataEs提供的工具类ElasticsearchRestTemplate操作es则可以解决该问题

                操作索引

                        创建索引

ElasticsearchRestTemplate创建索引无法设置索引结构,所以并不推荐使用该方法创建索引

使用template操作索引,首先我们需要获取到该索引的操作对象,然后通过该操作对象进行索引操作

@SpringBootTest
public class TestTemplate {

    @Autowired
    private ElasticsearchRestTemplate template;

    @Test
    public void addIndex(){
        IndexOperations ops = template.indexOps(Product.class);
        ops.create();
    }
}

                        删除文档

删除文档也是一样的只是调用的方法不同

@SpringBootTest
public class TestTemplate {

    @Autowired
    private ElasticsearchRestTemplate template;

    @Test
    public void delete(){
        IndexOperations ops = template.indexOps(Product.class);
        ops.delete();
    }



}

                增删改文档

template增删改文档和ElasticsearchRepository差不多,只是引用不同

@SpringBootTest
public class TestTemplate {

    @Autowired
    private ElasticsearchRestTemplate template;

    @Test
    public void addIndex(){
        //新增索引
        IndexOperations ops = template.indexOps(Product.class);
        ops.create();
    }

    @Test
    public void delete(){
        //删除索引
        IndexOperations ops = template.indexOps(Product.class);
        ops.delete();
    }

    @Test
    public void save(){
        //新增文档
        Product product = new Product(1,"HUAWEI MATE 30","照亮你的美");
        template.save(product);
    }

    @Test
    public void update(){
        //修改文档
        Product product = new Product(1,"OPPO RENO 6 PRO","充电五分钟,通话两小时");
        template.save(product);
    }

    @Test
    public void testDelete(){
        //删除文档
        template.delete("1",Product.class);
    }




}

                查询文档

使用template查询文档主要分为四步:

  1. 确定查询方式
  2. 构建查询条件
  3. 查询
  4. 处理查询结果
@Test
    public void searchDocument(){
        //查询文档
        //1、确定查询方式
        MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
        //2、构建查询条件
        NativeSearchQuery request = new NativeSearchQueryBuilder().withQuery(builder).build();
        //3、查询
        //参数一:查询条件
        //参数二:索引对应的类对象
        SearchHits<Product> hits = template.search(request, Product.class);
        //4、处理查询结果
        for (SearchHit<Product> hit : hits) {
            System.out.println(hit.getContent());
        }
    }

                复杂查询

通过template复杂条件查询可以实现动态的查询,根据是否传递某个参数动态修改dsl语句,查询数据

@Test
    public void boolSearch(){
        String productName = null;
        String productDesc = null;
        //1、确定查询方式
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (productName == null && productDesc == null){
            //参数一:匹配的字段
            //参数二:关键字
            MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
            boolQueryBuilder.must(queryBuilder);//必须匹配
        }else if (productDesc != null && productDesc.length() > 0){
            //参数一:匹配的字段
            //参数二:关键字
            MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productDesc", productDesc);
            boolQueryBuilder.must(queryBuilder);//必须匹配
        }else if (productName != null && productName.length() > 0){
            //参数一:匹配的字段
            //参数二:关键字
            MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productName", productName);
            boolQueryBuilder.must(queryBuilder);//必须匹配
        }
        //2、构建查询条件
        NativeSearchQuery request = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder).build();
        //3、查询
        SearchHits<Product> searchHits = template.search(request, Product.class);
        //4、处理查询结果
        for (SearchHit<Product> hit : searchHits) {
            System.out.println(hit.getContent());
        }
    }

                分页排序

template分页排序通过构建分页对象Pageable,设置第几页以及每页条数,然后将分页条件放入查询条件中,查询完成之后需要自己手动构造分页条件PageImpl,然后通过返回的Page对象就可以使用分页数据了

@Test
    public void limit(){
        //查询文档
        //1、确定查询方式
        MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
//        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productName", "oppo");
        //2、构建查询条件
        //构建分页条件
        Pageable pageable = PageRequest.of(0, 1);
        NativeSearchQuery request = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withPageable(pageable)
                .build();
        //3、查询
        SearchHits<Product> hits = template.search(request, Product.class);
        //4、处理查询结果
        //将结果封装为Page对象
        List<Product> list = new ArrayList<>();
        for (SearchHit<Product> hit : hits) {
            Product product = hit.getContent();
            list.add(product);
        }
        //参数一:具体数据
        //参数二:分页条件
        //参数三:总条数
        Page<Product> page = new PageImpl<>(list, pageable, hits.getTotalHits());
        System.out.println("每页条数:"+page.getTotalElements());
        System.out.println("总页数:"+page.getTotalPages());
        System.out.println("数据:"+page.getContent());
    }

                结果排序

template结果排序是通过构建结果排序对象,指定排序字段和排序方式,然后再构建查询条件的时候传入

@Test
    public void sort(){
        //查询文档
        //1、确定查询方式
        MatchAllQueryBuilder builder = QueryBuilders.matchAllQuery();
//        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productName", "oppo");
        //2、构建查询条件
        //构建结果排序对象
        FieldSortBuilder sortBuilder = SortBuilders.fieldSort("id").order(SortOrder.DESC);
        NativeSearchQuery request = new NativeSearchQueryBuilder()
                .withQuery(builder)
                .withSorts(sortBuilder)
                .build();
        //3、查询
        SearchHits<Product> hits = template.search(request, Product.class);
        //4、处理查询结果
        for (SearchHit<Product> hit : hits) {
            System.out.println(hit.getContent());
        }
    }

7、实战案例

        实现功能

本次案例我们要实现的是高亮字段以及自动补全的功能

        项目搭建

创建索引

这一段DSL语句的意思是:创建一个分词器,既有pinyin分词器的作用又有ik分词器的作用。为索引添加结构,创建域以及指定各个域的类型,并且给tags域的类型设置为completion,因为后面我们要给该字段进行自动补全功能的实现

PUT /news

{

  "settings": {

    "number_of_shards": 5,

    "number_of_replicas": 1,

    "analysis": {

      "analyzer": {

        "ik_pinyin": {

          "tokenizer": "ik_smart",

          "filter": "pinyin_filter"

         },

        "tag_pinyin": {

          "tokenizer": "keyword",

          "filter": "pinyin_filter"

         }

       },

      "filter": {

        "pinyin_filter": {

          "type": "pinyin",

          "keep_joined_full_pinyin": true,

          "keep_original": true,

          "remove_duplicated_term": true

         }

       }

     }

   },

  "mappings": {

    "properties": {

      "id": {

        "type": "integer",

        "index": true

       },

      "title": {

        "type": "text",

        "index": true,

        "analyzer": "ik_pinyin",

        "search_analyzer": "ik_smart"

       },

      "content": {

        "type": "text",

        "index": true,

        "analyzer": "ik_pinyin",

        "search_analyzer": "ik_smart"

       },

      "url": {

        "type": "keyword",

        "index": true

       },

      "tags": {

        "type": "completion",

        "analyzer": "tag_pinyin",

        "search_analyzer": "tag_pinyin"

       }

     }

   }

}

将mysql的数据同步到es中

先在mysql中添加news表的数据:

百度网盘链接
提取码:7bqy

自行下载logstash文件,该文件可以将mysql中的数据同步到es中

1、解压logstash-7.17.0-windows-x86_64.zip
logstash要和elastisearch版本一致

 

2、在解压路径下的/config中创建mysql.conf文件,文件写入以下脚本内容:

input {

   jdbc {

     jdbc_driver_library => "E:\新课\Elasticsearch\软件\案例\mysql-connector-java-5.1.37-bin.jar"   //找到jdbc驱动包

     jdbc_driver_class => "com.mysql.jdbc.Driver"     //找到驱动类

     "jdbc:mysql:///news?useUnicode=true&characterEncoding=utf-8&useSSL=false"      //找到指定的数据库,这后面的参数是一定要加的,不加会报错

     jdbc_user => "root"    //数据库用户名

     jdbc_password => "root"      //数据库密码

     schedule => "* * * * *"       //多长时间同步一次(这里是每分钟同步一次,如果logstash一直运行则每分钟一直同步)

     jdbc_default_timezone => "Asia/Shanghai"      //时区

     statement => "SELECT * FROM news;"      //执行的sql

   }

}

filter { //查到数据库后的操作

    mutate {

        split => {"tags" => ","}  //针对tags字段进行操作,通过逗号分割字段内容转为一个数组

    }

}

output {

   elasticsearch {

        hosts => ["192.168.0.187:9200","192.168.0.187:9201","192.168.0.187:9202"]  //es集群

     index => "news"  //索引名字

     document_id => "%{id}"  //从数据库查询到的id列作为索引里的文档id

   }

}

3、在解压路径下打开cmd黑窗口,运行命令:

bin\logstash -f config\mysql.conf

4、测试自动补齐

GET /news/_search

{

  "suggest": {

    "my_suggest": {

      "prefix": "li",

      "completion": {

        "field": "tags",

        "skip_duplicates": true,

        "size": 10

       }

     }

   }

}

一张表对应的就是一个对象,一个索引就是一张表,那么一个索引就对应了一个对象,所以我们需要创建News对象,属性对应的就是索引中的各个域

@Document(indexName = "news",createIndex = false)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class News {
    @Id
    @Field
    private Integer id;

    @Field
    private String title;

    @Field
    private String content;

    @Field
    private String keyword;

    @CompletionField    //Completion属性比较特殊需要使用@CompletionField修饰
    @Transient
    private Completion tags;

}

        编写Repository层

@Repository
public interface NewsRepository extends ElasticsearchRepository<News,Integer> {
}

        编写Service层

实现自动补全功能

1、创建补全请求

2、创建补全条件并且添加补全条件

3、查询

4、处理查询结果

@Service
public class NewsService {
    @Autowired
    private ElasticsearchRestTemplate template;

    public List<String> suggestion(String keyword){
        //1、创建补全请求
        SuggestBuilder suggestBuilder = new SuggestBuilder();

        //2、创建补全条件
        SuggestionBuilder suggestionBuilder = SuggestBuilders
                .completionSuggestion("tags") //补全字段
                .prefix(keyword) //补全前缀
                .skipDuplicates(true) //去除重复
                .size(10); //最大补全大小
        //添加补全条件
        suggestBuilder.addSuggestion("my_suggestion",suggestionBuilder);

        //3、查询
        SearchResponse response = template.suggest(suggestBuilder, IndexCoordinates.of("news"));

        //4、处理查询结果
        List<String> collect = response
                .getSuggest()
                .getSuggestion("my_suggestion")
                .getEntries()
                .get(0)
                .getOptions()
                .stream()
                .map(Suggest.Suggestion.Entry.Option::getText)
                .map(Text::toString)
                .collect(Collectors.toList());

        return collect;
    }
}

实现高亮字段功能

1、添加高亮查询方法

2、高亮查询

3、处理查询结果,将高亮字段设置到原始数据中

实现这个高亮字段功能需要先在repository接口中添加以findBy规则命名的方法,并且添加@HighLight注解

//该注解的作用就是设置需要高亮查询的字段

//该方法是按照SpringDataES规则命名,该方法可以通过对上传的titleContent参数进行match查询

//该方法的返回值是SearchHit的容器,具体看图

//SearchHit里的结构,如下图

@Repository
public interface NewsRepository extends ElasticsearchRepository<News,Integer> {
    @Highlight(fields = {@HighlightField(name = "title"),@HighlightField(name = "content")})
    public List<SearchHit<News>> findByTitleMatchesOrContentMatches(String title, String content);
}

实现功能

public List<News> highLight(String keyword){
        //构建新的news集合,该集合是有高亮字段的
        List<News> news = new ArrayList<>();
        //高亮查询
        List<SearchHit<News>> hits = repository.findByTitleMatchesOrContentMatches(keyword, keyword);
        //处理查询结果
        for (SearchHit<News> hit : hits) {
            News content = hit.getContent();//返回的是没有高亮字段的News
            //获取高亮字段
            Map<String, List<String>> highlightFields = hit.getHighlightFields();
            //如果有高亮字段匹配则将高亮字段设置进去
            if (highlightFields.get("title") != null){
                content.setTitle(highlightFields.get("title").get(0));
            }
            //如果有高亮字段匹配则将高亮字段设置进去
            if (highlightFields.get("content") != null){
                content.setContent(highlightFields.get("content").get(0));
            }
            news.add(content);
        }
        return news;
    }

//这里的get(0)只是为了方便测试,但是如果一条文档的域的值有多个高亮字段匹配的话,则会分开存入集合中

//这是因为es在进行高亮搜索的时候,如果一条文档啊的域的值有多个高亮字段匹配,会以数组的方式分开存放,所以在使用

//SpringDataEs操作es的时候就会以集合的方式存入

        编写Controller层

@RequestMapping所写的路径和方法参数名必须与我的匹配,因为后面提供的前端页面需要使用到

@RestController
public class NewsController {
    @Autowired
    private NewsService newsService;

    @RequestMapping("/autoSuggest")
    public List<String> suggest(String term){
        return newsService.suggestion(term);
    }

    @RequestMapping("/highLightSearch")
    public List<News> highlight(String term){
        return newsService.highLight(term);
    }

}

        编写前端页面

通过该链接获取前端资源,获取之后将该资源放到static目录中

百度网盘链接:https://pan.baidu.com/s/1SgcdqzdWC_530nWjJReLFA?pwd=tt5r 
提取码:tt5r

启动项目访问localhost:8080/news.html

测试自动补全:

 测试高亮字段:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值