Elasticsearch

1. 初识ElasticSearch

1.1 基于数据库查询的问题

在这里插入图片描述

  1. 查询效率低,模糊查询数据库不会使用索引
  2. 查询的准确率不高

1.2 倒排索引

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

以唐诗为例,所处包含“前”的诗句

正向索引:由《静夜思》–>窗前明月光—>“前”字

反向索引:“前”字–>窗前明月光–>《静夜思》

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

“床前明月光”–> 分词;将一段文本按照一定的规则,拆分为不同的词条(term)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eo4YB0Nr-1663903327650)(img/1580887683510.png)]

倒排索引中的Value通常是文档的唯一标识(ID)

在这里插入图片描述

1.3 ES存储和查询的原理

index(索引):相当于mysql的库

映射:相当于mysql 的表结构

document(文档):相当于mysql的表中的数据

数据库查询存在的问题:

  1. 性能低:使用模糊查询,左边有通配符,不会走索引,会全表扫描,性能低
  2. 功能弱:如果以”华为手机“作为条件,查询不出来数据

Es使用倒排索引,对title 进行分词

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tuoVuU8B-1663903327652)(img/1581143412491.png)]

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

    生成的倒排索引中,词条会排序,形成一颗树形结构,提升词条的查询速度

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

    华为:1,3

    手机:1,2,3

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-apwYhX1D-1663903327652)(img/1581143489911.png)]

1.4 ES 概念详解

  • ES是一个基于Lucene的搜索服务器

  • ES是一个分布式、高扩展、高实时的搜索与数据分析引擎

  • ES基于RESTful web接口

  • Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎

  • 官网:https://www.elastic.co/

应用场景

  1. 海量数据的查询/搜索
  2. 日志数据分析
  3. 实时数据分析

2. 安装ElasticSearch

下载地址:https://www.elastic.co/cn/downloads/elasticsearch

3. ElasticSearch核心概念

索引(index)

ElasticSearch存储数据的地方,可以理解成关系型数据库中的数据库概念。

映射(mapping)

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

文档(document)

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

倒排索引

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

类型(type)

一种type就像一类表。如用户表、角色表等。在Elasticsearch7.X默认type为_doc

ES 5.x中一个index可以有多种type。
ES 6.x中一个index只能有一种type。
ES 7.x以后,将逐步移除type这个概念,现在的操作已经不再使用,默认_doc

4. 脚本操作ES

4.1 RESTful风格

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

  • 基于HTTP。

  • 使用XML格式定义或JSON格式定义。

  • 每一个URI代表1种资源。

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

    • GET:用来获取资源
    • POST:用来新建资源(也可以用于更新资源)
    • PUT:用来更新资源
    • DELETE:用来删除资源

4.2 操作索引

  • 添加索引
PUT http://ip:端口/索引名称
  • 查询索引
GET http://ip:端口/索引名称               # 查询单个索引信息
GET http://ip:端口/索引名称1,索引名称2...  # 查询多个索引信息
GET http://ip:端口/_all                 # 查询所有索引信息
  • 删除索引
DELETE http://ip:端口/索引名称
  • 关闭/打开索引
POST http://ip:端口/索引名称/_close  # 关闭索引
POST http://ip:端口/索引名称/_open   # 打开索引

4.3 ES数据类型

1)简单数据类型
  • 字符串
text:会分词,不支持聚合					# 聚合:相当于mysql 中的sum(求和)
keyword:不会分词,将全部内容作为一个词条,支持聚合
  • 数值

  • 布尔:boolean

  • 二进制:binary

  • 范围类型

integer_range, float_range, long_range, double_range, date_range 
  • 日期:date
2)复杂数据类型
  • 数组:[ ] 数组类型的JSON对象

  • 对象:{ } 单个JSON对象

4.4 操作映射

  • 添加映射
# 添加索引
PUT person
# 查询索引 
GET person
# 给索引添加映射
PUT /person/_mapping
{
  "properties":{
    "name":{
      "type":"text"
    },
    "age":{
      "type":"integer"
    }
  }
}
  • 创建索引并添加映射
#创建索引并添加映射
PUT /person1
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "age": {
        "type": "integer"
      }
    }
  }
}
# 查询索引
GET person1/_mapping
  • 添加字段
