ElasticSearch的学习

news2024/12/27 1:41:37

介绍

ElasticSearch(简称ES)是一个开源的分布式搜索和数据分析引擎,是用Java开发并且是当前最流行的开源的企业级搜索引擎,能够达到近实时搜索,它专门设计用于处理大规模的文本数据和实现高性能的全文检索。

ElasticSearch是基于Restfull风格进行数据操作

应用场景:全文检索、日志分析、商业智能决策 等

ElasticStack生态介绍

ElasticSearch:核心搜索和分析引擎,提供存储、索引和分布式搜索功能

Logstach:数据处理管道,负责数据收集、处理和传输

Beats:边缘数据采集器,负责从各种来源采集数据并发送到Logstash或者ElasticSearch

Kibana:可视化和管理工具,提供数据展示和交互式查询

分词器

分词器可以让搜索的时候,能将指定的搜索词分成几个不同部分的搜索词来进行分别搜索。官方称之为文本分析器,顾名思义,是对文本进行分析处理的一种手段,基本处理逻辑为按照预先制定的分词规则,把原始文档分割成若干更小粒度的词项,粒度大小取决于分词器规则。

一般中文使用ik分词器,使用"analyzer":"ik_max_word","search_analyzer":"ik_smart"的方案。

ElasticSearch的核心概念

全文检索(Full-Text-Serach):全文检索是一种从大量文本数据中快速检索出包含指定词汇或者词语的信息的技术。

倒排索引:在一个文档集合中,每个文档都可以视为一个词语的集合,倒排索引则是将词语映射到包含这个词语的文档的数据结构

如何实现倒排索引:

  1. 文档预处理
  2. 构建词典
  3. 创建倒排列表
  4. 存储索引文件
  5. 查询处理

索引:类似于MySQL中的表,用于存储不同的数据

映射:类似于表中的schema,用于表示索引中的数据的结构

文档:索引中的一个数据实体,类似于MySQL中的一行记录

索引操作

创建索引

PUT /index_name

index_name代表索引名

可以直接用上述的语法去创建一个空的索引,也可以直接在创建的时候直接初始化好表的信息,例如:

PUT /index_name
{
  "settings":{
    //索引设置
  },
  "mapping":{
	  //字段映射
  }
}

必要的参数:

  • 索引名称(index_name):索引名必须是小写字母,可以包含数字和下划线

  • 索引设置(settings)

    • 分片数量(numberofshards):一个索引的分片数量决定了索引的并行度和数据分布,默认是一个
    • 副本数量(numberofreplicas):副本提高了数据的可用性和容错能力,默认是一个
  • 映射(mappings)

    • 字段属性(properties):定义索引中文档的字段及其类型。常用的字段包括:text,keyword,integer,float,date等。示例:
    {
      "properties":{
        "field_name1":{
          "type":"text",
          ……
        },
        "field_name2":{
          "type":"integer",
          ……
        }
      }
    }
    

删除索引

DELETE /index_name

修改索引

PUT /index_name_settings

修改的时候,带上要修改的跟设置相关的参数,例如:

PUT /index_name_settings
{
  "index": {
    "number_of_replicas": 2
  }
}

PUT /index_name/_mapping

修改的时候(添加新字段),带上要修改的跟映射相关的参数,例如:

PUT /index_name/_mapping
{
  "properties":{
    "content":{
      "type":"text",
      "analyzer":"ik_max_word",
      "search_analyzer":"ik_smart"
    }
  }
}

索引库和mapping一旦创建就无法更改,但是可以添加新的字段。

索引别名

aliases是一个和setting和mapping同级的参数,用于指定索引的别名。例如:

PUT /index2/_mapping
{
	"aliases":{
	  "index_alias":{}
	}
  "properties":{
    "content":{
      "type":"text",
      "analyzer":"ik_max_word",
      "search_analyzer":"ik_smart"
    }
  }
}

如此,就创建了一个索引index2,index2这个索引就有了index_alias这个别名

也可以给现有的索引增加一个别名:

POST /_aliases
{
  "actions":[
    {
	    "add":{
	      "index":"my_index",
	      "aliases":"my_index_alias"
	    }
    }
  ]
}

如此,现有的my_index索引就有了一个别名叫做my_index_alias

索引别名有什么作用呢?

