侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 352 篇文章
  • 累计创建 135 个标签
  • 累计收到 10 条评论

目 录CONTENT

文章目录

ES教程21-Springboot整合ElasticSearch详解

孔子说JAVA
2022-10-26 / 0 评论 / 0 点赞 / 125 阅读 / 37,675 字 / 正在检测是否收录...
广告 广告

在ES官方文档中可以看到,ES 为 Java REST Client 提供了两种方式的 Client:Java低级REST客户端(Java Low Level Client) 和 高级REST客户端(Java High Level REST Client)。低级别的客户端通过http与Elasticearch集群通信,版本兼容性好。高级REST客户端是基于低级客户端API的封装,版本兼容性差,需要的Java1.8以上的版本。Elasticsearch需要6.0以上。ElasticSearch7.15.x版本后,废弃了高级Rest客户端的功能,转为JavaAPI客户端。

image-1666748375544

1、Springboot整合高级REST客户端

1.1 新建项目并添加依赖

新建springboot项目并添加相关依赖。

image-1666697518540

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <!--  <scope>test</scope>  -->
    </dependency>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>7.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-client</artifactId>
        <version>7.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.6.2</version>
    </dependency>
</dependencies>

这里有个坑得注意下,springboot 默认有一个 elasticsearch 版本,如 springbootboot 2.2.13.RELEASE 默认的 elasticsearch-rest-high-level-client 版本是6.3.2,而我们使用的 elasticsearch-rest-high-level-client 版本是 7.6.2,如果不在 properties 标签中显示指定 <elasticsearch.version>7.6.2</elasticsearch.version> 版本,会导致RestHighLevelClient创建失败。显示指定示例如下:

<properties>
  <springboot.version>2.2.13.RELEASE</springboot.version>
  <!--  <springboot.version>2.3.1.RELEASE</springboot.version>-->
  
  <!-- fastjson版本号 -->
  <fastjson.version>1.2.76</fastjson.version>
  
  <!-- springboot 2.2.13.RELEASE 默认的 elasticsearch-rest-high-level-client 版本是6.3.2,此处需显示指定为7.6.2 -->
  <elasticsearch.version>7.6.2</elasticsearch.version>
</properties>

若没有显示指定 <elasticsearch.version>7.6.2</elasticsearch.version> 版本,报错信息如下:

java.lang.IllegalStateException: Failed to load ApplicationContext

	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
	at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
	at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'client' defined in class path resource [com/xk/bugvip/search/config/BugVipEsConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.elasticsearch.client.RestHighLevelClient]: Factory method 'client' threw exception; nested exception is java.lang.NoClassDefFoundError: org/elasticsearch/action/admin/cluster/repositories/cleanup/CleanupRepositoryRequest
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:627)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:456)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1321)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1160)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:845)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
	... 24 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.elasticsearch.client.RestHighLevelClient]: Factory method 'client' threw exception; nested exception is java.lang.NoClassDefFoundError: org/elasticsearch/action/admin/cluster/repositories/cleanup/CleanupRepositoryRequest
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622)
	... 42 more
