【Elasticsearch 中间件】Elasticsearch 客户端使用案例

文章目录

    • [一、安装 Elasticsearch](#一、安装 Elasticsearch)
      • [1.1 启动 Elasticsearch](#1.1 启动 Elasticsearch)
      • [1.2 启动 Kibana](#1.2 启动 Kibana)
    • 二、客户端代码
      • [2.1 导入依赖](#2.1 导入依赖)
      • [2.2 配置 application.yaml](#2.2 配置 application.yaml)
      • [2.3 定义实体类](#2.3 定义实体类)
      • [2.4 连接 Elasticserach](#2.4 连接 Elasticserach)
      • [2.5 定义 Service 层接口](#2.5 定义 Service 层接口)
      • [2.6 实现 Service 层功能](#2.6 实现 Service 层功能)
    • 三、测试项目
      • [3.1 添加数据](#3.1 添加数据)
      • [3.2 搜索数据](#3.2 搜索数据)
      • [3.3 更新数据](#3.3 更新数据)
      • [3.4 删除数据](#3.4 删除数据)
    • 参考资料

完整案例代码:https://github.com/idealzouhu/java-demos/tree/main/middleware-demos/spring-boot-elasticsearch/elasticsearch-client

一、安装 Elasticsearch

1.1 启动 Elasticsearch

运行以下命令启动 Elasticsearch :

shell 复制代码
$ docker network create elastic

$ docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.26

$ docker run --name es01-test ^
  --net elastic ^
  -p 127.0.0.1:9200:9200 ^
  -p 127.0.0.1:9300:9300 ^
  -e "discovery.type=single-node" ^
  -e "xpack.security.enabled=true" ^
  -e "ELASTIC_PASSWORD=Elastic@123456" ^
  -e "KIBANA_PASSWORD=Kibana@123456" ^
  docker.elastic.co/elasticsearch/elasticsearch:7.17.26

其中,具体参数的含义如下:

  • -e "xpack.security.enabled=true":启用 Elasticsearch 的安全功能。具体细节查看 Set up minimal security for Elasticsearch
  • -e "ELASTIC_PASSWORD=Elastic@123456":为 elastic 用户设置密码。 elastic 用户拥有超级权限。
  • -e "KIBANA_PASSWORD=Kibana@123456":为 kibana_system 用户设置密码。kibana_system 用户是一个为 Kibana 服务账户 创建的专用用户。它有足够的权限来与 Elasticsearch 通信,但它并没有 elastic 用户的超高权限。

1.2 启动 Kibana

Kibana 主要用来可视化和管理Elasticsearch数据。

运行以下命令启动 Kibana

shell 复制代码
$ docker pull docker.elastic.co/kibana/kibana:7.17.26

$ docker run --name kib01-test ^
  --net elastic ^
  -p 127.0.0.1:5601:5601 ^
  -e "ELASTICSEARCH_HOSTS=http://es01-test:9200" ^
  -e "ELASTICSEARCH_USERNAME=elastic" ^
  -e "ELASTICSEARCH_PASSWORD=Elastic@123456" ^
  docker.elastic.co/kibana/kibana:7.17.26

其中,具体参数的含义如下:

  • ELASTICSEARCH_HOSTS=http://es01-test:9200: 指定了 Kibana 应该连接到哪个 Elasticsearch 节点。

  • ELASTICSEARCH_USERNAME=elastic: 指定 Kibana 使用的用户名是 elastic

  • ELASTICSEARCH_PASSWORD=Elastic@123456elastic 用户的密码。

注意,我们也可以直接在 HTTP 请求里面设置用户名和参数,例如 curl -u elastic:your_elastic_password http://localhost:9200

在启动 Kibana 后,访问 Kibana 可视化界面 http://localhost:5601

二、客户端代码

2.1 导入依赖

创建 Spring Boot 3.0.6 项目,导入相关依赖

xml 复制代码
<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>7.17.26</version>
    <exclusions>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.17.26</version>
</dependency>

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

如果遇到相关依赖冲突,可以查看 Installation | Elasticsearch Java API Client 7.17

2.2 配置 application.yaml

yaml 复制代码
spring:
  application:
    name: elasticsearch-client

# 打印 es 的 http 请求,适合在开发调试中使用,正式环境请关闭
logging:
  level:
    tracer: TRACE

2.3 定义实体类

java 复制代码
@Data
@AllArgsConstructor
@RequiredArgsConstructor
public class Account {
    // ES中 ducument 的 _id
    private String id;
    // 解决ES中字段与实体类字段不一致的问题
    @JsonProperty("account_number")
    private Long accountNumber;
    private String address;
    private Integer age;
    private Long balance;
    private String city;
    private String email;
    private String employer;
    private String firstname;
    private String lastname;
    private String gender;
    private String state;
}

2.4 连接 Elasticserach

java 复制代码
@Configuration
public class ElasticsearchConfig {

    /**
     * 创建并返回一个 Elasticsearch 客户端。
     *
     * <p>
     *     使用 RestClientBuilder 构建 RestClient,并使用 BasicCredentialsProvider 设置用户名和密码。
     *     <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/connecting.html">官方客户端连接教程</a>
     *     <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/_basic_authentication.html">官方基本认证教程</a>
     * </p>
     *
     * @return 初始化好的 Elasticsearch 客户端
     */
    @Bean
    public ElasticsearchClient esClient() {
        // 1. 配置认证
        String userName = "elastic";
        String password = "Elastic@123456";
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
                AuthScope.ANY, new UsernamePasswordCredentials(userName, password)
        );

        // 2. 创建 low-level 客户端(使用身份验证)
        RestClient restClient = RestClient.builder(
                        new HttpHost("localhost", 9200))    // 指定 Elasticsearch 服务器的主机名和端口号
                .setHttpClientConfigCallback(httpClientBuilder ->
                        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
                )
                .build();

        // 3. 创建传输对象,使用 Jackson 映射器将 Java 对象转换为 JSON 格式
        ElasticsearchTransport transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper());

        // 4. 创建 API 客户端
        return new ElasticsearchClient(transport);
    }

}

2.5 定义 Service 层接口

java 复制代码
public interface AccountElasticsearchService {

    /**
     * 将 Account 对象索引到 Elasticsearch 中。
     *
     * @param account 需要索引的账户对象
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    void indexAccount(Account account) throws IOException;

    /**
     * 批量将多个 Account 对象索引到 Elasticsearch 中。
     *
     * @param accounts 需要索引的账户对象列表
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    public void indexMultipleAccounts(List<Account> accounts) throws IOException;

    /**
     * 根据账户编号搜索账户信息。
     *
     * @param accountNumber 账户编号
     * @return 匹配的账户信息,以字符串形式返回
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    String searchAccountByAccountNumber(Long accountNumber) throws IOException;

    /**
     * 根据账户 ID 从 Elasticsearch 获取账户信息。
     *
     * @param id 账户 ID
     * @return 对应账户的详细信息
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    String  searchAccountById(String id) throws IOException;

    /**
     * 根据 firstname 和 age 查询账户。
     *
     * @param firstName 姓名
     * @param age 年龄
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    public void searchByNameAndAge(String firstName, int age) throws IOException;

    /**
     * 更新账户信息。
     *
     * @param account 修改后的账户对象
     * @return 返回更新结果信息
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    String updateAccountById(Account account) throws IOException;

    /**
     * 根据账户 ID 从 Elasticsearch 中删除账户信息。
     *
     * @param accountId 要删除的账户 ID
     * @return 返回删除结果信息
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
     String deleteAccountById(String accountId) throws IOException;

}

2.6 实现 Service 层功能

java 复制代码
@Slf4j
@Service
@RequiredArgsConstructor
public class AccountElasticsearchServiceImpl implements AccountElasticsearchService {

    private final ElasticsearchClient esClient;

    /**
     * 将 Account 对象索引到 Elasticsearch 中。
     *
     * @param account 需要索引的账户对象
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    @Override
    public void indexAccount(Account account) throws IOException {
        // 创建索引请求并执行
        IndexResponse createResponse = esClient.index(i -> i
                .index("accounts")  // 指定索引名称
                .id(account.getId())      // 文档 ID, 使用 account.getId() 获取文档的唯一标识
                .document(account)        // 文档内容
        );

        log.info("Document indexed with ID:{}", createResponse.id());
    }

    /**
     * 批量将多个 Account 对象索引到 Elasticsearch 中。
     *
     * @param accounts 需要索引的账户对象列表
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    @Override
    public void indexMultipleAccounts(List<Account> accounts) throws IOException {
        // 创建批量请求构建器
        BulkRequest.Builder bulkRequest = new BulkRequest.Builder();

        // 循环添加每个账户对象的索引请求
        for (Account account : accounts) {
            bulkRequest.operations(op -> op
                    .index(idx -> idx
                            .index("accounts")
                            .id(account.getId())
                            .document(account)
                    )
            );
        }

        // 执行批量请求
        BulkResponse bulkResponse = esClient.bulk(bulkRequest.build());

        // 打印出成功的文档 ID
        if (bulkResponse.errors()) {
            log.error("Bulk had errors");
            for (BulkResponseItem  item : bulkResponse.items()) {
                if (item.error() != null) {
                    log.error(item.error().reason());
                }
            }
        } else {
            log.info("Bulk indexing completed successfully");
        }
    }

   
    /**
     * 根据账户编号搜索账户信息。
     *
     * @param accountNumber 账户编号
     * @return 匹配的账户信息,以字符串形式返回
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    @Override
    public String searchAccountByAccountNumber(Long accountNumber) throws IOException {
        // 使用 Elasticsearch 客户端进行搜索
        SearchResponse<Account> searchResponse = esClient.search(s -> s
                        .index("accounts")  // 指定索引名称
                        .query(q -> q
                                .term(t -> t
                                        .field("account_number")  // 搜索字段
                                        .value(v -> v.longValue(accountNumber))  // 动态传入搜索值
                                )),
                Account.class);

        // 获取总命中数
        TotalHits totalHits = searchResponse.hits().total();
        boolean isExactResult = totalHits.relation() == TotalHitsRelation.Eq;
        if (isExactResult) {
            log.info("Found exactly " + totalHits.value() + " matching account(s).");
        } else {
            log.info("Found more than " + totalHits.value() + " matching account(s).");
        }

        // 构建返回结果
        StringBuilder resultBuilder = new StringBuilder("Search results:\n");
        for (Hit<Account> hit : searchResponse.hits().hits()) {
            Account foundAccount = hit.source();  // 获取匹配到的账户信息
            if (foundAccount != null) {
                resultBuilder.append(String.format(
                        "Account: %s, Name: %s %s\n",
                        foundAccount.getAccountNumber(),
                        foundAccount.getFirstname(),
                        foundAccount.getLastname()));
            }
        }

        // 返回结果
        if (resultBuilder.length() == 0) {
            return "No matching accounts found for account number: " + accountNumber;
        }
        return resultBuilder.toString();
    }

    /**
     * 根据账户 ID 从 Elasticsearch 获取账户信息。
     *
     * @param id 账户 ID, 也是文档的唯一标识
     * @return 对应账户的详细信息
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    @Override
    public String searchAccountById(String id) throws IOException {
        // 创建查询请求
        GetResponse<Account> response = esClient.get(g -> g
                        .index("accounts")
                        .id(id),
                Account.class
        );

        // 判断是否找到对应的账户
        if (response.found()) {
            // 获取账户信息
            Account account = response.source();
            // 将账户信息转换为 JSON 字符串并返回
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(account);
        }else {
            return "Account not found";
        }
    }

    /**
     * 根据 firstname 和 age 查询账户。
     *
     * @param firstName 姓名
     * @param age       年龄
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    @Override
    public void searchByNameAndAge(String firstName, int age) throws IOException {
        // 创建查询条件
        Query firstNameQuery = MatchQuery.of(m -> m.field("firstname").query(firstName))._toQuery();
        Query  ageQuery = MatchQuery.of(m -> m.field("age").query(age))._toQuery();

        // 使用 bool 查询将多个条件组合起来
        BoolQuery boolQuery = BoolQuery.of(
                b -> b.must(firstNameQuery).must(ageQuery)
        );

        // 创建搜索请求
        SearchRequest searchRequest = SearchRequest.of(builder -> builder
                .index("accounts")
                .query(q -> q.bool(boolQuery))
        );

        // 执行查询
        SearchResponse<Account> searchResponse = esClient.search(searchRequest, Account.class);

        // 打印结果
        System.out.println("Search results:");
        for (Hit<Account> hit : searchResponse.hits().hits()) {
            System.out.println("Found account: " + hit.source());
        }
    }

    /**
     * 根据账户 ID 更新账户信息。
     *
     * @param account 要更新的账户信息
     * @return 返回更新结果信息
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    @Override
    public String updateAccountById(Account account) throws IOException {
        // 创建更新请求
        UpdateRequest<Account, Account> updateRequest = UpdateRequest.of(builder -> builder
                .index("accounts")
                .id(account.getId())
                .doc(account)
        );

        // 执行更新操作
        UpdateResponse<Account> updateResponse = esClient.update(updateRequest, Account.class);

        // 根据更新结果构建响应信息
        return "Document updated with result: " + updateResponse.result();
    }

    /**
     * 根据账户 ID 从 Elasticsearch 中删除账户信息。
     *
     * @param accountId 要删除的账户 ID
     * @return 返回删除结果信息
     * @throws IOException 当 Elasticsearch 请求失败时抛出
     */
    @Override
    public String deleteAccountById(String accountId) throws IOException {
        // 创建删除请求
        DeleteRequest deleteRequest =  DeleteRequest.of(builder -> builder
                .index("accounts")  // 指定索引
                .id(accountId)            // 指定文档 ID
        );

        // 执行删除操作
        DeleteResponse deleteResponse = esClient.delete(deleteRequest);

        // 根据删除结果构建响应信息
        return "Document deleted with result: " + deleteResponse.result();
    }

}

三、测试项目

3.1 添加数据

具体测试代码为:

java 复制代码
@SpringBootTest
class AccountElasticsearchServiceImplTest {

    @Autowired
    private AccountElasticsearchService accountElasticsearchService;

    @Test
    void indexAccount() throws IOException {
        // 创建测试数据
        Account account = new Account();
        account.setId("12345");
        account.setAccountNumber(12345L);
        account.setEmail("john.doe@example.com");

        // 调用 indexAccount 方法
        accountElasticsearchService.indexAccount(account);
    }
}

运行结果为:

sh 复制代码
2024-12-07T13:10:42.684+08:00 TRACE 3592 --- [elasticsearch-client] [           main] tracer                                   : curl -iX PUT 'http://localhost:9200/accounts/_doc/12345' -d '{"id":"12345","email":"john.doe@example.com","account_number":12345}'
# HTTP/1.1 201 Created
# Location: /accounts/_doc/12345
# X-elastic-product: Elasticsearch
# content-type: application/json; charset=UTF-8
# content-length: 160
#
# {"_index":"accounts","_type":"_doc","_id":"12345","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":4,"_primary_term":1}
2024-12-07T13:10:42.714+08:00  INFO 3592 --- [elasticsearch-client] [           main] .e.c.s.i.AccountElasticsearchServiceImpl : Document indexed with ID:12345

3.2 搜索数据

具体测试代码为:

java 复制代码
@SpringBootTest
class AccountElasticsearchServiceImplTest {

    @Autowired
    private AccountElasticsearchService accountElasticsearchService;

  @Test
    void searchAccountByAccountNumber() throws IOException {
        String result = accountElasticsearchService.searchAccountByAccountNumber(12345L);
        System.out.println(result);
    }

}

运行结果为:

shell 复制代码
2024-12-07T13:11:14.508+08:00 TRACE 34672 --- [elasticsearch-client] [           main] tracer                                   : curl -iX POST 'http://localhost:9200/accounts/_search?typed_keys=true' -d '{"query":{"term":{"account_number":{"value":12345}}}}'
# HTTP/1.1 200 OK
# X-elastic-product: Elasticsearch
# content-type: application/json; charset=UTF-8
# content-length: 303
#
# {"took":711,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":1.0,"hits":[{"_index":"accounts","_type":"_doc","_id":"12345","_score":1.0,"_source":{"id":"12345","email":"john.doe@example.com","account_number":12345}}]}}
2024-12-07T13:11:14.718+08:00  INFO 34672 --- [elasticsearch-client] [           main] .e.c.s.i.AccountElasticsearchServiceImpl : Found exactly 1 matching account(s).
Search results:
Account: 12345, Name: null null

3.3 更新数据

具体测试代码为:

java 复制代码
@SpringBootTest
class AccountElasticsearchServiceImplTest {

    @Autowired
    private AccountElasticsearchService accountElasticsearchService;

  @Test
    void searchAccountByAccountNumber() throws IOException {
        String result = accountElasticsearchService.searchAccountByAccountNumber(12345L);
        System.out.println(result);
    }

}

运行结果为:

shell 复制代码
2024-12-07T13:12:56.867+08:00 TRACE 11832 --- [elasticsearch-client] [           main] tracer                                   : curl -iX POST 'http://localhost:9200/accounts/_update/12345' -d '{"doc":{"id":"12345","email":"john.doe@example.com","firstname":"John","lastname":"Doe","account_number":12345}}'
# HTTP/1.1 200 OK
# X-elastic-product: Elasticsearch
# content-type: application/json; charset=UTF-8
# content-length: 160
#
# {"_index":"accounts","_type":"_doc","_id":"12345","_version":2,"result":"updated","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":5,"_primary_term":1}
Document updated with result: Updated

3.4 删除数据

具体测试代码为:

java 复制代码
@SpringBootTest
class AccountElasticsearchServiceImplTest {

    @Autowired
    private AccountElasticsearchService accountElasticsearchService;

    @Test
    void deleteAccountById() {
        try {
            String result = accountElasticsearchService.deleteAccountById("12345L");
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

运行结果为:

shell 复制代码
2024-12-07T13:15:10.064+08:00 TRACE 13804 --- [elasticsearch-client] [           main] tracer                                   : curl -iX DELETE 'http://localhost:9200/accounts/_doc/12345'
# HTTP/1.1 200 OK
# X-elastic-product: Elasticsearch
# content-type: application/json; charset=UTF-8
# content-length: 160
#
# {"_index":"accounts","_type":"_doc","_id":"12345","_version":3,"result":"deleted","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":6,"_primary_term":1}
Document deleted with result: Deleted

参考资料

Connecting | Elasticsearch Java API Client 7.17

ElasticSearch8 - SpringBoot整合ElasticSearch - 王谷雨 - 博客园

相关推荐
努力的布布4 小时前
Elasticsearch-模糊查询
大数据·elasticsearch·搜索引擎
jinan8864 小时前
数据安全中间件的好处
中间件
言之。4 小时前
[Python3] Sanic中间件
中间件
dbcat官方6 小时前
1.微服务灰度发布(方案设计)
java·数据库·分布式·微服务·中间件·架构
m0_748237056 小时前
Monorepo pnpm 模式管理多个 web 项目
大数据·前端·elasticsearch
嘤嘤怪呆呆狗6 小时前
【开发问题记录】使用 Docker+Jenkins+Jenkins + gitee 实现自动化部署前端项目 CI/CD(centos7为例)
前端·vue.js·ci/cd·docker·gitee·自动化·jenkins
张某人想退休6 小时前
Postman最新接口自动化持续集成
自动化·jenkins·postman
java1234_小锋7 小时前
ElasticSearch如何做性能优化?
大数据·elasticsearch·性能优化
Roy_Sashulin7 小时前
Sashulin升级啦,开箱即用!
java·中间件·sashulin
lifeng432116 小时前
Jenkins集成部署(图文教程、超级详细)
运维·jenkins