服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - SpringBoot整合ES-Elasticsearch的实例

SpringBoot整合ES-Elasticsearch的实例

2022-12-01 16:15融极 Java教程

这篇文章主要介绍了SpringBoot整合ES-Elasticsearch的实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

概述

本文介绍 Spring Boot 项目中整合 ElasticSearch 并实现 CRUD 操作,包括分页、滚动等功能。

添加Maven依赖

?
1
2
3
4
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置application.yml

?
1
2
3
4
spring:
  elasticsearch:
    rest:
      uris: 192.168.1.81:9200

创建索引对象

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.practice.elkstudy.entity;
import cn.hutool.core.date.DateTime;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import java.util.Date;
/**
 * @Description : 文档模型
 * @Version : V1.0.0
 * @Date : 2021/12/22 14:08
 */
@Document(indexName = "article")
@Data
public class ArticleEntity {
    @Id
    private String id;
    private String title;
    private String content;
    private Integer userId;
    private Date createTime = DateTime.now();
}

SpringBoot操作ES数据的三种方式

  • 实现ElasticsearchRepository接口
  • 引入ElasticsearchRestTemplate
  • 引入ElasticsearchOperations

实现索引对应的Repository

?
1
2
3
4
5
6
7
8
9
10
package com.practice.elkstudy.repository;
import com.practice.elkstudy.entity.ArticleEntity;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
 * @Description : article数据操作接口
 * @Version : V1.0.0
 * @Date : 2021/12/22 14:18
 */
public interface ArticleRepository extends ElasticsearchRepository<ArticleEntity,String> {
}

文档操作

下面可以使用这个 ArticleRepository 来操作 ES 中的 Article 数据。

我们这里没有手动创建这个 Article 对应的索引,由 elasticsearch 默认生成。

下面的接口,实现了 spring boot 中对 es 数据进行插入、更新、分页查询、滚动查询、删除等操作。可以作为一个参考。

其中,使用了 Repository 来获取、保存、删除 ES 数据;使用 ElasticsearchRestTemplate 或 ElasticsearchOperations 来进行分页/滚动查询。

文档保存、查询、删除

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.practice.elkstudy.controller.controller;
import com.practice.elkstudy.entity.ArticleEntity;
import com.practice.elkstudy.repository.ArticleRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Optional;
/**
 * @Description : article控制类
 * @Version : V1.0.0
 * @Date : 2021/12/22 14:11
 */
@RestController
@RequestMapping("/elk")
public class ArticleController {
    @Resource
    private ArticleRepository articleRepository;
    /**
     * 根据文档id查询数据
     *
     * @param id 文档id
     * @return 文档详情
     */
    @GetMapping("/byId")
    public String findById(@RequestParam String id) {
        Optional<ArticleEntity> record = articleRepository.findById(id);
        return  record.toString();
    }
    /**
     * 保存文档信息
     *
     * @param article 文档详情
     * @return 保存的文档信息
     */
    @PostMapping("/saveArticle")
    public String saveArticle(@RequestBody ArticleEntity article) {
        ArticleEntity result = articleRepository.save(article);
        return result.toString();
    }
    @DeleteMapping("/deleteById")
    public String deleteArticle(@RequestParam String id) {
        articleRepository.deleteById(id);
        return "success";
    }
}

分页查询与滚动查询

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.practice.elkstudy.controller.controller;
import com.practice.elkstudy.entity.ArticleEntity;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * @Description : article高级查询
 * @Version : V1.0.0
 * @Date : 2021/12/22 15:10
 */
@RestController
@RequestMapping("/elk")
public class ArticleAdvanceController {
    @Autowired
    private ElasticsearchRestTemplate restTemplate;
    @Autowired
    private ElasticsearchOperations operations;
    /**
     * 分页查询
     *
     * @param pageNum  页码,从0开始
     * @param pageSize 分页大小
     * @return 查询结果
     */
    @GetMapping("/queryPage")
    public String queryPage(@RequestParam int pageNum, @RequestParam int pageSize) {
        NativeSearchQuery query = new NativeSearchQuery(new BoolQueryBuilder());
        query.setPageable(PageRequest.of(pageNum, pageSize));
        // 方法1
        SearchHits<ArticleEntity> search = restTemplate.search(query, ArticleEntity.class);
        // 方法2
        // SearchHits<ArticleEntity> search = operations.search(query, ArticleEntity.class);
        List<ArticleEntity> articles = search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
        return articles.toString();
    }
    /**
     * 滚动查询
     *
     * @param scrollId 滚动id
     * @param pageSize 分页大小
     * @return 查询结果
     */
    @GetMapping(value = "/scrollQuery")
    public String scroll(String scrollId, Integer pageSize) {
        if (pageSize == null || pageSize <= 0) {
            return "please input query page num";
        }
        NativeSearchQuery query = new NativeSearchQuery(new BoolQueryBuilder());
        query.setPageable(PageRequest.of(0, pageSize));
        SearchHits<ArticleEntity> searchHits;
        if (StringUtils.isEmpty(scrollId) || scrollId.equals("0")) {
            // 开启一个滚动查询,设置该scroll上下文存在60s
            // 同一个scroll上下文,只需要设置一次query(查询条件)
            searchHits = restTemplate.searchScrollStart(60000, query, ArticleEntity.class, IndexCoordinates.of("article"));
            if (searchHits instanceof SearchHitsImpl) {
                scrollId = ((SearchHitsImpl) searchHits).getScrollId();
            }
        } else {
            // 继续滚动
            searchHits = restTemplate.searchScrollContinue(scrollId, 60000, ArticleEntity.class, IndexCoordinates.of("article"));
        }
        List<ArticleEntity> articles = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
        if (articles.size() == 0) {
            // 结束滚动
            restTemplate.searchScrollClear(Collections.singletonList(scrollId));
            scrollId = null;
        }
        if (Objects.isNull(scrollId)) {
            Map<String, String> result = new HashMap<>(2);
            result.put("articles", articles.toString());
            result.put("message", "已到末尾");
            return result.toString();
        } else {
            Map<String, String> result = new HashMap<>();
            result.put("count", String.valueOf(searchHits.getTotalHits()));
            result.put("pageSize", String.valueOf(articles.size()));
            result.put("articles", articles.toString());
            result.put("scrollId", scrollId);
            return result.toString();
        }
    }
}