如果要多索引检索,即同时检索多个索引,有两种方案:

  • 使用逗号对多个索引名称进程分隔

    POST index1,index2,index3,index4/_search
    
  • 使用通配符的方式

    POST index*/_search
    

这种方式会有很大的局限性,建议使用别名的方式

使用别名的方式:

  • 使用别名关联已有索引

    POST /_aliases
    {
      "actions":[
        {
    	    "add":{
    	      "index":"my_index1",
    	      "aliases":"my_index_alias"
    	    }
        },
        {
    	    "add":{
    	      "index":"my_index2",
    	      "aliases":"my_index_alias"
    	    }
        },
        {
    	    "add":{
    	      "index":"my_index3",
    	      "aliases":"my_index_alias"
    	    }
        }
      ]
    }
    

查询的时候,GET my_index_alias 就会查询关联这个别名的所有的表

若索引和和别名指向相同,则在相同检索条件下的检索效率是一致的,因为索引别名只是物理索引的软链接的名称而已。

对相同索引别名的物理索引建议由一致的映射,以提升检索效率

推荐充分发挥索引别名在检索方面的优势,但是在写入和更新的时候还要使用物理索引。

mapping的字段的属性

mapping是对索引库中文档的约束,常见的mapping的字段的属性包括:

  • type:字段数据类型,创建的类型有:
    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址,不可分词,是一个不可分割的整体)
    • 数值:long、integer、short、byte、double、float
    • 布尔:boolean
    • 日期:date
    • 对象:object

如果要使用数组的形式,type直接指定数组元素的类型即可,数据值也是以[xxx,xxx,xxx,……]数组的形式。

  • index:是否创建索引,给了值为true,就会为其创建倒排索引
  • analyzer:使用那种分词器,结合text类型的字段使用
  • properties:该字段的子字段
  • copy_to:可以将该字段的属性值拼接到指定哪个属性中

文档操作

新增文档

新增文档的DSL语法:

POST /索引库名/_doc/文档id
{
  "字段1":"值1",
  "字段2":{
    "子属性1":"值2",
    "子属性2":"值3"
  }
}

如果不指定新建的文档id,就会自动随机生成一个id,这种情况显然不好。建议在新增文档的时候,加上指定的文档id。

查看文档

GET /索引库名/_doc/文档id

删除文档

DELETE /索引库名/_doc/文档id

更新文档

方式一:全量修改,会删除旧文档,添加新文档

PUT /索引库名/_doc/文档id
{
  "字段1":"值1",
  "字段2":{
    "子属性1":"值2",
    "子属性2":"值3"
  }
}

如果文档id对应的文档存在,就删除旧文档,添加新文档。如果不存在,就直接增加一个新文档。

方式二:增量修改,修改指定字段值。

POST /索引库名/_update/文档id
{
  "doc":{
    "字段1":"值1",
	  "字段2":{
	    "子属性1":"值2",
	    "子属性2":"值3"
	  }
  }
}

这种方式只会修改一条文档中的指定的字段值

DSL查询文档

DSL Query的分类

ElasticSearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据。match_all
  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引中匹配。
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型的字段。
  • 地理(geo)查询:根据经纬度查询。
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。

DSL Query查询语法

查询的基本语法如下:

GET /index_name/_search
{
  "query":{
    "查询类型":{
      "查询条件":"条件值"
    }
  }
}

全文检索查询

全文检索查询,会对用户的输入内容进行分词,常用于搜索框搜索

  • match查询:全文检索查询中的一种,会对用户输入内容进行分词,然后去倒排索引库检索。

    GET /index_name/_search
    {
      "query":{
        "match":{
          "字段名":"搜索内容"
        }
      }
    }
    
  • multi_match:与match查询类似,只不过允许同时查询多个字段

    GET /hotel/_search
    {
      "query":{
        "multi_match":{
          "query":"搜索词",
          "fields":["字段1","字段2","字段3",……]
        }
      }
    }
    

推荐使用copy_to将要共同查询的字段汇总到一个总字段之后,使用match查询。使用multi_match查询的时候,参与查询的字段越多,查询性能越差。

精确查询

精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的方式有:

  • term:根据词条精确值查询

    GET /index_name/_search
    {
      "query":{
        "term":{
          "字段名":{
            "value":"搜索值"
          }
        }
      }
    }
    
  • range:根据值的范围查询

    GET /index_name/_search
    {
      "query":{
        "range":{
          "字段名":{
            "gte":"最小值",
            "lte":"最大值"
          }
        }
      }
    }
    
    • gte是表示大于等于,如果只想表示大于,使用gt
    • lte是表示小于等于,如果只想表示小于,使用lt

