0


Elasticsearch:使用最新的 Elasticsearch Java client 8.0 来创建索引并搜索

这篇文章,我来详细地描述如何使用最新的 Elasticsearch Java client 8.0 来创建索引并进行搜索。最新的 Elasticsearch Java client API 和之前的不同。在之前的一些教程中,我们使用 High Level API 来进行操作。在官方文档中,已经显示为 deprecated。

前提条件

  • Java 8 及以后的版本
  • 一个 JSON 对象映射库,允许你的应用程序类与 Elasticsearch API 无缝集成。 Java 客户端支持 Jackson 或像 Eclipse Yasson 的 JSON-B 库。

版本托管在 Maven Central 上。 如果你正在寻找 SNAPSHOT 版本,可以从 https://snapshots.elastic.co/maven/ 获得 Elastic Maven 快照存储库。

为什么需要一个新的 Java client?

也许有许多的开发者好奇为啥需要新的 client,以前的那个 High level rest client 不是好好的吗?以前的那个 High level REST client API 有如下的问题:

  • 和 Elasticsearch server 共享很多的代码 - 拉取大量依赖 (30 + MB)。很多代码并不实用- 容易误解:之前的 API 暴露了许多 Elasticsearch server 的内部情况
  • 用手来书写 API - API 在不同的版本中有时并不一致- 需要大量的维护工作(400 多个 endpoints)
  • 没有 JSON/object 映射的集成 - 你需要使用 byte buffers 来自己映射

新的 Java client API 具有一下的优点:

  • 使用代码来生成 API - 基于官方的 Elasticsearch API 正式文档- Java client API 是新一代 Elasticsearch client 的第一个。后续有针对其它的语言发布- 99% 的代码是自动生成的
  • 一个提供更加现代 API 接口的机会 - 流畅的 functional builders- 接近 Elasticsearch JSON 格式的分层 DSL- 到/从和应用程序类的自动映射- 保持 Java 8 的兼容性

安装

如果你还没有安装好自己的 Elasticsearch 及 Kibana 的话,请参阅我之前的文章:

  • 如何在 Linux,MacOS 及 Windows 上进行安装 Elasticsearch
  • Kibana:如何在 Linux,MacOS 及 Windows上安装 Elastic 栈中的 Kibana
  • Elasticsearch:设置 Elastic 账户安全

如果你想在 Elastic Stack 8.0 上试用的话。你可以参阅文章 “Elastic Stack 8.0 安装 - 保护你的 Elastic Stack 现在比以往任何时候都简单”。在本文章中,我们不启用 HTTPS 的访问。你需要查看文章中 “如何配置 Elasticsearch 只带有基本安全” 这个部分。我们为 Elasticsearch 配置基本安全。如果你想访问带有 HTTPS 连接的 Elasticsearch 集群,请参阅文章 “Elasticsearch:使用 Elasticsearch Java client 8.0 来连接带有 HTTPS 的集群”。

展示

在今天的展示中,我将使用 Maven 项目来进行展示尽管 gradle 也可以。为了方便大家的学习,我把我创建的项目上传到 github 上 GitHub - liu-xiao-guo/ElasticsearchJava-search8

首先,我们的 pom.xml 文件如下:

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>org.example</groupId>
  7. <artifactId>ElasticsearchJava-search8</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <properties>
  10. <maven.compiler.source>8</maven.compiler.source>
  11. <maven.compiler.target>8</maven.compiler.target>
  12. <elastic.version>8.0.1</elastic.version>
  13. </properties>
  14. <dependencies>
  15. <dependency>
  16. <groupId>co.elastic.clients</groupId>
  17. <artifactId>elasticsearch-java</artifactId>
  18. <version>${elastic.version}</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>com.fasterxml.jackson.core</groupId>
  22. <artifactId>jackson-databind</artifactId>
  23. <version>2.12.3</version>
  24. </dependency>
  25. <!-- Needed only if you use the spring-boot Maven plugin -->
  26. <dependency>
  27. <groupId>jakarta.json</groupId>
  28. <artifactId>jakarta.json-api</artifactId>
  29. <version>2.0.1</version>
  30. </dependency>
  31. </dependencies>
  32. </project>

如上所示,我们使用了 8.0.1 的版本。你也可以使用在地址 Maven Central Repository Search 上的最新版本 8.1.1。

接下来,我们创建一个叫做 Product.java 的文件:

Product.java

  1. public class Product {
  2. private String id;
  3. private String name;
  4. private int price;
  5. public Product() {
  6. }
  7. public Product(String id, String name, int price) {
  8. this.id = id;
  9. this.name = name;
  10. this.price = price;
  11. }
  12. public String getId() {
  13. return id;
  14. }
  15. public String getName() {
  16. return name;
  17. }
  18. public int getPrice() {
  19. return price;
  20. }
  21. public void setId(String id) {
  22. this.id = id;
  23. }
  24. public void setName(String name) {
  25. this.name = name;
  26. }
  27. public void setPrice(int price) {
  28. this.price = price;
  29. }
  30. @Override
  31. public String toString() {
  32. return "Product{" +
  33. "id='" + id + '\'' +
  34. ", name='" + name + '\'' +
  35. ", price=" + price +
  36. '}';
  37. }
  38. }

