ElasticSearch全文搜索引擎

目录

一.全文搜索Lucene入门

1.全文搜索概述

1.1.什么是全文搜索

1.2.为什么要使用全文搜索

1.3.常见的全文搜索

三.Lucene概述

1.什么是Lucene

转存失败重新上传取消​编辑​编辑 ​编辑 

4.4.批量查询

五.DSL查询与DSL过滤

1.DSL查询

2.DSL过滤

2.1.什么是DSL过滤

2.2.查询与过滤的区别

2.3.DSL查询+过滤语法

2.4.综合案例

3.查询方式

3.1.全匹配(match_all)

3.2.标准查询(match和multi_match)

3.3.单词搜索与过滤(Term和Terms)

3.4.组合条件搜索与过滤(Bool)

3.5.范围查询与过滤(range)

3.6.存在和缺失过滤器(exists和missing)

3.7.前匹配搜索与过滤(prefix)

3.8.通配符搜索(wildcard)

六.分词器安装和使用

1.基本概念

1.1.什么是分词

2.IK分词器

2.1.为什么用IK分词器

2.2.安装IK分词器

1.下载ES的IK分词器

2.解压elasticsearch-analysis-ik-6.8.6.zip文件

3.IK分词器配置

4.IK分词测试

七.文档类型映射

1.基本概念

1.1.什么是文档映射

1.2.默认的字段类型

1.3.映射规则

2.添加映射

2.1.创建新的索引库

2.2.单类型创建映射

2.3.多类型创建映射

2.4.数组/对象映射

1.对象映射

2.数组映射

3.对象数组

1.建索引库

2.全局映射

3.自定义映射

4.Java-API做CRUD

八.JavaApi-SpringBoot操作ES

1.集成ES

1.1.导入依赖

2.2.配置ES

2.3.定义DOC对象

2.4.初始化索引库和文档映射

2.5.定义Repository

2.ES的CRUD

2.1.添加

2.2.删除

2.3.获取

2.4.高级查询


一.全文搜索Lucene入门

目前的技术实现有Lucene,Solr,ElasticSearch等。全文检索过程分为索引、搜索两个过程:

  • 索引(Indexing)

    1. 从关系数据库中、互联网上、文件系统采集源数据(要搜索的目标信息),源数据的来源是非常广泛的。

    2. 将源数据采集到一个统一的地方,例如存储系统,要创建索引,将索引创建到一个索引库(文件系统)中,从源数据库中提取关键信息,从关键信息中抽取一个一个词,词和源数据是有关联的。也即创建索引时,词和源数据有关联,索引库中记录了这个关联,如果找到了词就说明找到了源数据。

  • 搜索(Search)

    1. 用户执行搜索(全文检索)编写查询关键字。

    2. 从索引库中搜索索引,根据查询关键字搜索索引库中的一个一个词。

    3. 展示搜索的结果。

1.全文搜索概述

1.1.什么是全文搜索

全文搜索引擎:就是把没有数据结构的数据,转换为有数据结构的数据,来加快对文本的快速搜索。

1.2.为什么要使用全文搜索
  • 搜索效率高;
  • 相关度最高的排在页面最前面;
  • 关键字高亮;
  • 只处理文本不处理语义,以单词方式搜索。
1.3.常见的全文搜索
  • 全文搜索工具包:Lucene
  • 全文搜索服务器:Lucene,Solr,ElasticSearch

三.Lucene概述

1.什么是Lucene
  • Lucene是apache下的一个开源的全文检索引擎工具包(一堆jar包)。
  • 为软件开发人员提供一个简单易用的工具包(类库),以方便的在小型目标系统中实现全文检索的功能。
  • Lucene适用于中小型项目 ,ES适用于中大型项目(它底层是基于lucene实现的) 。

 

 

 

4.4.批量查询

批量查询很重要,对相比单个查询来说,批量查询性能更高。

  • 不同索引库查询

GET _mget
{
    "docs" : [
        {
            "_index" : "itsource",
            "_type" : "blog",
            "_id" : 2
        },
        {
            "_index" : "itsource",
            "_type" : "employee",
            "_id" : 1,
            "_source": "email,age"
        }
    ]
}
  • 同索引库同类型 - 推荐

GET itsource/blog/_mget
{
    "ids" : [ "2", "1" ]
}

五.DSL查询与DSL过滤

1.DSL查询

1.1.什么是DSL查询

对于简单查询,使用查询字符串比较好,但是对于复杂查询,由于条件多,逻辑嵌套复杂,查询字符串不易组织与表达,且容易出错,因此推荐复杂查询通过DSL使用JSON内容格式的请求体代替。

