java与es8实战之四:SpringBoot应用中操作es8(无安全检查)
阅读原文时间:2023年08月29日阅读:8

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本篇是《java与es8实战》系列的第四篇,系列文章写到现在,连个HelloWorld都没运行起来,实在说不过去了…
  • 因此,本篇总体目标明确:实战在SpringBoot应用中操作elasticsearch8
  • 为了降低难度,本篇部署的elasticsearch8未设置安全检查,无需证书、账号、密码,只要连接到es的IP和端口就能执行操作
  • 总体目标可以拆解为两个子任务
  1. 在SpringBoot中连接elasticsearch8
  2. 在SpringBoot中使用elasticsearch8官方的Java API Client
  • 接下来直接开始

部署elasticsearch集群(无安全检查)

Java应用连接elasticsearch的核心套路

  • 不论是直连,还是带安全检查的连接,亦或是与SpringBoot的集成使之更方便易用,都紧紧围绕着一个不变的核心套路,该套路由两部分组成,掌握了它们就能在各种条件下成功连接es
  1. 首先,是builder pattern,连接es有关的代码,各种对象都是其builder对象的build方法创建的,建议您提前阅读《java与es8实战之一》一文,看完后,满屏的builder代码可以从丑变成美…

  2. 其次,就是java应用能向es发请求的关键:ElasticsearchClient对象,该对象的创建是有套路的,如下图,先创建RestClient,再基于RestClient创建ElasticsearchTransport,最后基于ElasticsearchTransport创建ElasticsearchClient,这是个固定的套路,咱们后面的操作都是基于此的,可能会加一点东西,但不会改变流程和图中的对象

  • 准备完毕,开始写代码

