Java与es8实战:用JSON创建请求对象(比builder pattern更加直观简洁)

  • 今天的文章,咱们先来体验用代码创建请求对象的不便之处,再尝试ES官方给我们提供的解决之道:用JSON创建请求对象
  • 接下来,咱们从一个假设的任务开始

任务安排

  • 现在咱们要创建一个索引,此索引记录的是商品信息
  1. 有一个副本(属于setting部分)
  2. 共三个分片(属于setting部分)
  3. 共三个字段:商品名称name(keyword),商品描述description(text),价格price(integer)(属于mapping部分)
  4. name字段值长为256,超出此长度的字段将不会被索引,但是会存储
  • 接下来,咱们在kibana上用JSON创建索引,再写代码创建相同索引,然后对比两种方式的复杂程度

kibana上创建索引

  • 如果在kibana上用json来创建,请求内容如下,索引名是product001
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-json">PUT product001
{
  <span style="color:#ff0000">"settings"</span>: {
    <span style="color:#ff0000">"number_of_shards"</span>: <span style="color:#880000">3</span>,
    <span style="color:#ff0000">"number_of_replicas"</span>: <span style="color:#880000">1</span>
  },
  <span style="color:#ff0000">"mappings"</span>: {
    <span style="color:#ff0000">"properties"</span>: {
      <span style="color:#ff0000">"name"</span>: {
        <span style="color:#ff0000">"type"</span>: <span style="color:#a31515">"keyword"</span>,
        <span style="color:#ff0000">"ignore_above"</span>: <span style="color:#880000">256</span>
      },
      <span style="color:#ff0000">"description"</span>: {
        <span style="color:#ff0000">"type"</span>: <span style="color:#a31515">"text"</span>
      },
      <span style="color:#ff0000">"price"</span>: {
        <span style="color:#ff0000">"type"</span>: <span style="color:#a31515">"integer"</span>
      }
    }
  }
}
</code></span></span>
  • 效果如下,符合预期

image-20220625110440090

  • 通过eshead观察,也是符合预期

image-20220625110708346

  • 可见基于JSON的操作简单明了,接下来看看创建相通索引的代码是什么样子

基于代码创建

  • 关于如何连接ES的代码并非本篇重点,而且前面的文章已有详细说明,就不多赘述了
  • 首先创建一个API,可以接受外部传来的Setting和Mapping设定,然后用这些设定来创建索引
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">    <span style="color:#2b91af">@Autowired</span>
    <span style="color:#0000ff">private</span> ElasticsearchClient elasticsearchClient;

    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">create</span>(String name,
                       Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn,
                       Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn) <span style="color:#0000ff">throws</span> IOException {
        elasticsearchClient
                .indices()
                .create(c -> c
                        .index(name)
                        .settings(settingFn)
                        .mappings(mappingFn)
                );
    }
</code></span></span>
  • 然后就是如何准备Setting和Mapping参数,再调用create方法完成创建,为了让代码顺利执行,我将调用create方法的代码写在单元测试类中,这样后面只需要执行单元测试即可调用create方法
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#2b91af">@SpringBootTest</span>
<span style="color:#0000ff">class</span> <span style="color:#a31515">EsServiceImplTest</span> {

    <span style="color:#2b91af">@Autowired</span>
    EsService esService;

    <span style="color:#2b91af">@Test</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">create</span>() <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">// 索引名</span>
        <span style="color:#a31515">String</span> <span style="color:#008000">indexName</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"product002"</span>;

        <span style="color:#008000">// 构建setting时,builder用到的lambda</span>
        Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn = sBuilder -> sBuilder
                .index(iBuilder -> iBuilder
                        <span style="color:#008000">// 三个分片</span>
                        .numberOfShards(<span style="color:#a31515">"3"</span>)
                        <span style="color:#008000">// 一个副本</span>
                        .numberOfReplicas(<span style="color:#a31515">"1"</span>)
                );

        <span style="color:#008000">// 新的索引有三个字段,每个字段都有自己的property,这里依次创建</span>
        <span style="color:#a31515">Property</span> <span style="color:#008000">keywordProperty</span> <span style="color:#ab5656">=</span> Property.of(pBuilder -> pBuilder.keyword(kBuilder -> kBuilder.ignoreAbove(<span style="color:#880000">256</span>)));
        <span style="color:#a31515">Property</span> <span style="color:#008000">textProperty</span> <span style="color:#ab5656">=</span> Property.of(pBuilder -> pBuilder.text(tBuilder -> tBuilder));
        <span style="color:#a31515">Property</span> <span style="color:#008000">integerProperty</span> <span style="color:#ab5656">=</span> Property.of(pBuilder -> pBuilder.integer(iBuilder -> iBuilder));

        <span style="color:#008000">// // 构建mapping时,builder用到的lambda</span>
        Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn = mBuilder -> mBuilder
                .properties(<span style="color:#a31515">"name"</span>, keywordProperty)
                .properties(<span style="color:#a31515">"description"</span>, textProperty)
                .properties(<span style="color:#a31515">"price"</span>, integerProperty);

        <span style="color:#008000">// 创建索引,并且指定了setting和mapping</span>
        esService.create(indexName, settingFn, mappingFn);

    }
}
</code></span></span>
  • 由于Java API Client中所有对象都统一使用builder pattern的方式创建,这导致代码量略多,例如setting部分,除了setting自身要用Lambda表达式,设置分片和副本的代码也要用Lambda的形式传入,这种嵌套效果在编码中看起来还是有点绕的,阅读起来可能会有点不适应
  • 执行单元测试,如下图,未发生异常

