来聊一聊elasticsearch最新版的java客户端(代码片段)

_江南一点雨 _江南一点雨     2023-01-22     598

关键词:

可能不少小伙伴都注意到了,从 ElasticSearch7.17 这个版本开始,原先的 Java 高级客户端
Java High Level REST Client 废弃了,不支持了。老实说,ElasticSearch 算是我用过的所有 Java 工具中,更新最为激进的一个了,在 Es7 中废弃了 TransportClient,7.17 又废弃了 TransportClient,那么现在用啥呢?现在的客户端叫做 Elasticsearch Java API Client。

一直偷懒选择无视 Elasticsearch Java API Client,不过最近工作中用到了,所以还是整篇文章和小伙伴们简单梳理一下 Elasticsearch Java API Client 的玩法。

下面的介绍我主要从索引操作和文档操作两个方面来给大家介绍。

不过需要跟大家强调的是,ElasticSearch 的 Java 客户端想要用的 6,必须要熟悉 ElasticSearch 的查询脚本,大家平时在工作中遇到 Es 相关的问题,我也都是建议先在 Kibana 中把操作脚本写好,然后再翻译成 Java 代码,或者直接拷贝到 Java 代码中,非常不建议上来就整 Java 代码,那样很容易出错。

如果你对 Es 的操作不熟悉,松哥录了免费的视频教程,大家可以参考:

不想看视频,也可以在微信公众号后台回复 es,有文档教程。

1. Elasticsearch Java API Client

Elasticsearch Java API Client 是 Elasticsearch 的官方 Java API,这个客户端为所有 Elasticsearch APIs 提供强类型的请求和响应。

这里跟大家解释下什么是强类型的请求和响应:因为所有的 Elasticsearch APIs 本质上都是一个 RESTful 风格的 HTTP 请求,所以当我们调用这些 Elasticsearch APIs 的时候,可以就当成普通的 HTTP 接口来对待,例如使用 HttpUrlConnection 或者 RestTemplate 等工具来直接调用,如果使用这些工具直接调用,就需要我们自己组装 JSON 参数,然后自己解析服务端返回的 JSON。而强类型的请求和响应则是系统把请求参数封装成一个对象了,我们调用对象中的方法去设置就可以了,不需要自己手动拼接 JSON 参数了,请求的结果系统也会封装成一个对象,不需要自己手动去解析 JSON 参数了。

小伙伴们看一下下面这个例子,我想查询 books 索引中,书名中包含 Java 关键字的图书:

public class EsDemo02 
    public static void main(String[] args) throws IOException 
        URL url = new URL("http://localhost:9200/books/_search?pretty");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");
        con.setRequestProperty("content-type","application/json;charset=utf-8");
        //允许输出流/允许参数
        con.setDoOutput(true);
        //获取输出流
        OutputStream out = con.getOutputStream();
        String params = "\\n" +
                "  \\"query\\": \\n" +
                "    \\"term\\": \\n" +
                "      \\"name\\": \\n" +
                "        \\"value\\": \\"java\\"\\n" +
                "      \\n" +
                "    \\n" +
                "  \\n" +
                "";
        out.write(params.getBytes());
        if (con.getResponseCode() == 200) 
            BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String str = null;
            while ((str = br.readLine()) != null) 
                System.out.println(str);
            
            br.close();
        
    

小伙伴们看到,这就是一个普通的 HTTP 请求,请求参数就是查询的条件,这个条件是一个 JSON 字符串,需要我们自己组装,请求的返回值也是一个 JSON 字符串,这个 JSON 字符串也需要我们自己手动去解析,这种可以算是弱类型的请求和响应。

Elasticsearch Java API Client 具有如下特性:

  • 为所有 Elasticsearch APIs 提供强类型的请求和响应。
  • 所有 API 都有阻塞和异步版本。
  • 使用构建器模式,在创建复杂的嵌套结构时,可以编写简洁而可读的代码。
  • 通过使用对象映射器(如 Jackson 或任何实现了 JSON-B 的解析器),实现应用程序类的无缝集成。
  • 将协议处理委托给一个 http 客户端,如 Java Low Level REST Client,它负责所有传输级的问题。HTTP 连接池、重试、节点发现等等由它去完成。