#添加字段
PUT /person1/_mapping
{
  "properties": {
      "address": {
        "type": "text"
      }
    }
}

4.5 操作文档

  • 添加文档,指定id
# 添加文档
POST /person1/_doc/2
{
  "name":"张三",
  "age":18,
  "address":"北京"
}

# 根据id查询文档
GET /person1/_doc/1
  • 添加文档,不指定id
#添加文档,不指定id
POST /person1/_doc/
{
  "name":"张三",
  "age":18,
  "address":"北京"
}

#查询所有文档
GET /person1/_search
  • 删除文档
#删除指定id文档
DELETE /person1/_doc/1

5. 分词器

5.1 分词器-介绍

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

  • 是一个基于Maven构建的项目,具有60万字/秒的高速处理能力

  • 支持用户词典扩展定义

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

5.2 ik分词器安装

执行如下命令时如果出现 打包失败(501码)将maven镜像换成阿里云的

mvn package
vim /opt/apache-maven-3.1.1/conf/setting.xml
<mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>
</mirror>

5.3 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": "乒乓球明年总冠军"分成了【乒乓球】【明年】【总冠军】

这样看的话,这样的分词效果达到了我们的要求。

5.4 查询文档

1)准备工作
  • 创建索引,添加映射,并指定分词器为ik分词器
PUT person2
{
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      },
      "address": {
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}
  • 添加文档
POST /person2/_doc/1
{
  "name":"张三",
  "age":18,
  "address":"北京海淀区"
}

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

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

  • 查询映射
GET person2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6IILwdxn-1663903327653)(img/1580879388109.png)]

  • 查看分词效果
GET _analyze
{
  "analyzer": "ik_max_word",
  "text": "北京海淀"
}
2)term查询

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

​ 查询person2中匹配到"北京"两字的词条

GET /person2/_search
{
  "query": {
    "term": {
      "address": {
        "value": "北京"
      }
    }
  }
}
3)match查询

match全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集

​ 全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集

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

6. JavaApi操作ES

6.1 SpringBoot整合ES

  1. 搭建SpringBoot工程

  2. 引入ElasticSearch相关坐标

<!--引入es的坐标-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

  1. 测试

配置ElasticSearchConfig

@ConfigurationProperties(prefix = "elasticsearch")
@Configuration
@Data
public class ElasticsearchConfig  extends AbstractElasticsearchConfiguration {
 
    private String host ;
    private Integer port ;
 
    @Override
    public RestHighLevelClient elasticsearchClient() {
        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
        RestHighLevelClient restHighLevelClient = new
                RestHighLevelClient(builder);
        return restHighLevelClient;
    }
    @Bean
    public ElasticsearchRestTemplate elasticsearchRestTemplate(RestHighLevelClient elasticsearchClient){
        return new ElasticsearchRestTemplate(elasticsearchClient);
    }
}

6.2 创建索引

  1. 添加索引,删除索引
/**
* <p>
* 测试 ElasticTemplate 的创建/删除
* </p>

*/
public class TemplateTest extends SpringBootDemoElasticsearchApplicationTests {
   @Autowired
   private ElasticsearchTemplate esTemplate;

   /**
    * 测试 ElasticTemplate 创建 index
    */
   @Test
   public void testCreateIndex() {
       // 创建索引,会根据Item类的@Document注解信息来创建
       esTemplate.createIndex(Person.class);

       // 配置映射,会根据Item类中的id、Field等字段来自动完成映射
       esTemplate.putMapping(Person.class);
   }

   /**
    * 测试 ElasticTemplate 删除 index
    */
   @Test
   public void testDeleteIndex() {
       esTemplate.deleteIndex(Person.class);
   }
}

6.3 创建实体类Person

/**
 * <p>
 * 用户实体类
 * </p>
 *
 * @author yangkai.shen
 * 
 */
@Document(indexName = contact.INDEX_NAME, type = EsConsts.TYPE_NAME, shards = 1, replicas = 0)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    /**
     * 主键
     */
    @Id
    private Long id;

    /**
     * 名字
     */
    @Field(type = FieldType.Keyword)
    private String name;

    /**
     * 国家
     */
    @Field(type = FieldType.Keyword)
    private String country;

    /**
     * 年龄
     */
    @Field(type = FieldType.Integer)
    private Integer age;

    /**
     * 生日
     */
    @Field(type = FieldType.Date)
    private Date birthday;

    /**
     * 介绍
     */
    @Field(type = FieldType.Text, analyzer = "ik_smart")
    private String remark;
}