DSL查询是由ES提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。DSL有两部分组成:DSL查询和DSL过滤。

1.2.DSL查询语法

一个常用的相对完整的DSL查询:

GET /crm/user/_search
{
    "query": {
           "match_all": {}
    },
    "from": 20,
    "size": 10,
    "_source": ["username", "age", "id"],
    "sort": [{"join_date": "desc"},{"age": "asc"}]
}

  • match_all 表示 查询所有数据,

  • _source :代表查询返回username,age和email几个列,

  • sort :按照加入日期和年龄进行排序

GET /crm/user/_search
{
    "query" : {
           "match" : {
               "username" : "Hello Java"
        }
    }
}
  • match : 会对查询的内容分词,如同:where username = hello or username = java

  • term : 如果把match换成term, 就不会分词,如同: where username = "Hello Java"

查询username中包含“老郑”的内容,match指的是“标准查询”,该查询方式会对查询的内容进行分词。DSL查询可以支持的查询方式很多,如term词元查询 ,range范围查询等等。

2.DSL过滤

2.1.什么是DSL过滤

DSL过滤语句和DSL查询语句非常相似,但是它们的使用目的却不同。通常我们把DSL查询和DSL过滤一起用,当我们把查询条件放到DSL查询语句中,那么对应的字段就会进行相关性排序(ES底层维护了一个分数,用来排序),如果是把查询条件放到DSL过滤语句中,则不会进行相关性排序,因此性能更高。

2.2.查询与过滤的区别

DSL过滤和DSL查询在性能上的区别:

  • 过滤结果可以缓存并应用到后续请求。

  • 查询语句同时匹配文档,计算相关性,所以更耗时,且不缓存。

  • 过滤语句可有效地配合查询语句完成文档过滤。

总结:原则上,使用DSL查询做全文本搜索(关键字搜索),或其他需要进行相关性评分的场景,其它全用DSL过滤。

2.3.DSL查询+过滤语法

下面是把DSL过滤语法和DSL查询语法组合起来一起使用,如下:

GET /crm/user/_search
{
    "query": {
        "bool": {         // 组合查询
            "must": [{    //与(must) 或(should) 非(must not)
                "match": { //match : 标准查询,会对查询的内容分词后去查询
                    "username": "hello world" //等同于:where username=hello or username=world
                },
            }],
            "filter": {    //过滤语句,写法和must一样
                "term": {    //次元查询,把查询的内容当成一个整体去查询
                    "name": "hello world" //等同于 where name = "hello world"
                }
            }
        }
    },
    "from": 20,
    "size": 10,
    "_source": ["name", "age", "username"],
    "sort": [{
        "join_date": "desc"
    }, {
        "age": "asc"
    }]
}

解释:

  • query : 查询,所有的查询条件在query里面

  • bool : 组合搜索bool可以组合多个查询条件为一个查询对象,这里包含了 DSL查询和DSL过滤的条件

  • must : 必须匹配 :与(must) 或(should) 非(must_not)

  • match:分词匹配查询,会对查询条件分词 , multi_match :多字段匹配

  • filter: 过滤条件

  • term:词元查询,不会对查询条件分词

  • from,size :分页

  • _source :查询结果中需要哪些列

  • sort:排序

2.4.综合案例

名称(name)中有 "zs" 的用户 ,性别sex是男生(1),年龄(age)在 18- 20之间,按照年龄(age)倒排序,查询第 1 页,每页10 条 ,查询结果中只需要 :id,name,username,age

GET /aigou/product/_search
{
    "query":{
        "bool": {
            "must": [{
                "match": {
                    "name": "zs"
                }
            }],
            
            "filter": [
                {
                    "range":{    //范围查询
                        "age":{
                            "gte":18,
                            "lte":20
                        }
                    }
                },
                {
                    "term": {    //词元查询
                        "sex": 1
                    }
                }
            ]
        }
    },
    "from": 1,
    "size": 10,
    "_source": ["id", "name", "age","username"],
    "sort": [{
        "age": "desc"
    }]
}

3.查询方式

在上面的案例中,我们接触了 matchrange 等查询方式(查询对象),在ES还有很多其他的查询方式,在不同的场景中我们需要根据情况进行合理的选择。

3.1.全匹配(match_all)

普通搜索(匹配所有文档)

GET _search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter": {
        "term": {
          "name": "zs1"
        }
      }
    }
  }
}

3.2.标准查询(match和multi_match)