Caused by: java.lang.NoClassDefFoundError: org/elasticsearch/action/admin/cluster/repositories/cleanup/CleanupRepositoryRequest
	at org.elasticsearch.client.RestHighLevelClient.<init>(RestHighLevelClient.java:247)
	at org.elasticsearch.client.RestHighLevelClient.<init>(RestHighLevelClient.java:275)
	at org.elasticsearch.client.RestHighLevelClient.<init>(RestHighLevelClient.java:267)
	at com.xk.bugvip.search.config.BugVipEsConfig.client(BugVipEsConfig.java:32)
	at com.xk.bugvip.search.config.BugVipEsConfig$$EnhancerBySpringCGLIB$$16b9b868.CGLIB$client$0(<generated>)
	at com.xk.bugvip.search.config.BugVipEsConfig$$EnhancerBySpringCGLIB$$16b9b868$$FastClassBySpringCGLIB$$366ec6d7.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
	at com.xk.bugvip.search.config.BugVipEsConfig$$EnhancerBySpringCGLIB$$16b9b868.client(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
	... 43 more
Caused by: java.lang.ClassNotFoundException: org.elasticsearch.action.admin.cluster.repositories.cleanup.CleanupRepositoryRequest
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 57 more

1.2 添加配置类

新建 ElasticSearchConfig.java 配置类,并在该配置类中配置一个RestHighLevelClient对象。配置使用的属性都是通过yml配置文件自动注入的。

package com.kz.es.config;

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * ElasticSearchConfig 配置类
 *
 * @Author kongzi
 * @Date 2022/10/24 16:35
 * @Version 1.0
 */
@Configuration
public class ElasticSearchConfig {

    @Value("${elasticsearch.host}")
    private String host;

    @Value("${elasticsearch.port}")
    private int port;

    @Value("${elasticsearch.connTimeout}")
    private int connTimeout;

    @Value("${elasticsearch.socketTimeout}")
    private int socketTimeout;

    @Value("${elasticsearch.connectionRequestTimeout}")
    private int connectionRequestTimeout;

    // 协议,默认http
    @Value("${elasticsearch.scheme:http}")
    private String scheme;

    // 用户名,默认elastic
    @Value("${elasticsearch.username:elastic}")
    private String username;

    // 密码,默认elastic
    @Value("${elasticsearch.password:elastic}")
    private String password;
    
    /**
     * 配置RestHighLevelClient对象
     * 将该对象交给Spring容器去管理
     *
     * @return RestHighLevelClient对象
     */
    @Bean(name = "highLevelClient")
    public RestHighLevelClient restHighLevelClient() {
//        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port))
//                .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder
//                        .setConnectTimeout(connTimeout)
//                        .setSocketTimeout(socketTimeout)
//                        .setConnectionRequestTimeout(connectionRequestTimeout));
        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
        RestHighLevelClient restClient = new RestHighLevelClient(builder);
        return restClient;
    }
    
    /**
     * 配置RestHighLevelClient对象(es有密码的配置方式)
     * 将该对象交给Spring容器去管理
     *
     * @return RestHighLevelClient对象
     */
    @Bean
    public RestClient getRestClient() {
        // 创建用户名密码认证
        final CredentialsProvider credentialsProvider =
                new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY,
                new UsernamePasswordCredentials(username, password));

        // 使用 low level rest client 进行连接和认证
        return RestClient.builder(
                new HttpHost(host, port, scheme)
        ).setHttpClientConfigCallback(httpAsyncClientBuilder ->
                httpAsyncClientBuilder.setDefaultCredentialsProvider(
                        credentialsProvider)
        ).build();
    }

    @Bean
    public RestClientBuilder restClientBuilder() {
        return RestClient.builder(new HttpHost(host, port, "http"));
    }
}

application.yml文件内容:

elasticsearch:
  host: 172.19.82.206
  port: 9200
  connTimeout: 3000
  socketTimeout: 5000
  connectionRequestTimeout: 500
  scheme: http
  username: elastic
  password: elastic

至此,springboot已经整合好了ElasticSearch,接下来要做的就是具体操作了。

1.3 编写测试类

1.3.1 索引测试类

创建 HighLevelClientIndexApiTest 索引测试类,注入RestHighLevelClient。测试类上面需要加上 @RunWith, @SpringBootTest 2个注解。

  • 注意:@SpringBootTest(classes = ElasticsearchDemoApplication.class)中的classes的值要指向启动类才可以正常注入属性。也就是说 ElasticsearchDemoApplication 是springboot的启动类。若不加这个属性,运行测试用例时会报RestHighLevelClient的空指针。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchDemoApplication.class)
public class HighLevelClientIndexApiTest {

    @Autowired
    private RestHighLevelClient highLevelClient;
    
}

在索引测试类 HighLevelClientIndexApiTest 中新增创建索引、获取索引、删除索引的测试方法,完整代码如下。

package com.kz.es.service;