image-20220625114226165

  • 用kibana查看新建的索引

image-20220625114553023

  • 最后,将product001和product002的mapping放在一起对比,可见一模一样

image-20220625114939286

  • 再用eshead对比分片和副本的效果,也是一模一样

image-20220625115042349

小结和感慨

  • 至此,可以得出结论:
  1. Java API Client的对ES的操作,能得到kibana+JSON相同的效果
  2. 然而,用java代码来实现JSON的嵌套对象的内容,代码的复杂程度上升,可读性下降(纯属个人感觉)
  • 另外,在开发期间,我们也常常先用kibana+JSON先做基本的测试和验证,然后再去编码
  • 因此,如果能在代码中直接使用kibana的JSON,以此取代复杂的builder pattern代码去创建各种增删改查的请求对象,那该多好啊
  • ES官方预判了我的预判,在Java API Client中支持使用JSON来构建请求对象

image-20220625153336739

能用JSON的根本原因

  • 动手实践之前,有个问题先思考一下

  • 刚才咱们写了那么多代码,才能创建出CreateIndexResponse对象(注意代码:elasticsearchClient.indices().create),怎么就能用JSON轻易的创建出来呢?有什么直接证据或者关键代码吗?

  • 来看看CreateIndexResponse的builder的源码,集成了父类,也实现了接口,

