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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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 2 3 4 5 6 7 8 9 10 RestClient restClient = RestClient.builder(new HttpHost ("localhost" , 9200 )).build();ElasticsearchTransport transport = new RestClientTransport (restClient, new JacksonJsonpMapper ()); ElasticsearchClient client = new ElasticsearchClient (transport);
Spring Boot 中使用
在配置文件 application.yml 中配置如下的 Elasticsearch 连接信息:
1 2 3 4 5 6 spring: elasticsearch: uris: - https://my-deployment-ce7ca3.es.us-central1.gcp.cloud.es.io:9243 username: elastic password: qTjgYVKSuExX
因为我们使用的是 Spring Boot 项目,当我们引入了 Java API Client 的 maven 相关依赖时,Spring Boot 的自动配置类 ElasticsearchRestClientAutoConfiguration
生效,会自动为我们配置一个 RestClient。所以上一节连接三步骤的第一步Create the low-level client
可以省略。
ElasticsearchRestClientAutoConfiguration.java 🔗 1 2 3 4 5 6 7 8 @AutoConfiguration @ConditionalOnClass({RestClientBuilder.class}) @EnableConfigurationProperties({ElasticsearchProperties.class}) @Import({RestClientBuilderConfiguration.class, RestClientConfiguration.class, RestClientSnifferConfiguration.class}) public class ElasticsearchRestClientAutoConfiguration { public ElasticsearchRestClientAutoConfiguration () { } }
添加我们自己的 Elasticsearch 配置类,配置一个 ElasticsearchClient 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Configuration public class EsConfig { @Bean public ElasticsearchClient elasticsearchClient (RestClient restClient) { ElasticsearchTransport transport = new RestClientTransport ( restClient, new JacksonJsonpMapper ()); return new ElasticsearchClient (transport); } }
使用示例
批量插入数据
BookToEsTask.java 🔗 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public ReturnT<String> saveToEs () { try { 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) .gt(DatabaseConsts.BookTable.COLUMN_WORD_COUNT, 0 ) .last(DatabaseConsts.SqlEnum.LIMIT_30.getSql()); bookInfos = bookInfoMapper.selectList(queryWrapper); if (bookInfos.isEmpty()) { break ; } BulkRequest.Builder br = new BulkRequest .Builder(); for (BookInfo book : bookInfos) { br.operations(op -> op .index(idx -> idx .index(EsConsts.BookIndex.INDEX_NAME) .id(book.getId().toString()) .document(EsBookDto.build(book)) ) ).timeout(Time.of(t -> t.time("10s" ))); maxId = book.getId(); } BulkResponse result = elasticsearchClient.bulk(br.build()); if (result.errors()) { log.error("Bulk had errors" ); for (BulkResponseItem item : result.items()) { if (item.error() != null ) { log.error(item.error().reason()); } } } } return ReturnT.SUCCESS; } catch (Exception e) { log.error(e.getMessage(), e); return ReturnT.FAIL; } }
全文检索
EsSearchServiceImpl.java 🔗 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 @SneakyThrows @Override public RestResp<PageRespDto<BookInfoRespDto>> searchBooks (BookSearchReqDto condition) { SearchResponse<EsBookDto> response = esClient.search(s -> { SearchRequest.Builder searchBuilder = s.index(EsConsts.BookIndex.INDEX_NAME); 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()); searchBuilder.highlight(h -> h.fields(EsConsts.BookIndex.FIELD_BOOK_NAME, t -> t.preTags("<em style='color:red'>" ).postTags("</em>" )) .fields(EsConsts.BookIndex.FIELD_AUTHOR_NAME, t -> t.preTags("<em style='color:red'>" ).postTags("</em>" ))); return searchBuilder; }, EsBookDto.class ); TotalHits total = response.hits().total(); List<BookInfoRespDto> list = new ArrayList <>(); List<Hit<EsBookDto>> hits = response.hits().hits(); for (var hit : hits) { EsBookDto book = hit.source(); assert book != null ; if (!CollectionUtils.isEmpty(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME))) { book.setBookName(hit.highlight().get(EsConsts.BookIndex.FIELD_BOOK_NAME).get(0 )); } if (!CollectionUtils.isEmpty( hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME))) { book.setAuthorName( hit.highlight().get(EsConsts.BookIndex.FIELD_AUTHOR_NAME).get(0 )); } 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 -> { b.must(RangeQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_WORD_COUNT) .gt(JsonData.of(0 )) )._toQuery()); if (!StringUtils.isBlank(condition.getKeyword())) { b.must((q -> q.multiMatch(t -> t .fields(EsConsts.BookIndex.FIELD_BOOK_NAME + "^2" , EsConsts.BookIndex.FIELD_AUTHOR_NAME + "^1.8" , EsConsts.BookIndex.FIELD_BOOK_DESC + "^0.1" ) .query(condition.getKeyword()) ) )); } if (Objects.nonNull(condition.getWorkDirection())) { b.must(TermQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_WORK_DIRECTION) .value(condition.getWorkDirection()) )._toQuery()); } if (Objects.nonNull(condition.getCategoryId())) { b.must(TermQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_CATEGORY_ID) .value(condition.getCategoryId()) )._toQuery()); } if (Objects.nonNull(condition.getWordCountMin())) { b.must(RangeQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_WORD_COUNT) .gte(JsonData.of(condition.getWordCountMin())) )._toQuery()); } if (Objects.nonNull(condition.getWordCountMax())) { b.must(RangeQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_WORD_COUNT) .lt(JsonData.of(condition.getWordCountMax())) )._toQuery()); } if (Objects.nonNull(condition.getUpdateTimeMin())) { b.must(RangeQuery.of(m -> m .field(EsConsts.BookIndex.FIELD_LAST_CHAPTER_UPDATE_TIME) .gte(JsonData.of(condition.getUpdateTimeMin().getTime())) )._toQuery()); } return b; }); searchBuilder.query(q -> q.bool(boolQuery)); }