我们再接下来创建 ElasticsearchJava.java 文件:

在上面,代码也非常直接。我们使用如下的代码来连接到 Elasticsearch:

  1. private static synchronized void makeConnection() {
  2. // Create the low-level client
  3. final CredentialsProvider credentialsProvider =
  4. new BasicCredentialsProvider();
  5. credentialsProvider.setCredentials(AuthScope.ANY,
  6. new UsernamePasswordCredentials("elastic", "password"));
  7. RestClientBuilder builder = RestClient.builder(
  8. new HttpHost("localhost", 9200))
  9. .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
  10. @Override
  11. public HttpAsyncClientBuilder customizeHttpClient(
  12. HttpAsyncClientBuilder httpClientBuilder) {
  13. return httpClientBuilder
  14. .setDefaultCredentialsProvider(credentialsProvider);
  15. }
  16. });
  17. RestClient restClient = builder.build();
  18. // Create the transport with a Jackson mapper
  19. ElasticsearchTransport transport = new RestClientTransport(
  20. restClient, new JacksonJsonpMapper());
  21. // And create the API client
  22. client = new ElasticsearchClient(transport);
  23. asyncClient = new ElasticsearchAsyncClient(transport);
  24. }

在上面,我们使用 elastic 这个超级用户来进行访问。它的密码是 password。这个在实际的使用中,需要根据自己的情况来进行设置。

在下面,我们使用如下的两种格式来写入数据到 products 索引中:

  1. // Index data to an index products
  2. Product product = new Product("abc", "Bag", 42);
  3. IndexRequest<Object> indexRequest = new IndexRequest.Builder<>()
  4. .index("products")
  5. .id("abc")
  6. .document(product)
  7. .build();
  8. client.index(indexRequest);
  9. Product product1 = new Product("efg", "Bag", 42);
  10. client.index(builder -> builder
  11. .index("products")
  12. .id(product1.getId())
  13. .document(product1)
  14. );

上述的写入类似于在 Kibana 中输入如下的指令:

  1. PUT products/_doc/abc
  2. {
  3. "id": "abc",
  4. "name": "Bag",
  5. "price": 42
  6. }

我们可以在 Kibana 中进行查看:

  1. GET products/_search

上面的命令显示:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 2,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 1.0,
  16. "hits" : [
  17. {
  18. "_index" : "products",
  19. "_id" : "abc",
  20. "_score" : 1.0,
  21. "_source" : {
  22. "id" : "abc",
  23. "name" : "Bag",
  24. "price" : 42
  25. }
  26. },
  27. {
  28. "_index" : "products",
  29. "_id" : "efg",
  30. "_score" : 1.0,
  31. "_source" : {
  32. "id" : "efg",
  33. "name" : "Bag",
  34. "price" : 42
  35. }
  36. }
  37. ]
  38. }
  39. }

显然我们写入的数据是成功的。

接下来,我使用了如下的两种格式来进行搜索:

  1. // Search for a data
  2. TermQuery query = QueryBuilders.term()
  3. .field("name")
  4. .value("bag")
  5. .build();
  6. SearchRequest request = new SearchRequest.Builder()
  7. .index("products")
  8. .query(query._toQuery())
  9. .build();
  10. SearchResponse<Product> search =
  11. client.search(
  12. request,
  13. Product.class
  14. );
  15. for (Hit<Product> hit: search.hits().hits()) {
  16. Product pd = hit.source();
  17. System.out.println(pd);
  18. }
  19. SearchResponse<Product> search1 = client.search(s -> s
  20. .index("products")
  21. .query(q -> q
  22. .term(t -> t
  23. .field("name")
  24. .value(v -> v.stringValue("bag"))
  25. )),
  26. Product.class);
  27. for (Hit<Product> hit: search1.hits().hits()) {
  28. Product pd = hit.source();
  29. System.out.println(pd);
  30. }

这个搜索相当于:

  1. GET products/_search
  2. {
  3. "query": {
  4. "term": {
  5. "name": {
  6. "value": "bag"
  7. }
  8. }
  9. }
  10. }

上面的搜索结果为:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 2,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 0.18232156,
  16. "hits" : [
  17. {
  18. "_index" : "products",
  19. "_id" : "abc",
  20. "_score" : 0.18232156,
  21. "_source" : {
  22. "id" : "abc",
  23. "name" : "Bag",
  24. "price" : 42
  25. }
  26. },
  27. {
  28. "_index" : "products",
  29. "_id" : "efg",
  30. "_score" : 0.18232156,
  31. "_source" : {
  32. "id" : "efg",
  33. "name" : "Bag",
  34. "price" : 42
  35. }
  36. }
  37. ]
  38. }
  39. }

Java 代码输出的结果为:

  1. Product{id='abc', name='Bag', price=42}
  2. Product{id='efg', name='Bag', price=42}
  3. Product{id='abc', name='Bag', price=42}
  4. Product{id='efg', name='Bag', price=42}