import com.kz.es.ElasticsearchDemoApplication;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

/**
 * RestHighLevelClient 索引测试类
 *
 * @Author kongzi
 * @Date 2022/10/25 15:52
 * @Version 1.0
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchDemoApplication.class)
public class HighLevelClientIndexApiTest {

    @Autowired
    private RestHighLevelClient highLevelClient;

    /**
     * 创建索引测试
     */
    @Test
    public void createIndex() throws IOException {
        //1、构建 创建索引的请求
        CreateIndexRequest request = new CreateIndexRequest("test_index");//索引名
        //2、客户端执行请求,获取响应
        CreateIndexResponse response = highLevelClient.indices().create(request, RequestOptions.DEFAULT);
        //3、打印
        System.out.println("创建成功,创建的索引名为:" + response.index());
    }

    /**
     * 获取索引测试
     */
    @Test
    public void getIndex() throws IOException {
        //1、构建 获取索引的请求
        GetIndexRequest request = new GetIndexRequest("test_index");
        //2、客户端判断该索引是否存在
        boolean exists = highLevelClient.indices().exists(request, RequestOptions.DEFAULT);
        //3、打印
        System.out.println("该索引是否存在:"+exists);
    }

    /**
     * 删除索引测试
     */
    @Test
    public void deleteIndex() throws IOException {
        //1、构建 删除索引请求
        DeleteIndexRequest request = new DeleteIndexRequest("test_index");
        //2、客户段执行删除的请求
        AcknowledgedResponse response = highLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        //3、打印
        System.out.println("是否删除成功:"+response.isAcknowledged());
    }

}

启动springboot项目后,鼠标右键在 RestHighLevelClient 索引测试类的每个方法上分别运行测试用例,可以看到索引能够正确的创建、获取和删除。通过 head插件 或 Kibana 查询也可以看到同样的结果。

1.3.2 文档测试类

创建文档就相当于数据库中添加一条记录,因此,在这里,我们首先新建一个实体类User,一个User对象就相当于一个文档。

package com.kz.es.domain;

/**
 * User实体类
 *
 * @Author kongzi
 * @Date 2022/10/25 18:13
 * @Version 1.0
 */
public class User {
    private Integer id;
    private String username;

    public Integer getId() {
        return id;
    }

    public User setId(Integer id) {
        this.id = id;
        return this;
    }

    public String getUsername() {
        return username;
    }

    public User setUsername(String username) {
        this.username = username;
        return this;
    }
}

创建 HighLevelClientDocumentApiTest 文档测试类,注入RestHighLevelClient。测试类上面需要加上 @RunWith, @SpringBootTest 2个注解。

  • 注意:@SpringBootTest(classes = ElasticsearchDemoApplication.class)中的classes的值要指向启动类才可以正常注入属性。也就是说 ElasticsearchDemoApplication 是springboot的启动类。若不加这个属性,运行测试用例时会报RestHighLevelClient的空指针。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchDemoApplication.class)
public class HighLevelClientDocumentApiTest {

    @Autowired
    private RestHighLevelClient highLevelClient;
    
}

在文档测试类 HighLevelClientDocumentApiTest 中新增创建文档、获取文档、更新文档、删除文档、批量插入数据、查询的测试方法,完整代码如下。

package com.kz.es.service;

import com.alibaba.fastjson.JSONObject;
import com.kz.es.ElasticsearchDemoApplication;
import com.kz.es.domain.User;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * RestHighLevelClient 文档测试类
 *
 * @Author kongzi
 * @Date 2022/10/25 15:52
 * @Version 1.0
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchDemoApplication.class)
public class HighLevelClientDocumentApiTest {

    @Autowired
    private RestHighLevelClient highLevelClient;

