SpringBoot中使用Elasticsearch

一、前言

Elasticsearch 是一个分布式的 RESTful 风格的搜索和数据分析引擎,能够解决越来越多的用例。作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。
Elasticsearch 是一个实时的分布式搜索分析引擎, 它能让你以一个之前从未有过的速度和规模,去探索你的数据。 它被用作全文检索、结构化搜索、分析以及这三个功能的组合。

二、安装

2.1 环境配置

在安装Elasticsearch引擎之前,必须安装ES需要的软件环境,安装Java JDK和配置JAVA_HOME环境变量,ElasticSearch对JRE的版本是敏感的,错误的版本,会导致ElasticSearch无法运行,下载归档的JRE,请从Java Archive下载合适的版本。

2.2 下载ElasticSearch

从官方下载中心 ElasticSearch Download 下载ElasticSearch安装包,在楼主截图时,ES的最新版本是8.0.0,本文使用旧版本7.16.2,打开 past releases 选择过去的版本安装,下载zip文件格式。

2.3 开启ElasticSearch 服务

将zip文件解压到"E:\Program",进入 "E:\Program\elasticsearch-7.16.2\bin" 目录,双击执行 elasticsearch.bat,该脚本文件执行 ElasticSearch 安装程序,稍等片刻,打开浏览器,输入 http://localhost:9200 ,显式以下画面,说明ES安装成功。

2.4 将ElasticSearch 安装成Windows服务(可选)

1,打开DOS命令行界面,切换到ElasticSearch的bin目录,执行

service.bat install

2,启动ElasticSearch服务

service.bat start

3,在run界面,查看ElasticSearch服务

services.msc

三、Java整合Elasticsearch比较

Java操作ElasticSearch有两种方式,一个是通过ES的9300端口使用TCP的方式操作,另一种是通过ES的9200端口使用HTTP的方式。
1)9300 TCP

spring-data-elasticsearch:transport-api.jar

springboot 版本不同, transport-api.jar 不同,不能适配 es 版本

7.x 已经不建议使用,8 以后就要废弃

2)9200 HTTP

JestClient:非官方,更新慢

RestTemplate:模拟发 HTTP 请求,ES 很多操作需要自己封装,麻烦

HttpClient:同上

Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作,API 层次分明,上手简单

综上所述,选择Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)是最优的选择,下面记录如何整合
Elasticsearch-Rest-Client 官方文档

四、SpringBoot整合Spring Data Elasticsearch

4.1 引入依赖

新建一个基础的springboot项目,本文使用的springboot版本为:2.6.2,新增以下依赖:

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

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.71</version>
        </dependency>

4.2 配置 application.yml

spring:
  data:
    elasticsearch:
      client:
        reactive:
          endpoints: 127.0.0.1:9200
  elasticsearch:
    rest:
      uris: 127.0.0.1:9200

4.3 编码

定义实体
User.java

@Builder
@Setter
@Getter
@Setting(shards = 3, replicas = 1)
@Document(indexName = "index_user", createIndex = false)
public class User {

    @Id
    @Field(store = true, name = "user_id", type = FieldType.Keyword)
    private String userId;

    @Field(store = true, searchAnalyzer = "ik_max_word", analyzer = "ik_max_word")
    private String name;

    @Field(store = true, name = "login_name", type = FieldType.Keyword)
    private String loginName;

    @Field(store = true, type = FieldType.Integer)
    private Integer age;

    @Field(store = true, type = FieldType.Date)
    private Date birthday;

    @Field(store = true, searchAnalyzer = "ik_max_word", analyzer = "ik_max_word")
    private String desc;

    @Field(store = true, name = "head_url", type = FieldType.Keyword)
    private String headUrl;

}

UserService.java

public interface UserService {

    User save(User user);

    void update(User user);

    User getById(String userId);

    String delete(String userId);

    SearchHits<User> searchForPage(User user);

    SearchHits<User> highlight(User user);

    SearchHits<User> sort(User user);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Override
    public User save(User user) {
        User u = elasticsearchRestTemplate.save(user);
        return u;
    }