<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">Builder</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">WithJsonObjectBuilderBase</span><Builder>
			<span style="color:#0000ff">implements</span>
				<span style="color:#a31515">ObjectBuilder</span><CreateIndexRequest> {
</code></span></span>
  • 用IDEA查看类图的功能,Builder的继承和实现关系一目了然,注意红色箭头指向的WithJson接口,它是Builder父类实现的接口,也是让CreateIndexResponse可以通过JSON来创建的关键

image-20220625155614986

  • 强大的IDEA,可以在上图直接展开WithJson接口的所有方法签名,如下图,一目了然,三个方法三种入参,证明了使用者可以用三种方式将JSON内容传给Builder,再由Builer根据传入的内容生成CreateIndexResponse实例

image-20220625160132898

创建工程

<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-xml"><span style="color:#2b91af"><?xml version="1.0" encoding="UTF-8"?></span>

<span style="color:#0000ff"><<span style="color:#0000ff">project</span> <span style="color:#ff0000">xmlns</span>=<span style="color:#a31515">"http://maven.apache.org/POM/4.0.0"</span> <span style="color:#ff0000">xmlns:xsi</span>=<span style="color:#a31515">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span style="color:#ff0000">xsi:schemaLocation</span>=<span style="color:#a31515">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span>
    <span style="color:#008000"><!-- 请改为自己项目的parent坐标 --></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">parent</span>></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>elasticsearch-tutorials<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>com.bolingcavalry<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">version</span>></span>1.0-SNAPSHOT<span style="color:#0000ff"></<span style="color:#0000ff">version</span>></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">relativePath</span>></span>../pom.xml<span style="color:#0000ff"></<span style="color:#0000ff">relativePath</span>></span>
    <span style="color:#0000ff"></<span style="color:#0000ff">parent</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">modelVersion</span>></span>4.0.0<span style="color:#0000ff"></<span style="color:#0000ff">modelVersion</span>></span>
    <span style="color:#008000"><!-- 请改为自己项目的artifactId --></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>object-from-json<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">packaging</span>></span>jar<span style="color:#0000ff"></<span style="color:#0000ff">packaging</span>></span>
    <span style="color:#008000"><!-- 请改为自己项目的name --></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">name</span>></span>object-from-json<span style="color:#0000ff"></<span style="color:#0000ff">name</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">url</span>></span>https://github.com/zq2599<span style="color:#0000ff"></<span style="color:#0000ff">url</span>></span>

    <span style="color:#008000"><!--不用spring-boot-starter-parent作为parent时的配置--></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">dependencyManagement</span>></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">dependencies</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.springframework.boot<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>spring-boot-dependencies<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>

                <span style="color:#0000ff"><<span style="color:#0000ff">version</span>></span>${springboot.version}<span style="color:#0000ff"></<span style="color:#0000ff">version</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">type</span>></span>pom<span style="color:#0000ff"></<span style="color:#0000ff">type</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">scope</span>></span>import<span style="color:#0000ff"></<span style="color:#0000ff">scope</span>></span>
            <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependencies</span>></span>
    <span style="color:#0000ff"></<span style="color:#0000ff">dependencyManagement</span>></span>

    <span style="color:#0000ff"><<span style="color:#0000ff">dependencies</span>></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.springframework.boot<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>spring-boot-starter-actuator<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#008000"><!-- 不加这个,configuration类中,IDEA总会添加一些提示 --></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.springframework.boot<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>spring-boot-configuration-processor<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">optional</span>></span>true<span style="color:#0000ff"></<span style="color:#0000ff">optional</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.projectlombok<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>lombok<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.springframework.boot<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>spring-boot-starter-web<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.springframework.boot<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>spring-boot-starter-test<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">scope</span>></span>test<span style="color:#0000ff"></<span style="color:#0000ff">scope</span>></span>

            <span style="color:#008000"><!-- exclude junit 4 --></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">exclusions</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">exclusion</span>></span>
                    <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>junit<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
                    <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>junit<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
                <span style="color:#0000ff"></<span style="color:#0000ff">exclusion</span>></span>
            <span style="color:#0000ff"></<span style="color:#0000ff">exclusions</span>></span>

        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#008000"><!-- junit 5 --></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.junit.jupiter<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>junit-jupiter-api<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">scope</span>></span>test<span style="color:#0000ff"></<span style="color:#0000ff">scope</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.junit.jupiter<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>junit-jupiter-engine<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">scope</span>></span>test<span style="color:#0000ff"></<span style="color:#0000ff">scope</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#008000"><!-- elasticsearch引入依赖  start --></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>co.elastic.clients<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>elasticsearch-java<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>com.fasterxml.jackson.core<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>jackson-databind<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#008000"><!-- 使用spring boot Maven插件时需要添加该依赖 --></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>jakarta.json<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>jakarta.json-api<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>

        <span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.springframework.boot<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>spring-boot-starter-web<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>
    <span style="color:#0000ff"></<span style="color:#0000ff">dependencies</span>></span>

    <span style="color:#0000ff"><<span style="color:#0000ff">build</span>></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">plugins</span>></span>
            <span style="color:#008000"><!-- 需要此插件,在执行mvn test命令时才会执行单元测试 --></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">plugin</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.apache.maven.plugins<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>maven-surefire-plugin<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">version</span>></span>3.0.0-M4<span style="color:#0000ff"></<span style="color:#0000ff">version</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">configuration</span>></span>
                    <span style="color:#0000ff"><<span style="color:#0000ff">skipTests</span>></span>false<span style="color:#0000ff"></<span style="color:#0000ff">skipTests</span>></span>
                <span style="color:#0000ff"></<span style="color:#0000ff">configuration</span>></span>
            <span style="color:#0000ff"></<span style="color:#0000ff">plugin</span>></span>

            <span style="color:#0000ff"><<span style="color:#0000ff">plugin</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.springframework.boot<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>spring-boot-maven-plugin<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">configuration</span>></span>
                    <span style="color:#0000ff"><<span style="color:#0000ff">excludes</span>></span>
                        <span style="color:#0000ff"><<span style="color:#0000ff">exclude</span>></span>
                            <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.projectlombok<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
                            <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>lombok<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
                        <span style="color:#0000ff"></<span style="color:#0000ff">exclude</span>></span>
                    <span style="color:#0000ff"></<span style="color:#0000ff">excludes</span>></span>
                <span style="color:#0000ff"></<span style="color:#0000ff">configuration</span>></span>
            <span style="color:#0000ff"></<span style="color:#0000ff">plugin</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">plugins</span>></span>

        <span style="color:#0000ff"><<span style="color:#0000ff">resources</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">resource</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">directory</span>></span>src/main/resources<span style="color:#0000ff"></<span style="color:#0000ff">directory</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">includes</span>></span>
                    <span style="color:#0000ff"><<span style="color:#0000ff">include</span>></span>**/*.*<span style="color:#0000ff"></<span style="color:#0000ff">include</span>></span>
                <span style="color:#0000ff"></<span style="color:#0000ff">includes</span>></span>
            <span style="color:#0000ff"></<span style="color:#0000ff">resource</span>></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">resources</span>></span>
    <span style="color:#0000ff"></<span style="color:#0000ff">build</span>></span>
<span style="color:#0000ff"></<span style="color:#0000ff">project</span>></span>
</code></span></span>
  • 是个普通的SpringBoot应用,入口类FromJsonApplication.java如下,非常简单
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">package</span> com.bolingcavalry.fromjson;

<span style="color:#0000ff">import</span> org.springframework.boot.SpringApplication;
<span style="color:#0000ff">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;

<span style="color:#2b91af">@SpringBootApplication</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">FromJsonApplication</span> {
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">main</span>(String[] args) {
        SpringApplication.run(FromJsonApplication.class, args);
    }
}
</code></span></span>
  • 然后是连接ES的配置类ClientConfig.java,关于如何连接ES,在《java与es8实战之四》一文已经详细说明,不再赘述,直接使用配置类的elasticsearchClient方法创建的ElasticsearchClient对象即可操作ES
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#2b91af">@ConfigurationProperties(prefix = "elasticsearch")</span> <span style="color:#008000">//配置的前缀</span>
<span style="color:#2b91af">@Configuration</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ClientConfig</span> {

    <span style="color:#2b91af">@Setter</span>
    <span style="color:#0000ff">private</span> String hosts;

    <span style="color:#008000">/**
     * 解析配置的字符串,转为HttpHost对象数组
     * <span style="color:#808080">@return</span>
     */</span>
    <span style="color:#0000ff">private</span> HttpHost[] toHttpHost() {
        <span style="color:#0000ff">if</span> (!StringUtils.hasLength(hosts)) {
            <span style="color:#0000ff">throw</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">RuntimeException</span>(<span style="color:#a31515">"invalid elasticsearch configuration"</span>);
        }

        String[] hostArray = hosts.split(<span style="color:#a31515">","</span>);
        HttpHost[] httpHosts = <span style="color:#0000ff">new</span> <span style="color:#a31515">HttpHost</span>[hostArray.length];
        HttpHost httpHost;
        <span style="color:#0000ff">for</span> (<span style="color:#a31515">int</span> <span style="color:#008000">i</span> <span style="color:#ab5656">=</span> <span style="color:#880000">0</span>; i < hostArray.length; i++) {
            String[] strings = hostArray[i].split(<span style="color:#a31515">":"</span>);
            httpHost = <span style="color:#0000ff">new</span> <span style="color:#a31515">HttpHost</span>(strings[<span style="color:#880000">0</span>], Integer.parseInt(strings[<span style="color:#880000">1</span>]), <span style="color:#a31515">"http"</span>);
            httpHosts[i] = httpHost;
        }

        <span style="color:#0000ff">return</span> httpHosts;
    }

    <span style="color:#2b91af">@Bean</span>
    <span style="color:#0000ff">public</span> ElasticsearchClient <span style="color:#a31515">elasticsearchClient</span>() {
        HttpHost[] httpHosts = toHttpHost();
        <span style="color:#a31515">RestClient</span> <span style="color:#008000">restClient</span> <span style="color:#ab5656">=</span> RestClient.builder(httpHosts).build();
        <span style="color:#a31515">RestClientTransport</span> <span style="color:#008000">transport</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">RestClientTransport</span>(restClient, <span style="color:#0000ff">new</span> <span style="color:#a31515">JacksonJsonpMapper</span>());
        <span style="color:#008000">// And create the API client</span>
        <span style="color:#0000ff">return</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">ElasticsearchClient</span>(transport);
    }
}
</code></span></span>
  • 最后是配置文件application.yml
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-yaml language-yml"><span style="color:#ff0000">elasticsearch:</span>
  <span style="color:#008000"># 多个IP逗号隔开</span>
  <span style="color:#ff0000">hosts:</span> <span style="color:#880000">127.0</span><span style="color:#880000">.0</span><span style="color:#880000">.1</span><span style="color:#a31515">:9200</span>