标准查询,可以理解为,分词查询有点像模糊匹配(like),会对查询的内容进行分词后,得到多个单词,分别带着多个单词去检索ES库,只要有一个单词能查出结果,整个查询就有结果。不管你需要全文本查询还是精确查询基本上都要用到它。

如下面的搜索会对Steven King分词,并找到包含Steven或King的文档,然后给出排序分值。

{
    "query": {
        "match": {
            "fullName": "Steven King"        
        }
    }
}

注意:上面效果如同 where fullName like "%Steven%" or fullName like "%King%"

提示:match一般只用于全文字段的匹配与查询,一般不用于过滤。

multi_match 查询允许你做 match查询的基础上同时搜索多个字段:

{
    "query": {

        "multi_match": {

            "query": "Steven King",

            "fields": ["fullName", "title"]

        }
    }
}

注意:上面的搜索同时在fullName和title字段中匹配。

如同:where fullName = Steven or fullName = King or title = steven or title = King

3.3.单词搜索与过滤(Term和Terms)

单词/词元查询 , 可以理解为等值查询,字符串,数字等都可以使用它,把查询的内容看成一个整体去检索ES库

{
    "query": {
        "bool": {
            "must": {
                "match_all": {
                    
                }
            },
            "filter": {
                "term": {
                    "username": "Steven King"
                }
            }
        }
    }
}

提示:上面的“Steven King”会被当成一个当成去term中匹配,它跟match不同的地方在于match会把“Steven King”分成“steven”和“king”分别取username中查询。

Terms支持多个字段查询

{
    "query": {
        "terms": {
            "tags": [
                "jvm",
                "hadoop",
                "lucene"
            ],
            "minimum_match": 1
        }
    }
}

提示:minimum_match:至少匹配个数,默认为1

如同:where tags in (jvm , hadoop , lucene)

3.4.组合条件搜索与过滤(Bool)

组合搜索bool可以组合多个查询条件为一个查询对象,查询条件包括must、should和must_not。

例如:查询爱好有美女,同时也有喜欢游戏或运动,且出生于1990-06-30及之后的人。

{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "hobby": "美女"
                    }
                }
            ],
            "should": [
                {
                    "term": {
                        "hobby": "游戏"
                    }
                },
                {
                    "term": {
                        "hobby": "运动"
                    }
                }
            ],
            "must_not": [
                {
                    "range": {
                        "birth_date": {
                            "lt": "1990-06-30"
                        }
                    }
                }
            ],
            "filter": [
                ...
            ]
        }
    }
}

上面案例如同:Hobby=美女 and (hobby=游戏 or hobby=运动) and birth_date >= 1990-06-30

提示: 如果 bool 查询下没有must子句,那至少应该有一个should子句。但是 如果有 must子句,那么没有 should子句也可以进行查询。

3.5.范围查询与过滤(range)

range过滤允许我们按照指定范围查找一批数据

{
    "query": {
        "range": {
            "age": {
                "gte": 20,
                "lt": 30
            }
        }
    }
}

上例中查询年龄大于等于20并且小于30。

gt:> gte:>= lt:< lte:<=

3.6.存在和缺失过滤器(exists和missing)

{
    "query": {
        "bool": {
            "must": [
                {
                    "match_all": {
                        
                    }
                }
            ],
            "filter": {
                "exists": {
                    "field": "gps"
                }
            }
        }
    }
}

提示:exists和missing只能用于过滤结果。

3.7.前匹配搜索与过滤(prefix)

和term查询相似,前匹配搜索不是精确匹配,而是类似于SQL中的like ‘key%’

{
    "query": {
        "prefix": {
            "fullName": "王"
        }
    }
}

提示:上例即查询姓倪的所有人。

3.8.通配符搜索(wildcard)

使用*代表0~N个,使用?代表1个。

{
    "query": {
        "wildcard": {
            "fullName": "倪*华"
        }
    }
}

六.分词器安装和使用

1.基本概念

1.1.什么是分词

在全文检索理论中,文档的查询是通过关键字查询文档倒排索引来进行匹配,因此将文本拆分为有意义的单词,对于搜索结果的准确性至关重要,因此,在建立索引的过程中和分析搜索语句的过程中都需要对文本串分词。ES的倒排索引是分词的结果

2.IK分词器

2.1.为什么用IK分词器

ES默认对英文文本的分词器支持较好,但和lucene一样,如果需要对中文进行全文检索,那么需要使用中文分词器,同lucene一样,在使用中文全文检索前,需要集成IK分词器 - 大家都在用IK