地理查询

根据经纬度查询。常见方式有:

  • geo_bounding_box:查询geo_point值落在某个矩形范围内的所有文档

    GET /index_name/_search
    {
      "query":{
        "geo_bounding_box":{
          "字段名":{
            "top_left":{ //左上角
              "lat":"纬度值",
              "lon":"经度值"
            },
            "bottom_right":{ //右下角
              "lat":"纬度值",
              "lon":"经度值"
            }
          }
        }
      }
    }
    
    • top_left属性就是矩形左上角的点的经纬度
    • bottom_right属性就是矩形右下角的点的经纬度
  • geo_distance:查询到指定中心点小于某个距离值的所有文档

    GET /index_name/_search
    {
      "query":{
        "geo_distance":{
          "distance":"距离(直接以m、km等等为距离单位即可)"
          "字段名":"字段值"
        }
      }
    }
    

复合查询

符合查询可以将其他简单查询组合起来,实现更复杂的搜索逻辑。

function score:算分函数查询,可以控制文档相关性算分,控制文档排名

相关性算分:当我利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果的时候按照分值降序排列。TF(词条频率)= 词条出现次数/文档中词条总数。还有一个TF-IDF算法,即也考虑逆文档频率,逆文档频率 = log(文档总数/包含词条的文档总数)。但是目前使用的是更高级的BM25算法,这种算法不会受词频影响较大,得分增长比较平缓,后面趋于水平。

Function Score Query

使用function score query,可以修改文档的相关性算分(query score),根据新得到的算分排序。

GET /index_name/_search
{
  "query":{
    "function_score":{
      //原始查询条件,搜索文档并根据相关性打分(query_score)
	    "query":{
	      "查询类型":{
		      "字段名":"搜索内容"
		    }
	    },
	    "function":[
	      {
	        "filter":{"查询类型":{"字段名":"字段值"}},
	        "算分函数":……
	      }
	    ],
	    "boost_mode":"multiply" //加权模式
    }
  }
}

算分函数,算分函数的结果称为function score,将来会与query score运算,得到新的算分,常见的有:

  • weight:给一个常量值,作为函数结果(function score)
  • field_value_factor:用文档中的某个字段值作为函数结果
  • random_score:随机生成一个值,作为函数结果
  • script_score:自定义计算公式,公式结果作为函数结果

加权模式,定义function score与query score的运算方式,包括:

  • multiply:两者相乘。默认就是这个运算方式。
  • replace:用function score替换这个query score
  • 其他:sum、avg、max、min

Boolean Query

布尔查询是一个或者多个查询子句的组合,子查询的组合方式有:

  • must:必须匹配每个子查询,类似于“与”
  • should:选择性匹配子查询,类似于“或”
  • must_not:必须不匹配,不参与算分,类似于“非”
  • filter:必须匹配,不参与算分

语法:

GET /hotel/_search
{
  "query":{
    "bool":{
      "must":[
        {"查询方式":{"字段名":"字段值"}},
        {"查询方式":{"字段名":"字段值"}},
        ……
      ],
      "should":[
        {"查询方式":{"字段名":"字段值"}},
        {"查询方式":{"字段名":"字段值"}},
        ……
      ],
      "must_not":[
        {"查询方式":{"字段名":"字段值"}},
        ……
      ],
      "filter":[
        {"查询方式":{"字段名":"字段值"}},
        ……
      ]
    }
  }
}

搜索结果处理

排序

elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序的字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

GET /index_name/_search
{
  //正常搜索内容部分
  "query":{
    "搜索方式":{
      "字段名":"搜索内容"
    }
  },
  "sort":[
    {
      "字段名":"排序方式(desc/asc)"
    },
    {
      "字段名":"排序方式(desc/asc)"
    },
    ……
  ]
}

sort数组中的一个元素代表根据一个字段排序及其排序方式,排序的优先级是按数组中的顺序,从前到后,依次递减。

如果是地理坐标类型的数据,sort数组里面的元素的写法要有些区别:

"sort":[
    {
      "_geo_distance":{
        "字段名":"经度值,纬度值",  //坐标
        "order":"asc",  //排序方式
        "unit":"km"  //单位
      }
    },
    ……
  ]