    @Override
    public void update(User user) {
        try {
            Document document = Document.from(objectToMap(user));
            UpdateQuery updateQuery = UpdateQuery.builder(user.getUserId())  // 2 是文档的ID
                    .withDocument(document)
                    .build();
            UpdateResponse result = elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("index_user"));
        } catch (IllegalAccessException e) {

        }
    }

    @Override
    public User getById(String userId) {
        return  elasticsearchRestTemplate.get(userId, User.class);
    }

    @Override
    public String delete(String userId) {
        String result = elasticsearchRestTemplate.delete(userId, User.class);
        return result;
    }

    @Override
    public SearchHits<User> searchForPage(User user) {
        Pageable pageable = PageRequest.of(0,10);  // page 从第 0 页开始

        Query query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchQuery("desc", user.getDesc()))
                .withQuery(QueryBuilders.termQuery("age", user.getAge()))
                .withPageable(pageable)
                .build();

        SearchHits<User> result = elasticsearchRestTemplate.search(query, User.class);

        return result;
    }

    @Override
    public SearchHits<User> highlight(User user) {
        Pageable pageable = PageRequest.of(0,10);  // page 从第 0 页开始

        HighlightBuilder.Field highlightField = new HighlightBuilder.Field("desc")
                .preTags("<span>")
                .postTags("</span>");

        Query query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchQuery("desc", user.getDesc()))
                .withHighlightFields(highlightField)
                .withPageable(pageable)
                .build();

        SearchHits<User> result = elasticsearchRestTemplate.search(query, User.class);
        return result;
    }

    @Override
    public SearchHits<User> sort(User user) {
        Pageable pageable = PageRequest.of(0,10);  // page 从第 0 页开始

        HighlightBuilder.Field highlightField = new HighlightBuilder.Field("desc")
                .preTags("<span>")
                .postTags("</span>");

        SortBuilder<FieldSortBuilder> sortBuilder
                = new FieldSortBuilder("age").order(SortOrder.DESC);

        Query query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchQuery("desc", user.getDesc()))
                .withHighlightFields(highlightField)
                .withSort(sortBuilder)  // 排序可加多个
                .withPageable(pageable)
                .build();

        SearchHits<User> result = elasticsearchRestTemplate.search(query, User.class);

        return result;
    }

    public Map<String, Object> objectToMap(Object obj) throws IllegalAccessException {
        Map<String, Object> map = new HashMap<String, Object>();
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = field.get(obj);
            if (!fieldName.equals("userId")) {
                map.put(fieldName, value);
            }
        }
        return map;
    }

}

4.4 测试

UserController.java

@RestController
@RequestMapping("/api/user")
public class UserController {

    @Autowired
    private UserService userService;


    @RequestMapping( value = "/saveUser")
    public String saveUser(@RequestParam(value ="birth") @DateTimeFormat(pattern="yyyy-MM-dd") Date birthday, User user){
        user.setBirthday(birthday);
        userService.save(user);
        return "sucss";
    }

    @RequestMapping( value = "/updateUser")
    public String updateUser(User user){
        userService.update(user);
        return "sucss";
    }

    @RequestMapping( value = "/getUser", method = RequestMethod.GET)
    public User getUser(String userId){
        return userService.getById(userId);
    }

    @RequestMapping( value = "/deleteUser", method = RequestMethod.GET)
    public String deleteUser(String userId){
        userService.delete(userId);
        return "sucss";
    }

    @RequestMapping( value = "/searchForPage", method = RequestMethod.GET)
    public SearchHits<User> searchForPage(User user){
        return userService.searchForPage(user);
    }

    @RequestMapping( value = "/highlight", method = RequestMethod.GET)
    public SearchHits<User> highlight(User user){
        return userService.highlight(user);
    }

    @RequestMapping( value = "/sort", method = RequestMethod.GET)
    public SearchHits<User> sort(User user){
        return userService.sort(user);
    }
}

新增:
1a356210-6a63-4e93-87ff-4daa07b8d54c.png
更新:
8a8db32b-3261-49c1-ad96-2e622e35bb09.png
根据条件查询:
358c1c91-1dce-48a3-a724-3f9e74cab119.png
翻页查询:
751a8433-e02a-495e-86cc-9667c734a6a2.png
高亮查询:
4d69241f-b828-46b9-bd7e-ff7a5da334a5.png

五、SpringBoot整合RestHighLevelClient

5.1 引入依赖

新建一个基础的springboot项目,本文使用的springboot版本为:2.6.2,新增以下依赖:

<!-- https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client -->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.16.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.16.2</version>
        </dependency>

5.2 配置 application.yml

spring:
  elasticsearch:
    nodes: 127.0.0.1:9200
    username:
    password:

elasticsearch:
  ip: 127.0.0.1:9200