关于第三点,松哥吐槽一句,确实简洁,但是可读性一般般吧。

另外还有两点需要注意:

  • Elasticsearch Java 客户端是向前兼容的,即该客户端支持与 Elasticsearch 的更大或相等的次要版本进行通信。
  • Elasticsearch Java 客户端只向后兼容默认的发行版本,并且没有做出保证。

好了,那就不废话了,开整吧。

2. 引入 Elasticsearch Java API Client

首先需要我们加依赖,对 JDK 的版本要求是 1.8,我们需要添加如下两个依赖:

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

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

如果是 Spring Boot 项目,就不用添加第二个依赖了,因为 Spring Boot 的 Web 中默认已经加了这个依赖了,但是 Spring Boot 一般需要额外添加下面这个依赖,出现这个原因是由于从 JavaEE 过渡到 JakartaEE 时衍生出来的一些问题,这里我就不啰嗦了,咱们直接加依赖即可:

<dependency>
  <groupId>jakarta.json</groupId>
  <artifactId>jakarta.json-api</artifactId>
  <version>2.0.1</version>
</dependency>

3. 建立连接

接下来我们需要用我们的 Java 客户端和 ElasticSearch 之间建立连接,建立连接的方式如下:

RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200)).build();
ElasticsearchTransport transport = new RestClientTransport(
    restClient, new JacksonJsonpMapper());
ElasticsearchClient client = new ElasticsearchClient(transport);

小伙伴们看到,这里一共有三个步骤:

  1. 首先创建一个低级客户端,这个其实松哥之前的视频中和大家讲过低级客户端的用法,这里就不再赘述。
  2. 接下来创建一个通信 Transport,并利用 JacksonJsonpMapper 做数据的解析。
  3. 最后创建一个阻塞的 Java 客户端。

上面这个是创建了一个阻塞的 Java 客户端,当然我们也可以创建非阻塞的 Java 客户端,如下:

RestClient restClient = RestClient.builder(
        new HttpHost("localhost", 9200)).build();
ElasticsearchTransport transport = new RestClientTransport(
        restClient, new JacksonJsonpMapper());
ElasticsearchAsyncClient client = new ElasticsearchAsyncClient(transport);

只有第三步和前面的不一样,其他都一样。

利用阻塞的 Java 客户端操作 Es 的时候会发生阻塞,也就是必须等到 Es 给出响应之后,代码才会继续执行;非阻塞的 Java 客户端则不会阻塞后面的代码执行,非阻塞的 Java 客户端一般通过回调函数处理请求的响应值。

有时候,我们可能还需要和 Es 之间建立 HTTPS 连接,那么需要在前面代码的基础之上,再套上一层 SSL,如下:

String fingerprint = "<certificate fingerprint>";
SSLContext sslContext = TransportUtils
    .sslContextFromCaFingerprint(fingerprint); 
BasicCredentialsProvider credsProv = new BasicCredentialsProvider(); 
credsProv.setCredentials(
    AuthScope.ANY, new UsernamePasswordCredentials(login, password)
);
RestClient restClient = RestClient
    .builder(new HttpHost(host, port, "https")) 
    .setHttpClientConfigCallback(hc -> hc
        .setSSLContext(sslContext) 
        .setDefaultCredentialsProvider(credsProv)
    )
    .build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
ElasticsearchClient client = new ElasticsearchClient(transport);

好了,关于建立连接,差不多就这些点。

4. 索引操作

Elasticsearch Java API Client 中最大的特色就是建造者模式+Lambda 表达式。例如,我想创建一个索引,方式如下:

@Test
public void test99() throws IOException 
    RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200)).build();
    ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());
    ElasticsearchClient client = new ElasticsearchClient(transport);
    CreateIndexResponse createIndexResponse = client.indices().create(
            c ->
                    c.index("javaboy_books")
                            .settings(s ->
                                    s.numberOfShards("3")
                                            .numberOfReplicas("1"))
                            .mappings(m ->
                                    m.properties("name", p -> p.text(f -> f.analyzer("ik_max_word")))
                                            .properties("birthday", p -> p.date(d -> d.format("yyyy-MM-dd"))))
                            .aliases("books_alias", f -> f.isWriteIndex(true)));
    System.out.println("createResponse.acknowledged() = " + createIndexResponse.acknowledged());
    System.out.println("createResponse.index() = " + createIndexResponse.index());
    System.out.println("createResponse.shardsAcknowledged() = " + createIndexResponse.shardsAcknowledged());