</code></span></span>
  • 现在工程已经建好,接下来开始实践如何通过JSON得到请求对象,通过刚才对WithJson接口的分析,JSON转请求对象共有三种方式
  1. ImputStream
  2. JSON字符串
  3. Parse
  • 接下来逐个实践

第一种:InputStream作为入参

  • 最简单的方式莫过通过InputStream转换,InputStream是大家常用到的IO类,相信您已经胸有成竹了,流程如下图

流程图 (12)

  • 开始编码,首先创建一个接口EsService.java,里面有名为create的方法,这是创建索引用的,入参是索引名和包含有JSON内容的InputStream
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">public</span> <span style="color:#0000ff">interface</span> <span style="color:#a31515">EsService</span> {
    <span style="color:#008000">/**
     * 以InputStream为入参创建索引
     * <span style="color:#808080">@param</span> name 索引名称
     * <span style="color:#808080">@param</span> inputStream 包含JSON内容的文件流对象
     */</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">create</span>(String name, InputStream inputStream) <span style="color:#0000ff">throws</span> IOException;
}
</code></span></span>
  • 接下来是重点:EsService接口的实现类EsServiceImpl.java,可见非常简单,只要调用builder的withJson方法,将InputStream作为入参传入即可
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#2b91af">@Service</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">EsServiceImpl</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">EsService</span> {

    <span style="color:#2b91af">@Autowired</span>
    <span style="color:#0000ff">private</span> ElasticsearchClient elasticsearchClient;

    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">create</span>(String name, InputStream inputStream) <span style="color:#0000ff">throws</span> IOException {
        <span style="color:#008000">// 根据InputStrea创建请求对象</span>
        <span style="color:#a31515">CreateIndexRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> CreateIndexRequest.of(builder -> builder
                .index(name)
                .withJson(inputStream));

        elasticsearchClient.indices().create(request);
    }
}
</code></span></span>
  • 为了验证EsServiceImpl的create方法,先准备好json文件,文件名为product003.json,完整路径是:/Users/will/temp/202206/25/product003.json
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-json">{
  <span style="color:#ff0000">"settings"</span>: {
    <span style="color:#ff0000">"number_of_shards"</span>: <span style="color:#880000">3</span>,
    <span style="color:#ff0000">"number_of_replicas"</span>: <span style="color:#880000">1</span>
  },
  <span style="color:#ff0000">"mappings"</span>: {
    <span style="color:#ff0000">"properties"</span>: {
      <span style="color:#ff0000">"name"</span>: {
        <span style="color:#ff0000">"type"</span>: <span style="color:#a31515">"keyword"</span>,
        <span style="color:#ff0000">"ignore_above"</span>: <span style="color:#880000">256</span>
      },
      <span style="color:#ff0000">"description"</span>: {
        <span style="color:#ff0000">"type"</span>: <span style="color:#a31515">"text"</span>
      },
      <span style="color:#ff0000">"price"</span>: {
        <span style="color:#ff0000">"type"</span>: <span style="color:#a31515">"integer"</span>
      }
    }
  }
}
</code></span></span>
  • 最后写一个单元测试类,调用EsServiceImpl的create方法,将product003.json转成InputStream对象作为其入参,验证create方法的功能是否符合预期,如下所示,代码非常简单
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">    <span style="color:#2b91af">@Test</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">createByInputStream</span>() <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">// 文件名</span>
        <span style="color:#a31515">String</span> <span style="color:#008000">filePath</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"/Users/will/temp/202206/25/product003.json"</span>;
        <span style="color:#008000">// 索引名</span>
        <span style="color:#a31515">String</span> <span style="color:#008000">indexName</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"product003"</span>;
        <span style="color:#008000">// 通过InputStream创建索引</span>
        esService.create(indexName, <span style="color:#0000ff">new</span> <span style="color:#a31515">FileInputStream</span>(filePath));
    }