ES深度分页 vs 滚动查询

之前遇到的一个问题,日志检索的接口太慢了。

开始使用的是深度分页,即1,2,3…10,这样的分页查询,查询条件较多(十多个参数)、查询数据量较大(单个日志索引约2亿条数据)。

分页查询速度慢的原因在于:ES的分页查询,如查询第100页数据,每页10条,是先从每个分区(shard,一个索引默认是5个shard)中把命中的前100*10条数据查出来,然后协调节点进行合并操作,最后给出100页的数据。也就是说,实际被加载到内存的数据远远超过理想情况。

这样,索引分片数越多,查询页数越多,查询速度就越慢。ES默认的max_result_window是10000条,也就是正常情况下,用分页查询到10000条数据时,就不会在返回下一页数据了。

如果不需要进行跳页,比如直接查询第100页数据,或者数据量非常大,那么可以考虑用scroll查询。在scroll查询下,第1次需要根据查询参数开启一个scroll上下文,设置上下文缓存时间。以后的滚动只需要根据第一次返回的scrollId来进行即可。

scroll只支持往下滚动,如果想要往前滚动,还可以根据scrollId缓存查询结果,这样就可以实现上下文滚动查询了一一就像大家经常使用的淘宝商品检索时上下滚动一样。 

SpringBoot集成ES基本使用

?
1
2
3
#配置es
#Liunx 上的ip地址和配置端口号
spring.elasticsearch.rest.uris=192.168.113.129:9200

在test中测试

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import com.alibaba.fastjson.JSON;
import com.hzx.pojo.User;
import com.hzx.utils.ESconst;
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.support.master.AcknowledgedResponse;
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.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
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.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@Autowired
private RestHighLevelClient client;
@Test
void contextLoads() throws IOException {
    //创建索引请求
    CreateIndexRequest request = new CreateIndexRequest("hong_index");
    //客户端执行请求 IndicesClient  create创建请求  RequestOptions.DEFAULT默认请求参数
    CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
    //获取返回的参数
    System.out.println(createIndexResponse);
}
@Test
void test2() throws IOException {
    //获取指定索引库
    GetIndexRequest request = new GetIndexRequest("hong_index2");
    //判断获取索引是否存在
    boolean exists = client.indices().exists(request,RequestOptions.DEFAULT);
    //如果索引存在就返回为true  或者 为false
    System.out.println(exists);
}
@Test
void test3() throws IOException {
    //删除指定索引库
    DeleteIndexRequest request = new DeleteIndexRequest("hong_index");
    //获取删除索引
    AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
    //检查索引是否被删除
    System.out.println(delete.isAcknowledged());
}
    //测试添加文档
    @Test
    void test4() throws IOException {
        //创建对象
        User user = new User("枣信",18);
        //创建索引库
        IndexRequest request = new IndexRequest("hong_index");
        //规则 为 put /hong_index/_doc/1
        //创建的id
        request.id("1");
        //创建的时间
        request.timeout(TimeValue.timeValueSeconds(1));
//        request.timeout("1s");
        //将数据放入到请求     JSON.toJSONString(user)将对象转换为json
        request.source(JSON.toJSONString(user), XContentType.JSON);
        //客户端发送请求   向索引中添加数据
        IndexResponse indices = client.index(request, RequestOptions.DEFAULT);
        //获取返回的json对象
        System.out.println(indices.toString());
        //获取发送请求的状态 添加为CREATED  更新为OK
        System.out.println(indices.status());
    }
