前置知识需求:根据某个条件查询es中的数据,并更新es中的部分字段。
更新需求细化:
直接修改/添加一个字段,这里是指直接修改/添加一个一级字段。(我们知道es是json格式,通常情况下,我们会有多层次的字段。本文中,对于多层次的字段,我用一级字段,二级字段来描述。如果还不明白,直接看我的案例吧)这个是最简单的,不会有什么错误。修改一个二级字段,这里有一个问题,我们需要考虑到到一级字段不存在的情况。否则会报一个空指针异常。如果是嵌套类型,该如何处理。删除一个字段。如果字段上已经有值,再天添加一个值,并且不能覆盖之前的。
我在遇到这些问题的时候,先看了es的官网,官网只有简单的例子,并无法完成上边的需求。然后查了网上已有的文章。都挺乱的,所以有了这篇文章,我想以完成需求的方式,来掌握这些知识点。
- 已经会用es了。对es的 updateByQuery 有一定了解。可以使用代码来 *** 作。需要了解script。需要了解painless语法。这里是最重要的。
ps~ 不会也没有关系,通过这篇文章,都可以掌握的。
本文目标- 完成本文开头的需求。熟悉 updateByQuery API。可以写出dsl语句来。可以在kibana上成功执行。可以使用java代码完成上边的需求。熟悉painless语法。其实关键点就是语法。
案例会和上边的需求对齐
案例1 (对应需求1:直接修改/添加一个一级字段)我们用一个人员的索引,当做案例(不方便把生产环境的索引拿出来,从网上借用的案例)
对应的表结构 mapping#可以直接在kibnaa上执行下边的创建索引的语句。 PUT twitter/_mapping { "mappings": { "properties": { "DOB": { "type": "date" }, "address": { "type": "keyword" }, "city": { "type": "keyword" }, "country": { "type": "keyword" } "uid": { "type": "long" }, "user": { "type": "keyword" }, "hobby": { "type": "keyword" }, "message": { "type": "text" }, "location": { "properties": { "lat": { "type": "keyword" }, "lon": { "type": "keyword" } } } } } }推测试数据进去
{ "index" : { "_index" : "twitter", "_id": 1} } {"user":"张三","message":"今儿天气不错啊,出去转转去","uid":2,"city":"北京","province":"北京","country":"中国","address":"中国北京市海淀区","location":{"lat":"39.970718","lon":"116.325747"}, "DOB":"1980-12-01"} { "index" : { "_index" : "twitter", "_id": 2 }} {"user":"老刘","message":"出发,下一站云南!","uid":3,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区台基厂三条3号","location":{"lat":"39.904313","lon":"116.412754"}, "DOB":"1981-12-01"}在kibana上使用 updateByQuery API 修改
假设我们要根据指定条件修改一个字段:把张三的生日改成 1996-12-01
如下命令
POST twitter/_update_by_query { "query": { "term": { "user": "张三" } }, "script": { "source": "ctx._source.DOB = params.DOB", "params": { "DOB": 1996-12-01 } } }
同样添加一个新的一级字段,也是使用上边的命令,比如,我们上边的数据,没有hobby这个字段。我们想要给名字叫张三的人添加一个爱好。
POST twitter/_update_by_query { "query": { "term": { "user": "张三" } }, "script": { "source": "ctx._source.hobby = params.hobby", "params": { "hobby": "和女朋友爬山" } } }案例一 在java中的代码
这里需要你已经会使用java调用es了,其它的知识就不展开了。
参看es官方文档:Update API | Java REST Client [7.15] | Elastic
更新数据的底层方法代码:
public void updateByQuery(QueryBuilder queryBuilder, String index, script script) throws IOException { UpdateByQueryRequest request = new UpdateByQueryRequest(index); request.setConflicts("proceed"); request.setQuery(queryBuilder); request.setMaxDocs(1000); request.setscript(script); getClient(esEntity).updateByQuery(request, RequestOptions.DEFAULT); }
接着,调用这个方法,
需要传入 script 对象(执行脚本)。看上层的调用方法。
public void updateHobby(String user, ESEntity esEntity) throws IOException { final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("user", user)); // 这个实际上就是对应给脚本中传参数的对象。 HashMapparams = new HashMap<>(16); params.put("hobby", "和女朋友爬山"); final script script = new script( scriptType.INLINE, "painless", "ctx._source.hobby = params.hobby", params); updateByQuery(queryBuilder, "索引名称", script); }
第一个需求案例已经写完了,串一下知识点。在java中调用,我们是要new 一个 script对象的。里边写了脚本的内容。想要给脚本传参数进去,实际上就是 一个 Map
这里有一个问题,我们需要考虑到到一级字段不存在的情况。否则会报一个空指针异常。所以我们需要在脚本中多加一层判断。
还用案例1的数据结构,这次我们改一下 location 中的二级字段,这里location相当于是一级字段,lat 和 lon相当于都是二级字段。
如果location这个一级字段一定存在的话,可以这样写脚本,这样是可以执行成功的。但是如果一级字段location不存在的话,就会报空指针异常。
POST twitter/_update_by_query { "query": { "term": { "user": "张三" } }, "script": { "source": "ctx._source.location.lon = params.location.lon", "params": { "location": { "lat": "39.970788", "lon": "119.970718" } } } }
那应该如何才能在没有一级字段的情况下,添加一个二级字段?
实际上很简单,我们参数中的对象直接放进去呗。
POST twitter/_update_by_query { "query": { "term": { "user": "张三" } }, "script": { "source": "ctx._source.location = params.location", "params": { "location": { "lat": "39.970788", "lon": "119.970718" } } } }
如果不能确定一级字段是否存在,也就是可能有也可能没有的时候如何写脚本呢?
POST twitter/_update_by_query { "query": { "term": { "user": "张三" } }, "script": { "source": "if(ctx._source.containsKey('location')){ ctx._source.location.lon = params.location.lon; }else{ ctx._source.location = params.location; }", "params": { "location": { "lat": "39.970788", "lon": "119.970718" } } } }
对应的java代码
public void updateLocation(String user, ESEntity esEntity, String lon, String lat) throws IOException { final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("user", user)); // 这个实际上就是对应给脚本中传参数的对象。 HashMap案例3 (对应需求3:如果是嵌套类型)params = new HashMap<>(16); JSonObject location = new JSonObject(); if(StringUtils.isNotBlank(lon)){ location.put("lon", lon); } if(StringUtils.isNotBlank(lat)){ location.put("lat", lat); } params.put("location", location); inal script script = new script( scriptType.INLINE, "painless", "if(ctx._source.containsKey('location')){ n" + " ctx._source.location.lon = params.location.lon;n" + " }else{n" + " ctx._source.location = params.location;n" + " }", params); updateByQuery(queryBuilder, "索引名称", script); }
POST twitter/_update_by_query { "query": { "term": { "user": "张三" } }, "script": { "source": "if(ctx._source.containsKey('location')){ ctx._source.location.lon = params.location.lon; }else{ ctx._source.location = [params.location]; // 注意这里的区别,区别只在这里。是一个中括号。 }", "params": { "location": { "lat": "39.970788", "lon": "119.970718" } } } }
对应的java代码:
public void updateLocation(String user, ESEntity esEntity, String lon, String lat) throws IOException { final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("user", user)); // 这个实际上就是对应给脚本中传参数的对象。 HashMap案例4 (对应需求4:删除一个字段)params = new HashMap<>(16); JSonObject location = new JSonObject(); if(StringUtils.isNotBlank(lon)){ location.put("lon", lon); } if(StringUtils.isNotBlank(lat)){ location.put("lat", lat); } params.put("location", location); inal script script = new script( scriptType.INLINE, "painless", "if(ctx._source.containsKey('location')){ n" + " ctx._source.location.lon = params.location.lon;n" + " }else{n" + " ctx._source.location = [params.location];n" + " }", params); updateByQuery(queryBuilder, "索引名称", script); }
这要熟悉一下painless语法
参考文章:painless语法入门_忽如一夜听春雨的博客-CSDN博客_painless语法
ctx._source.field: add, contains, remove, indexOf, length (针对文档字段内容做 *** 作)ctx.op: The operation that should be applied to the document: index or delete(针对文档本身做 *** 作,可以直接删除这个文档)ctx._index: Access to document metadata fields_score 只在script_score中有效doc[‘field’], doc[‘field’].value: add, contains, remove, indexOf, length
POST twitter/_update_by_query { "query": { "term": { "user": "张三" } }, "script": { "source": "ctx._source.remove(params.removeField)", "params": { "removeField": "hobby" } } }
java代码此处省略了,如果前边的案例有看懂,这里应该很容易写出来了。
案例5 (对应需求5:如果字段上已经有值,再天添加一个值,并且不能覆盖之前的)POST twitter/_update_by_query { "query": { "term": { "user": "张三" } }, "script": { "source": "ctx._source.hobby.add(params.hobby)", "params": { "hobby": "做运动" } } }
java代码此处省略了,如果前边的案例有看懂,这里应该很容易写出来了。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)