</code></span></span>
  • 运行单元测试代码,一切正常

image-20220625181209377

  • 用kibana查看product003索引,如下所示,符合预期

image-20220625181448280

  • 再用eshead查看副本和分片,和之前的两个索引一致

image-20220625181537006

分析Reader类

  • 接下来尝试WithJson接口的第二个方法
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">default</span> T <span style="color:#a31515">withJson</span>(Reader input) {
        <span style="color:#a31515">JsonpMapper</span> <span style="color:#008000">mapper</span> <span style="color:#ab5656">=</span> SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
        <span style="color:#0000ff">return</span> withJson(mapper.jsonProvider().createParser(input), mapper);
    }
</code></span></span>
  • 先来看看这个Reader的继承关系,本篇不会详细分析Reader代码,咱们重点关注它的两个比较重要的子类:StringReader和FileReader

image-20220625194541347

  • 接下来先用FileReader作为withJson方法的入参,验证用文件来创建请求对象,再用StringReader作为withJson方法的入参,验证用字符串来创建请求对象

第二种:FileReader作为入参

  • 首先,给EsService接口新增一个方法
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">    <span style="color:#008000">/**
     * 以Reader为入参创建索引
     * <span style="color:#808080">@param</span> name 索引名称
     * <span style="color:#808080">@param</span> reader 包含JSON内容的文件流对象
     */</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">create</span>(String name, Reader reader) <span style="color:#0000ff">throws</span> IOException;
