java與es8實戰之四:SpringBoot應用中操作es8(無安全檢查)

2023-08-29 09:00:27

歡迎存取我的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內容如下

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!-- 請改為自己專案的parent座標 -->
    <parent>
        <artifactId>elasticsearch-tutorials</artifactId>
        <groupId>com.bolingcavalry</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <!-- 請改為自己專案的artifactId -->
    <artifactId>basic-crud</artifactId>
    <packaging>jar</packaging>
    <!-- 請改為自己專案的name -->
    <name>basic-crud</name>
    <url>https://github.com/zq2599</url>

    <!--不用spring-boot-starter-parent作為parent時的設定-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>

                <version>${springboot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 不加這個,configuration類中,IDEA總會新增一些提示 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>

            <!-- exclude junit 4 -->
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>

        </dependency>

        <!-- junit 5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- elasticsearch引入依賴  start -->
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <!-- 使用spring boot Maven外掛時需要新增該依賴 -->
        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </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>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>

        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

編碼:組態檔

  • 先準備好組態檔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 < 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<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn = sBuilder -> sBuilder
                .index(iBuilder -> iBuilder
                        // 三個分片
                        .numberOfShards("3")
                        // 一個副本
                        .numberOfReplicas("1")
                );

        // 新的索引有三個欄位,每個欄位都有自己的property,這裡依次建立
        Property keywordProperty = Property.of(pBuilder -> pBuilder.keyword(kBuilder -> kBuilder.ignoreAbove(256)));
        Property textProperty = Property.of(pBuilder -> pBuilder.text(tBuilder -> tBuilder));
        Property integerProperty = Property.of(pBuilder -> pBuilder.integer(iBuilder -> iBuilder));

        // // 構建mapping時,builder用到的lambda
        Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn = mBuilder -> 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) [email protected]:zq2599/blog_demos.git 該專案原始碼的倉庫地址,ssh協定
  • 這個git專案中有多個資料夾,本次實戰的原始碼在elasticsearch-tutorials資料夾下,如下圖紅框
  • elasticsearch-tutorials是個父工程,裡面有多個module,本篇實戰的module是basic-crud,如下圖紅框

歡迎關注部落格園:程式設計師欣宸

學習路上,你不孤單,欣宸原創一路相伴...