    /**
     * 创建文档
     */
    @Test
    public void createDocument() throws IOException {
        User user = new User().setId(1).setUsername("张三");

        //1、构建请求(若指定的索引不存在,会自动创建)
        IndexRequest request = new IndexRequest("user_index");

        //2、设置规则  PUT /user_index/_doc/1
        request.id("1");//设置id
        request.timeout(TimeValue.timeValueSeconds(1));//设置超时时间

        //3、将数据放入到请求中,以JSON的格式存放
        request.source(JSONObject.toJSONString(user), XContentType.JSON);

        //4、客户端发送请求,获取响应结果
        IndexResponse response = highLevelClient.index(request, RequestOptions.DEFAULT);

        //5、打印
        System.out.println("响应结果:"+response.toString());
    }

    /**
     * 获取文档
     */
    @Test
    public void getDocument() throws IOException {
        //获取id为1的文档的信息
        GetRequest request = new GetRequest("user_index","1");

        boolean exists = highLevelClient.exists(request, RequestOptions.DEFAULT);
        System.out.println("文档是否存在:"+exists);
        //如果存在,获取文档信息
        if (exists){
            GetResponse response = highLevelClient.get(request, RequestOptions.DEFAULT);
            System.out.println("文档内容为:"+response.getSourceAsString());
        }
    }

    /**
     * 更新文档
     */
    @Test
    public void updateDocument() throws IOException {
        //更新id为1的文档的信息
        UpdateRequest request = new UpdateRequest("user_index", "1");

        User user = new User();
        user.setUsername("李四");
        request.doc(JSONObject.toJSONString(user), XContentType.JSON);

        //客户端执行更新请求
        UpdateResponse response = highLevelClient.update(request, RequestOptions.DEFAULT);
        System.out.println("更新状态:" +response.status());
    }

    /**
     * 删除文档
     */
    @Test
    public void deleteDocument() throws IOException {
        //构建删除请求
        DeleteRequest request = new DeleteRequest("user_index", "1");
        //客户端执行删除请求,并获取响应结果
        DeleteResponse response = highLevelClient.delete(request, RequestOptions.DEFAULT);
        //打印
        System.out.println("删除状态:"+response.status());
    }

    /**
     * 批量插入数据
     */
    @Test
    public void createBulkDocument() throws IOException {
        //构建批量插入的请求
        BulkRequest request = new BulkRequest();
        //设置超时时间
        request.timeout("10s");

        //设置数据
        List<User> list = new ArrayList<>();
        list.add(new User().setId(1).setUsername("张三"));
        list.add(new User().setId(2).setUsername("李四"));
        list.add(new User().setId(3).setUsername("王五"));
        list.add(new User().setId(4).setUsername("赵六"));

        //批量插入请求设置
        for (int i = 0; i < list.size(); i++) {
            request.add(
                    new IndexRequest("user_index")//设置索引
                            .id(String.valueOf(i+1))//设置文档的id,如果没有指定,会随机生成,自己测试
                            .source(JSONObject.toJSONString(list.get(i)), XContentType.JSON)//设置要添加的资源,类型为JSON
            );
        }
        BulkResponse response = highLevelClient.bulk(request, RequestOptions.DEFAULT);
        System.out.println("批量插入是否失败:"+response.hasFailures());
    }

    /**
     * 查询
     */
    @Test
    public void query() throws IOException {
        //1、构建搜索请求
        SearchRequest request = new SearchRequest("user_index");

        //2、设置搜索条件,使用该构建器进行查询
        SearchSourceBuilder builder = new SearchSourceBuilder();//生成构建器

        //查询条件我们可以用工具类QueryBuilders来构建
        //QueryBuilders.termQuery():精确匹配
        //QueryBuilders.matchAllQuery():全文匹配

        //构建精确匹配查询条件
        //构建精确匹配查询条件
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("username.keyword", "李四");
//        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
//        WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("username", "张");
        builder.query(termQueryBuilder);

        //3、将搜索条件放入搜索请求中
        request.source(builder);
        //4、客户端执行搜索请求
        SearchResponse response = highLevelClient.search(request, RequestOptions.DEFAULT);

        //5、打印测试
        SearchHit[] hits = response.getHits().getHits();
        System.out.println("共查询到"+hits.length+"条数据");
        System.out.println("查询结果:");
        for (int i = 0; i < hits.length; i++) {
            System.out.println(hits[i].getSourceAsString());
        }
    }
}