//获取文档信息
@Test
void test6() throws IOException {
    //根据索引传入的id获取
    GetRequest getRequest = new GetRequest("hong_index","1");
    //通过get获取信息
    GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
    //根据指定的Source获取对应内容
    System.out.println(getResponse.getSourceAsString());
    //打印json对象
    System.out.println(getResponse);
}
//更新 修改信息
@Test
void test7() throws IOException {
    //根据索引库传入的id更新
    UpdateRequest updateRequest = new UpdateRequest("hong_index","1");
    //更新时间
    updateRequest.timeout("1s");
    //创建对象
    User user = new User("李四", 26);
    //更新    将对象转换为json
    updateRequest.doc(JSON.toJSONString(user),XContentType.JSON);
    //客户端发送请求,进行更新
    UpdateResponse update = client.update(updateRequest, RequestOptions.DEFAULT);
    //获取更新状态
    System.out.println(update.status());
}
//删除文档信息
@Test
void test8() throws IOException {
    //根据传入的索引id进行删除
    DeleteRequest request = new DeleteRequest("hong_index","1");
    //发送请求,删除
    DeleteResponse delete = client.delete(request, RequestOptions.DEFAULT);
    //获取删除的状态   没有删除成功为NOT_FOUND 删除成功为OK
    System.out.println(delete.status());
}
//批量添加数据
@Test
void test9() throws IOException {
    //创建批量添加
    BulkRequest bulkRequest = new BulkRequest();
    //添加时间
    bulkRequest.timeout("8s");
    //创建一个arraylist集合
    ArrayList<User> userList = new ArrayList<>();
    userList.add(new User("李四",19));
    userList.add(new User("王五",25));
    userList.add(new User("赵刚",30));
    userList.add(new User("张三",21));
    userList.add(new User("赵六",36));
    userList.add(new User("小武",20));
    //批量处理请求
    for (int i = 0; i < userList.size(); i++) {
        //批量更新和删除 在这修改对应的请求即可       不添加id(""+(i+1)) 会默认随机id,在大数据情况下,让他默认随机id
        bulkRequest.add(new IndexRequest("hong_index").id(""+(i+1)).source(JSON.toJSONString(userList.get(i)),XContentType.JSON));
    }
    //批量添加发送请求
    BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    //获取批量添加的状态 返回false代表添加成功
    System.out.println(bulk.hasFailures());
}
//查询索引信息
@Test
void test10() throws IOException {
    //查询
    SearchRequest searchRequest = new SearchRequest(ESconst.ES_INDEX);
    //构建搜索条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    //查询条件,可以使用QueryBuilders工具来实现
    // QueryBuilders.termQuery精确查询
    // QueryBuilders.matchQuery()查询所有
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "李四");
    //查询的时间
    sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
    //将查询的sourceBuilder放入searchRequest中
    searchRequest.source(sourceBuilder);
    //发送请求
    SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
    //获取信息
    System.out.println(JSON.toJSONString(search.getHits()));
    //循环变量出信息
    for(SearchHit documentFields : search.getHits().getHits()){
        //获取所有信息
        System.out.println(documentFields.getSourceAsMap());
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。 

原文链接:https://blog.csdn.net/tianzhonghaoqing/article/details/122071875

延伸 · 阅读

精彩推荐
  • Java教程spring redis 如何实现模糊查找key

    spring redis 如何实现模糊查找key

    这篇文章主要介绍了spring redis 如何实现模糊查找key的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    路过君_P6232021-11-12
  • Java教程Java Web程序中利用Spring框架返回JSON格式的日期

    Java Web程序中利用Spring框架返回JSON格式的日期

    这里我们来介绍一下Java Web程序中利用Spring框架返回JSON格式的日期的方法,前提注意使用@DatetimeFormat时要引入一个类库joda-time-版本.jar,否则会无法访问相应...

    hellostory5552020-05-05
  • Java教程Java软件编程培训机构靠谱吗

    Java软件编程培训机构靠谱吗

    随着网络信息化的快速发展,Java培训受到越来越多人的青睐,目前Java工程师的薪资水平在不断攀升,但是有好多企业还是招不到合适的人才,为什么呢...

    Java之家5652020-09-14
  • Java教程Spring多个数据源配置详解

    Spring多个数据源配置详解

    在实际场景中,会有需要配置多个数据源的场景,本文就介绍一下如何配置Spring多数据源,具有一定的参考价值,感兴趣的可以了解一下...

    程序员阿牛9382021-12-05
  • Java教程Java多边形重心计算

    Java多边形重心计算

    今天小编就为大家分享一篇关于Java多边形重心计算,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    staHuri9082021-06-26
  • Java教程Java 快速排序(QuickSort)原理及实现代码

    Java 快速排序(QuickSort)原理及实现代码

    这篇文章主要介绍了Java 快速排序(QuickSort)原理及实现代码,有需要的朋友可以参考一下 ...

    java开发网3882019-11-01
  • Java教程swing登录注册界面设计

    swing登录注册界面设计

    这篇文章主要为大家详细介绍了swing登录注册界面的设计方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    lsh呵呵9882021-04-29
  • Java教程Feign 集成 Hystrix实现不同的调用接口不同的设置方式

    Feign 集成 Hystrix实现不同的调用接口不同的设置方式

    这篇文章主要介绍了Feign 集成 Hystrix实现不同的调用接口不同的设置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望...

    帆影匆匆7512021-09-13