新建子工程

  • 为了便于管理依赖库版本和源码,《java与es8实战》系列的所有代码都以子工程的形式存放在父工程elasticsearch-tutorials中

  • 《java与es8实战之二:实战前的准备工作》一文说明了创建父工程的详细过程

  • 在父工程elasticsearch-tutorials中新建名为basic-crud的子工程,其pom.xml内容如下

    http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    elasticsearch-tutorials com.bolingcavalry 1.0-SNAPSHOT ../pom.xml
    4.0.0

    basic-crud
    jar

    basic-crud
    https://github.com/zq2599

    <!--不用spring-boot-starter-parent作为parent时的配置-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
            &lt;version&gt;${springboot.version}&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
    </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
    &lt;!-- 不加这个,configuration类中,IDEA总会添加一些提示 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-configuration-processor&lt;/artifactId&gt;
        &lt;optional&gt;true&lt;/optional&gt;
    &lt;/dependency&gt;
    
    &lt;dependency&gt;
        &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
        &lt;artifactId&gt;lombok&lt;/artifactId&gt;
    &lt;/dependency&gt;
    
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    
        &lt;!-- exclude junit 4 --&gt;
        &lt;exclusions&gt;
            &lt;exclusion&gt;
                &lt;groupId&gt;junit&lt;/groupId&gt;
                &lt;artifactId&gt;junit&lt;/artifactId&gt;
            &lt;/exclusion&gt;
        &lt;/exclusions&gt;
    
    &lt;/dependency&gt;
    
    &lt;!-- junit 5 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
        &lt;artifactId&gt;junit-jupiter-api&lt;/artifactId&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    
    &lt;dependency&gt;
        &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
        &lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    
    &lt;!-- elasticsearch引入依赖  start --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;co.elastic.clients&lt;/groupId&gt;
        &lt;artifactId&gt;elasticsearch-java&lt;/artifactId&gt;
    &lt;/dependency&gt;
    
    &lt;dependency&gt;
        &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
        &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
    &lt;/dependency&gt;
    
    &lt;!-- 使用spring boot Maven插件时需要添加该依赖 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;jakarta.json&lt;/groupId&gt;
        &lt;artifactId&gt;jakarta.json-api&lt;/artifactId&gt;
    &lt;/dependency&gt;
    
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    </dependencies> <build> <plugins> <!-- 需要此插件,在执行mvn test命令时才会执行单元测试 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M4</version> <configuration> <skipTests>false</skipTests> </configuration> </plugin>
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;excludes&gt;
                    &lt;exclude&gt;
                        &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
                        &lt;artifactId&gt;lombok&lt;/artifactId&gt;
                    &lt;/exclude&gt;
                &lt;/excludes&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
    
    &lt;resources&gt;
        &lt;resource&gt;
            &lt;directory&gt;src/main/resources&lt;/directory&gt;
            &lt;includes&gt;
                &lt;include&gt;**/*.*&lt;/include&gt;
            &lt;/includes&gt;
        &lt;/resource&gt;
    &lt;/resources&gt;
    </build>

编码:配置文件

  • 先准备好配置文件application.yml,内容如下,很简单,只有es的地址信息

    elasticsearch:
    # 多个IP逗号隔开
    hosts: 127.0.0.1:9200

编码:配置类

  • 首先把启动类写好,平平无奇的启动类BasicCrudApplication.java

    @SpringBootApplication
    public class BasicCrudApplication {
    public static void main(String[] args) {
    SpringApplication.run(BasicCrudApplication.class, args);
    }
    }

  • 然后是配置类ClientConfig.java,这是本篇的关键,操作ES所需的ElasticsearchClient实例如何创建,ES的IP地址如何传入,全部写在这里了

    package com.bolingcavalry.basic.config;

    import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
    import co.elastic.clients.elasticsearch.ElasticsearchClient;
    import co.elastic.clients.json.jackson.JacksonJsonpMapper;
    import co.elastic.clients.transport.rest_client.RestClientTransport;
    import lombok.Setter;
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.util.StringUtils;

    @ConfigurationProperties(prefix = "elasticsearch") //配置的前缀
    @Configuration
    public class ClientConfig {

    @Setter
    private String hosts;
    
    /**
     * 解析配置的字符串,转为HttpHost对象数组
     * @return
     */
    private HttpHost[] toHttpHost() {
        if (!StringUtils.hasLength(hosts)) {
            throw new RuntimeException("invalid elasticsearch configuration");
        }
    String[] hostArray = hosts.split(",");
    HttpHost[] httpHosts = new HttpHost[hostArray.length];
    HttpHost httpHost;
    for (int i = 0; i &lt; hostArray.length; i++) {
        String[] strings = hostArray[i].split(":");
        httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "http");
        httpHosts[i] = httpHost;
    }
    
    return httpHosts;
    } @Bean public ElasticsearchClient elasticsearchClient() { HttpHost[] httpHosts = toHttpHost(); RestClient restClient = RestClient.builder(httpHosts).build(); RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); return new ElasticsearchClient(transport); } @Bean public ElasticsearchAsyncClient elasticsearchAsyncClient() { HttpHost[] httpHosts = toHttpHost(); RestClient restClient = RestClient.builder(httpHosts).build(); RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); return new ElasticsearchAsyncClient(transport); }

    }

  • 从上面的代码可以看出,配置类已经向Spring容器注册了ElasticsearchClient实例,后面的业务都可以使用此实例来操作ES

编码:服务类

  • 本篇只是为了演示SpringBoot应用如何连接和操作ES,还不会深入ES操作的细节,因此只对索引做一些基本操作即可

  • 先写一个接口IndexService.java,里面定义了多个索引操作的方法

    package com.bolingcavalry.basic.service;

    import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
    import co.elastic.clients.elasticsearch.indices.IndexSettings;
    import co.elastic.clients.util.ObjectBuilder;

    import java.io.IOException;
    import java.util.function.Function;

    public interface IndexService {

    /**
     * 新建指定名称的索引
     * @param name
     * @throws IOException
     */
    void addIndex(String name) throws IOException;
    
    /**
     * 检查指定名称的索引是否存在
     * @param name
     * @return
     * @throws IOException
     */
    boolean indexExists(String name) throws IOException;
    
    /**
     * 删除指定索引
     * @param name
     * @throws IOException
     */
    void delIndex(String name) throws IOException;
    
    /**
     * 创建索引,指定setting和mapping
     * @param name 索引名称
     * @param settingFn 索引参数
     * @param mappingFn 索引结构
     * @throws IOException
     */
    void create(String name,
                Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn,
                Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn) throws IOException;

    }

  • 然后接口的实现,可见所有操作都是在调用ElasticsearchClient实例的API

    package com.bolingcavalry.basic.service.impl;

    import co.elastic.clients.elasticsearch.ElasticsearchClient;
    import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
    import co.elastic.clients.elasticsearch.indices.IndexSettings;
    import co.elastic.clients.util.ObjectBuilder;
    import com.bolingcavalry.basic.service.IndexService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;

    import java.io.IOException;
    import java.util.function.Function;

    @Service
    public class IndexServiceImpl implements IndexService {

    @Autowired
    private ElasticsearchClient elasticsearchClient;
    
    @Override
    public void addIndex(String name) throws IOException {
        ApplicationContext applicationContext;
        elasticsearchClient.indices().create(c -> c.index(name));
    }
    
    @Override
    public boolean indexExists(String name) throws IOException {
        ApplicationContext a;
        return elasticsearchClient.indices().exists(b -> b.index(name)).value();
    }
    
    @Override
    public void delIndex(String name) throws IOException {
        elasticsearchClient.indices().delete(c -> c.index(name));
    }
    
    @Override
    public void create(String name,
                       Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn,
                       Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn) throws IOException {
       elasticsearchClient
               .indices()
               .create(c -> c
                       .index(name)
                       .settings(settingFn)
                       .mappings(mappingFn)
               );
    }

    }

  • 以上就是本篇的功能代码了,连接ES在其上进行索引相关操作

