ElasticSearch 8.x 创建父子文档,用Join类型字段以及用has_child、has_parent 检索

ElasticSearch

1、ElasticSearch学习随笔之基础介绍
2、ElasticSearch学习随笔之简单操作
3、ElasticSearch学习随笔之java api 操作
4、ElasticSearch学习随笔之SpringBoot Starter 操作
5、ElasticSearch学习随笔之嵌套操作
6、ElasticSearch学习随笔之分词算法
7、ElasticSearch学习随笔之高级检索
8、ELK技术栈介绍
9、Logstash部署与使用
10、ElasticSearch 7.x 版本使用 BulkProcessor 实现批量添加数据
11、ElasticSearch 8.x 弃用了 High Level REST Client,移除了 Java Transport Client,推荐使用 Elasticsearch Java API
12、ElasticSearch 8.x 使用 snapshot(快照)进行数据迁移
13、ElasticSearch 8.x 版本如何使用 SearchRequestBuilder 检索
14、ElasticSearch 8.x 使用 High Level Client 以 HTTPS 方式链接,SSL 证书、主机名验证器 各是什么,如何忽略
15、ElasticSearch 8.x 创建父子文档,用Join类型字段以及用has_child、has_parent 检索

ElasticSearch,创始人 Shay Banon(谢巴农)
本文主要讲解ElasticSearch 父子文档实战,来满足复杂的业务场景,还是用 Kibana 来操作。



前言

经常在工作中遇到如下情况,就是一对多的情况,数据 A 关联到多个 数据 B,那这种情况在关系型数据库中存储是非常简单且方便的,只需要加入一个外键就可以用 join 来检索了,不用很关心 数据 B 的量有多大,反正对于关系型数据库来说一个 join 不成大问题;
那对于 ES 来说,这种情况只能有两种方式来做了,第一就是字段嵌套,需要定义一个 nested(嵌套)类型的字段,字段中有多个 对象,第二就是本文要讲到的 父子文档嵌套并用 has_child、has_parent 来检索。

一、Kibana 中操作

1.1 Join 类型字段

首先我们在 Kibana 中用如下指令创建一个索引(my-index-000001),并且添加 添加 join 类型的字段,下面案例摘自官网,感兴趣可以点击 “我要去官网学习” 去瞅瞅。
我要去官网学习!

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "my_id": {
        "type": "keyword"
      },
      "my_join_field": { 
        "type": "join",
        "relations": {
          "question": "answer" 
        }
      }
    }
  }
}

my_join_field: join 类型字段名称。
relations: 定义关系,“question”: “answer” 表示 questionanswer 的父文档。

1.2、写入关联数据

  1. 首先写入父文档,也就是我们定义的 mapping 中的 relations 的关系是 question,可以看下面两条示例数据,字段 my_join_field 的值是 question
PUT my-index-000001/_doc/1?refresh
{
  "my_id": "1",
  "text": "This is a question 001",
  "my_join_field": {
    "name": "question" 
  }
}
PUT my-index-000001/_doc/2?refresh
{
  "my_id": "2",
  "text": "This is another question 002",
  "my_join_field": {
    "name": "question"
  }
}
  1. 紧接着写入子文档,也就是定义的 relation 的关系是 answer,可以看下面两条示例数据,字段 my_join_field 的值是 answer
PUT my-index-000001/_doc/3?routing=1&refresh 
{
  "my_id": "3",
  "text": "This is an answer for question 001",
  "my_join_field": {
    "name": "answer", 
    "parent": "1" 
  }
}
PUT my-index-000001/_doc/4?routing=1&refresh
{
  "my_id": "4",
  "text": "This is another answer for question 001",
  "my_join_field": {
    "name": "answer",
    "parent": "1"
  }
}

注意: 在写入子文档时,要注意以下三点:

  • 必须指定 routing,routing 表示路由,路由值是必须的,因为父子文档必须在同一分片上进行索引。
  • my_join_field 字段需要指定是 answer。
  • 需要指定 parent,就是父文档的 id。

1.3、数据检索

下面通过 has_child 和 has_parent 来执行检索,需要注意的是如果你是要在父或者子文档中继续用条件检索,在 里面的 query 中继续添加检索条件,下面示例中都是 match_all

1.3.1 has_child 检索

GET my-index-000001/_search
{
  "query": {
    "has_child": {
      "type": "answer",
      "query": {
        "match_all": {}
      }
    }
  }
}

通过 has_child 就可以检索出来拥有子文档的文档,也就是我们最终得到的是 父文档 的内容,也就是 question

1.3.2 has_parent 检索