启动springboot项目后,鼠标右键在 HighLevelClientDocumentApiTest 文档测试类的每个方法上分别运行测试用例,可以看到文档能够正确的创建、获取、删除、查询等。通过 head插件 或 Kibana 查询也可以看到同样的结果。

1.4 SpringBoot模版方式接入(不建议)

还可以用SpringBoot的模版来直接接入使用的,也就是以下这样的接入方式。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

但是看下java api的官方文档:

Deprecated in 7.0.0.
The TransportClient is deprecated in favour of the Java High Level REST Client and will be removed in Elasticsearch 8.0. The migration guide describes all the steps needed to migrate.

再看看模版方式引入的源码:

image-1666698526026

可以看到直接模版方式的java api调用方式,后续官方会不支持了,不建议使用,要使用Java High Level REST Client来代替,Elasticsearch 8.0版本后直接移除,为避免以后更新换代还得做迁移,所以不建议使用。

2、Springboot整合Java API客户端

在Elasticsearch7.15版本之后,Elasticsearch官方将它的高级客户端RestHighLevelClient标记为弃用状态。同时推出了全新的Java API客户端Elasticsearch Java API Client,该客户端也将在Elasticsearch8.0及以后版本中成为官方推荐使用的客户端。以下来源ElasticSearch官网:

The Elasticsearch Java API Client is an entirely new client library that has no relation to the older High Level Rest Client (HLRC). This was a deliberate choice to provide a library that is independent from the Elasticsearch server code and that provides a very consistent and easier to use API for all Elasticsearch features.
From https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/migrate-hlrc.html

  • Elasticsearch Java API Client 支持除 Vector tile search API 和 Find structure API 之外的所有 Elasticsearch API。且支持所有API数据类型,并且不再有原始JsonValue属性。它是针对Elasticsearch8.0及之后版本的客户端。

2.1 新建项目并添加依赖

新建springboot项目并添加相关依赖。

<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>7.16.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.0.1</version>
</dependency>

这里有个坑得注意下,springboot 默认有一个 elasticsearch 版本,如 springbootboot 2.2.13.RELEASE 默认的 elasticsearch-rest-client 版本是6.3.2,与elasticsearch-java不兼容,所以我们要使用更高版本的springboot,如 2.3.1.RELEASE。另外如果项目启动错误,可以在 properties 标签中显示指定 <elasticsearch-java.version>7.16.3</elasticsearch-java.version> 版本,可以避免部分类找不到的问题。

<properties>
  <springboot.version>2.3.1.RELEASE</springboot.version>
  
  <!-- springboot 2.3.1.RELEASE 默认的 elasticsearch-rest-client 版本是7.6.2,若项目启动错误,此处可以显示指定为7.16.3 -->
  <elasticsearch-java.version>7.16.3</elasticsearch-java.version>
</properties>

2.2 添加配置类

新建 ElasticSearchConfig.java 配置类,并在该配置类中配置一个ElasticsearchClient对象。配置使用的属性都是通过yml配置文件自动注入的。

package com.kz.es.config;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * ElasticSearchConfig 配置类
 *
 * @Author kongzi
 * @Date 2022/10/24 16:35
 * @Version 1.0
 */
@Configuration
public class ElasticSearchConfig {

    @Value("${elasticsearch.host}")
    private String host;

    @Value("${elasticsearch.port}")
    private int port;

    @Value("${elasticsearch.connTimeout}")
    private int connTimeout;

    @Value("${elasticsearch.socketTimeout}")
    private int socketTimeout;

    @Value("${elasticsearch.connectionRequestTimeout}")
    private int connectionRequestTimeout;

    // 协议,默认http
    @Value("${elasticsearch.scheme:http}")
    private String scheme;

    // 用户名,默认elastic
    @Value("${elasticsearch.username:elastic}")
    private String username;

    // 密码,默认elastic
    @Value("${elasticsearch.password:elastic}")
    private String password;
    