6.4 配置Person的Dao类,PersonRepository.java

/**
 * <p>
 * 用户持久层
 * </p>
  
 */
public interface PersonRepository extends ElasticsearchRepository<Person, Long> {

    /**
     * 根据年龄区间查询
     *
     * @param min 最小值
     * @param max 最大值
     * @return 满足条件的用户列表
     */
    List<Person> findByAgeBetween(Integer min, Integer max);
}

6.5 添加文档

1.添加文档

     /**
     * 测试新增
     */
    @Test
    public void save() {
        Person person = new Person(1L, "刘备", "蜀国", 18, DateUtil.parse("1990-01-02 03:04:05"), "刘备(161年-223年6月10日),即汉昭烈帝(221年-223年在位),又称先主,字玄德,东汉末年幽州涿郡涿县(今河北省涿州市)人,西汉中山靖王刘胜之后,三国时期蜀汉开国皇帝、政治家。\n刘备少年时拜卢植为师;早年颠沛流离,备尝艰辛,投靠过多个诸侯,曾参与镇压黄巾起义。先后率军救援北海相孔融、徐州牧陶谦等。陶谦病亡后,将徐州让与刘备。赤壁之战时,刘备与孙权联盟击败曹操,趁势夺取荆州。而后进取益州。于章武元年(221年)在成都称帝,国号汉,史称蜀或蜀汉。《三国志》评刘备的机权干略不及曹操,但其弘毅宽厚,知人待士,百折不挠,终成帝业。刘备也称自己做事“每与操反,事乃成尔”。\n章武三年(223年),刘备病逝于白帝城,终年六十三岁,谥号昭烈皇帝,庙号烈祖,葬惠陵。后世有众多文艺作品以其为主角,在成都武侯祠有昭烈庙为纪念。");
        Person save = repo.save(person);
        log.info("【save】= {}", save);
    }

6.6 修改、查询、删除文档

  1. 修改文档:添加文档时,如果id存在则修改,id不存在则添加
/**
 * 修改文档:添加文档时,如果id存在则修改,id不存在则添加
 */
 @Test
    public void update() {
        repo.findById(1L).ifPresent(person -> {
            person.setRemark(person.getRemark() + "\n更新更新更新更新更新");
            Person save = repo.save(person);
            log.info("【save】= {}", save);
        });
    }
  1. 根据id查询文档
/**
 * 根据id查询文档
 */
    @Test
    public void findById(){
        Person person = repo.findById(1L).get();
        System.out.println(person);
    }
  1. 根据id删除文档
/**
 * 根据id删除文档
 */
  @Test
    public void deleteById(){
        repo.deleteById(1L);
    }

6.7 ElasticSearch批量操作

查询所有

JavaAPI操作

//查询所有
    @Test
    public void findAll(){
        Iterable<Person> persons = repo.findAll();
        for (Person person : persons) {
            System.out.println(person);
        }
    }

批量新增