</code></span></span>
  • 接下来是重点:EsService接口的实现类EsServiceImpl.java,可见非常简单,只要调用builder的withJson方法,将Reader作为入参传入即可
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">create</span>(String name, Reader reader) <span style="color:#0000ff">throws</span> IOException {
        <span style="color:#008000">// 根据Reader创建请求对象</span>
        <span style="color:#a31515">CreateIndexRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> CreateIndexRequest.of(builder -> builder
                .index(name)
                .withJson(reader));

        elasticsearchClient.indices().create(request);
    }
</code></span></span>
  • json文件继续使用刚才创建的product003.json文件

  • 单元测试代码中也增加一个方法,用于验证刚才写的create方法

<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">    <span style="color:#2b91af">@Test</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">createByReader</span>() <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">// 文件名</span>
        <span style="color:#a31515">String</span> <span style="color:#008000">filePath</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"/Users/will/temp/202206/25/product003.json"</span>;
        <span style="color:#008000">// 索引名</span>
        <span style="color:#a31515">String</span> <span style="color:#008000">indexName</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"product004"</span>;

        <span style="color:#008000">// 通过InputStream创建索引</span>
        esService.create(indexName, <span style="color:#0000ff">new</span> <span style="color:#a31515">FileReader</span>(filePath));
    }