小伙伴们看到,这里都是建造者模式和 Lambda 表达式,方法名称其实都很好理解(前提是你得熟悉 ElasticSearch 操作脚本),例如:

  • index 方法表示设置索引名称
  • settings 方法表示配置 setting 中的参数
  • numberOfShards 表示索引的分片数
  • numberOfReplicas 表示配置索引的副本数
  • mapping 表示配置索引中的映射规则
  • properties 表示配置索引中的具体字段
  • text 方法表示字段是 text 类型的
  • analyzer 表示配置字段的分词器
  • aliases 表示配置索引的别名

反正这里的方法都是见名知义的,上面这个就类似于下面这个请求:

PUT javaboy_books

  "settings": 
    "number_of_replicas": 1,
    "number_of_shards": 3
  ,
  "mappings": 
    "properties": 
      "name":
        "type": "text",
        "analyzer": "ik_max_word"
      ,
      "birthday":
        "type": "date",
        "format": "yyyy-MM-dd"
      
    
  ,
  "aliases": 
    "xxxx":
      
    
  

小伙伴们在写的时候,脑子里要先有下面这个脚本,然后 Java 方法可以顺手拈来了。

最终创建好的索引如下图:

有的小伙伴可能觉得调这一大堆方法太啰里啰唆了,来个简单的,直接上 JSON,那也不是不可以,如下:

@Test
public void test98() throws IOException 
    RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200)).build();
    ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());
    ElasticsearchClient client = new ElasticsearchClient(transport);
    StringReader json = new StringReader("\\n" +
            "  \\"settings\\": \\n" +
            "    \\"number_of_replicas\\": 1,\\n" +
            "    \\"number_of_shards\\": 3\\n" +
            "  ,\\n" +
            "  \\"mappings\\": \\n" +
            "    \\"properties\\": \\n" +
            "      \\"name\\":\\n" +
            "        \\"type\\": \\"text\\",\\n" +
            "        \\"analyzer\\": \\"ik_max_word\\"\\n" +
            "      ,\\n" +
            "      \\"birthday\\":\\n" +
            "        \\"type\\": \\"date\\",\\n" +
            "        \\"format\\": \\"yyyy-MM-dd\\"\\n" +
            "      \\n" +
            "    \\n" +
            "  ,\\n" +
            "  \\"aliases\\": \\n" +
            "    \\"xxxx\\":\\n" +
            "      \\n" +
            "    \\n" +
            "  \\n" +
            "");
    CreateIndexResponse createIndexResponse = client.indices().create(
            c ->
                    c.index("javaboy_books").withJson(json));
    System.out.println("createResponse.acknowledged() = " + createIndexResponse.acknowledged());
    System.out.println("createResponse.index() = " + createIndexResponse.index());
    System.out.println("createResponse.shardsAcknowledged() = " + createIndexResponse.shardsAcknowledged());

这是直接把 JSON 参数给拼接出来,就不需要一堆建造者+Lambda 了。

如果你想删除索引呢?如下:

@Test
public void test06() throws IOException 
    RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200)).build();
    ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());
    ElasticsearchClient client = new ElasticsearchClient(transport);
    //删除一个索引
    DeleteIndexResponse delete = client.indices().delete(f ->
            f.index("my-index")
    );
    System.out.println("delete.acknowledged() = " + delete.acknowledged());

这个表示删除一个名为 my-index 的索引。

好了,关于索引的操作我就说这两点。

可能有的小伙伴会说,ElasticSearch 中创建索引可以配置很多参数你都没讲。在我看来,哪些很多参数其实跟这个 Java API 没有多大关系,只要你会写查询脚本,就自然懂得 Java API 中该调用哪个方法,退一万步讲,你会脚本,不懂 Java API 的方法,那么就像上面那样,直接把你的 JSON 拷贝过来,作为 Java API 的参数即可。

