上一篇:ElasticSearch 03 -- 基础使用_fengxianaa的博客-CSDN博客
使用 _bulk 命令,是es提供的一种批量增删改的操作API。
bulk对JSON串有着严格的要求:每个JSON串一行
- POST _bulk
- {"delete":{"_index":"hero","_id":"3"}}
- {"create":{"_index":"hero","_id":"4"}}
- {"name":"西施","skill":"最有价值之物,给最珍贵之人"}
- {"update":{"_index":"person","_id":"2"}}
- {"doc":{"skill":"让妲己看看你的心"}}
解释:
结果:id=3的已经删除掉,成功添加 id=4 的数据,成功修改 id=2 的数据

使用 java API:
- private static void bulk() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
- //1. 组装bulk请求对象
- BulkRequest bulkRequest = new BulkRequest();
- //1.1 删除id=4的文档
- DeleteRequest delReq = new DeleteRequest("hero","4");
- bulkRequest.add(delReq);
-
- //1.2 创建id=5的文档
- Map<String,Object> map = new HashMap<>();
- map.put("name","甄姬");
- map.put("skill","小女子尚在阵前,大丈夫却要回城?");
- IndexRequest createDoc = new IndexRequest("hero")//hero 是索引名
- .id("5")//指定id
- .source(map);//参数也可以螫map
- bulkRequest.add(createDoc);
-
- //1.3 更新id=1的文档
- Map<String,Object> map2 = new HashMap<>();
- map2.put("skill","没有心,就不会受伤");
- //更新数据也可以使用UpdateRequest,更新指定字段
- UpdateRequest updateRequest = new UpdateRequest("hero","2");
- updateRequest.doc(map2);
- bulkRequest.add(updateRequest);
-
- BulkResponse responses = client.bulk(bulkRequest, RequestOptions.DEFAULT);
- //输出每个请求的执行结果
- for(BulkItemResponse item : responses.getItems()){
- System.out.println(item.status());
- }
- client.close();
- }
Kibana查询:id=4 的已经删除掉,成功添加 id=5 的数据,成功修改 id=2 的数据

使用场景:批量导入数据库中的数据
- private static void importData() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
- BulkRequest bulkRequest = new BulkRequest();
- for(int i =6;i<100;i++){
- Map<String,Object> map = new HashMap<>();
- map.put("name","嫦娥_" + i);
- map.put("skill","像做梦一样");
- IndexRequest createDoc = new IndexRequest("hero")//hero 是索引名
- .id(i + "")//指定id
- .source(map);//参数也可以螫map
- bulkRequest.add(createDoc);
- }
- BulkResponse responses = client.bulk(bulkRequest, RequestOptions.DEFAULT);
- System.out.println(responses.status());
- client.close();
- }
- # 标准写法
- GET hero/_search
- {
- "query": {
- "match_all": {}
- }
- }
- # 非标准:GET person/_search
但是 ES 默认返回10条数据,当然也可以指定返回的条数
- # from:从哪里开始,size:指定返回的条数,可以用这俩参数做分页查询
- GET hero/_search
- {
- "query": {
- "match_all": {}
- },
- "from": 0,
- "size": 3
- }

- private static void queryAll() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- //1. 创建请求对象
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- //1.1 查询条件构造器
- SearchSourceBuilder builder = new SearchSourceBuilder();
- //1.2 组装查询条件,目前是查询所有
- builder.query(QueryBuilders.matchAllQuery());
- //1.3 设置分页条件
- builder.from(0);
- builder.size(5);
- //1.4 把查询条件放到requst中
- request.source(builder);
-
- //2. 发送查询请求
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- //3. 拿到返回结果
- SearchHits hits = response.getHits();
- //3.1 输出总条数
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- //3.2 取出数据内容
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }
结果:

使用“term”查询,不会对关键字分词,适合查询类型是“keyword”的字段
- GET hero/_search
- {
- "query": {
- "term": {
- "name": {
- "value": "亚瑟"
- }
- }
- }
- }
结果:

java API
- private static void termQuery() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- // term查询,使用 termQuery 方法
- builder.query(QueryBuilders.termQuery("name","亚瑟"));
- builder.from(0);
- builder.size(5);
- request.source(builder);
-
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }
结果:

先更新一条数据
- POST hero/_doc/2
- {
- "name":"妲己",
- "skill":"王者荣耀,没有心,就不会受伤"
- }
会对查询的关键字分词,然后用分词后的结果分别查询,最后取并集
- # match查询
- GET hero/_search
- {
- "query": {
- "match": {
- "skill": "王者不可阻挡"
- }
- }
- }
结果:

因为“王者不可阻挡”的分词结果是:王者、不可、阻挡

