还在用 High Level Rest Client,试试全新的 Elasticsearch Java API Client 吧!

原创
2022/05/24 06:25
阅读数 1.2K
AI总结

Elasticsearch Java API Client 是自 7.16 版本开始稳定发布的官方 Java API 客户端。该客户端为所有 Elasticsearch API 提供强类型请求和响应。主要特性如下:

  • 所有 Elasticsearch API 的强类型请求和响应。
  • 所有 API 的阻塞和异步版本。
  • 在创建复杂的嵌套结构时,使用流利的构建器和功能模式允许编写简洁易读的代码。
  • 通过使用对象映射器(例如 Jackson)或任何 JSON-B 实现来无缝集成应用程序类。
  • 将协议处理委托给 http 客户端,例如 Java Low Level REST Client ,该客户端负责处理所有传输级别的问题:HTTP 连接池、重试、节点发现等。

Elasticsearch Java API Client 是一个全新的客户端库,与旧的 High Level Rest Client (HLRC) 没有任何关系。它提供了一个独立于 Elasticsearch 服务器代码的库,并为所有 Elasticsearch 功能提供了一个非常一致且更易于使用的 API。

安装要求

  • Java 8 或更高版本。
  • 一个 JSON 对象映射库,允许我们应用程序类与 Elasticsearch API 无缝集成。Java API Client 支持 Jackson 或 Eclipse Yasson 等 JSON-B 库 。

安装

添加以下的 maven 依赖来安装 Java API Client:

<dependencies>

    <dependency>
      <groupId>co.elastic.clients</groupId>
      <artifactId>elasticsearch-java</artifactId>
      <version>8.2.0</version>
    </dependency>

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

  </dependencies>

连接

Java API Client 围绕三个主要组件构建:

  • API 客户端类。它们为 Elasticsearch API 提供强类型数据结构和方法。由于 Elasticsearch API 很大,它以功能组(也称为“命名空间”)的形式构成,每个组都有自己的客户端类。Elasticsearch 核心功能在 ElasticsearchClient 类中实现。
  • JSON 对象映射器。将应用程序类映射到 JSON 并将它们与 API 客户端无缝集成。
  • 传输层实现。这是所有 HTTP 请求处理发生的地方。

以下代码片段创建并将这三个组件连接在一起:

// 1. Create the low-level client
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200)).build();

// 2. Create the transport with a Jackson mapper
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());

// 3. And create the API client
ElasticsearchClient client = new ElasticsearchClient(transport);

Spring Boot 中使用

  1. 在配置文件 application.yml 中配置如下的 Elasticsearch 连接信息:
spring:
  elasticsearch:
    uris:
      - https://my-deployment-ce7ca3.es.us-central1.gcp.cloud.es.io:9243
    username: elastic
    password: qTjgYVKSuExX
  1. 因为我们使用的是 Spring Boot 项目,当我们引入了 Java API Client 的 maven 相关依赖时,Spring Boot 的自动配置类 ElasticsearchRestClientAutoConfiguration 生效,会自动为我们配置一个 RestClient。所以上一节连接三步骤的第一步Create the low-level client可以省略。
@AutoConfiguration
@ConditionalOnClass({RestClientBuilder.class})
@EnableConfigurationProperties({ElasticsearchProperties.class, ElasticsearchRestClientProperties.class})
@Import({RestClientBuilderConfiguration.class, RestHighLevelClientConfiguration.class, RestClientFromRestHighLevelClientConfiguration.class, RestClientConfiguration.class, RestClientSnifferConfiguration.class})
public class ElasticsearchRestClientAutoConfiguration {
    public ElasticsearchRestClientAutoConfiguration() {
    }
}
  1. 添加我们自己的 Elasticsearch 配置类,配置一个 ElasticsearchClient 如下:
/**
 * elasticsearch 相关配置
 *
 * @author xiongxiaoyang
 * @date 2022/5/23
 */
@Configuration
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true")
@RequiredArgsConstructor
public class EsConfig {

    @Bean
    public ElasticsearchClient elasticsearchClient(RestClient restClient) {

        // Create the transport with a Jackson mapper
        ElasticsearchTransport transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper());

        // And create the API client
        return new ElasticsearchClient(transport);
    }

}

使用示例

  1. 批量插入数据