编码:单元测试

  • 为了验证上述代码是否生效,接下来写一个单元测试类IndexServiceTest.java,可以重点关注createIndex方法,里面演示了Builder pattern构建参数的详细步骤

    package com.bolingcavalry.basic.service;

    import co.elastic.clients.elasticsearch._types.mapping.Property;
    import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
    import co.elastic.clients.elasticsearch.indices.IndexSettings;
    import co.elastic.clients.util.ObjectBuilder;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;

    import java.util.function.Function;

    @SpringBootTest
    class IndexServiceTest {

    @Autowired
    IndexService indexService;
    
    @Test
    void addIndex() throws Exception {
        String indexName = "test_index";
    Assertions.assertFalse(indexService.indexExists(indexName));
    indexService.addIndex(indexName);
    Assertions.assertTrue(indexService.indexExists(indexName));
    indexService.delIndex(indexName);
    Assertions.assertFalse(indexService.indexExists(indexName));
    } @Test void indexExists() throws Exception { indexService.indexExists("a"); } @Test void createIndex() throws Exception { // 索引名 String indexName = "product002";
    // 构建setting时,builder用到的lambda
    Function&lt;IndexSettings.Builder, ObjectBuilder&lt;IndexSettings&gt;&gt; settingFn = sBuilder -&gt; sBuilder
            .index(iBuilder -&gt; iBuilder
                    // 三个分片
                    .numberOfShards("3")
                    // 一个副本
                    .numberOfReplicas("1")
            );
    
    // 新的索引有三个字段,每个字段都有自己的property,这里依次创建
    Property keywordProperty = Property.of(pBuilder -&gt; pBuilder.keyword(kBuilder -&gt; kBuilder.ignoreAbove(256)));
    Property textProperty = Property.of(pBuilder -&gt; pBuilder.text(tBuilder -&gt; tBuilder));
    Property integerProperty = Property.of(pBuilder -&gt; pBuilder.integer(iBuilder -&gt; iBuilder));
    
    // // 构建mapping时,builder用到的lambda
    Function&lt;TypeMapping.Builder, ObjectBuilder&lt;TypeMapping&gt;&gt; mappingFn = mBuilder -&gt; mBuilder
            .properties("name", keywordProperty)
            .properties("description", textProperty)
            .properties("price", integerProperty);
    
    // 创建索引,并且指定了setting和mapping
    indexService.create(indexName, settingFn, mappingFn);
    }

    }

  • 确保不做安全检查的ES集群运行正常,再执行单元测试,如下图,顺利通过,证明所有对ES的操作都符合预期

  • 再用eshead观察product002索引的情况,如下图,三个分片,一个副本,与代码中设置的一致

  • 至此最简单的连接和操作ES实战已经完成,希望本篇能给您一些参考,助您顺利完成基本操作

是不是线程安全的

源码下载

名称

链接

备注

项目主页

https://github.com/zq2599/blog_demos

该项目在GitHub上的主页

git仓库地址(https)

https://github.com/zq2599/blog_demos.git

该项目源码的仓库地址,https协议

git仓库地址(ssh)

git@github.com:zq2599/blog_demos.git

该项目源码的仓库地址,ssh协议

  • 这个git项目中有多个文件夹,本次实战的源码在elasticsearch-tutorials文件夹下,如下图红框

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

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴…