### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本篇是《java與es8實戰》系列的第四 ...
歡迎訪問我的GitHub
這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos
本篇概覽
- 本篇是《java與es8實戰》系列的第四篇,系列文章寫到現在,連個HelloWorld都沒運行起來,實在說不過去了...
- 因此,本篇總體目標明確:實戰在SpringBoot應用中操作elasticsearch8
- 為了降低難度,本篇部署的elasticsearch8未設置安全檢查,無需證書、賬號、密碼,只要連接到es的IP和埠就能執行操作
- 總體目標可以拆解為兩個子任務
- 在SpringBoot中連接elasticsearch8
- 在SpringBoot中使用elasticsearch8官方的Java API Client
- 接下來直接開始
部署elasticsearch集群(無安全檢查)
- 關於快速部署elasticsearch集群(無安全檢查),可以參考《docker-compose快速部署elasticsearch-8.x集群+kibana》
Java應用連接elasticsearch的核心套路
- 不論是直連,還是帶安全檢查的連接,亦或是與SpringBoot的集成使之更方便易用,都緊緊圍繞著一個不變的核心套路,該套路由兩部分組成,掌握了它們就能在各種條件下成功連接es
- 首先,是builder pattern,連接es有關的代碼,各種對象都是其builder對象的build方法創建的,建議您提前閱讀《java與es8實戰之一》一文,看完後,滿屏的builder代碼可以從醜變成美...
- 其次,就是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://discuss.elastic.co/t/is-elasticsearch-client-is-thread-safe/151745
- 有人在問這個問題,es團隊的成員Simon Willnauer的回答是:java的client肯定是線程安全的,golang的不清楚
- 對我來說,有他這話就夠了,至於golang的嘛,在本文就不討論了
源碼下載
- 本篇實戰的完整源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos)
名稱 | 鏈接 | 備註 |
---|---|---|
項目主頁 | 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,如下圖紅框