    /**
     * 配置API客户端
     *
     * @return ElasticsearchClient对象
     */
    @Bean
    public ElasticsearchClient elasticsearchClient() {
        // 创建低级客户端
        RestClient restClient = RestClient.builder(new HttpHost(host, port)).build();
        // 使用Jackson映射器创建传输层
        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
        // 创建API客户端
        return new ElasticsearchClient(transport);

        // es设置了密码,可以这样连接
		/*
		CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("账号", "密码"));
        RestClient restClient = RestClient.builder(new HttpHost(host, port)).setHttpClientConfigCallback(httpAsyncClientBuilder -> {
            httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            return httpAsyncClientBuilder;
        }).build();
        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
        return new ElasticsearchClient(transport);
        */
    }
}

application.yml文件内容:

elasticsearch:
  host: 172.19.82.206
  port: 9200
  connTimeout: 3000
  socketTimeout: 5000
  connectionRequestTimeout: 500
  scheme: http
  username: elastic
  password: elastic

至此,springboot已经整合好了ElasticSearch,接下来要做的就是具体操作了。

2.3 编写测试类

2.3.1 索引测试类

创建 ElasticsearchClientIndexApiTest 索引测试类,注入 ElasticsearchClient。测试类上面需要加上 @RunWith, @SpringBootTest 2个注解。

  • 注意:@SpringBootTest(classes = ElasticsearchDemoApplication.class)中的classes的值要指向启动类才可以正常注入属性。也就是说 ElasticsearchDemoApplication 是springboot的启动类。若不加这个属性,运行测试用例时会报 ElasticsearchClient 的空指针。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchDemoApplication.class)
public class ElasticsearchClientIndexApiTest {

    @Autowired
    private ElasticsearchClient elasticsearchClient;

}

在索引测试类 ElasticsearchClientIndexApiTest 中新增创建索引、检查索引、获取索引、删除索引的测试方法,完整代码如下。

package com.kz.es.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.mapping.KeywordProperty;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.mapping.TextProperty;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import com.kz.es.ElasticsearchDemoApplication;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 *  ElasticsearchClient 索引测试类
 *
 * @Author kongzi
 * @Date 2022/10/26 07:54
 * @Version 1.0
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchDemoApplication.class)
public class ElasticsearchClientIndexApiTest {

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    //对应的kibana语句
    /*
    PUT /book
    {
        "mappings": {
        "properties": {
            "name":{
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_smart"
            },
            "price":{
                "type": "keyword"
            },
            "imgPath":{
                "type": "keyword"
            }
        }
    }
    }
    */
    // 创建索引
    @Test
    public void createIndex() throws IOException {

        // 配置索引
        Map<String, Property> property = new HashMap<>();
        /*
            分析器主要有两种情况会被使用:
                第一种是插入文档时,将text类型的字段做分词然后插入倒排索引,
                第二种就是在查询时,先对要查询的text类型的输入做分词,再去倒排索引搜索
            analyzer: 分词器
            searchAnalyzer: 查询分词器
         */
        property.put("name", new Property(new TextProperty.Builder().analyzer("ik_max_word").searchAnalyzer("ik_smart").index(true).store(true).build()));
        property.put("price", new Property(new KeywordProperty.Builder().index(true).store(true).build()));
        property.put("imgPath", new Property(new KeywordProperty.Builder().index(true).store(true).build()));

        TypeMapping typeMapping = new TypeMapping.Builder().properties(property).build();

        /*
            索引设置:
            numberOfReplicas:是数据分片数,默认为5,有时候设置为3
            numberOfReplicas 是数据备份数,如果只有一台机器,设置为0
         */
        IndexSettings indexSettings = new IndexSettings.Builder().numberOfReplicas("5").build();

        CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder()
                .index("book")
                .aliases("b1", new Alias.Builder().isWriteIndex(true).build())
                .mappings(typeMapping)
                .settings(indexSettings)
                .build();

        CreateIndexResponse createIndexResponse = elasticsearchClient.indices().create(createIndexRequest);
        System.out.println(createIndexResponse.acknowledged());

        // 或下面的方式
//        // 创建索引
//        CreateIndexResponse createIndexResponse = elasticsearchClient.indices().create(c -> c.index("book"));
//        // 打印结果
//        System.out.println(createIndexResponse.acknowledged());
    }