查询时,根据:王者、不可、阻挡,分别查询,最后再获取并集,所以结果是2条
但是有时,需要最终结果中同时包含:王者、不可、阻挡,这 3 个词
- # match查询,取交集
- GET hero/_search
- {
- "query": {
- "match": {
- "skill": {
- "query": "王者不可阻挡",
- "operator": "and"
- }
- }
- }
- }
分别查询后,取交集,结果:

Java API
- private static void matchQuery() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- // match 查询,使用 matchQuery 方法
- builder.query(QueryBuilders.matchQuery("skill","王者不可阻挡").operator(Operator.AND));
- builder.from(0);
- builder.size(5);
- request.source(builder);
-
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }
结果:

1. 问题
执行下面命令:
- # match查询
- GET hero/_search
- {
- "query": {
- "match": {
- "skill": {
- "query": "王"
- }
- }
- }
- }
发现一个结果都没有

这是因为:
所以,没有结果
2. wildcard
对查询的关键字分词,还可以使用通配符进行模糊查询
- # wildcard 模糊查询
- GET hero/_search
- {
- "query": {
- "wildcard": {
- "skill": {
- "value": "王?"
- }
- }
- }
- }

java
- private static void wildcard() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- // match 查询,使用 matchQuery 方法
- builder.query(QueryBuilders.wildcardQuery("skill","王?"));
- builder.from(0);
- builder.size(5);
- request.source(builder);
-
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }
结果:

3. 正则
利用正则表达式去查询,只做了解
一个不错的正则网站:regex101: build, test, and debug regex

- # 正则表达式查询
- GET hero/_search
- {
- "query": {
- "regexp": {
- "skill": ".*荣耀"
- }
- }
- }
结果:

4. 前缀查询
前缀查询也是一般用于“keyword”类型的字段
- # 前缀查询
- GET hero/_search
- {
- "query": {
- "prefix": {
- "name": {
- "value": "嫦娥_"
- }
- }
- }
- }

5. java API
- private static void likeQuery() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- // wildcard 查询,使用 wildcardQuery 方法
- // builder.query(QueryBuilders.wildcardQuery("hero","王?"));
- // 正则查询,使用 regexpQuery 方法
- // builder.query(QueryBuilders.regexpQuery("hero",".*荣耀"));
- // 前缀查询,使用 prefixQuery 方法
- builder.query(QueryBuilders.prefixQuery("name","嫦娥_"));
- builder.from(0);
- builder.size(5);
- request.source(builder);
-
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }
范围查询也是很常见的操作,比如:

目前我们的数据不支持范围查询,所以重新导入一些
- private static void importData() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
- BulkRequest bulkRequest = new BulkRequest();
- for(int i =6;i<100;i++){
- Map<String,Object> map = new HashMap<>();
- map.put("name","嫦娥_" + i);
- map.put("skill","像做梦一样");
- map.put("skill_num", i);//增加一个字段
- IndexRequest createDoc = new IndexRequest("hero")//hero 是索引名
- .id(i + "")//指定id
- .source(map);//参数也可以螫map
- bulkRequest.add(createDoc);
- }
- BulkResponse responses = client.bulk(bulkRequest, RequestOptions.DEFAULT);
- System.out.println(responses.status());
- client.close();
- }
- # 范围查询
- GET hero/_search
- {
- "query": {
- "range": {
- "skill_num": {
- "gte": 6,
- "lte": 7
- }
- }
- }
- }
- # 查询 skill_num>=6 && skill_num<=7 的数据
- # 查询 skill_num>6的数据,应该这样写:"gt": 6,

- private static void rangeQuery() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- // 范围查询,使用 rangeQuery 方法
- builder.query(QueryBuilders.rangeQuery("skill_num").gte(6).lte(7));
- builder.from(0);
- builder.size(5);
- request.source(builder);
-
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }

根据范围查询一般都会排个序
- # 范围查询
- GET hero/_search
- {
- "query": {
- "range": {
- "skill_num": {
- "gte": 6,
- "lte": 7
- }
- }
- },
- "sort": [
- {
- "skill_num": {
- "order": "desc"
- }
- }
- ]
- }
- # desc 降序

java API
- private static void rangeQuery() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- // 范围查询,使用 rangeQuery 方法
- builder.query(QueryBuilders.rangeQuery("skill_num").gte(6).lte(7));
- builder.from(0);
- builder.size(5);
- // 排序
- builder.sort("skill_num", SortOrder.DESC);
- request.source(builder);
-
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }

上面我们都是查询单个字段,其实也可以同时查询多个字段
1. queryString
- # 分词后,查询多个字段
- GET hero/_search
- {
- "query": {
- "query_string": {
- "fields": ["skill","name"],
- "query": "亚瑟的荣耀",
- "analyzer": "ik_max_word"
- }
- }
- }

- # 分词后,查询多个字段
- GET hero/_search
- {
- "query": {
- "query_string": {
- "fields": ["skill","name"],
- "query": "亚瑟 AND 荣耀",
- "analyzer": "ik_max_word"
- }
- }
- }
- # "亚瑟 AND 荣耀":结果中要同时包含 亚瑟 和 荣耀 这两个词条
- # "亚瑟 OR 荣耀":结果中要包含 亚瑟 或 荣耀

2. sampleQueryString
它跟 queryString 的区别是:不支持 OR、AND 这样的连接符
- # simple_query_string
- GET hero/_search
- {
- "query": {
- "simple_query_string": {
- "fields": ["skill","name"],
- "query": "亚瑟 AND 荣耀",
- "analyzer": "ik_max_word"
- }
- }
- }
- # "亚瑟 AND 荣耀":会切分成 亚瑟 AND 荣耀 3个词, 然后分别查询,最后取并集
返回两个结果

3. java API
- private static void queryString() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- // queryString 查询,使用 queryStringQuery 方法
- // builder.query(QueryBuilders.queryStringQuery("亚瑟的荣耀")
- // .field("skill").field("name").analyzer("ik_max_word"));
-
- // simpleQueryString 查询,使用 simpleQueryStringQuery 方法
- builder.query(QueryBuilders.simpleQueryStringQuery("亚瑟 AND 荣耀").field("skill").field("name").analyzer("ik_max_word"));
-
- request.source(builder);
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }

连接多个查询条件,比如:

关键字:
默认情况下,ES 会给查询到的结果计算分数,得分高的放到前面

- GET hero/_search
- {
- "query": {
- "bool": {
- "must": [
- {
- "term": {
- "name": {
- "value": "亚瑟"
- }
- }
- },
- {
- "match": {
- "skill":"王者"
- }
- }
- ]
- }
- }
- }

- # 查询 skill 包含 “王者”,name="妲己",的数据
- GET hero/_search
- {
- "query": {
- "bool": {
- "must": [
- {
- "match": {
- "skill": "王者"
- }
- }
- ],
- "filter": [
- {
- "term": {
- "name": "妲己"
- }
- }
- ]
- }
- }
- }

- # 查询 name=亚瑟 或 老亚瑟 的 数据
- GET hero/_search
- {
- "query": {
- "bool": {
- "should": [
- {
- "term": {
- "name": {
- "value": "亚瑟"
- }
- }
- },
- {
- "term": {
- "name":{
- "value": "老亚瑟"
- }
- }
- }
- ]
- }
- }
- }

- private static void boolQuery() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// person:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- // bool 查询,使用 boolQuery 方法
- builder.query(QueryBuilders.boolQuery()
- .must(QueryBuilders.matchQuery("skill","王者"))
- .filter(QueryBuilders.termQuery("name","妲己")));
-
- request.source(builder);
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- String content = hit.getSourceAsString();//以字符串形式获取数据内容
- System.out.println(content);
- }
- client.close();
- }

分为两种:
重新生成数据
- DELETE hero
-
- # 创建索引,并添加映射
- PUT hero
- {
- "mappings": {
- "properties": {
- "id":{
- "type": "integer"
- },
- "name":{
- "type":"keyword"
- },
- "skill":{
- "type":"text"
- },
- "skill_num":{
- "type": "integer"
- },
- "type":{
- "type": "keyword"
- }
- }
- }
- }
-
- POST _bulk
- {"create":{"_index":"hero","_id":"1"}}
- {"id":1,"name":"亚瑟","skill":"王者,以圣剑的名义,冲锋","skill_num":3,"type":"战士"}
- {"create":{"_index":"hero","_id":"2"}}
- {"id":2,"name":"妲己","skill":"王者,没有心,就不会受伤","skill_num":3,"type":"法师"}
- {"create":{"_index":"hero","_id":"3"}}
- {"id":3,"name":"甄姬","skill":"王者,还人间一片净土","skill_num":3,"type":"法师"}
- {"create":{"_index":"hero","_id":"4"}}
- {"id":4,"name":"西施","skill":"王者,最有价值之物,给最珍贵之人","skill_num":3,"type":"法师"}
-
- GET hero/_search
- # 查询 skill 中包含 王者 的数据,并拿到结果中最大的skill_num
- GET hero/_search
- {
- "query": {
- "match": {
- "skill": "王者"
- }
- },
- "aggs": {
- "max_skill_num": {
- "max": {
- "field": "skill_num"
- }
- }
- }
- }
- # "max_skill_num" 是自己定义的字段名,之后会在结果中展示