JavaAPI操作

    @Test
    public void saveList() {
        List<Person> personList = Lists.newArrayList();
        personList.add(new Person(2L, "曹操", "魏国", 20, DateUtil.parse("1988-01-02 03:04:05"), "曹操(155年-220年3月15日),字孟德,一名吉利,小字阿瞒,沛国谯县(今安徽亳州)人。东汉末年杰出的政治家、军事家、文学家、书法家,三国中曹魏政权的奠基人。\n曹操曾担任东汉丞相,后加封魏王,奠定了曹魏立国的基础。去世后谥号为武王。其子曹丕称帝后,追尊为武皇帝,庙号太祖。\n东汉末年,天下大乱,曹操以汉天子的名义征讨四方,对内消灭二袁、吕布、刘表、马超、韩遂等割据势力,对外降服南匈奴、乌桓、鲜卑等,统一了中国北方,并实行一系列政策恢复经济生产和社会秩序,扩大屯田、兴修水利、奖励农桑、重视手工业、安置流亡人口、实行“租调制”,从而使中原社会渐趋稳定、经济出现转机。黄河流域在曹操统治下,政治渐见清明,经济逐步恢复,阶级压迫稍有减轻,社会风气有所好转。曹操在汉朝的名义下所采取的一些措施具有积极作用。\n曹操军事上精通兵法,重贤爱才,为此不惜一切代价将看中的潜能分子收于麾下;生活上善诗歌,抒发自己的政治抱负,并反映汉末人民的苦难生活,气魄雄伟,慷慨悲凉;散文亦清峻整洁,开启并繁荣了建安文学,给后人留下了宝贵的精神财富,鲁迅评价其为“改造文章的祖师”。同时曹操也擅长书法,唐朝张怀瓘在《书断》将曹操的章草评为“妙品”。"));
        personList.add(new Person(3L, "孙权", "吴国", 19, DateUtil.parse("1989-01-02 03:04:05"), "孙权(182年-252年5月21日),字仲谋,吴郡富春(今浙江杭州富阳区)人。三国时代孙吴的建立者(229年-252年在位)。\n孙权的父亲孙坚和兄长孙策,在东汉末年群雄割据中打下了江东基业。建安五年(200年),孙策遇刺身亡,孙权继之掌事,成为一方诸侯。建安十三年(208年),与刘备建立孙刘联盟,并于赤壁之战中击败曹操,奠定三国鼎立的基础。建安二十四年(219年),孙权派吕蒙成功袭取刘备的荆州,使领土面积大大增加。\n黄武元年(222年),孙权被魏文帝曹丕册封为吴王,建立吴国。同年,在夷陵之战中大败刘备。黄龙元年(229年),在武昌正式称帝,国号吴,不久后迁都建业。孙权称帝后,设置农官,实行屯田,设置郡县,并继续剿抚山越,促进了江南经济的发展。在此基础上,他又多次派人出海。黄龙二年(230年),孙权派卫温、诸葛直抵达夷州。\n孙权晚年在继承人问题上反复无常,引致群下党争,朝局不稳。太元元年(252年)病逝,享年七十一岁,在位二十四年,谥号大皇帝,庙号太祖,葬于蒋陵。\n孙权亦善书,唐代张怀瓘在《书估》中将其书法列为第三等。"));
        personList.add(new Person(4L, "诸葛亮", "蜀国", 16, DateUtil.parse("1992-01-02 03:04:05"), "诸葛亮(181年-234年10月8日),字孔明,号卧龙,徐州琅琊阳都(今山东临沂市沂南县)人,三国时期蜀国丞相,杰出的政治家、军事家、外交家、文学家、书法家、发明家。\n早年随叔父诸葛玄到荆州,诸葛玄死后,诸葛亮就在襄阳隆中隐居。后刘备三顾茅庐请出诸葛亮,联孙抗曹,于赤壁之战大败曹军。形成三国鼎足之势,又夺占荆州。建安十六年(211年),攻取益州。继又击败曹军,夺得汉中。蜀章武元年(221年),刘备在成都建立蜀汉政权,诸葛亮被任命为丞相,主持朝政。蜀后主刘禅继位,诸葛亮被封为武乡侯,领益州牧。勤勉谨慎,大小政事必亲自处理,赏罚严明;与东吴联盟,改善和西南各族的关系;实行屯田政策,加强战备。前后六次北伐中原,多以粮尽无功。终因积劳成疾,于蜀建兴十二年(234年)病逝于五丈原(今陕西宝鸡岐山境内),享年54岁。刘禅追封其为忠武侯,后世常以武侯尊称诸葛亮。东晋政权因其军事才能特追封他为武兴王。\n诸葛亮散文代表作有《出师表》《诫子书》等。曾发明木牛流马、孔明灯等,并改造连弩,叫做诸葛连弩,可一弩十矢俱发。诸葛亮一生“鞠躬尽瘁、死而后已”,是中国传统文化中忠臣与智者的代表人物。"));
        Iterable<Person> people = repo.saveAll(personList);
        log.info("【people】= {}", people);
    }
批量删除

JavaAPI操作

@Test
public void deleteAll(){
  repo.deleteAll(repo.findAll());
}

6.8 termQuery-词条查询

term查询不会对查询条件进行分词

1)脚本操作

ElasticSearch两个数据类型

text:会分词,不支持聚合
keyword:不会分词,将全部内容作为一个词条,支持聚合