    //测试判断是否拥有某个索引
    @Test
    public void existsIndex() throws IOException {
        //创建获取索引请求
        ExistsRequest existsRequest = new ExistsRequest.Builder().index("b2").build();
        //执行获取索引请求判断是否有这个索引
        BooleanResponse booleanResponse = elasticsearchClient.indices().exists(existsRequest);
        System.out.println(booleanResponse.value());
    }

    //获取索引信息
    @Test
    public void getIndex() throws IOException {
        //创建获取索引请求
        GetIndexRequest getIndexRequest = new GetIndexRequest.Builder().index("book").build();
        //执行获取索引请求判断是否有这个索引
        GetIndexResponse getIndexResponse = elasticsearchClient.indices().get(getIndexRequest);
        System.out.println(getIndexResponse.result());

        // 或下面的方式
//        GetIndexResponse getIndexResponse = elasticsearchClient.indices().get(e->e.index("book"));
//        System.out.println(String.join(",", getIndexResponse.result().keySet()));
    }

    //DELETE /book
    // 删除索引
    @Test
    public void deleteIndex() throws IOException {
        //创建删除索引请求
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest.Builder().index("book").build();
        //执行
        DeleteIndexResponse deleteIndexResponse = elasticsearchClient.indices().delete(deleteIndexRequest);
        System.out.println(deleteIndexResponse.acknowledged());

        // 或下面的方式
//        DeleteIndexResponse deleteIndexResponse = elasticsearchClient.indices().delete(e->e.index("book"));
//        System.out.println(deleteIndexResponse.acknowledged());
    }
}

2.3.2 文档测试类

创建文档就相当于数据库中添加一条记录,因此,在这里,我们首先新建一个实体类Book,一个Book对象就相当于一个文档。

package com.kz.es.domain;

/**
 * Book实体类
 *
 * @Author kongzi
 * @Date 2022/10/26 08:21
 * @Version 1.0
 */
public class Book {
    private String name;
    private String price;
    private String imgPath;

    public Book(){
    }

    public Book(String name, String price, String imgPath) {
        this.name = name;
        this.price = price;
        this.imgPath = imgPath;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    public String getImgPath() {
        return imgPath;
    }

    public void setImgPath(String imgPath) {
        this.imgPath = imgPath;
    }
}

创建 ElasticsearchClientDocumentApiTest 文档测试类,注入 ElasticsearchClient。测试类上面需要加上 @RunWith, @SpringBootTest 2个注解。

  • 注意:@SpringBootTest(classes = ElasticsearchDemoApplication.class)中的classes的值要指向启动类才可以正常注入属性。也就是说 ElasticsearchDemoApplication 是springboot的启动类。若不加这个属性,运行测试用例时会报RestHighLevelClient的空指针。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchDemoApplication.class)
public class ElasticsearchClientDocumentApiTest {

    @Autowired
    private ElasticsearchClient elasticsearchClient;
    
}

在文档测试类 ElasticsearchClientDocumentApiTest 中新增创建文档、获取文档、更新文档、删除文档、批量插入数据、查询的测试方法,完整代码如下。