GET my-index-000001/_search
{
  "query": {
    "has_parent": {
      "parent_type": "question",
      "query": {
        "match_all": {}
      }
    }
  }
}

通过 has_child 就可以检索出拥有父文档的子文档,那我们最终拿到的是 “子文档” 的内容,也就是 answer

四、High Level REST Client 操作

4.0 初始化 ES 客户端

/**
 * 通过认证连接ES,获取客户端
 */
public static RestHighLevelClient createClient(){
    String hostname = "192.168.*.*";
    int port = 9200;
    String username = "your es username";
    String password = "your es password";
    final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
    RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost(hostname, port))
            .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
    return new RestHighLevelClient(restClientBuilder);
}

4.1 创建索引(join字段)

我们要创建一个带有 join 字段的索引,所以在创建索引的时候需要提供 mapping,下面是 mapping 示例,是一个 json 文件。

src/main/resources/mapping/myIndexMapping.json
{
  "mappings": {
    "properties": {
      "my_id": {
        "type": "keyword"
      },
      "my_join_field": {
        "type": "join",
        "relations": {
          "question": "answer"
        }
      }
    }
  }
}

写好了 mapping ,下面就来创建索引。

/**
 *创建 mapping 索引
 */
private static void createSchemaIndex() {
    String indexName = "my-index-000001";
    // 获取客户端实例
    RestHighLevelClient client = createClient();
    try {
        // 读取 classpath 下的 JSON 文件内容
        String file = ClasspathResourceLoader.class.getResource("/mapping/myIndexMapping.json").getFile();
        BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
        byte[] buffer = new byte[inputStream.available()];
        inputStream.read(buffer);
        String jsonMapping = new String(buffer);
        // 创建索引请求
        CreateIndexRequest request = new CreateIndexRequest(indexName);
        // 设置 schema
        request.source(jsonMapping, XContentType.JSON);
        CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
        if (createIndexResponse.isAcknowledged()) {
            System.out.println("成功创建索引:" + indexName);
        } else {
            System.out.println("创建索引失败");
        }
    } catch (IOException e){
        e.printStackTrace();
    }finally {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.2 添加数据

  1. 添加 question 数据,代码如下:
/**
 * 添加 question 数据
 */
private static void addData2Index(){
    String indexName = "my-index-000001";
    // 获取客户端实例
    RestHighLevelClient client = createClient();
    // 准备question测试数据
    JSONObject question = new JSONObject();
    question.put("my_id", "1");
    question.put("text", "This is a question 001");
    JSONObject join = new JSONObject();
    join.put("name", "question");
    question.put("my_join_field", join);
    try {
        IndexRequest request = new IndexRequest(indexName).id("1").source(question.toJSONString(), XContentType.JSON);
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        // 打印插入结果
        System.out.println(response.getResult().name());
    }catch (IOException e){
        e.printStackTrace();
    }finally {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  1. 添加 answer 数据,代码如下:
/**
 * 添加 answer 数据
 */
private static void addData2Index(){
    String indexName = "my-index-000001";
    // 获取客户端实例
    RestHighLevelClient client = createClient();
    // 准备question测试数据
    JSONObject question = new JSONObject();
    question.put("my_id", "3");
    question.put("text", "This is an answer for question 001");
    JSONObject join = new JSONObject();
    join.put("name", "answer");
    join.put("parent", "1");
    question.put("my_join_field", join);
    try {
        IndexRequest request = new IndexRequest(indexName).id("3");
        request.routing("1");
        request.source(question.toJSONString(), XContentType.JSON);
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        // 打印插入结果
        System.out.println(response.getResult().name());
    }catch (IOException e){
        e.printStackTrace();
    }finally {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.3 检索数据

/**
 * 查询
 */
private static void searchIndex(){
    String indexName = "my-index-000001";
    // 获取客户端实例
    RestHighLevelClient client = createClient();
    try {
        SearchRequest request = new SearchRequest(indexName);
        SearchSourceBuilder builder = new SearchSourceBuilder();
        // has_child 查询
        //HasChildQueryBuilder hasChildQueryBuilder = JoinQueryBuilders.hasChildQuery("answer", QueryBuilders.matchAllQuery(), ScoreMode.None);
        //builder.query(hasChildQueryBuilder);
        
        // has_parent 查询
        HasParentQueryBuilder hasParentQueryBuilder = JoinQueryBuilders.hasParentQuery("question", QueryBuilders.matchAllQuery(), false);
        builder.query(hasParentQueryBuilder);
        builder.from(0);
        builder.size(10);
        request.source(builder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println(hit.getSourceAsString());
        }
    }catch (IOException e){
        e.printStackTrace();
    }finally {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值