2.2.安装IK分词器
1.下载ES的IK分词器

插件源码地址:GitHub - medcl/elasticsearch-analysis-ik at v6.8.6

2.解压elasticsearch-analysis-ik-6.8.6.zip文件

并将解压后的内容放置于ES根目录/plugins/ik

3.IK分词器配置

在ik/config 目录可以对分词器进行配置,如停词 , 自定义字典等。

4.IK分词测试

POST _analyze
{
  "analyzer":"ik_smart",
  "text":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
}

提示:IK分词器指定:ik_smart ; ik_max_word , ik_max_word 相比 ik_smart 来说会将文本做最细粒度的拆分。

七.文档类型映射

1.基本概念

1.1.什么是文档映射

ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型。就如同Mysql创建表时候指定的每个column列的类型。 为了方便字段的检索,我们会指定存储在ES中的字段是否进行分词,但是有些字段类型可以分词,有些字段类型不可以分词,所以对于字段的类型需要我们自己去指定。

需要注意的是,我们在使用ES的正确流程应该是:①创建索引 ; ②文档映射 ;③文档CRUD;

1.2.默认的字段类型

查看索引类型的映射配置:GET {indexName}/_mapping/{typeName}

  • 基本字段类型

字段类型
字符串text(分词) ;keyword(不分词) ;StringField(不分词文本);TextFiled(要分词文本)
数字longintegershortdoublefloat
日期date
逻辑boolean
  • 复杂字段类型

字段类型取值
对象类型object
数组类型array
地理位置geo_point,geo_shape
  • 默认映射

ES在没有配置Mapping的情况下新增文档,ES会尝试对字段类型进行猜测,并动态生成字段和类型的映射关系。

内容默认映射类型
JSON typeField type
Boolean: true or false"boolean"
Whole number: 123"long"
Floating point: 123.45"double"
String, valid date:"2014-09-15""date"
String: "foo bar""text"
1.3.映射规则

字段映射的常用属性配置列表 - 即给某个字段执行类的时候可以指定以下属性

type类型:基本数据类型,integer,long,date,boolean,keyword,text...
enable是否启用:默认为true。 false:不能索引、不能搜索过滤,仅在_source中存储
boost权重提升倍数:用于查询时加权计算最终的得分。
format格式:一般用于指定日期格式,如 yyyy-MM-dd HH:mm:ss.SSS
ignore_above长度限制:长度大于该值的字符串将不会被索引和存储。
ignore_malformed转换错误忽略:true代表当格式转换错误时,忽略该值,被忽略后不会被存储和索引。
include_in_all是否将该字段值组合到_all中。
null_value默认控制替换值。如空字符串替换为”NULL”,空数字替换为-1
store是否存储:默认为false。true意义不大,因为_source中已有数据
index索引模式:analyzed (索引并分词,text默认模式), not_analyzed (索引不分词,keyword默认模式),no(不索引)
analyzer索引分词器:索引创建时使用的分词器,如ik_smart,ik_max_word,standard
search_analyzer搜索分词器:搜索该字段的值时,传入的查询内容的分词器。
fields多字段索引:当对该字段需要使用多种索引模式时使用。

多字段索引如:城市搜索New York, 既可以分词,又可以不分词

"city":{
    "type": "text",   
    "analyzer": "ik_smart",   
    "fields": {      
        "raw": {         
            "type":  "keyword"       
        }   
    }
}

解释:相当于给 city取了一个别名 city.raw,city的类型为text , city.raw的类型keyword; 搜索 city分词 ; 搜索city.raw 不分词那么以后搜索过滤和排序就可以使用city.raw字段名

2.添加映射

注意:如果索引库已经有数据了,就不能再添加映射了

2.1.创建新的索引库
put aigou
2.2.单类型创建映射

put aigou/goods/_mapping
{
    "goods": {
        "properties": {
            "id": {
                "type": "long"
            },
            "name": {
                "type": "text",
                "analyzer": "ik_smart",
                "search_analyzer": "ik_smart"
            }
        }
    }
}

解释:给aigou索引库中的是goods类型创建映射 ,id指定为long类型 , name指定为text类型(要分词),analyzer分词使用ik,查询分词器也使用ik

2.3.多类型创建映射

PUT aigou
{
  "mappings": {
    "user": {
      "properties": {
        "id": {
          "type": "integer"
        },
        "info": {
          "type": "text",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        }
      }
    },
    "dept": {
      "properties": {
        "id": {
          "type": "integer"
        },
        ....更多字段映射配置
      }
    }
  }
}