</code></span></span>
  • 接下来是执行单元测试方法,在kibana和eshead上验证product004索引和之前新建的几个索引是否一致,这里就不多占用篇幅了,结论是一模一样
  • 其实吧,用InputStream或者Reader作为参数,内部实现是一回事,来看看FileReader构造方法的源码吧,里面是InputStream
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">FileReader</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">InputStreamReader</span> {

    <span style="color:#0000ff">public</span> <span style="color:#a31515">FileReader</span>(String fileName) <span style="color:#0000ff">throws</span> FileNotFoundException {
        <span style="color:#0000ff">super</span>(<span style="color:#0000ff">new</span> <span style="color:#a31515">FileInputStream</span>(fileName));
    }
</code></span></span>

第三种:字符串作为入参

  • 接下来要验证的是用字符串来创建请求对象,这个比较实用,用字符串创建请求对象,给我们的应用开发提供了很大的自由度,废话少说,开始写代码

  • 首先还是给EsService接口新增一个方法,入参是索引名称和JSON字符串

<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">    <span style="color:#008000">/**
     * 以字符串为入参创建索引
     * <span style="color:#808080">@param</span> name 索引名称
     * <span style="color:#808080">@param</span> jsonContent 包含JSON内容的字符串
     */</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">create</span>(String name, String jsonContent) <span style="color:#0000ff">throws</span> IOException;
</code></span></span>
  • 接下来是重点:EsService接口的实现类EsServiceImpl.java,可见非常简单,用字符串创建StringReader对象,然后只要调用builder的withJson方法,将StringReader对象作为入参传入即可
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">create</span>(String name, String jsonContent) <span style="color:#0000ff">throws</span> IOException {
        <span style="color:#008000">// 根据Reader创建请求对象</span>
        <span style="color:#a31515">CreateIndexRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> CreateIndexRequest.of(builder -> builder
                .index(name)
                .withJson(<span style="color:#0000ff">new</span> <span style="color:#a31515">StringReader</span>(jsonContent)));

        elasticsearchClient.indices().create(request);
    }
</code></span></span>
  • 为了验证上面的create方法,在单元测试类中新增一个方法来验证
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">    <span style="color:#2b91af">@Test</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">createByString</span>() <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">// 文件名</span>
        <span style="color:#a31515">String</span> <span style="color:#008000">jsonContent</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"{\n"</span> +
                <span style="color:#a31515">"  \"settings\": {\n"</span> +
                <span style="color:#a31515">"    \"number_of_shards\": 3,\n"</span> +
                <span style="color:#a31515">"    \"number_of_replicas\": 1\n"</span> +
                <span style="color:#a31515">"  },\n"</span> +
                <span style="color:#a31515">"  \"mappings\": {\n"</span> +
                <span style="color:#a31515">"    \"properties\": {\n"</span> +
                <span style="color:#a31515">"      \"name\": {\n"</span> +
                <span style="color:#a31515">"        \"type\": \"keyword\",\n"</span> +
                <span style="color:#a31515">"        \"ignore_above\": 256\n"</span> +
                <span style="color:#a31515">"      },\n"</span> +
                <span style="color:#a31515">"      \"description\": {\n"</span> +
                <span style="color:#a31515">"        \"type\": \"text\"\n"</span> +
                <span style="color:#a31515">"      },\n"</span> +
                <span style="color:#a31515">"      \"price\": {\n"</span> +
                <span style="color:#a31515">"        \"type\": \"integer\"\n"</span> +
                <span style="color:#a31515">"      }\n"</span> +
                <span style="color:#a31515">"    }\n"</span> +
                <span style="color:#a31515">"  }\n"</span> +
                <span style="color:#a31515">"}\n"</span>;

        <span style="color:#008000">// 索引名</span>
        <span style="color:#a31515">String</span> <span style="color:#008000">indexName</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"product005"</span>;

        <span style="color:#008000">// 通过InputStream创建索引</span>
        esService.create(indexName, jsonContent);
    }
</code></span></span>
  • 接下来是执行单元测试方法,在kibana和eshead上验证product004索引和之前新建的几个索引是否一致,这里就不多占用篇幅了,结论是一模一样

第四种:JsonParser和JsonpMapper作为入参

  • 基于JSON创建ES请求对象的最后一种方法如下,入参是JsonParser和JsonpMapper
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java">T <span style="color:#a31515">withJson</span>(JsonParser parser, JsonpMapper mapper)
</code></span></span>
  • 前面三种方法,咱们都写了代码去验证,不过最后这种就不写代码验证了,原因很简单:没必要,咱们先来看看WithJson接口的源码
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">public</span> <span style="color:#0000ff">interface</span> <span style="color:#a31515">WithJson</span><T> {

    <span style="color:#0000ff">default</span> T <span style="color:#a31515">withJson</span>(InputStream input) {
        <span style="color:#a31515">JsonpMapper</span> <span style="color:#008000">mapper</span> <span style="color:#ab5656">=</span> SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
        <span style="color:#0000ff">return</span> withJson(mapper.jsonProvider().createParser(input), mapper);
    }

    <span style="color:#0000ff">default</span> T <span style="color:#a31515">withJson</span>(Reader input) {
        <span style="color:#a31515">JsonpMapper</span> <span style="color:#008000">mapper</span> <span style="color:#ab5656">=</span> SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
        <span style="color:#0000ff">return</span> withJson(mapper.jsonProvider().createParser(input), mapper);
    }

    T <span style="color:#a31515">withJson</span>(JsonParser parser, JsonpMapper mapper);
}
</code></span></span>
  • 可见,前面使用过的withJson(InputStream input)withJson(Reader input),其实都是在调用withJson(JsonParser parser, JsonpMapper mapper),所以,在实际使用中,掌握withJson(InputStream input)withJson(Reader input)就已经够用了,如果一定要使用withJson(JsonParser parser, JsonpMapper mapper),就参考上面的代码去构造JsonParser即可