查询title:ES对text数据分词

GET contact/_search
{
  "query": {
    "term": {
      "name": {
        "value": "曹操"
      }
    }
  }
}

查询address:ES对keyword没有分词

GET contact/_search
{
  "query": {
    "term": {
      "country": {
        "value": "魏国"
      }
    }
  }
}
2)JavaAPI操作

    @Test
    public void advanceSelect() {
        // QueryBuilders 提供了很多静态方法,可以实现大部分查询条件的封装
        MatchQueryBuilder queryBuilder = QueryBuilders.termQuery("name", "sunquan");
        log.info("【queryBuilder】= {}", queryBuilder.toString());
        repo.search(queryBuilder).forEach(person -> log.info("【person】= {}", person));
    }

6.9 分页查询

@Autowired
private PersonDao personDao;

@Test
public void findByPageable(){
    //设置排序(排序方式,正序还是倒序,排序的 id)
    Sort sort = Sort.by(Sort.Direction.DESC,"id");
    int currentPage=0;//当前页,第一页从 0 开始, 1 表示第二页
    int pageSize = 5;//每页显示多少条
    //设置查询分页
    PageRequest pageRequest = PageRequest.of(currentPage, pageSize,sort);
    //分页查询
    Page<Person> personPage = repo.findAll(pageRequest);
    for (Person person : personPage.getContent()) {
        System.out.println(person);
    }
}

6.10termQuery加分页

  @Test
    public void customAdvanceSelect() {
        // 构造查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本的分词条件
        queryBuilder.withQuery(QueryBuilders.termQuery("remark", "donghan"));
        // 排序条件
        queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC));
        // 分页条件
        queryBuilder.withPageable(PageRequest.of(0, 2));
        Page<Person> people = repo.search(queryBuilder.build());
        log.info("【people】总条数 = {}", people.getTotalElements());
        log.info("【people】总页数 = {}", people.getTotalPages());
        people.forEach(person -> log.info("【person】= {},年龄 = {}", person.getName(), person.getAge()));
    }

6.11 matchQuery-分词查询

  1. 会对查询条件进行分词。
  2. 然后将分词后的查询条件和词条进行等值匹配
  3. 默认取并集(OR)
1)脚本操作
# match查询
GET contact/_search
{
  "query": {
    "match": {
      "name": "曹操"
    }
  },
  "size": 500
}
2)JavaAPI操作
  @Test
    public void advanceSelect() {
        // QueryBuilders 提供了很多静态方法,可以实现大部分查询条件的封装
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "曹操");
        log.info("【queryBuilder】= {}", queryBuilder.toString());
        repo.search(queryBuilder).forEach(person -> log.info("【person】= {}", person));
    }

总结:

  • term query:对查询条件不分词查询
  • match query:对查询条件分词查询

6.12 模糊查询

1)脚本操作
1. wildcard查询

wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)

"*华*"  包含华字的
"华*"   华字后边多个字符
"华?"  华字后边多个字符
"*华"或"?华" 会引发全表(全索引)扫描 注意效率问题
# wildcard 查询。查询条件分词,模糊查询
GET contact/_search
{
  "query": {
    "wildcard": {
      "name": {
        "value": "孙*"
      }
    }
  }
}
2. 正则查询
\W:匹配包括下划线的任何单词字符,等价于 [A-Z a-z 0-9_]   开头的反斜杠是转义符

+号多次出现

(.)*为任意字符
正则查询取决于正则表达式的效率
GET contact/_search
{
  "query": {
    "regexp": {
      "name": "\\w+(.)*"
    }
  }
}

3. 前缀查询

对keyword类型支持比较好

# 前缀查询 对keyword类型支持比较好
GET contact/_search
{
  "query": {
    "prefix": {
      "name": {
        "value": "张"
      }
    }
  }
}
2)JavaAPI操作
//模糊查询
WildcardQueryBuilder query = QueryBuilders.wildcardQuery("name", "孙");
//正则查询
RegexpQueryBuilder query = QueryBuilders.regexpQuery("name", "\\w+(.)*");
//前缀查询
PrefixQueryBuilder query = QueryBuilders.prefixQuery("name", "孙");

6.13 范围&排序查询

1)脚本操作
# 范围查询