分页

elasticsearch默认情况下只返回top10的数据,如果要查询更多数据,就需要修改分页参数了。

elasticsearch中通过修改from、size参数来控制要返回的分页结果:

GET /hotel/_search
{
  "query":{
    "搜索方式":{
      "字段名":"搜索内容"
    }
  },
  "from":60,  //分页开始的位置如果想第n页,值为(n-1)*size
  "size":20
}

深度分页问题

es分页是选取从第一个到指定分页中最后一个的所有数据,再把前面的不要的数据丢掉,在集群的时候,就会面临问题。

es一般是分布式的,所以会面临深度分页问题。比如要搜数据中的前一千条,就是从每个集群中搜索指定的一千条数据,再汇总这些每个一千条,从汇总中取出新的前一千条。

深度分页,es提供了俩种解决方案:

search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。

scroll:将排序数据形成快照,保存在内存中。官方已经不推荐使用。

高亮

高亮就是在搜索结果中把搜索关键字突出显示。

语法:

GET /hotel/_search
{
  "query":{
    "搜索方式":{
      "字段名":"搜索内容"
    }
  },
  "highlight":{
    "fields":{
      "字段名":{
        "pre_tags":"<em>",  //用来标记高亮字段的前置标签
        "post_tags":"</em>" //用来标记高亮字段的后置标签 
      },
      "字段名2":{
        "pre_tags":"<em>",   //用来标记高亮字段的前置标签
        "post_tags":"</em>", //用来标记高亮字段的后置标签 
        "require_field_match":"false"
      }
    }
  }
}

但是默认情况下,es搜索字段必须要和高亮字段一致,如果不是一致的字段,要加上require_field_match配置项

数据聚合

聚合(aggregations)可以实现对文档数据的统计、分析、运算。常见的聚合有三类:

  • 桶(Bucket)聚合:用来对文档做分组。常见的有以下两种聚合类型

    • TermAggregation:按照文档字段值进行分组。
    • DateHistogram:按照日期阶梯进行分组,例如一周为一组,或者一个月为一组。

    DSL语法:

    GET /hotel/_search
    {
      "size":0, //定义size为零,结果中不包含文档,只包含聚合结果
      "aggs":{
        "聚合名":{  //给聚合自定义一个名字 
          "聚合类型":{
            "field":"字段名",
            "size":20 //希望聚合的结果的数量
          }
        }
      }
    }
    

    默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。

    可以加一个order字段,进行修改结果排序方式:

    GET /hotel/_search
    {
      "size":0, //定义size为零,结果中不包含文档,只包含聚合结果
      "aggs":{
        "聚合名":{  //给聚合自定义一个名字 
          "聚合类型(同查询类型)":{
            "field":"字段名",
            "order":{
              "字段名":"asc/desc"  //排序方式
            },
            "size":20 //希望聚合的结果的数量
          }
        }
      }
    }
    

    默认情况下,Bucket聚合是对索引库中的所有的文档做聚合,我们可以限定文档要聚合的文档范围,只要添加query条件即可:

    GET /hotel/_search
    {
      "query":{
        "搜索方式":{
          "字段名":"搜索内容"
        }
      },
      "size":0, //定义size为零,结果中不包含文档,只包含聚合结果
      "aggs":{
        "聚合名":{  //给聚合自定义一个名字 
          "聚合类型(同查询类型)":{
            "field":"字段名",
            "order":{
              "字段名":"asc/desc"  //排序方式
            },
            "size":20 //希望聚合的结果的数量
          }
        }
      }
    }
    
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等。

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求Max、Min、Avg、Sum等。
    GET /hotel/_search
    {
      "size":0, //定义size为零,结果中不包含文档,只包含聚合结果
      "aggs":{
        "聚合名":{  //给聚合自定义一个名字 
          "聚合类型(同查询类型)":{
            "field":"字段名",
            "size":20 //希望聚合的结果的数量
          }
          "aggs":{  //是上一层聚合的子聚合,也就是分组后对每组进行计算
            "聚合名称":{  //子聚合名称
              "stats":{  //metric聚合类型
                "field":"字段名"  //要聚合的字段
              }
            }
          }
        }
      }
    }
    

    排序的时候,就可以使用子聚合中的结果来排序,直接将子聚合的聚合名作为字段名即可,使用stats的时候,就使用 子聚合名.avg 、 子聚合名.min 等等作为字段名即可。

  • 管道(pipeline)聚合:其他聚合的结果为基础再做聚合。