代码和JSON内容混用

  • 有时候用代码和JSON混合使用来创建请求对象,既能用JSON省去大量代码工作,又能用代码保持该有的灵活性,如下所示,查询用JSON字符串,聚合参数用builder的API生成
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#a31515">Reader</span> <span style="color:#008000">queryJson</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">StringReader</span>(
    <span style="color:#a31515">"{"</span> +
    <span style="color:#a31515">"  \"query\": {"</span> +
    <span style="color:#a31515">"    \"range\": {"</span> +
    <span style="color:#a31515">"      \"@timestamp\": {"</span> +
    <span style="color:#a31515">"        \"gt\": \"now-1w\""</span> +
    <span style="color:#a31515">"      }"</span> +
    <span style="color:#a31515">"    }"</span> +
    <span style="color:#a31515">"  }"</span> +
    <span style="color:#a31515">"}"</span>);

<span style="color:#a31515">SearchRequest</span> <span style="color:#008000">aggRequest</span> <span style="color:#ab5656">=</span> SearchRequest.of(b -> b
    .withJson(queryJson) 
    .aggregations(<span style="color:#a31515">"max-cpu"</span>, a1 -> a1 
        .dateHistogram(h -> h
            .field(<span style="color:#a31515">"@timestamp"</span>)
            .calendarInterval(CalendarInterval.Hour)
        )
        .aggregations(<span style="color:#a31515">"max"</span>, a2 -> a2
            .max(m -> m.field(<span style="color:#a31515">"host.cpu.usage"</span>))
        )
    )
    .size(<span style="color:#880000">0</span>)
);

Map<String, Aggregate> aggs = client
    .search(aggRequest, Void.class) 
    .aggregations();
</code></span></span>
  • 另外,不光是请求对象,与请求对象有关的实例也能用JSON生成,回顾本文最开始的那段代码中,构造CreateIndexResponse对象时还要创建Property对象,实际上这个Property是可以通过JSON生成的,参考代码如下
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#a31515">String</span> <span style="color:#008000">json</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"{ "</span> +
            <span style="color:#a31515">"        \"type\": \"text\","</span> +
            <span style="color:#a31515">"        \"fields\": {"</span> +
            <span style="color:#a31515">"          \"some_field\": { "</span> +
            <span style="color:#a31515">"            \"type\": \"keyword\","</span> +
            <span style="color:#a31515">"            \"normalizer\": \"lowercase\""</span> +
            <span style="color:#a31515">"          }"</span> +
            <span style="color:#a31515">"        }"</span> +
            <span style="color:#a31515">"      }"</span>;

        <span style="color:#a31515">Property</span> <span style="color:#008000">p</span> <span style="color:#ab5656">=</span> Property.of(b -> b
            .withJson(<span style="color:#0000ff">new</span> <span style="color:#a31515">StringReader</span>(json))
        );
</code></span></span>
  • 至此,基于JSON构造ES请求对象的实战就完成了,今后在kibana上验证通过的JSON请求体,可以直接放在代码中用于使用,这将有效的降低代码量,也提升了整体可读性

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) [email protected]:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本次实战的源码在elasticsearch-tutorials文件夹下,如下图红框

    在这里插入图片描述

  • elasticsearch-tutorials是个父工程,里面有多个module,本篇实战的module是object-from-json,如下图红框

image-20220717205709101

猜你喜欢

转载自blog.csdn.net/wdj_yyds/article/details/132607353