5. 文档操作

5.1 添加文档

先来看文档的添加操作。

如下表示我想给一个名为 books 的索引中添加一个 id 为 890 的书:

@Test
public void test07() throws IOException 
    RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200)).build();
    ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());
    ElasticsearchClient client = new ElasticsearchClient(transport);
    Book book = new Book();
    book.setId(890);
    book.setName("深入理解Java虚拟机");
    book.setAuthor("xxx");
    //添加一个文档
    //这是一个同步请求,请求会卡在这里
    IndexResponse response = client.index(i -> i.index("books").document(book).id("890"));
    System.out.println("response.result() = " + response.result());
    System.out.println("response.id() = " + response.id());
    System.out.println("response.seqNo() = " + response.seqNo());
    System.out.println("response.index() = " + response.index());
    System.out.println("response.shards() = " + response.shards());

添加成功之后,返回的 IndexResponse 对象其实就是对下面这个 JSON 的封装:

现在我们只需要调用相应的方法,就可以获取到 JSON 相关的属性了。

5.2 删除文档

如下表示删除 books 索引中 id 为 891 的文档:

@Test
public void test09() 
    RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200)).build();
    ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());
    ElasticsearchAsyncClient client = new ElasticsearchAsyncClient(transport);
    client.delete(d -> d.index("books").id("891")).whenComplete((resp, e) -> 
        System.out.println("resp.result() = " + resp.result());
    );

删除这里我用了异步非阻塞的客户端来给小伙伴们演示的,异步非阻塞的话,就使用 whenComplete 方法处理回调就行了,里边有两个参数,一个是正常情况下返回的对象,另外一个则是出错时候的异常。

5.3 查询文档

最后,就是查询了。这应该是大家日常开发中使用较多的功能项了,不过我还是前面的态度,查询的关键不在 Java API,而在于你对 ElasticSearch 脚本的掌握程度。

所以我这里举个简单的例子,小伙伴们大致了解下 Java API 的方法即可:

@Test
public void test01() throws IOException 
    RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200)).build();
    ElasticsearchTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper());
    ElasticsearchClient client = new ElasticsearchClient(transport);
    SearchRequest request = new SearchRequest.Builder()
            //去哪个索引里搜索
            .index("books")
            .query(QueryBuilders.term().field(来聊一聊elasticsearch最新版的java客户端(代码片段)

可能不少小伙伴都注意到了,从ElasticSearch7.17这个版本开始,原先的Java高级客户端JavaHighLevelRESTClient废弃了,不支持了。老实说,ElasticSearch算是我用过的所有Java工具中,更新最为激进的一个了,在Es7中废... 查看详情

没事来聊一聊

我是新手  想在这平台认识高手packagecn.Happ.day03;importjava.util.Scanner;publicclassHappy{ publicstaticvoidmain(String[]args){  //TODOAuto-generatedmethodstub  System.out.println 查看详情

面试官:我们来聊一聊redis吧,你了解多少就答多少

哈喽!大家好,我是小奇,一位不靠谱的程序员小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧文章持续更新,可以微信搜索【小奇JAVA面试... 查看详情

聊一聊elasticsearch的健康状态

...完成,然后es能正常工作就OK了,然而事实却并非如此,elasticsearch得索引状态和集群状态得不同传达着 查看详情

来聊一聊开发人员与非技术同事沟通时最崩溃的11件事

客户需要这个产品在IE6上面运行在工作场所,开发人员在非开发人员的眼中就魔术师一样,他们想要的产品经过开发人员的变魔术一般的代码就实现了。作为开发人员,你可能整天都在构建复杂的API集成,或者只需在Steam上点击W... 查看详情

聊一聊promise的前世今生(代码片段)

...现很久了,浏览器、nodejs都已经全部实现promise了。现在来聊,是不是有点过时了?  确实,如果不扯淡,这篇随笔根本不会有太多内容。所以,我就尽可能的,多扯一扯,聊一聊promise的另一面。  大家应该都知道怎么创建... 查看详情

今天,我们来聊一聊互联网真的有你所期待的那么好吗?来自一个老码农的碎碎念

最近很多要毕业的小伙伴或是正在实习的同学,甚至已经工作几年了朋友都在给我发私信想问问我对互联网这个行业的看法。说自己对自己的现状很迷茫,要毕业的对自己的实习工作迷茫,对自己实习要去哪迷茫。正在实习的对... 查看详情

聊一聊即将来临的跳槽季(代码片段)

...章中都是聊的技术,今天在九月即将开始的前一天,咱们来聊一聊跳槽季古之大佬们都有云:金三银四,金九银十。这句话也被很多职场新人奉为真理,毕竟在那些躁动的几个月里,公司中请假的、裸辞的、休假的的人都变多了... 查看详情

面向未来,我们来聊一聊什么是现代化数据架构

在不那么遥远的旧IT时代,有这样一个段子——假如把数据库们”聚在一起“开会”。Oracle:我们需要企业级数据库。MySQL:Oracle不开源。PostgreSQL:MySQL的功能不够多。SQLite:你可以把我嵌入到任何地方。这样,4种数据库够大... 查看详情

面向未来,我们来聊一聊什么是现代化数据架构

在不那么遥远的旧IT时代,有这样一个段子——假如把数据库们”聚在一起“开会”。Oracle:我们需要企业级数据库。MySQL:Oracle不开源。PostgreSQL:MySQL的功能不够多。SQLite:你可以把我嵌入到任何地方。这样,4种数据库够大... 查看详情

透过现象看本质——聊一聊docker的硬件资源控制与验证(代码片段)

...常用的一些命令的可选项的含义的理解,本文在此基础上来聊一聊基于硬件层面是上有关docker的资源(物理)控制。一、docker管理资源机制——Controlgroup?Controlgroup是Linux内核提供的一种限制所使用物理资源的机制,这些资源主要... 查看详情

聊一聊grpc中的拦截器(代码片段)

...f0c;这些就需要拦截器来完成,今天松哥先和小伙伴们来聊一聊gRPC中拦截器的基本用法,后面我再整一篇文章和小伙伴们做一个基于拦截器实现的JWT认证的gRPC。gRPC中的拦截器整体上来说可以分为两大类:服务端拦截... 查看详情

聊一聊grpc中的拦截器(代码片段)

...f0c;这些就需要拦截器来完成,今天松哥先和小伙伴们来聊一聊gRPC中拦截器的基本用法,后面我再整一篇文章和小伙伴们做一个基于拦截器实现的JWT认证的gRPC。gRPC中的拦截器整体上来说可以分为两大类:服务端拦截... 查看详情

聊一聊2d地图的迷雾效果

在经历了17年诸般变动后,现在到了2018年。怀旧是因为之前几年发生太多,好与不好都已过去,17年迎来新的开始,所以也就有了后来些许感慨,有机会再说说这些行业感受了。今天先让我们专注于川最新解决的实际项目问题。... 查看详情

聊一聊面向对象的三大特征

学习Java语言程序设计也有一段时间了。现在我想对封装、继承和多态,在Java中面向对象的三大特征,总结一下我的理解,不妥当的地方望大家包涵。   封装。给我的感觉封装就是一个包装,一个代码是否健壮,与用不用... 查看详情

聊一聊线程是如何运行的

线程运行的基本原理在​​java​​应用程序中,使用​​newThread().start()​​来启动一个线程时,底层会进行怎样的处理?我们通过一个简单的流程图来进一步分析:如上图,​​java​​代码中创建并启动了一个线程,在​​JVM... 查看详情

今天聊一聊java引用类型的强制类型转换

实际上基本类型也是存在强制类型转换的,这里简单提一下。概括来讲分为两种:  1、自动类型转换,也叫隐式类型转换,即数据范围小的转换为数据范围大的,此时编译器自动完成类型转换,无需我们写代码  2、强制类... 查看详情

系统容量预估

...可以看看我前面那篇文章:《聊一聊PV和并发》。今天再来聊一聊容量预估。   电商公司的朋友,,这样的场景是否似曾相识:    运营和产品神秘兮兮的跑过来问:    我们晚上要做搞个促销,服务器能抗住... 查看详情