自动补全

自定义分词器

elasticsearch中分词器(analyzer)的组成包含三部分:

  • character filter:在tokenizer之前对文本进行处理。例如删除字符、替换字符。
  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等。

要实现在拼音分词的之前,能先进行ik分词,可以在创建索引的时候,通过settings来配置自定义的analyzer分词器,而且,也要设置一些拼音分词器的参数,让其能达到更好的拼音分词效果:

PUT /test
{
  "settings":{
    "analysis":{
	    "analyzer":{ //用于创建自定义分词
	      "自定义分词器名称":{
	        "tokenizer":"ik_max_word",
	        "filter":"pinyin"
	      }
	    },
	    "filter": { // 自定义tokenizer filter
	      "py": { // 自定义过滤器名称
	        "type": "pinyin", // 过滤器类型,这里是pinyin
		      "keep_full_pinyin": false,
	        "keep_joined_full_pinyin": true,
	        "keep_original": true,
	        "limit_first_letter_length": 16,
	        "remove_duplicated_term": true,
	        "none_chinese_pinyin_tokenize": false
	      }
	    }
    }
  },
  "mappings":{
    "properties":{
      "字段名":{
        "type":"字段类型",
        "analyzer":"my_analyzer",
        "search_analyzer":"ik_smart"
      }
    }
  }
}

因为不同的同音中文对应的拼音会一致,为了防止搜出同音词,应该要在倒排索引的时候使用拼音分词器,而搜索的时候不应该使用拼音分词器。要再添加一个search_analyzer,设置为ik_smart,如此搜索的时候,就会使用search_analyzer配置的分词器而不会走analyzer的分词器。

自动补全查询

completion suggester查询

这个查询会匹配用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中的字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型。
  • 字段的内容一般是用来补全的多个词条形成的数组。

查询语法如下:

GET /test/_search
{
  "suggest":{
    "title_suggest":{
      "text":"搜索内容",
      "completion":{
        "field":"title",  //补全查询的字段
        "skip_duplicates":true,
        "size":10
      }
    }
  }
}

可以在后端的业务里,将要进行自动补全查询的字段,加到一个list里面,再将这个list赋值给这个自动补全查询字段

数据同步

当关于某个实体的数据更新或者增加的时候,当然在es和数据库中的数据都要做相应的数据更新。

  • 方案一:同步调用
    • 即先更新数据库,数据库中更新完数据之后,再调用更新索引中的文档的接口,更新es中的数据。
    • 优点:简单
    • 缺点:耦合度高
  • 方案二:异步调用
    • 更新数据库的操作时候,发送一条消息给mq,mq监听消息之后,调用更新索引的文档的接口,更新es中的数据。
    • 优点:低耦合
    • 缺点:依赖mq的可靠性
  • 方案三:监听binlog
    • 使用一些中间件,比如canal,监听binlog,来监听mysql中的增删改操作,当相应的数据发生改变的时候,触发相应的更新es中的操作。
    • 优点:完全解除服务间的耦合
    • 缺点:增加数据库负担

SpringBoot整合ElasticSearch(Elasticsearch Java API Client操作索引库)

初始化

maven导入依赖:

<!--        elasticsearch的导入-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

配置类配置基本的es连接信息(记得Java这里的import不要导入错了依赖)

yaml:

elasticsearch:
  host: xxx.xxx.xxx.xxx #主机地址
  port: 9200 #es端口
  scheme: http #协议

连接信息配置类:

@ConfigurationProperties(prefix = "elasticsearch")
@Component
@Data
public class EsProperties {
    private String host;
    private Integer port;
    private String scheme;
}

es操作的组件的配置类:

builder中的new的HttpHost对象就是在配置好client的连接信息。

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import jakarta.annotation.Resource;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticSearchConfig {
    @Resource
    private EsProperties esProperties;
  
    @Bean
    public ElasticsearchClient esClient() {
  
        RestClient restClient = RestClient
                .builder(new HttpHost(esProperties.getHost(),esProperties.getPort(),esProperties.getScheme()))
                .build();  
  
        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
        return new ElasticsearchClient(transport);  
    }
}

索引操作

由于我感觉,索引操作好像一般不会放在springboot中进行,一般进行的应该都是文档操作之类的,就简单展示一下索引操作的Java代码:

// 索引名字  
String indexName = "student"; 
  
// 索引是否存在  
BooleanResponse books = esClient.indices().exists(e -> e.index(indexName));  
System.out.println("索引是否存在:" + books.value());  
  
// 创建索引  
esClient.indices().create(c -> c  
        .index(indexName)  
        .mappings(mappings -> mappings  // 映射  
                .properties("name", p -> p  
                        .text(t -> t // text类型,index=false  
                                .index(false)  
                        )  
                )  
                .properties("age", p -> p  
                        .long_(t -> t) // long类型 
                )  
        )  
); 
  
// 删除索引  
esClient.indices().delete(d -> d.index(indexName));

esClient就是上面的配置文件中配置的ioc容器中的ElasticSearchClient类型的组件,直接使用autowired或者resource注解注入即可。

文档操作

新增:

// 新增
CreateResponse createResponse = esClient.create(c -> c  
        .index(indexName) // 索引名字  
        .id(account.getId()) // id  
        .document(account) // 实体类  
);

我使用的是es8的方式,使用ElasticsearchClient进行操作,这种方式中有着较多的lambda的写法。且会有多层调用,可以像我这种写法,能将参数分析清楚。

删除:

DeleteResponse deleteResp = esClient.delete(d -> d.index(indexName).id("1"));

批量新增:

List<Account> accountList = ...
BulkRequest.Builder br = new BulkRequest.Builder();  
for (Account acc : accountList) {  
    br.operations(op -> op  
            .create(c -> c  
                    .index(indexName)  
                    .id(acc.getId())  
                    .document(acc)  
            )  
    );  
}  
BulkResponse bulkResp = esClient.bulk(br.build());

批量新增操作要使用到Bulk。

根据id查找:

      	GetResponse<ArticleVo> getResp = esClient.get(g ->
                g.index("article_index1")
                        .id("17770146669-1732611392477")
                , ArticleVo.class);
        if (getResp.found()) {
            ArticleVo source = getResp.source();  // 这就是得到的实体类
            source.setArticleId(getResp.id());
            System.out.println(source);
        }

高亮、分页、排序查找:

三个知识点我就放在一起了,其实和普通的DSL的写法大致,只要DSL掌握的好,再按这个结果写,写起来会很简单。

    public ResultData<List<ArticleESVo>> searchArticlePage(String message, Integer pageSize, Integer pageNum) throws IOException {
        SearchResponse<ArticleESVo> search = esClient.search(s -> s
                        .index("article_index1")  //指明索引
                        .from((pageNum-1) * pageSize)  //分页开始数
                        .size(pageSize)  //每页的页大小
                        .sort(so ->so  //排序配置
                                .field(f -> f
                                        .field("publicTimeView") //排序字段
                                        .order(SortOrder.Desc)
                                        .field("likes")  //排序字段
                                        .order(SortOrder.Desc)
                                )
                        )
                        .query(q -> q  //查询配置
                                .match(t -> t
                                        .field("all")  //查询字段,这里直接查all
                                        .query(message)      //用户的查询内容
                                )
                        )
                        .highlight(h ->h  //高光配置
                                .preTags("<span color='red'>")  //高光部分的前置标签
                                .postTags("</span>")  //高光部分的后置标签
                                .fields("title",hi ->hi)    //要高光的字段
                                .fields("mainContent",hi2->hi2)   //要高光的字段
                                .requireFieldMatch(false)   //设置为不需要匹配查询字段也行
                        )
                , ArticleESVo.class
        );
        List<ArticleESVo> articleESVoList = new ArrayList<>();
        System.out.println(search + "es给回来的数据数据是这样子的");

        List<Hit<ArticleESVo>> hits = search.hits().hits();  //目标实体的数据都在这里面,但是还是存在一个Hit对象里面,高光和普通数据,都要从这个hit里面获取。
        for (Hit<ArticleESVo> hit : hits) {
            List<String> listTitleHighLight = hit.highlight().get("title");  //获取高光部分的内容
            List<String> listMainContentHighLight = hit.highlight().get("mainContent");  //获取高光部分的内容
            ArticleESVo articleESVo = hit.source();
            if (listTitleHighLight != null){
                articleESVo.setTitle(listTitleHighLight.get(0));  //如果标题有高光,就替换掉
            }
            if (listMainContentHighLight != null){
                articleESVo.setMainContent(listMainContentHighLight.get(0));  //如果文章摘要有高光,就替换掉
            }
            articleESVoList.add(articleESVo);  //将目标数据加到要返回的集合中
        }

        System.out.println(articleESVoList + " 最终结果");
        return ResultData.success(articleESVoList);
    }