GET contact/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 18,
        "lte": 19
      }
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}
2)JavaAPI操作
 /**
     * 测试普通查询,按生日倒序
     */
    @Test
    public void select() {
        repo.findAll(Sort.by(Sort.Direction.DESC, "birthday"))
                .forEach(person -> log.info("{} 生日: {}", person.getName(), DateUtil.formatDateTime(person.getBirthday())));
    }
   /**
     * 自定义查询,根据年龄范围查询
     */
    @Test
    public void customSelectRangeOfAge() {
        repo.findByAgeBetween(18, 19).forEach(person -> log.info("{} 年龄: {}", person.getName(), person.getAge()));
    }

6.14 queryString-查询

queryString:在多个字段中进行查询

  • 会对查询条件进行分词。

  • 然后将分词后的查询条件和词条进行等值匹配

  • 默认取并集(OR)

  • 可以指定多个查询字段

1)脚本操作

query_string:识别query中的连接符(or 、and)

# queryString

GET contact/_search
{
  "query": {
    "query_string": {
      "fields": ["name","country"], 
      "query": "张三 AND 中国"	# 字段中要同时包含张三和中国
    }
  }
}

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

GET contact/_search
{
  "query": {
    "simple_query_string": {
      "fields": ["name","country"], 
      "query": "张三 AND 中国"	
    }
  }
}

query_string:有default_operator连接符的脚本

GET contact/_search
{
  "query": {
    "query_string": {
      "fields": ["name","country"],
      "query": "张三",
      "default_operator": "AND"
    }
  }
}

2)JavaAPI操作
QueryStringQueryBuilder query = QueryBuilders.queryStringQuery("country")
    .field("name")
    .field("")
    .defaultOperator(Operator.AND);

注意

query中的orand是在查询时是判断字段中是(or)或(and)包含查询条件

efault_operator的orand是对结果进行 并集(or)、交集(and)

6.15 布尔查询-脚本

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

  • must(and):条件必须成立

  • must_not(not):条件必须不成立

  • should(or):条件可以成立

  • filter:条件必须成立,性能比must高。不会计算得分

**得分:**值条件匹配度,匹配度越高,得分越高

1)脚本操作
# boolquery
#must和filter配合使用时,must 默认数组形式
GET contact/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "name": {
              "value": "张三"		# 查询名称为张三
            }
          }
        }
      ],
      "filter":[ 
        {
        "term": {
          "country": "中国"		# 国家在上海
        }
       },
       {
         "range":{				
          "age": {					# 年龄在10-50之间
            "gte": 10,
            "lte": 50
         }
         }
       }
      ]
    }
  }
}
#filter 单独使用   filter可以是单个条件,也可多个条件(数组形式)
GET contact/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "name": {
              "value": "张三"
            }
          }
        }
      ]
    }
  }
}
2)JavaAPI

布尔查询:boolQuery

  1. 询name名称为:张三
  2. 查询国家包含:中国
  3. 查询年龄在:10-50

must 、filter为连接方式

term、match为不同的查询方式

 @Test
    public void query() {
        //1.构建boolQuery
        BoolQueryBuilder query = QueryBuilders.boolQuery();

        //2.构建各个查询条件
        //2.1 查询name名称为:张三
        QueryBuilder termQuery = QueryBuilders.matchQuery("name","张三");
        query.must(termQuery);

        //2.2. 查询国家包含:中国
        QueryBuilder matchQuery = QueryBuilders.matchQuery("country","中国");
        query.filter(matchQuery);

        //2.3 查询年龄在:10-50
        QueryBuilder rangeQuery = QueryBuilders.rangeQuery("age");
        ((RangeQueryBuilder) rangeQuery).gte(10);
        ((RangeQueryBuilder) rangeQuery).lte(50);
        query.filter(rangeQuery);

        Iterable<Person> persons = repo.search(query);
        for (Person person : persons) {
            System.out.println(person);
        }
    }

6.16 高亮查询

高亮三要素:

  • 高亮字段

  • 添加前缀标签

  • 添加后缀标签

默认前后缀 :em

<em>张三</em>
1)脚本操作
GET goods/_search
{
  "query": {
    "match": {
      "name": "张三"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<font color='red'>",
        "post_tags": "</font>"
      }
    }
  }
}
2)JavaAPI操作

高亮查询操作步骤:

  1. 设置高亮

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

   @Test
    public void testOps(){
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本分词查询
        String preTags = "<strong>";
        String postTags = "</strong>";
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags(preTags);//设置前缀
        highlightBuilder.postTags(postTags);//设置后缀
        highlightBuilder.field("name");//设置高亮字段
        String name = "张三";
        queryBuilder.withQuery(QueryBuilders.matchQuery("name", name));
        queryBuilder.withHighlightBuilder(highlightBuilder);

        NativeSearchQuery query = queryBuilder.build();

        SearchHits<Person> search = elasticsearchRestTemplate.search(query, 			         Person.class);

        long totalHits = search.getTotalHits();
        System.out.println("totalHits = " + totalHits);
        Iterator<SearchHit<Person>> iterator = search.stream().iterator();
        while (iterator.hasNext()) {
            SearchHit<Person> next = iterator.next();
            Person content = next.getContent();
            List<String> firstname = next.getHighlightField("name");
            for (String s : firstname) {
                System.out.println(s);
            }
        }

    }

6.17 聚合查询-脚本

  • 指标聚合:相当于MySQL的聚合函数。max、min、avg、sum等

  • 桶聚合:相当于MySQL的 group by 操作。(不要对text类型的数据进行分组,会失败)

1)脚本操作
# 聚合查询
# 指标聚合 聚合函数
GET contact/_search
{
  "query": {  
    "match": {
      "name": "张三"
    }
  },
  "aggs": {
    "max_age": {
      "max": {
        "field": "age"
      }
    }
  }
}
# 桶聚合  分组
GET contact/_search
{
  "query": {
    "match": {
      "name": "张三"
    }
  },
  "aggs": {
    "person_country": {
      "terms": {
        "field": "country",
        "size": 100
      }
    }
  }
}
2)JavaAPI

    /**
     * 测试聚合,测试平均年龄
     */
    @Test
    public void agg() {
        // 构造查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));

        // 平均年龄
        queryBuilder.addAggregation(AggregationBuilders.avg("avg").field("age"));

        log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build()));

        AggregatedPage<Person> people = (AggregatedPage<Person>) repo.search(queryBuilder.build());
        double avgAge = ((InternalAvg) people.getAggregation("avg")).getValue();
        log.info("【avgAge】= {}", avgAge);
    }

    /**
     * 测试高级聚合查询,每个国家的人有几个,每个国家的平均年龄是多少
     */
    @Test
    public void advanceAgg() {
        // 构造查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));

        // 1. 添加一个新的聚合,聚合类型为terms,聚合名称为country,聚合字段为age
        queryBuilder.addAggregation(AggregationBuilders.terms("country").field("country")
                // 2. 在国家聚合桶内进行嵌套聚合,求平均年龄
                .subAggregation(AggregationBuilders.avg("avg").field("age")));

        log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build()));

        // 3. 查询
        AggregatedPage<Person> people = (AggregatedPage<Person>) repo.search(queryBuilder.build());

        // 4. 解析
        // 4.1. 从结果中取出名为 country 的那个聚合,因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
        StringTerms country = (StringTerms) people.getAggregation("country");
        // 4.2. 获取桶
        List<StringTerms.Bucket> buckets = country.getBuckets();
        for (StringTerms.Bucket bucket : buckets) {
            // 4.3. 获取桶中的key,即国家名称  4.4. 获取桶中的文档数量
            log.info("{} 总共有 {} 人", bucket.getKeyAsString(), bucket.getDocCount());
            // 4.5. 获取子聚合结果:
            InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg");
            log.info("平均年龄:{}", avg);
        }
    }

}

7 重建索引

在项目开发过程中,如果需要更新索引,一般情况ES是不允许映射的已有字段进行修改(只能添加新的字段),此时可以通过新建索引再重新导入数据的方式进行。

步骤可以共3步:

1. 新建索引
2. 导入数据
3. 创建别名,保证业务代码不用变更

准备环境

# -------重建索引-----------

# 新建student_index_v1。索引名称必须全部小写

PUT student_index_v1
{
  "mappings": {
    "properties": {
      "birthday":{
        "type": "date"
      }
    }
  }
}
#查看 student_index_v1 结构
GET student_index_v1
#添加数据
PUT student_index_v1/_doc/1
{
  "birthday":"1999-11-11"
}
#查看数据
GET student_index_v1/_search

#添加数据
PUT student_index_v1/_doc/1
{
  "birthday":"1999年11月11日"
}
  1. 重建索引
  2. 将student_index_v1数据拷贝到 student_index_v2
# 业务变更了,需要改变birthday字段的类型为text

# 1. 创建新的索引 student_index_v2
# 2. 将student_index_v1 数据拷贝到 student_index_v2

# 创建新的索引 student_index_v2
PUT student_index_v2
{
  "mappings": {
    "properties": {
      "birthday":{
        "type": "text"
      }
    }
  }
}
# 将student_index_v1 数据拷贝到 student_index_v2
# _reindex 拷贝数据
POST _reindex
{
  "source": {
    "index": "student_index_v1"
  },
  "dest": {
    "index": "student_index_v2"
  }
}

GET student_index_v2/_search



PUT student_index_v2/_doc/2
{
  "birthday":"1999年11月11日"
}

  1. 创建索引库别名:

注意:DELETE student_index_v1 这一操作将删除student_index_v1索引库,并不是删除别名

# 思考: 现在java代码中操作es,还是使用的实student_index_v1老的索引名称。
# 1. 改代码(不推荐)
# 2. 索引别名(推荐)

# 步骤:
# 0. 先删除student_index_v1
# 1. 给student_index_v2起个别名 student_index_v1



# 先删除student_index_v1
DELETE student_index_v1 这一操作将删除student_index_v1索引库
# 索引库默认的别名与索引库同名,无法删除
# 给student_index_v1起个别名 student_index_v11
POST student_index_v2/_alias/student_index_v11
#测试删除命令
POST /_aliases
{
    "actions": [
        {"remove": {"index": "student_index_v1", "alias": "student_index_v11"}}
    ]
}

# 给student_index_v2起个别名 student_index_v1
POST student_index_v2/_alias/student_index_v1

#查询别名
GET goods/_alias/

GET student_index_v1/_search
GET student_index_v2/_search

index_v1/_doc/1
{
“birthday”:“1999-11-11”
}
#查看数据
GET student_index_v1/_search

#添加数据
PUT student_index_v1/_doc/1
{
“birthday”:“1999年11月11日”
}


1. 重建索引
2. 将student_index_v1数据拷贝到 student_index_v2

```json
# 业务变更了,需要改变birthday字段的类型为text

# 1. 创建新的索引 student_index_v2
# 2. 将student_index_v1 数据拷贝到 student_index_v2

# 创建新的索引 student_index_v2
PUT student_index_v2
{
  "mappings": {
    "properties": {
      "birthday":{
        "type": "text"
      }
    }
  }
}
# 将student_index_v1 数据拷贝到 student_index_v2
# _reindex 拷贝数据
POST _reindex
{
  "source": {
    "index": "student_index_v1"
  },
  "dest": {
    "index": "student_index_v2"
  }
}

GET student_index_v2/_search



PUT student_index_v2/_doc/2
{
  "birthday":"1999年11月11日"
}

  1. 创建索引库别名:

注意:DELETE student_index_v1 这一操作将删除student_index_v1索引库,并不是删除别名

# 思考: 现在java代码中操作es,还是使用的实student_index_v1老的索引名称。
# 1. 改代码(不推荐)
# 2. 索引别名(推荐)

# 步骤:
# 0. 先删除student_index_v1
# 1. 给student_index_v2起个别名 student_index_v1



# 先删除student_index_v1
DELETE student_index_v1 这一操作将删除student_index_v1索引库
# 索引库默认的别名与索引库同名,无法删除
# 给student_index_v1起个别名 student_index_v11
POST student_index_v2/_alias/student_index_v11
#测试删除命令
POST /_aliases
{
    "actions": [
        {"remove": {"index": "student_index_v1", "alias": "student_index_v11"}}
    ]
}

# 给student_index_v2起个别名 student_index_v1
POST student_index_v2/_alias/student_index_v1

#查询别名
GET goods/_alias/

GET student_index_v1/_search
GET student_index_v2/_search

github 源码地址:https://github.com/xkcoding/spring-boot-demo/tree/master/demo-elasticsearch

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值