介绍ES的基本操作。如创建索引、mappings、doc的一些基本操作。在doc操作中将分别介绍查询单条数据和多条数据。通过DSL的方式和JAVA api的方式体现。
3.1 索引操作
索引操作是一种很危险的操作。索引就像相当于关系型数据库的表。在操作表索引的需要慎重慎重再慎重。不慎重操作就会导致数据的丢失。
3.1.1 创建索引
ES 操作数据的第一步就是创建索引。可以按照需求对进行主分片和副分片的设置。
DSL操作
PUT /${index_name}
{ "settings":{
…
}
"mappings":{
…
}
}
- 创建索引是通过PUT的形式。
- index_name:就是索引名称
- settings:可以设置主分片和副分片数
- mappings:可以设置JSON字段的映射
假如:需要设置主分片数为2(默认5)、副分片数为15(默认0)索引名称为hotel1的索引则可以DSL语句
PUT hoteld1
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 15
},
"mappings": {
"properties": {
"title":{
"type": "text"
},
"city":{
"type": "text"
},
"price":{
"type": "double"
}
}
}
}
3.1.2 删除索引
删除索引 慎重操作
DSL操作
DELETE /${index_name}
- DELETE 请求方式
- index_name:为索引名称
假如:删除上一步创建的索引hotel1
DELETE hoteld1
// 相应结果
{
"acknowledged" : true
}
3.1.3 关闭索引
有些时候暂时不想要某一索引了,但是过一段时间又想要这个索引。所以是不能直接删除这个索引的。这个时候就需要用到关闭索引了。索引关闭时期是不能够查询文档和修改文档的。
DSL
POST /${index_name}/_close
- 请求方式为POST
- index_name为索引名称
假如:关闭hotel1索引。查询该索引下的数据、修改数据。看返回结果。
- 关闭hotel1
POST /hotel1/_close
// 结果
{
"acknowledged" : true,
"shards_acknowledged" : true,
"indices" : {
"hoteld1" : {
"closed" : true
}
}
}
- 操作索引中的数据
// 新增
POST hoteld1/_doc/002
{
"title":"如家",
"city":"145",
"price":222.00
}
// 删除
DELETE hoteld1/_doc/001
// 都是下方结果
{
"error" : {
"root_cause" : [
{
"type" : "index_closed_exception",
"reason" : "closed",
"index_uuid" : "mbKcYEvyTeqvp_KL9cB4oQ",
"index" : "hoteld1"
}
],
"type" : "index_closed_exception",
"reason" : "closed",
"index_uuid" : "mbKcYEvyTeqvp_KL9cB4oQ",
"index" : "hoteld1"
},
"status" : 400
}
3.1.4 打开索引
打开索引后。该索引下面的数据都可以操作了。
DSL
POST /${index_name}/_open
- 请求方式:POST
- index_name:将要打开的索引名称
3.1.5 索引别名
别名是指给一个或者多个索引定义另外一个名称,使索引别名和索引之间可以建立某种逻辑关系。可以用别名来标识索引别名和索引之间的关联关系。
假如:酒店日志是根据月来简历索引的,当我们想要根据一个索引去查询这些索引的数据时就需要用到了索引别名。
假如现在是4月份。那么就已经创建了三个索引日志了。january_log、february_log、march_log。创建索引别名last_three_month就可以根据别名查询数据了。
关系图
示例
1. 创建 january_log、february_log、march_log三个索引 三个mappings都一样。
PUT january_log、february_log、march_log
{
"mappings":{
"properties":{
"uid":{
"type":"keyword"
},
"hotel_id":{
"type":"keyword"
},
"check_in_date":{
"type":"keyword"
}
}
}
}
2. 分别像三个索引插入一条数据
DSL
POST /march_log/_doc/001
{
"uid":"001",
"hotel_id":"33224",
"check_in_date":"2021-02-23"
}
POST /january_log/_doc/001
{
"uid":"001",
"hotel_id":"33224",
"check_in_date":"2021-02-23"
}
POST /february_log/_doc/001
{
"uid":"001",
"hotel_id":"33224",
"check_in_date":"2021-02-23"
}
3. 创建索引别名
DSL
POST /_aliases
{
"actions": [
{
"add": { //为索引january_log建立别名last_three_month
"index": "january_log",
"alias": "last_three_month"
}
},
{
"add": { //为索引february_log建立别名last_three_month
"index": "february_log",
"alias": "last_three_month"
}
},
{
"add": { //为索引march_log建立别名last_three_month
"index": "march_log",
"alias": "last_three_month"
}
}
]
}
// 相应结果
{
"acknowledged" : true
}
- 请求方式:POST
- _aliases:路径
- actions
- add:标识添加索引和别名做关联
- index:索引名称
- alias:别名名称
- add:标识添加索引和别名做关联
4. 查询索引别名信息
DSL
GET /last_three_month/_search
{
"query": {
"match": {
"uid": "001"
}
}
}
结果:
{
"took" : 28,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "february_log", // 索引名
"_type" : "_doc",
"_id" : "001",
"_score" : 0.2876821,
"_source" : {
"uid" : "001",
"hotel_id" : "33224",
"check_in_date" : "2021-02-23"
}
},
{
"_index" : "january_log", // 索引名
"_type" : "_doc",
"_id" : "001",
"_score" : 0.2876821,
"_source" : {
"uid" : "001",
"hotel_id" : "33224",
"check_in_date" : "2021-02-23"
}
},
{
"_index" : "march_log", // 索引名
"_type" : "_doc",
"_id" : "001",
"_score" : 0.2876821,
"_source" : {
"uid" : "001",
"hotel_id" : "33224",
"check_in_date" : "2021-02-23"
}
}
]
}
}
查询索引别名会把这三个索引下面的符合的数据都查询出来。
注意:
当一个索引别名只包含一个索引的时候,新增数据或者修改数据时可以通过别名来新增。但是当一个别名对应多个索引的时候操作数据的时候就会报错。因为没有指定的情况下别名不知道分发到哪个索引进行数据操作。
验证下上面的想法。
DSL
POST /last_three_month/_doc/002
{
"uid":"002",
"hotel_id":"33225",
"check_in_date":"2021-02-23"
}
// 结果
{
"error" : {
"root_cause" : [
{
"type" : "illegal_argument_exception",
"reason" : "no write index is defined for alias [last_three_month]. The write index may be explicitly disabled using is_write_index=false or the alias points to multiple indices without one being designated as a write index"
}
],
"type" : "illegal_argument_exception",
"reason" : "no write index is defined for alias [last_three_month]. The write index may be explicitly disabled using is_write_index=false or the alias points to multiple indices without one being designated as a write index"
//没有为别名[last_three_month]定义写索引。可以使用is_write_index=false显式禁用写索引,或者别名指向多个索引,而没有一个索引被指定为写索引
},
"status" : 400
}
上方索引关联别名的情况可以根据 is_write_index 参数来解决。
POST _aliases
{
"actions": [
{
"add": {
"index": "january_log",
"alias": "last_three_month",
"is_write_index":true
}
}
]
}
- is_write_index:将索引设置为可以文档可操作索引
在操作上述添加文档操作
POST /last_three_month/_doc/002
{
"uid":"002",
"hotel_id":"33225",
"check_in_date":"2021-02-23"
}
{
"_index" : "january_log",
"_type" : "_doc",
"_id" : "002",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
别名的用途
别名可以根据随着业务的增长需要扩充索引的分片时可以用到。因为主分片再创建索引的时候就不能够更改了。
1. 创建索引别名
2. 项目中的查询索引名称改为索引别名名称
3. 老索引 关联 索引名称
4. 创建新索引
5. 新索引同步老索引数据
6. 新索引关联索引别名,老索引删除索引
7. 项目长时间稳定后就可以把老索引给删除掉了。
示例:
- 创建hoteld_1 && 和写入数据
PUT hoteld_1
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 15
},
"mappings": {
"properties": {
"title":{
"type": "text"
},
"city":{
"type": "text"
},
"price":{
"type": "double"
}
}
}
}
POST /hotel_1/_doc/001
{
"title":"好再来酒店",
"city":"青岛",
"price":578.23
}
- 创建别名并关联
POST /_aliases
{
"actions": [
{
"add": {
"index": "hoteld_1",
"alias": "hoteldAlias"
}
}
]
}
- 查询数据 进行验证
GET /hoteldAlias/_search
{
"query": {
"match": {
"title": "再来"
}
}
}
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "hoteld_1",
"_type" : "_doc",
"_id" : "001",
"_score" : 0.5753642,
"_source" : {
"title" : "好再来酒店",
"city" : "青岛",
"price" : 578.23
}
}
]
}
}
- 创建新索引 并设置分片数 && 同步新索引的数据
PUT hoteld_2
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"title":{
"type": "text"
},
"city":{
"type": "text"
},
"price":{
"type": "double"
}
}
}
}
POST /hotel_2/_doc/001
{
"title":"好再来酒店",
"city":"青岛",
"price":578.23
}
- 新索引绑定索引别名 老索引删除索引别名关联
POST /_aliases
{
"actions": [
{
"add": {
"index": "hoteld_2",
"alias": "hoteldAlias"
}
},
{
"remove": {
"index": "hoteld_1",
"alias": "hoteldAlias"
}
}
]
}
- 查询数据
GET /hoteldAlias/_search
{
"query": {
"match": {
"title": "再来"
}
}
}
结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "hotel_2",
"_type" : "_doc",
"_id" : "001",
"_score" : 0.5753642,
"_source" : {
"title" : "好再来酒店",
"city" : "青岛",
"price" : 578.23
}
}
]
}
}
3.2 映射操作
映射操作就相当于关系型数据库中表字段的设置。作为无模式搜索引擎,Es可以在数据写入时猜测数据字段的类型。但是有可能猜的不对,这时候就需要我们在创建索引的时候来设置每个字段的类型了。
本节首先介绍映射的创建、查看和修改操作,然后详细介绍ES中的基本数据类型和复杂的数据类型,并且会对常用的类型用法进行示范,最后介绍映射的常用参数和动态映射的使用。
3.2.1查看映射
GET /${index_name}/_mapping
// 用例
GET /hoteld/_mapping
{
"hoteld" : {
"mappings" : {
"properties" : {
"city" : {
"type" : "keyword"
},
"price" : {
"type" : "double"
},
"title" : {
"type" : "text"
}
}
}
}
}
- 请求方式:POST
- index_name:索引名称
3.2.2 扩展映射
映射中的类型是不能修改的。但是可以扩展索引中的映射字段。最常见的增加字段和为Object对象类型新增字段。
POST /${index_name}/_mapping
{
"properties": {
"tag": { //索引中新增字段tag,类型为keyword
"type": "keyword"
}
}
}
这样就新增了一个字段
POST /hoteld/_mapping
{
"properties":{
"tag":{
"type":"keyword"
}
}
}
- 请求方式:POST
- index_name:索引名称
3.2.3 基本数据类型
1. keyword 类型
keyword类型是不进行切分的字符串类型。这里的“不进行切分”指的是:在索引时,对keyword类型的数据不进行切分,直接构建倒排索引。在现实场景中,keyword经常用于描述姓名、产品类型、用户ID、URL和状态码等。keyword类型数据一般用于比较字符串是否相等,不对数据进行部分匹配,因此一般查询这种类型的数据时使用term查询。
示例:
创建一个人名索引,姓名设置为keyword
PUT /user
{
"mappings": {
"properties": {
"name":{
"type": "keyword"
}
}
}
}
POST /user/_doc/001
{
"name":"小白兔"
}
查询数据
GET /user/_search
{
"query": {
"term": {
"name": "小白"
}
}
}
{
"took" : 39,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
--------------------------------------------------------------------------------------------
GET /user/_search
{
"query": {
"term": {
"name": "小白兔"
}
}
}
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "user",
"_type" : "_doc",
"_id" : "001",
"_score" : 0.2876821,
"_source" : {
"name" : "小白兔"
}
}
]
}
}
keyword类型:是String类型,不会拆分进行倒排索引。所以在用trem查询的时候是精确匹配。
2. text类型
Text是可切分的字符串类型。可以按照相对应的切词算法对文本进行切分,然后创建倒排索引。对该类型会按照对应的切词算法进行切分。然后对切分后的部分匹配打分。
例如,一个酒店搜索项目,我们希望可以根据酒店名称即title字段进行模糊匹配,因此可以设定title字段为text字段,建立酒店索引的DSL如下:
PUT /hotel
{
"mappings": {
"properties": {
"title":{
"type": "text"
}
}
}
}
写入一条数据
POST /hotel/_doc/001
{
"title":"小白酒店"
}
使用trem查询。观察是否能搜索到刚刚添加的数据
GET /hotel/_search
{
"query": {
"term": {
"title": {
"value": "小白酒店"
}
}
}
}
结果
{
"took" : 460,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
根据结果可以看到trem是精确匹配。Text类型进行了切分所以当所以搜素精确查询时是获取不到数据的。一般情况下,搜索text类型的数据时应使用match搜索
GET /hotel/_search
{
"query": {
"match": {
"title": "酒店"
}
}
}
结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "001",
"_score" : 0.5753642,
"_source" : {
"title" : "小白酒店"
}
}
]
}
}
3.数值类型
数值类型可以分为:long、integer、short、byte、double、float、half_float、scaled_float和unsigned_long。提升存储空间和搜索索引的效率,在实际应用中,在满足需求的情况下应尽可能选择范围小的数据类型
取值范围:https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html
示例:
PUT /hotel
{
"mappings":{
"properties":{
"title":{
"type":"text"
},
"city":{
"type":"keyword"
},
"price":{
"type":"double"
},
"star":{
"type":"byte"
},
"comment_count":{
"type":"integer"
}
}
}
}
// 插入数据
POST /hotel/_doc/001
{
"title":"小白酒店",
"price":"301"
}
// 查询price300到400的酒店
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 300,
"lte": 400
}
}
}
}
// 结果
{
"took" : 281,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "001",
"_score" : 1.0,
"_source" : {
"title" : "小白酒店",
"price" : "301"
}
}
]
}
}
4.Boolean类型
true false
判断酒店房间是否满房
// 新增满房字段 字段类型为Boolean
POST /hotel/_mapping
{
"properties":{
"full_room":{
"type":"boolean"
}
}
}
// 添加数据
POST /hotel/_doc
{
"title":"小周酒店",
"full_room": true
}
// 查询满房的数据 使用trem
GET /hotel/_search
{
"query": {
"term": {
"fool_room":true
}
}
}
// 结果
{
"took" : 12,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "hotel",
"_type" : "_doc",
"_id" : "HL8EVYYBY_9l3N9bI2fM",
"_score" : 0.2876821,
"_source" : {
"title" : "小周酒店",
"fool_room" : true
}
}
]
}
}
5.Date类型
在ES中,日期类型的名称为date。ES中存储的日期是标准的UTC格式。下面定义索引hotel,该索引有一个create_time字段,现在把它定义成date类型。
一般使用如下形式表示日期类型数据:
- 格式化的日期字符串。·
- 毫秒级的长整型,表示从1970年1月1日0点到现在的毫秒数。·
- 秒级别的整型,表示从1970年1月1日0点到现在的秒数。
日期类型的默认格式为strict_date_optional_time||epoch_millis。其中,strict_date_optional_time的含义是严格的时间类型,支持yyyy-MM-dd、yyyyMMdd、yyyyMMddHHmmss、yyyy-MM-ddTHH:mm:ss、yyyy-MM-ddTHH:mm:ss.SSS和yyyy-MM-ddTHH:mm:ss.SSSZ等格式,epoch_millis的含义是从1970年1月1日0点到现在的毫秒数
日期类型默认不支持yyyy-MM-dd HH:mm:ss格式,如果经常使用这种格式,可以在索引的mapping中设置日期字段的format属性为自定义格式。下面的示例将设置create_time字段的格式为yyyy-MM-dd HH:mm:ss:
用例
PUT /hotel
{
"mappings":{
"properties":{
"title":{
"type":"text"
},
"city":{
"type":"keyword"
},
"price":{
"type":"double"
},
"create_time":{
"type":"date"
}
}
}
}
// 使用format字段
"createDate1":{
"type":"date",
"format":"yyyy-MM-dd hh:mm:ss"
}
3.2.4 复杂数据类型
1. 数组类型
ES数组没有定义方式。其方式是开箱即用的,及无需事先声明。在写入数据时使用[]括起来就行了。
POST /hotel/_doc
{
"city":["1","145"]
}
但是如果存储了[]的方式。存储时如果不是按照数组方式则会报错。
2. 对象类型
ES对象没有定义方式。也是开箱即用的。无需声明。插入数据的时候直接插入就可以了。
但是也可以设置映射的方式来使用。
比如: 创建一个pojo对象 里面有name 和 agel两个字段。
POST /hotel/_mappings
{
"properties":{
"pojo1":{
"properties":{
"name":{
"type":"keyword"
},
"age":{
"type":"integer"
}
}
}
}
}
3.地理类型
可以存储地理位置
类型设置为:geo_point
POST /hotel/_mappings
{
"properties":{
"location":{
"type":"geo_point" // 地理类型
}
}
}
3.2.5 动态映射
当字段没有定义的时候,存储数据的时候会根据存入的数据自动设置字段的数据类型。这样称为动态操作。但是可能自动生成的映射不符合我们的使用。所以最好还是自己设置字段类型。提前定义好数据类型并将索引创建语句纳入SVN或Git管理范围是良好的编程习惯,同时还能增强项目代码的连贯性和可读性。
3.2.6 多字段
针对同一个字段,有时需要不同的数据类型,这通常表现在为了不同的目的以不同的方式索引相同的字段。
比如name字段有时候需要分词搜索,有需要进行排序。这是name字段就需要两个类型了text 和 keyword
POST /hotel/_mappings
{
"properties":{
"name":{
"type":"text",
"fields":{
"user_name":{
"type":"keyword"
}
}
}
}
}
POST /hotel/_doc
{
"name":"A B"
}
{
"name":"B B"
}
{
"name":"C B"
}
{
"name":"D B"
}
// 查询分词后为包含B的并且通过name排序
GET /hotel/_search
{
"query": {
"match": {
"name": "A B"
}
},
"sort": [
{
"name.user_name": {
"order": "desc"
}
}
]
}
3.3文档操作
使用ES构建搜索引擎时需要经常对文档进行操作。除了简单的单条文档操作,有时还需要进行批量操作。本节将介绍文档的各种日常操作。此外,在生产环境中,对文档的批量操作一般需要借助编程语言来完成,因此在介绍DSL的同时本节还将演示Java客户端的使
3.3.1 单条插入文档
DSL
POST /${index_name}/_doc/_id
{
...
}
- 请求方式:POST。
- index_name: 索引名称。
- _doc:文档路径。
- _id:自定义的id。不指定随机数。
JAVA API 分两种方式插入
1. spring-boot-starter-data-elasticsearch
@Data
@Document(indexName = "hoteld")
public class Hotel1 {
@Id
private String id;
private String title;
private String city;
private Double price;
}
/**
* @author liruiqing
*/
public interface EsRepository extends CrudRepository<Hotel1,String> {
/**
* 根据 title 查询符合的酒店
* @param title
* @return
*/
List<Hotel1> findByTitleLike(String title);
}
public void singleIndexDoc(Hotel1 hotel1) {
// 单条插入
Hotel1 save = repository.save(hotel1);
// 多条插入
List<Hotel1> hotel1s = new ArrayList<>();
repository.saveAll(hotel1s);
System.out.println("id" + hotel1);
}
2. elasticsearch-rest-high-level-client
@Autowired
private RestHighLevelClient restHighLevelClient;
public void singleIndexDoc(Map<String,Object> hotel, String indexName, String indexId) throws IOException {
IndexRequest indexRequest = new IndexRequest(indexName).id(indexId).source(hotel);
IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
// 获取索引名称
String index = indexResponse.getIndex();
// 获取插入索引的id
String id = indexResponse.getId();
// 获取版本号
long version = indexResponse.getVersion();
System.out.println("index:"+index + "id" + id + "version" + version);
}
@Test
void contextLoads() {
HashMap<String, Object> map = new HashMap<>();
map.put("id","002");
map.put("price",33.0);
map.put("city","145");
map.put("title","不好就不要再来");
try {
esUtils.singleIndexDoc(map,"hoteld","002");
} catch (IOException e) {
e.printStackTrace();
}
}
3.3.2 多条文档插入
DSL
POST /_bulk //批量请求
{"index":{"_index":"${index_name}"}} //指定批量写入的索引
{…} //设定写入的文档内容
{"index":{"_index":"${index_name}"}}
{…} //设定写入的文档内容
测试
POST /_bulk
{"index":{"_index":"hoteld"}}
{"city":145,"price":22.3,"title":"在也不来酒店"}
{"index":{"_index":"hoteld"}}
{"city":145,"price":22.3,"title":"在也不来酒店"}
- 请求方式:POST
- _bulk:批量插入path
- index_name:索引名称
- 后面跟要插入的数据。
- 上面测试是没有指定文档id的。也可以指定文档id。
- {“index”:{“_index”:“KaTeX parse error: Expected 'EOF', got '}' at position 14: {index_name}"}̲,"id":{"_id":"{doc_id}”}}
POST /_bulk
{"index":{"_index":"hoteld"},"id":{"_id":"008"}}
{"city":145,"price":22.3,"title":"在也不来酒店"}
JAVA API 分两种方式
1. spring-boot-starter-data-elasticsearch
public void singleIndexDoc(Hotel1 hotel1) {
// 单条插入
Hotel1 save = repository.save(hotel1);
// 多条插入
List<Hotel1> hotel1s = new ArrayList<>();
repository.saveAll(hotel1s);
System.out.println("id" + hotel1);
}
2. elasticsearch-rest-high-level-client
/**
*
* @param hotels 需要批量插入的数据
* @param indexName 索引名称
* @throws IOException
*/
public void bulkIndexDoc(List<Map<String,Object>> hotels, String indexName){
BulkRequest bulkRequest = new BulkRequest(indexName);
for (Map<String, Object> map : hotels) {
String docId = map.get("id").toString();
IndexRequest indexRequest = new IndexRequest(indexName).id(docId).source(map);
bulkRequest.add(indexRequest);
}
try {
BulkResponse bulkResponse = bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()){
System.out.println("bulk fail,message" + bulkResponse.buildFailureMessage());
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.3.3单条更新
DSL
POST /${index_name}/_update/${doc_id}
{
"doc": {
"city":222
}
}
- 请求方式:POST
- index_name:索引名称
- doc_id:需要更新的文档id
- doc:里面是需要更新的字段。
演示:
POST /hoteld/_update/001
{
"doc": {
"city":222
}
}
结果:
{
"_index" : "hoteld",
"_type" : "_doc",
"_id" : "001",
"_version" : 2,
"_seq_no" : 2,
"_primary_term" : 3,
"found" : true,
"_source" : {
"city" : 222,
"price" : 33.0,
"id" : "001",
"title" : "不好就不要再来"
}
}
由结果可知:city变为了222。version变成了2.
JAVA API分两种形式
1. spring-boot-starter-data-elasticsearch
/**
* 单条更新数据
*/
public void updateSingleIndexDoc(Hotel1 hotel1){
repository.save(hotel1);
}
@Test
void updateSingleIndexDoc(){
Hotel1 hotel1 = new Hotel1();
hotel1.setId("001");
hotel1.setCity("888");
hotel1.setPrice(25.1);
hotel1.setTitle("好再来酒店");
esUtils.updateSingleIndexDoc(hotel1);
}
save接口只要传了文档id 如果该id已经被占用则就更新数据。否则增加数据。
2. elasticsearch-rest-high-level-client
/**
* 单条更新数据
* @param source 需要更新的数据
* @param index 索引
* @param docIdKey 文档id
*/
public void updateSingleIndexDoc(Map<String,Object> source,String index,String docIdKey){
UpdateRequest updateRequest = new UpdateRequest(index,docIdKey);
updateRequest.doc(source);
try {
UpdateResponse update = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
String index1 = update.getIndex();
long version = update.getVersion();
String id = update.getId();
System.out.println("index:"+index1 + " version:" + version + "id : " + id);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
void updateSingleIndexDoc(){
HashMap<String, Object> map = new HashMap<>();
map.put("id","001");
map.put("price",33.0);
map.put("city","999");
map.put("title","不好就不要再来");
esUtils.updateSingleIndexDoc(map,"hoteld","001");
}
通过创建UpdateRequest并设置索引、文档id。把需要更新的数据通过doc()放进去。
调用restHighLevelClient.update方法就可以了。
3.3.4 批量更新
DSL
POST _bulk
{"update":{"_index":"${index_name}","_id":"${doc_id}"}}
{"doc":{"city":"999","title":"你再来","name":"没有的字段"}}
- 请求方式:POST
- _bulk :请求路径
- update:代表是更新语句
- index_name:索引名称
- doc_id:文档id
注意:如果更新了一个从来没有过的字段。那么就会把这个字段存储到改文档中。
JAVA API分两种形式
1. spring-boot-starter-data-elasticsearch
- saveAll接口就可以了。
2.elasticsearch-rest-high-level-client
/**
* 批量更新
* @param sourceList 需要批量更新的数据
* @param index 索引名称
*/
public void updateListDocIndex(List<Map<String,Object>> sourceList,String index){
BulkRequest bulkRequest = new BulkRequest();
for (Map<String, Object> stringObjectMap : sourceList) {
UpdateRequest updateRequest = new UpdateRequest(index, stringObjectMap.get("id").toString());
// 删除id字段 要不然文档数据中就会多一个id字段。
updateRequest.doc(stringObjectMap);
bulkRequest.add(updateRequest);
}
try {
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()){
System.out.println("bulk fail,message" + bulkResponse.buildFailureMessage());
}
} catch (IOException e) {
e.printStackTrace();
}
}
- 创建BulkRequest()对象。然后把组装好的UpdateRequest添加进去。调用bulk方法即可。
3.3.5 根据条件更新
有的时候我们需要根据某些条件更新。就像关系型数据库中的update table set title = ‘’ where name = ‘小白’ 这种方式更新name为小白的数据。在ES中也有类型的场景。
DSL
POST hoteld/_update_by_query
{
"query": {
"term": {
"price": {
"value": 22
}
}
},
"script": {
"source": "ctx._source['city']='22'",
"lang": "painless"
}
}
- 请求方式:POST
- query:是查询条件
- script:source就是需要更新的字段
- 如果不传query则是全量更新
JAVA API分两种形式
1. spring-boot-starter-data-elasticsearch
/**
* 根据索引id更新数据
* @param docId
*/
public void updateSingleIndexDoc(String docId){
Document document = Document.create();
document.putIfAbsent("title","你是不是饿的慌");
UpdateQuery build = UpdateQuery.builder(docId).withDocument(document).build();
UpdateResponse updateResponse = elasticsearchRestTemplate.update(build, IndexCoordinates.of("hoteld"));
System.out.println(updateResponse);
}
- elasticsearchRestTemplate.update():根据docId为条件更新文档信息。相当于只更新一条
- Document:需要更新的字段
/**
* 根据条件批量更新字段
*/
public void updateDoc(){
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(QueryBuilders.matchQuery("title","不"));
UpdateQuery updateQuery = UpdateQuery.builder(nativeSearchQuery).withScriptType(ScriptType.INLINE).withScript("ctx._source['city']='44'").withLang("painless").build();
ByQueryResponse byQueryResponse = elasticsearchRestTemplate.updateByQuery(updateQuery, IndexCoordinates.of("hoteld"));
List<ByQueryResponse.Failure> failures = byQueryResponse.getFailures();
System.out.println(failures);
}
- elasticsearchRestTemplate.updateByQuery():根据条件过滤 批量更新符合的数据。
- 需要用到script函数。
2. elasticsearch-rest-high-level-client
/**
* 根据条件批量更新
* @param index 索引名称
* @param name 根据名称条件更新城市
*/
public void updateByQuery(String index,String name,String city){
UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(index);
updateByQueryRequest
.setQuery(QueryBuilders.matchQuery("name",name))
.setScript(new Script("ctx._source['city']='"+city+"'"));
try {
BulkByScrollResponse bulkByScrollResponse = restHighLevelClient.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
}
3.3.6 删除单条文档
删除单条文档通常都是根据文档id删除的。
DSL
DELETE /${index_name}/_doc/${doc_id}
- 请求方式:DELETE
- index_name:索引名称
- doc_id:文档id
JAVA API分两种形式
1. spring-boot-starter-data-elasticsearch
/**
* 根据文档id删除文档数据
* @param index 索引名称
* @param docId 文档id
*/
public void deleteByDocId(String index,String docId){
String id = elasticsearchRestTemplate.delete(docId, IndexCoordinates.of(index));
System.out.println(id);
}
2. elasticsearch-rest-high-level-client
/**
* 根据文档id删除文档数据
* @param index 索引名称
* @param docId 文档id
*/
public void deleteByDocId(String index,String docId){
DeleteRequest deleteRequest = new DeleteRequest(index,docId);
try {
DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(delete.getId());
} catch (IOException e) {
e.printStackTrace();
}
}
3.3.7 批量删除数据
批量删除不需要提供JSON数据
DSL
PUT /_bulk
{"delete":{"_index":${index_name},"_id":${doc_id}}}
JAVA API分两种形式
1. spring-boot-starter-data-elasticsearch
/**
* 批量根据ids删除文档
* @param docIds 批量删除
*/
public void deleteByDocIds(Set<String> docIds){
repository.deleteAllById(docIds);
}
2. elasticsearch-rest-high-level-client
/**
* 根据id列表批量删除
* @param index 索引名称
* @param docIds 文档id列表
*/
public void deleteByDocIdList(String index,List<String> docIds){
BulkRequest bulkRequest = new BulkRequest(index);
for (String docId : docIds) {
DeleteRequest deleteRequest = new DeleteRequest(index, docId);
bulkRequest.add(deleteRequest);
}
try {
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulk.hasFailures()) {
System.out.println(bulk.getIngestTookInMillis());
}
for (BulkItemResponse item : bulk.getItems()) {
System.out.println(item.getId());
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.3.8 根据条件删除数据
根据条件删除数据,跟更新一样只要,只是不需要写script字段。只需要query字段。
DSL
POST /${index_name}/_delete_by_query
{
"query":{
....
}
}
- 请求方式:POST
- index_name:索引名称
- _delete_by_query:路径名称
- query:需要删除的请求条件
JAVA API分两种形式
1 spring-boot-starter-data-elasticsearch
public void deleteByQuery(String index){
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(QueryBuilders.termQuery("city", "22"));
ByQueryResponse byQueryResponse = elasticsearchRestTemplate.delete(nativeSearchQuery, String.class, IndexCoordinates.of(index));
System.out.println(byQueryResponse);
}
2. elasticsearch-rest-high-level-client
/**
* 根据条件删除符合的的文档数据
* @param index
*/
public void deleteByQueryDoc(String index){
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(index);
deleteByQueryRequest.setQuery(QueryBuilders.termQuery("city","145"));
try {
BulkByScrollResponse bulkByScrollResponse = restHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
}