高光字段是存在hit参数里面的highlight参数里面的,通过get方法指定高光处理后的字段值,再覆盖掉原来source中获取的实体类的相应字段(source中的实体对象中的字段信息是没有经过高光处理的),如果没有指定的高光信息,就不能赋值过去的,不然会报空指针异常,所以要先进行一个判断。

自动补全:

    public ResultData<List<String>> suggestSearch(String message) throws IOException {
        SearchResponse<ArticleESVo> search = esClient.search(s -> s
                        .index("article_index")
                        .suggest(sug -> sug
                                .suggesters("suggest_article", fs -> fs
                                        .text(message).completion(te -> te
                                                .field("suggestion")
                                                .skipDuplicates(true)
                                                .size(10)
                                        )
                                )
                        )
                , ArticleESVo.class);

        List<String> list = new ArrayList<>();
        System.out.println(search + "推荐结果");

        Suggestion<ArticleESVo> suggest_article = search.suggest().get("suggest_article").get(0);
        List<CompletionSuggestOption<ArticleESVo>> options = suggest_article.completion().options();
        for (CompletionSuggestOption<ArticleESVo> option : options) {
            String text = option.text();
            list.add(text);
        }
        return ResultData.success(list);
    }

可以看出,自动补全的Elasticsearch Java API Client的写法看起来和DSL的写法非常相似,可见,掌握好DSL的语法,对使用Elasticsearch Java API Client非常有帮助。我这里的写法是用一个String类型的集合,存储所有的自动补全词条,再将其返回给前端。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2251867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习模型: BERT(Bidirectional Encoder Representations from Transformers)详解

一、引言 自然语言处理&#xff08;NLP&#xff09;领域在过去几十年取得了显著的进展。从早期基于规则的方法到统计机器学习方法&#xff0c;再到如今基于深度学习的模型&#xff0c;NLP 不断向着更高的准确性和效率迈进。BERT 的出现为 NLP 带来了新的突破&#xff0c;它能够…

亚马逊开发视频人工智能模型,The Information 报道

根据《The Information》周三的报道&#xff0c;电子商务巨头亚马逊&#xff08;AMZN&#xff09;已开发出一种新的生成式人工智能&#xff08;AI&#xff09;&#xff0c;不仅能处理文本&#xff0c;还能处理图片和视频&#xff0c;从而减少对人工智能初创公司Anthropic的依赖…

LLM学习笔记(13)分词器 tokenizer

由于神经网络模型不能直接处理文本&#xff0c;因此我们需要先将文本转换为数字&#xff0c;这个过程被称为编码 (Encoding)&#xff0c;其包含两个步骤&#xff1a; 使用分词器 (tokenizer) 将文本按词、子词、字符切分为 tokens&#xff1b;将所有的 token 映射到对应的 tok…

通过LabVIEW项目判断开发环境是否正版

在接收或分析他人提供的LabVIEW项目时&#xff0c;判断其开发环境是否为正版软件对于保护知识产权和避免使用非法软件至关重要。本文将详细介绍如何通过项目文件、可执行程序及开发环境信息判断LabVIEW是否为正版。 ​ 1. 从项目文件判断 LabVIEW项目的源码&#xff08;VI 文件…

node.js基础学习-url模块-url地址处理(二)

前言 前面我们创建了一个HTTP服务器&#xff0c;如果只是简单的http://localhost:3000/about这种链接我们是可以处理的&#xff0c;但是实际运用中一般链接都会带参数&#xff0c;这样的话如果我们只是简单的判断链接来分配数据&#xff0c;就会报404找不到链接。为了解决这个问…

思科网络设备常用命令整理

思科网络设备的配置命令非常丰富&#xff0c;广泛应用于路由器、交换机和其他网络设备的管理与配置。以下是一些常见的思科设备配置命令&#xff0c;按照功能分类&#xff0c;以帮助你快速查找和使用。 一、基本命令 查看当前配置和状态 show running-config&#xff1a;查看…

2024年信号处理与神经网络应用(SPNNA 2024)

会议官网&#xff1a;www.spnna.org 会议时间&#xff1a;2024年12月13-15日 会议地点&#xff1a;中国武汉