我们使用如下的代码来简化一个复杂的 DSL:

  1. // Splitting complex DSL
  2. TermQuery termQuery = TermQuery.of(t ->t.field("name").value("bag"));
  3. SearchResponse<Product> search2 = client.search(s -> s
  4. .index("products")
  5. .query(termQuery._toQuery()),
  6. Product.class
  7. );
  8. for (Hit<Product> hit: search2.hits().hits()) {
  9. Product pd = hit.source();
  10. System.out.println(pd);
  11. }

同样上面的输出结果为:

  1. Product{id='abc', name='Bag', price=42}
  2. Product{id='efg', name='Bag', price=42}

我们使用如下的代码:

  1. // Search by product name
  2. Query byName = MatchQuery.of(m -> m
  3. .field("name")
  4. .query("bag")
  5. )._toQuery();
  6. // Search by max price
  7. Query byMaxPrice = RangeQuery.of(r -> r
  8. .field("price")
  9. .gte(JsonData.of(10))
  10. )._toQuery();
  11. // Combine name and price queries to search the product index
  12. SearchResponse<Product> response = client.search(s -> s
  13. .index("products")
  14. .query(q -> q
  15. .bool(b -> b
  16. .must(byName)
  17. .should(byMaxPrice)
  18. )
  19. ),
  20. Product.class
  21. );
  22. List<Hit<Product>> hits = response.hits().hits();
  23. for (Hit<Product> hit: hits) {
  24. Product product2 = hit.source();
  25. System.out.println("Found product " + product2.getId() + ", score " + hit.score());
  26. }

来实现如下的一个搜索:

  1. GET products/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "name": "bag"
  9. }
  10. }
  11. ],
  12. "should": [
  13. {
  14. "range": {
  15. "price": {
  16. "gte": 10
  17. }
  18. }
  19. }
  20. ]
  21. }
  22. }
  23. }

它显示的结果是:

  1. {
  2. "took" : 1,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 1.287682,
  16. "hits" : [
  17. {
  18. "_index" : "products",
  19. "_id" : "abc",
  20. "_score" : 1.287682,
  21. "_source" : {
  22. "id" : "abc",
  23. "name" : "Bag",
  24. "price" : 42
  25. }
  26. }
  27. ]
  28. }
  29. }

而 Java 的输出结果为:

  1. Found product abc, score 1.287682

最后,使用了一个 aggregation:

  1. // Creating aggregations
  2. SearchResponse<Void> search3 = client.search( b-> b
  3. .index("products")
  4. .size(0)
  5. .aggregations("price-histo", a -> a
  6. .histogram(h -> h
  7. .field("price")
  8. .interval(20.0)
  9. )
  10. ),
  11. Void.class
  12. );
  13. long firstBucketCount = search3.aggregations()
  14. .get("price-histo")
  15. .histogram()
  16. .buckets().array()
  17. .get(0)
  18. .docCount();
  19. System.out.println("doc count: " + firstBucketCount);
  20. }

上面的 Void.class 表示在相应中没有文档。我们可以通过如上所示的代码导航聚合的结果:

上面的 aggregation 相当于如下的请求:

  1. GET products/_search
  2. {
  3. "size": 0,
  4. "aggs": {
  5. "price-histo": {
  6. "histogram": {
  7. "field": "price",
  8. "interval": 20
  9. }
  10. }
  11. }
  12. }

我们的 Java 代码的输出结果为:

  1. doc count: 2

上面的聚合,我们可以甚至直接使用 JSON 结构的字符串来进行操作:

  1. String aggstr = "\n" +
  2. " { \n" +
  3. " \"size\": 0, \n" +
  4. " \"aggs\": { \n" +
  5. " \"price-histo\": { \n" +
  6. " \"histogram\": { \n" +
  7. " \"field\": \"price\", \n" +
  8. " \"interval\": 20 \n" +
  9. " } \n" +
  10. " } \n" +
  11. " } \n" +
  12. " } ";
  13. System.out.println("agg is: " + aggstr );
  14. InputStream agg = new ByteArrayInputStream(aggstr.getBytes());
  15. SearchResponse<Void> searchAgg = client
  16. .search(b -> b
  17. .index("products")
  18. .withJson(agg),
  19. Void.class
  20. );
  21. firstBucketCount = searchAgg.aggregations()
  22. .get("price-histo")
  23. .histogram()
  24. .buckets().array()
  25. .get(0)
  26. .docCount();
  27. System.out.println("doc count: " + firstBucketCount);

上面代码显示的结果和之上的结果是一样的:

  1. doc count: 2

更多阅读,请参阅 “Elasticsearch:使用 Elasticsearch Java client 8.0 来连接带有 HTTPS 的集群”。


本文转载自: https://blog.csdn.net/UbuntuTouch/article/details/123839857
版权归原作者 Elastic 中国社区官方博客 所有, 如有侵权,请联系我们删除。

“Elasticsearch:使用最新的 Elasticsearch Java client 8.0 来创建索引并搜索”的评论:

还没有评论