public void saveToEs() {
        QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
        List<BookInfo> bookInfos;
        long maxId = 0;
        for(;;) {
            queryWrapper.clear();
            queryWrapper
                    .orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName())
                    .gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxId)
                    .last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
            bookInfos = bookInfoMapper.selectList(queryWrapper);
            if (bookInfos.isEmpty()) {
                break;
            }
            BulkRequest.Builder br = new BulkRequest.Builder();

            for (BookInfo book : bookInfos) {
                EsBookDto esBook = buildEsBook(book);
                br.operations(op -> op
                        .index(idx -> idx
                                .index(EsConsts.IndexEnum.BOOK.getName())
                                .id(book.getId().toString())
                                .document(esBook)
                        )
                ).timeout(Time.of(t -> t.time("10s")));
                maxId = book.getId();
            }

            BulkResponse result = elasticsearchClient.bulk(br.build());

            // Log errors, if any
            if (result.errors()) {
                log.error("Bulk had errors");
                for (BulkResponseItem item : result.items()) {
                    if (item.error() != null) {
                        log.error(item.error().reason());
                    }
                }
            }

        }

    }
  1. 全文检索
@SneakyThrows
@Override
public RestResp<PageRespDto<BookInfoRespDto>> searchBooks(BookSearchReqDto condition) {

    SearchResponse<EsBookDto> response = esClient.search(s -> {

                SearchRequest.Builder searchBuilder = s.index(EsConsts.IndexEnum.BOOK.getName());
                // 构建检索条件
                buildSearchCondition(condition, searchBuilder);
                // 排序
                if (!StringUtils.isBlank(condition.getSort())) {
                    searchBuilder.sort(o ->
                            o.field(f -> f.field(condition.getSort()).order(SortOrder.Desc))
                    );
                }
                // 分页
                searchBuilder.from((condition.getPageNum() - 1) * condition.getPageSize())
                        .size(condition.getPageSize());

                return searchBuilder;
            },
            EsBookDto.class
    );

    TotalHits total = response.hits().total();

    List<BookInfoRespDto> list = new ArrayList<>();
    List<Hit<EsBookDto>> hits = response.hits().hits();
    for (Hit<EsBookDto> hit : hits) {
        EsBookDto book = hit.source();
        list.add(BookInfoRespDto.builder()
                .id(book.getId())
                .bookName(book.getBookName())
                .categoryId(book.getCategoryId())
                .categoryName(book.getCategoryName())
                .authorId(book.getAuthorId())
                .authorName(book.getAuthorName())
                .wordCount(book.getWordCount())
                .lastChapterName(book.getLastChapterName())
                .build());
    }
    return RestResp.ok(PageRespDto.of(condition.getPageNum(), condition.getPageSize(), total.value(), list));
    
}

/**
 * 构建检索条件
 */
private void buildSearchCondition(BookSearchReqDto condition, SearchRequest.Builder searchBuilder) {

    BoolQuery boolQuery = BoolQuery.of(b -> {

        if (!StringUtils.isBlank(condition.getKeyword())) {
            // 关键词匹配
            b.must((q -> q.multiMatch(t -> t
                    .fields("bookName^2","authorName^1.8","bookDesc^0.1")
                    .query(condition.getKeyword())
            )
            ));
        }

        // 精确查询
        if (Objects.nonNull(condition.getWorkDirection())) {
            b.must(TermQuery.of(m -> m
                    .field("workDirection")
                    .value(condition.getWorkDirection())
            )._toQuery());
        }

        if (Objects.nonNull(condition.getCategoryId())) {
            b.must(TermQuery.of(m -> m
                    .field("categoryId")
                    .value(condition.getCategoryId())
            )._toQuery());
        }

        // 范围查询
        if (Objects.nonNull(condition.getWordCountMin())) {
            b.must(RangeQuery.of(m -> m
                    .field("wordCount")
                    .gte(JsonData.of(condition.getWordCountMin()))
            )._toQuery());
        }

        if (Objects.nonNull(condition.getWordCountMax())) {
            b.must(RangeQuery.of(m -> m
                    .field("wordCount")
                    .lt(JsonData.of(condition.getWordCountMax()))
            )._toQuery());
        }

        if (Objects.nonNull(condition.getUpdateTimeMin())) {
            b.must(RangeQuery.of(m -> m
                    .field("lastChapterUpdateTime")
                    .gte(JsonData.of(condition.getUpdateTimeMin().getTime()))
            )._toQuery());
        }

        return b;

    });

    searchBuilder.query(q -> q.bool(boolQuery));

}

注:Elasticsearch Java API Client 8.2.0 版本已在 Spring Boot 3 + Vue 3 学习型开源项目中全面应用。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
AI总结
返回顶部
顶部