Leecode经典题3-删除排序数组中的重复项

删除排序数组中的重复项 题目描述&#xff1a; 给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 …

无人机数据处理系统:原理与核心系统

一、数据处理系统的运行原理 数据获取&#xff1a;无人机在飞行过程中&#xff0c;通过搭载的传感器&#xff08;如相机、激光雷达等&#xff09;采集到各种类型的数据&#xff0c;例如图像、点云等。这些数据是后续处理和分析的基础。 数据传输&#xff1a;采集到的数据会通…

ElasticSearch学习篇19_《检索技术核心20讲》搜推广系统设计思想

目录 主要是包含搜推广系统的基本模块简单介绍&#xff0c;另有一些流程、设计思想的分析。 搜索引擎 基本模块检索流程 查询分析查询纠错 广告引擎 基于标签倒排索引召回基于向量ANN检索召回打分机制&#xff1a;非精确打分精准深度学习模型打分索引精简&#xff1a;必要的…

【尚筹网】五、管理员维护

【尚筹网】五、管理员维护 任务清单分页管理管理员信息目标思路代码引入 PageHelperAdminMapper 中编写 SQL 语句AdminMapper 接口生成方法AdminServiceAdminHandler页面显示主体在页面上使用 Pagination 实现导航条 关键词查询页面上调整表单在翻页时保持关键词查询条件 单条删…

MySQL 启动失败问题分析与解决方案:`mysqld.service failed to run ‘start-pre‘ task`

目录 前言1. 问题背景2. 错误分析2.1 错误信息详解2.2 可能原因 3. 问题排查与解决方案3.1 检查 MySQL 错误日志3.2 验证 MySQL 配置文件3.3 检查文件和目录权限3.4 手动启动 MySQL 服务3.5 修复 systemd 配置文件3.6 验证依赖环境 4. 进一步优化与自动化处理结语 前言 在日常…

Apache storm UI如何更换默认8080端口

在搭建Apache storm环境的时候&#xff0c;遇到Apache storm UI默认端口是8080&#xff0c;但是这个端口会被其他java程序占用&#xff0c;导致Apache storm UI服务无法启动。报错Exception in thread “main” java.lang.RuntimeException: java.io.IOException: Failed to bi…

FPGA实现串口升级及MultiBoot(十)串口升级SPI FLASH实现

本文目录索引 工程架构example9工程设计Vivado设计Vitis设计example9工程验证1、读取FLASH ID2、擦除整个FLASH3、Blank-Check4、烧写Golden区位流5、读取FLASH内容6、烧写MultiBoot区位流(升级位流)7、MultiBoot区位流(升级位流)启动example10工程设计Vivado设计Vitis设计exam…

图解人工智能:从规则到深度学习的全景解析

&#x1f31f;作者简介&#xff1a;热爱数据分析&#xff0c;学习Python、Stata、SPSS等统计语言的小高同学~&#x1f34a;个人主页&#xff1a;小高要坚强的博客&#x1f353;当前专栏&#xff1a;Python之机器学习&#x1f34e;本文内容&#xff1a;图解人工智能&#xff1a;…

Binder架构

一、架构 如上图&#xff0c;binder 分为用户层和驱动层两部分&#xff0c;用户层有客户端&#xff08;Client&#xff09;、服务端&#xff08;Server&#xff09;、服务管理&#xff08;ServiceManager&#xff09;。 从用户空间的角度&#xff0c;使用步骤如下&#xff08;…

基于springboot中小型制造企业质量管理系统源码和论文

信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古以来的…

Flutter 权限申请

这篇文章是基于permission_handler 10.2.0版本写的 前言 在App开发过程中我们经常要用到各种权限&#xff0c;我是用的是permission_handler包来实现权限控制的。 pub地址&#xff1a;https://pub.dev/packages/permission_handler permission_handler 权限列表 变量 Androi…

MATLAB期末复习笔记(下)

五、数据和函数的可视化 1.MATLAB的可视化对象 图形对象是 MATLAB用来创建可视化数据的组件。每个对象都有一个名为句柄 的唯一标识符。使用该句柄&#xff0c;您可以通过设置对象 属性 来操作现有图形对象的特征 ROOT: &#xff1a;即电脑屏幕 Figure &#xff1a;图窗…

web安全从0到1:burp-suite3

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…