解释:同时给user和dept创建文档映射

2.4.数组/对象映射

基本类型字段映射非常简单,直接配置对应的类型即可,但是数组和对象如何指定类型呢?

1.对象映射

{
  "id" : 1,
  "girl" : {
      "name" : "王小花",
      "age"  : 22
  }
}

文档映射

{
  "properties": {
       "id": {"type": "long"},
       "girl": {
           "properties":{
                   "name": {"type": "keyword"},
                   "age": {"type": "integer"}
           }
        }
  }
}

2.数组映射

{
    "id" : 1,
    "hobby" : ["王小花","林志玲"]
}

文档映射

{
        "properties": {
            "id": {"type": "long"},
            "hobby": {"type": "keyword"}
     }
}

解释:数组的映射只需要映射一个元素即可,因为数组中的元素类型是一样的。

3.对象数组

{
    "id" : 1,
    "girl":[{"name":"林志玲","age":32},{"name":"赵丽颖","age":22}]
}

文档映射

"properties": {
        "id": {
            "type": "long"
        },
        "girl": {
            "properties": {
              "age": { "type": "long" },
              "name": { "type": "text" }
            }
        }
}

在实际项目中,我们按照如下流程操作ES

1.建索引库
2.全局映射
3.自定义映射
4.Java-API做CRUD

八.JavaApi-SpringBoot操作ES

1.集成ES

官方文档API:Java Transport Client (deprecated) | Elastic

1.1.导入依赖

下面采用ES提供的Jar进行ES操作

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

2.2.配置ES

spring:
  application:
    name: service-search
  elasticsearch:
    rest:
      uris: http://localhost:9200

2.3.定义DOC对象

该文档对象用来如下几个事情

  • 索引库的创建

  • 文档的映射

  • 存储到ES的数据封装

/**
 * 针对于  Employee 表的文档映射
 * indexName:索引库
 * type:类型(表类型)
 */
@Document(indexName = "hrm" , type = "employee")
public class EmployeeDoc {

//对应文档的id  PUT  /index/type/id
    @Id
    private Long id;

    @Field(type = FieldType.Keyword)    //指定为 不分词
    private String userName;

    private int age;

    @Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    private String intro;
    ...

2.4.初始化索引库和文档映射

ElasticsearchTemplate 是一个ES的工具类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = EsServiceApplication2050.class)
public class ESTest {

    @Autowired
    private ElasticsearchRestTemplate template;

    @Test
    public void testCreateIndex() {
        //创建索引
        template.createIndex(EmployeeDoc.class);
        //做文档映射
        template.putMapping(EmployeeDoc.class);
    }
}

2.5.定义Repository

SpringBoot提供了一个接口 ElasticsearchRepository 其中包含了对ES的CRUD操作

@Repository
public interface EmployeeElasticsearchRepository  extends ElasticsearchRepository<EmployeeDoc,Long> {}

泛型分别是:Repository管理的实体类对象 ,和ID的类型

2.ES的CRUD

2.1.添加

courseElasticsearchRepository.save(doc)

2.2.删除

courseElasticsearchRepository.deleteById(1L);

2.3.获取

Optional<CourseDoc> optional = courseElasticsearchRepository.findById(1L);
CourseDoc courseDoc = optional.get();

2.4.高级查询

//需求:查询课程名 name 中包含 java : DSL查询 - must - match
// 价格 price 在 1000 - 3000 : DSL过滤 - filter - range
// 每页 10 条,取第一页 ,按照价格倒排
@Test
public void testSearch(){

    //创建一个本机查询builder
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
    //=================================================
    //1.查询条件
    //组合查询
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

    //查询课程名 name 中包含 java : DSL查询 - must - match
    boolQueryBuilder.must(QueryBuilders.matchQuery("name","php"));

    //价格 price 在 1000 - 3000 : DSL过滤 - filter - range
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(1000).lte(3000));

    builder.withQuery(boolQueryBuilder);

    //2.排序 按照价格倒排
    builder.withSort(new FieldSortBuilder("price").order(SortOrder.DESC));
    //3.分页
    builder.withPageable(PageRequest.of(0,10));
    //=================================================
    //构建一个查询对象
    NativeSearchQuery searchQuery  = builder.build();

    //执行查询,得到结果
    Page<CourseDoc> page = courseElasticsearchRepository.search(searchQuery);
    //page  -> PageList

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

    //结果列表
    List<CourseDoc> content = page.getContent();

    content.forEach(courseDoc -> {
        System.out.println(courseDoc);
    });
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值