package com.kz.es.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.kz.es.ElasticsearchDemoApplication;
import com.kz.es.domain.Book;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  ElasticsearchClient 文档测试类
 *
 * @Author kongzi
 * @Date 2022/10/26 08:14
 * @Version 1.0
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchDemoApplication.class)
public class ElasticsearchClientDocumentApiTest {

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    // kibana语句
    /*
    PUT /book/_doc/1
    {
        "name":"高性能Mysql",
        "price":"81.32",
        "imgPath":"https://img30.360buyimg.com/vc/jfs/t24145/294/1264419103/700620/6d764088/5b580f1eN305e28c1.jpg"
    }
    */
    //创建文档
    @Test
    public void createDocument() throws IOException {
        // 创建对象
        Book book = new Book("高性能Mysql", "81.32", "https://img30.360buyimg.com/vc/jfs/t24145/294/1264419103/700620/6d764088/5b580f1eN305e28c1.jpg");
        // 创建添加文档的请求
        IndexRequest<Book> indexRequest = new IndexRequest.Builder<Book>().index("book").document(book).id("1").build();

        // 执行
        IndexResponse indexResponse = elasticsearchClient.index(indexRequest);

        System.out.println(indexResponse.toString());//返回索引信息
        System.out.println(indexResponse.result());//返回id

        // 或下面的方式
        // 其中,index为文档Doc所属索引的名字,id为该文档的id,document参数现在可以直接传入Java对象了。
//        // 构建一个创建Doc的请求
//        CreateResponse createResponse = elasticsearchClient.create(e->e.index("book").id("1").document(book));
//        // 打印请求结果
//        System.out.println(createResponse.result());
    }

    //查看是否存在
    @Test
    public void existsDocument() throws IOException {
        GetRequest getRequest = new GetRequest.Builder().index("book").id("1").build();
        GetResponse<Book> bookGetResponse = elasticsearchClient.get(getRequest, Book.class);
        //查看是否存在
        System.out.println(bookGetResponse.found());
    }


    //DELETE /book/_doc/1
    //删除文档信息
    @Test
    public void deleteDocument() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest.Builder().index("book").id("1").build();
        DeleteResponse delete = elasticsearchClient.delete(deleteRequest);
        System.out.println(delete.result());//删除状态
        System.out.println(delete.toString());//删除信息

        // 或下面的方式
        // 构建删除文档请求
//        DeleteResponse response = elasticsearchClient.delete(e -> e.index("book").id("1"));
//        // 打印请求结果
//        System.out.println(response.result());
    }

    /**
     * 获取文档
     * @throws IOException
     */
    @Test
    public void getDocument() throws IOException {
        // get /index/_doc/1
        GetRequest getRequest = new GetRequest.Builder().index("book").id("1").build();
        GetResponse<Book> bookGetResponse = elasticsearchClient.get(getRequest, Book.class);

        Book book = bookGetResponse.source();
        System.out.println("book = " + book);

        // 或下面的方式
//        // 构建查询请求
//        GetResponse<Book> response = elasticsearchClient.get(e -> e.index("book").id("1"), Book.class);
//        // 打印查询结果
//        System.out.println(response.source().toString());
    }

    /**
     * 分页获取文档
     */
    @Test
    public void getDocumentByPage() throws IOException {
        SearchRequest searchRequest = new SearchRequest.Builder().index("book").from(0).size(10).build();
        SearchResponse<Book> bookSearchResponse = elasticsearchClient.search(searchRequest, Book.class);

        List<Hit<Book>> bookList = bookSearchResponse.hits().hits();
        bookList.forEach(item->System.out.println(item.source()));
    }

    /**
     * 更新文档
     */
    @Test
    public void updateDocument() throws IOException {
        Book book = new Book();
        book.setName("算法指南");

        UpdateRequest<Book, Book> bookBookUpdateRequest = new UpdateRequest.Builder<Book, Book>().index("book").id("1").doc(book).build();
        UpdateResponse<Book> personUpdateResponse = elasticsearchClient.update(bookBookUpdateRequest, Book.class);
        // 执行结果
        System.out.println(personUpdateResponse.result());

        // 或下面的方式
        // 构建需要修改的内容,这里使用了Map
//        Map<String, Object> map = new HashMap<>();
//        map.put("name", "算法指南");
//
//        // 构建修改文档的请求
//        UpdateResponse<Book> response = elasticsearchClient.update(e -> e.index("book").id("1").doc(map), Book.class);
//        // 打印请求结果
//        System.out.println(response.result());
    }
}
0

评论区