5.3 ElasticsearchRestClient

ElasticsearchRestClient.java

@Configuration
public class ElasticsearchRestClient {

   public static final RequestOptions COMMON_OPTIONS;

   /**
    * ES地址,ip:port
    */
   @Value("${elasticsearch.ip}")
   String ipPort;

   static {
      // RequestOptions类保存了请求的部分,这些部分应该在同一个应用程序中的许多请求之间共享。
      // 创建一个singqleton实例,并在所有请求之间共享它。可以设置请求头之类的一些配置
      RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
      // builder.addHeader("Authorization", "Bearer " + TOKEN);
      /* builder.setHttpAsyncResponseConsumerFactory(
               new HttpAsyncResponseConsumerFactory
                       .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 *1024));*/
      COMMON_OPTIONS = builder.build();
   }
   //创建ES实例
   @Bean
   public RestHighLevelClient restHighLevelClient(){
      return new RestHighLevelClient(RestClient.builder(makeHttpHost(ipPort)));
   }

   private HttpHost makeHttpHost(String s) {
      String[] address = s.split(":");
      String ip = address[0];
      int port = Integer.parseInt(address[1]);
      return new HttpHost(ip, port, "http");
   }

}

5.4 测试

这里我们使用测试单元进行功能测试,测试基础使用代码如下:

@SpringBootTest
class ElasticsearchClientApplicationTests {

    @Data
    @ToString
    @Accessors(chain=true)
    class User{
        private String name;
        private int age;
        private char sex;
        private Date birth;
    }

    @Autowired
    RestHighLevelClient restHighLevelClient;
}

数据插入示例

    @Test
    void insertJson() throws IOException {
        //测试保存
        IndexRequest request = new IndexRequest("personinfo");
        request.id("2");
        String jsonString = "{" +
                "\"name\":\"wno706\"," +
                "\"address\":\"陕西省西安市未央区建元二路158号-1105\"," +
                "\"age\":\"24\"," +
                "\"salary\":\"88888\"," +
                "\"birth\":\"2013-06-10\"," +
                "\"desc\":\"trying out Elasticsearch\"" +
                "}";
        request.source(jsonString, XContentType.JSON);
        IndexResponse index=
                restHighLevelClient.index(request, ElasticsearchRestClient.COMMON_OPTIONS);
        System.out.printf("index-%s---%s-------------:",index.getId(),index.toString());
    }

    @Test
    void insertMap() throws IOException {
        //测试保存
        IndexRequest request = new IndexRequest("personinfo");
        request.id("2");
        request.source("name","wno706","address","陕西省西安市未央区建元二路158号-1105","birth","2013-06-10");
        IndexResponse index=
                restHighLevelClient.index(request, ElasticsearchRestClient.COMMON_OPTIONS);
        System.out.printf("index-%s---%s-------------:",index.getId(),index.toString());
    }

创建索引

    @Test
    void createIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("user");
        CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        boolean ack = response.isAcknowledged();
        System.out.printf("%s-------------:",ack);
    }

删除索引

    @Test
    void deleteIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("user");
        AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        System.out.printf("索引操作===>%s",acknowledgedResponse.isAcknowledged());
    }

新增数据

    @Test
    void insertDoc() throws IOException {
        IndexRequest request = new IndexRequest();
        request.index("user").id("1");
        User user = new User().setName("wno704").setAge(18).setSex('男').setBirth(new Date());
        ObjectMapper mapper = new ObjectMapper();
        String userJson = mapper.writeValueAsString(user);
        request.source(userJson, XContentType.JSON);
        IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        System.out.printf("Result===>%s==%s===%s",indexResponse.getId(),indexResponse,indexResponse.getResult());
    }
	

查询数据

    @Test
    void selectDoc() throws IOException{
        GetRequest request = new GetRequest();
        request.index("user").id("1");
        GetResponse documentFields = restHighLevelClient.get(request, RequestOptions.DEFAULT);
        System.out.printf("===>%s",documentFields.getSourceAsString());

    }

删除数据

    @Test
    void deleteDoc() throws IOException{
        DeleteRequest request = new DeleteRequest();
        request.index("user").id("1");
        DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        System.out.printf("===>%s",response.toString());
    }