- # 查询所有数据,并对结果中的 type 进行分组,取分组后的前10条数据
- GET hero/_search
- {
- "query": {
- "match_all": {}
- },
- "aggs": {
- "type_group": {
- "terms": {
- "field": "type",
- "size": 10
- }
- }
- }
- }

- private static void aggQuery() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- builder.query(QueryBuilders.matchAllQuery());
- /**
- * 组装聚合条件
- * name_group:自定义的名字,获取数据使用
- * name:分组的字段名
- */
- AggregationBuilder aggBuilder = AggregationBuilders.terms("type_group").field("type").size(10);
- builder.aggregation(aggBuilder);
-
- request.source(builder);
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 获取聚合结果
- Aggregation groupResult = response.getAggregations().asMap().get("type_group");
- // 获取分组结果,需要把 groupResult 转换为 Term
- List<? extends Terms.Bucket> buckets = ((Terms) groupResult).getBuckets();
- for(Terms.Bucket bucket : buckets){
- System.out.println(bucket.getKey() + "----" + bucket.getDocCount());
- }
- client.close();
- }

让查询的结果高亮显示,比如:

其实本质就是设置了一个样式

- # 对查询结果高亮
- GET hero/_search
- {
- "query": {
- "match": {
- "skill": "王者荣耀"
- }
- },
- "highlight": {
- "fields": {
- "skill": {
- "pre_tags": "<font color='red'>",
- "post_tags": "</font>"
- }
- }
- }
- }

- private static void highLight() throws IOException {
- RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
- new HttpHost("192.168.56.109",9200,"http")));
-
- SearchRequest request = new SearchRequest("hero");// hero:索引名
- SearchSourceBuilder builder = new SearchSourceBuilder();
- builder.query(QueryBuilders.matchQuery("skill","王者荣耀"));
-
- //设置高亮条件
- HighlightBuilder highlightBuilder = new HighlightBuilder();
- highlightBuilder.field("skill").preTags("<font color='red'>").postTags("</font>");
- builder.highlighter(highlightBuilder);
- request.source(builder);
-
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- SearchHits hits = response.getHits();
- System.out.println("一共有:" + hits.getTotalHits().value + " 条数据");
-
- SearchHit[] arr = hits.getHits();
- for(SearchHit hit : arr){
- //获取高亮结果
- Map<String, HighlightField> highlightFields = hit.getHighlightFields();
- //拿到高亮的字段
- HighlightField addressField = highlightFields.get("skill");
- //获取高亮的数据,是一个数组,我们文档比较简单,取数组的第一个就行了
- String address = addressField.getFragments()[0].string();
- Map<String, Object> map = hit.getSourceAsMap();
- //用高亮的数据替换查询到的结果
- map.put("skill", address);
- System.out.println(map);
- }
- client.close();
- }

随着业务的发展,索引的结构可能发生变化。但是 ES 规定,一旦索引创建就只能添加字段,不能修改字段
因为改变字段需要重建:倒排索引,性能太低。
ES提供了另一种方式:重建索引,并把老的索引数据导入到新的索引中

演示:
- # 1. 新建一个 student1 索引,只有一个 birth 字段,date类型
- PUT student1
- {
- "mappings": {
- "properties": {
- "birth":{
- "type": "date"
- }
- }
- }
- }
- # 2. 存储一条数据
- PUT student1/_doc/1
- {
- "birth":"2000-01-01"
- }
这时候业务变更,birth字段需要存储 “2000年1月1日”,这样的字符串,如果还用老的索引结构会报错
- PUT student1/_doc/2
- {
- "birth":"2000年1月1日"
- }

并且 ES 也不支持修改字段类型

这时候只能重建索引
- # 1. 建立一个新的索引:student2, 设置 birth 为 text 类型
- PUT student2
- {
- "mappings": {
- "properties": {
- "birth":{
- "type": "text"
- }
- }
- }
- }
- # 2. 导入老的索引数据
- POST _reindex
- {
- "source": {
- "index": "student1"
- },
- "dest": {
- "index": "student2"
- }
- }
- # 3. 存放一条数据
- PUT student2/_doc/2
- {
- "birth":"2000年1月1日"
- }
-
- # 4. 查询数据
- GET student2/_search
查询 student2 的数据

但是这时,我们代码中用的还是老索引名,再修改代码明显不现实
ES 提供了给索引设置别名,来解决这种问题:
- # 1. 删除老索引
- DELETE student1
-
- # 2. 给新的索引设置别名
- POST student2/_alias/student1
-
- # 3. 通过别名也能查询到数据
- GET student1/_search