批量添加数据

    @Test
    void bulkInsertDoc() throws IOException{
        BulkRequest request = new BulkRequest();
        ObjectMapper mapper = new ObjectMapper();
        User user1 = new User().setName("ww1").setAge(18).setSex('男');
        User user2 = new User().setName("ww2").setAge(17).setSex('女');
        User user3 = new User().setName("ww3").setAge(16).setSex('女');

        request.add(new IndexRequest().index("user").id("1").source(mapper.writeValueAsString(user1),XContentType.JSON));
        request.add(new IndexRequest().index("user").id("2").source(mapper.writeValueAsString(user2),XContentType.JSON));
        request.add(new IndexRequest().index("user").id("3").source(mapper.writeValueAsString(user3),XContentType.JSON ));

        BulkResponse response = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
        System.out.printf("Took===>%s",response.getTook());
        System.out.printf("Items===>%s",response.getItems());
    }

批量删除数据

    @Test
    void bulkDeleteDoc() throws IOException{
        BulkRequest request = new BulkRequest();

        request.add(new DeleteRequest().index("user").id("1"));
        request.add(new DeleteRequest().index("user").id("2"));
        request.add(new DeleteRequest().index("user").id("3"));

        BulkResponse response = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
        System.out.printf("Took===>%s",response.getTook());
        System.out.printf("Items===>%s",response.getItems());
    }

查询全部

    @Test
    void searchDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        request.source(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()));
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h.getSourceAsString());
        });

    }

条件查询

    @Test
    void searchQueryDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        request.source(new SearchSourceBuilder().query(QueryBuilders.termQuery("age",18)));
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h.getSourceAsString());
        });

    }

#### 分页查询

```java
    @Test
    void searchPageDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
        builder.from(0);
        builder.size(2);
        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h.getSourceAsString());
        });

    }

查询排序

    @Test
    void searchOrderDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
        builder.sort("age", SortOrder.DESC);
        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h.getSourceAsString());
        });

    }

查询指定字段

    @Test
    void searchSourceDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
        builder.sort("age", SortOrder.ASC);
        String[] excludes = {"sex"};
        String[] includes = {};
        builder.fetchSource(includes, excludes);
        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h.getSourceAsString());
        });

    }

组合查询

    @Test
    void searchBoolDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder();

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.matchQuery("age", 18));
        boolQueryBuilder.mustNot(QueryBuilders.matchQuery("sex", "女"));

        builder.query(boolQueryBuilder);

        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h.getSourceAsString());
        });

    }

范围查询

    @Test
    void searchRangeDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder();

        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age");
        rangeQuery.gte(17);
        rangeQuery.lte(18);
        builder.query(rangeQuery);

        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h.getSourceAsString());
        });

    }

模糊查询

    @Test
    void searchFuzzyDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.fuzzyQuery("name", "ww").fuzziness(Fuzziness.ONE));
        //.fuzziness(Fuzziness.ONE)匹配多少个

        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h.getSourceAsString());
        });
    }

高亮查询

    @Test
    void searchHighlighterDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder();
        TermsQueryBuilder termQueryBuilder = QueryBuilders.termsQuery("name", "ww1"); //准确查询
        //FuzzyQueryBuilder fuzzyQueryBuilde = QueryBuilders.fuzzyQuery("name", "ww"); //模糊

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<font color='red'>");
        highlightBuilder.postTags("</font>");
        highlightBuilder.field("name");

        builder.highlighter(highlightBuilder);
        builder.query(termQueryBuilder);
        //builder.query(fuzzyQueryBuilde);


        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());

        hits.forEach(h ->{
            System.out.printf("===>%s",h);
        });
    }

聚合查询

    @Test
    void searchAggregationDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder();

        MaxAggregationBuilder aggregationBuilder = AggregationBuilders.max("maxAge").field("age");

        builder.aggregation(aggregationBuilder);

        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());
        System.out.printf("Aggregations===>%s",response.getAggregations());
    }

聚合查询-分组

    @Test
    void searchAggregationGroupDoc() throws IOException{
        SearchRequest request = new SearchRequest();
        request.indices("user");
        SearchSourceBuilder builder = new SearchSourceBuilder();

        AggregationBuilder aggregationBuilder = AggregationBuilders.terms("ageGroup").field("age");

        builder.aggregation(aggregationBuilder);

        request.source(builder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.printf("TotalHits===>%s",hits.getTotalHits());
        System.out.printf("Took===>%s",response.getTook());
        System.out.printf("Aggregations===>%s",response.getAggregations());
    }

posted on 2026-08-10 15:38  爱河  阅读(2)  评论(0)    收藏  举报

导航