暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用RestHighLevelClient操作ES

超超学堂 2021-01-03
1163

在上次的文章Spring Data Elasticsearch的使用中解释了我在项目中使用ES
的原因,但是元旦前公司检查出来使用的ES
没有设置用户名和密码,因为当时安装的时候ES
是在内网访问的,所以就没有设置用户名和密码,本来设置密码简简单单就完事了,结果项目整合ES
就出了问题。这里说下相关的版本问题,项目使用的Spring Boot
2.1.13
ES
版本则是6.8.6
,自己网上看了下相关的资料如果使用用户名和密码访问ES
需要使用x-pack-client
,但是引入之后项目启动各种报错。后来看了下文档,发现版本有差别,而且自己也尝试了几种方案最后都是失败了,无奈之下只能放弃使用Spring Data Elasticsearch
,然后我看到文档说建议使用 High Level REST Client,所以我就换上了RestHighLevelClient
,所以今天就学习用它来对ES
进行操作。

一、 修改项目依赖

如果你的Spring Data Elasticsearch
版本是3.2.*
或以上是包含了High Level REST Client
依赖的。而公司项目使用的Spring Data Elasticsearch
3.1.*
,所以在公司的项目中我是移除了Spring Data Elasticsearch
依赖,单独重新添加了elasticsearch
elasticsearch-rest-high-level-client
两个依赖。当然本次项目是Spring Data Elasticsearch
版本是4.1.1
,不过我也是移除了Spring Data Elasticsearch
依赖,添加elasticsearch
elasticsearch-rest-high-level-client
两个依赖,本次项目是在上次项目的基础上进行修改,项目源码见我的github:https://github.com/ypcfly/elastic,本次项目pom.xml
如下:

 1<?xml version="1.0" encoding="UTF-8"?>
2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

4    <modelVersion>4.0.0</modelVersion>
5    <parent>
6        <groupId>org.springframework.boot</groupId>
7        <artifactId>spring-boot-starter-parent</artifactId>
8        <version>2.4.0</version>
9        <relativePath/> <!-- lookup parent from repository -->
10    </parent>
11    <groupId>com.ypc.spring.data</groupId>
12    <artifactId>elastic</artifactId>
13    <version>1.0-SNAPSHOT</version>
14    <name>elastic</name>
15    <description>ES project for Spring Boot</description>
16
17    <properties>
18        <java.version>1.8</java.version>
19        <es.version>7.6.0</es.version>
20    </properties>
21
22    <dependencies>
23        <dependency>
24            <groupId>org.springframework.boot</groupId>
25            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
26        </dependency>
27        <dependency>
28            <groupId>org.springframework.boot</groupId>
29            <artifactId>spring-boot-starter-web</artifactId>
30        </dependency>
31        <dependency>
32            <groupId>cn.hutool</groupId>
33            <artifactId>hutool-all</artifactId>
34            <version>5.5.2</version>
35        </dependency>
36        <dependency>
37            <groupId>org.projectlombok</groupId>
38            <artifactId>lombok</artifactId>
39            <optional>true</optional>
40        </dependency>
41        <dependency>
42            <groupId>org.springframework.boot</groupId>
43            <artifactId>spring-boot-starter-test</artifactId>
44            <scope>test</scope>
45        </dependency>
46        <dependency>
47            <groupId>org.elasticsearch</groupId>
48            <artifactId>elasticsearch</artifactId>
49            <version>${es.version}</version>
50        </dependency>
51        <dependency>
52            <groupId>org.elasticsearch.client</groupId>
53            <artifactId>elasticsearch-rest-high-level-client</artifactId>
54            <version>${es.version}</version>
55        </dependency>
56        <dependency>
57            <groupId>org.springframework.data</groupId>
58            <artifactId>spring-data-commons</artifactId>
59        </dependency>
60    </dependencies>
61    <build>
62        <plugins>
63            <plugin>
64                <groupId>org.springframework.boot</groupId>
65                <artifactId>spring-boot-maven-plugin</artifactId>
66            </plugin>
67        </plugins>
68    </build>
69
70</project>

二、配置RestHighLevelClient

项目的配置文件修改如下:

1spring.elasticsearch.rest.uris=127.0.0.1:9200
2spring.elasticsearch.rest.connection-timeout=6s
3spring.elasticsearch.rest.read-timeout=10s
4spring.elasticsearch.rest.password=123456
5spring.elasticsearch.rest.username=elastic

因为没有了自动配置,因此需要我们自己创建相应的Bean
,另外因为 ES
设置了用户名和密码,在配置类当中也要进行权限配置,代码如下:

 1@Configuration
2public class ESConfig {
3
4    @Value("${spring.elasticsearch.rest.uris}")
5    private List<String> uris;
6
7    @Value("${spring.elasticsearch.rest.password}")
8    private String userName;
9
10    @Value("${spring.elasticsearch.rest.username}")
11    private String password;
12
13    @Bean
14    public RestHighLevelClient restHighLevelClient() {
15        HttpHost[] httpHosts = createHosts();
16        RestClientBuilder restClientBuilder = RestClient.builder(httpHosts)
17                .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
18                    @Override
19                    public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
20                        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
21                        credentialsProvider.setCredentials(AuthScope.ANY,new UsernamePasswordCredentials(userName,password));
22                        return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
23                    }
24                });
25        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder);
26        return restHighLevelClient;
27    }
28
29    private HttpHost[] createHosts() {
30        HttpHost[] httpHosts = new HttpHost[uris.size()];
31        for (int i = 0; i < uris.size(); i++) {
32            String hostStr = uris.get(i);
33            String[] host = hostStr.split(":");
34            httpHosts[i] = new HttpHost(StrUtil.trim(host[0]),Integer.valueOf(StrUtil.trim(host[1])));
35        }
36        return httpHosts;
37    }
38}

三、RestHighLevelClient使用

RestHighLevelClient
的使用无非就是对ES
进行一些操作,增删改查等等。另外因为我们没有使用了Spring Data Elasticsearch
所以没法通过注解的方式来创建index
setting
mapping
。这些都需要我们自己去创建。所以需要创建一个Runner
在项目启动之后校验index
是否存在,不存在则创建index
和它的setting
mapping
,代码如下:

 1@Slf4j
2@Component
3public class ElasticsearchRunner implements ApplicationRunner {
4
5    @Autowired
6    private RestHighLevelClient restHighLevelClient;
7    private static final String USER_INDEX_NAME = "user_entity";
8    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
9
10    @Override
11    public void run(ApplicationArguments args) throws Exception {
12        GetIndexRequest getIndexRequest = new GetIndexRequest(USER_INDEX_NAME);
13        Boolean exist = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
14        // 不存在则创建index和setting mapping
15        if (!exist) {
16            CreateIndexRequest createIndexRequest = new CreateIndexRequest(USER_INDEX_NAME);
17            Settings settings = Settings.builder()
18                    .put("index.number_of_shards",1)
19                    .put("index.number_of_replicas",1)
20                    .build();
21            Map<String,Object> propertyMap = createIndexMapping();
22            createIndexRequest.settings(settings).mapping(propertyMap);
23
24            CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest,RequestOptions.DEFAULT);
25            if (!createIndexResponse.isAcknowledged()) {
26                log.error(">>>> 创建索引和映射关系失败! <<<<");
27                throw new RuntimeException("创建索引和映射关系失败");
28            }
29        }
30    }
31
32    private Map<String,Object> createIndexMapping() {
33        Map<String,Object> resultMap = new HashMap<>();
34        Map<String,Object> fieldsMap = new HashMap<>();
35        UserEntity userEntity = new UserEntity();
36        Map<String,Object> beanMap = BeanUtil.beanToMap(userEntity,false,false);
37        for (Map.Entry<String,Object> entry : beanMap.entrySet()) {
38            String key = entry.getKey();
39            Map<String,Object> map = new HashMap<>();
40            if ("id".equals(key)) {
41                Map<String,Object> map2 = new HashMap<>();
42                map2.put("type","keyword");
43                map2.put("ignore_above",256);
44                Map<String,Object> map1 = new HashMap<>();
45                map1.put("keyword",map2);
46                map.put("type","text");
47                map.put("fields",map1);
48            } else if ("orderEntityList".equals(key)) {
49                map = createNested();
50            } else {
51                map.put("type","keyword");
52                map.put("store",true);
53            }
54            fieldsMap.put(key,map);
55        }
56        resultMap.put("properties",fieldsMap);
57        return resultMap;
58    }
59
60    private Map<StringObject> createNested() {
61        Map<String,Object> resultMap = new HashMap<>();
62        resultMap.put("type","nested");
63        Map<String,Object> nestedMap = generateMap();
64        resultMap.put("properties",nestedMap);
65        return resultMap;
66    }
67
68    private Map<StringObject> generateMap() {
69        OrderEntity orderEntity = new OrderEntity();
70        Map<String,Object> map = BeanUtil.beanToMap(orderEntity,false,false);
71        Map<String,Object> resultMap = new HashMap<>();
72        for (Map.Entry<String,Object> entry: map.entrySet()) {
73            String key = entry.getKey();
74            Map<String,Object> field = new HashMap<>();
75            if ("updateTime".equals(key) || "createTime".equals(key)) {
76                field.put("type","date");
77                field.put("store",true);
78                field.put("format",DATE_FORMAT);
79            } else {
80                field.put("type","keyword");
81                field.put("store",true);
82            }
83            resultMap.put(key,field);
84        }
85        return resultMap;
86    }
87}

上面的代码其实比较麻烦的就是创建索引的mapping
,当然创建的方式有多种,我这里使用了Map
,其实最终都是json
的形式。如果字段多确实比较繁琐,这时候还是觉得Spring Data Elasticsearch
比较方便。
接下来我们就开始使用
RestHighLevelClient
进行增删改查,为了方便我将基本沿用原来代码,只是会修改了具体实现。

1 、添加

因为嵌套类型的关于时间的属性的类型是Date
,而在上面创建mapping
的代码中可以看出,我指定了时间格式,因此必须将Date
类型转换为指定格式的字符串,这点一定需要注意,不然会报错。新增代码如下:

 1    @Override
2    public UserEntity save(UserEntity userEntity) {
3        String id = IdUtil.simpleUUID();
4        userEntity.setId(id);
5        List<OrderEntity> orderEntityList = new ArrayList<>();
6        for (int i = 0; i < 4; i++) {
7            OrderEntity orderEntity = new OrderEntity();
8            setProperties(orderEntity,i);
9            orderEntity.setUserId(id);
10            orderEntityList.add(orderEntity);
11        }
12        userEntity.setOrderEntityList(orderEntityList);
13        Map<String,Object> sourceMap = createSourceMap(userEntity);
14        IndexRequest indexRequest = new IndexRequest(USER_INDEX_NAME)
15                .opType(DocWriteRequest.OpType.CREATE).id(id).source(sourceMap);
16        try {
17            IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
18            if (indexResponse.status().getStatus() != RestStatus.CREATED.getStatus()) {
19                log.error(">>>> 新增数据失败,返回结果状态码={},错误信息={} <<<<",indexResponse.status().getStatus());
20            }
21        } catch (IOException e) {
22            log.error(">>>> 新增数据出现异常,异常信息={} <<<<",e.getMessage());
23        }
24        return userEntity;
25    }
26    private Map<StringObject> createSourceMap(UserEntity userEntity) {
27        Map<String,Object> resultMap =  BeanUtil.beanToMap(userEntity,false,true);
28        List<Map<String,Object>> nestedMap = createNestedMap(userEntity.getOrderEntityList());
29        resultMap.put("orderEntityList",nestedMap);
30        return resultMap;
31    }
32    private List<Map<StringObject>> createNestedMap(List<OrderEntity> orderEntityList) {
33        List<Map<String,Object>> list = new ArrayList<>(orderEntityList.size());
34        String format = "yyyy-MM-dd HH:mm:ss";
35        for (OrderEntity orderEntity : orderEntityList) {
36            Map<String,Object> beanMap = BeanUtil.beanToMap(orderEntity,false,true);
37            Date createTime = (Date) beanMap.get("createTime");
38            if (Objects.nonNull(createTime)) {
39                beanMap.put("createTime",DateUtil.format(createTime,format));
40            }
41            Date updateTime = (Date) beanMap.get("updateTime");
42            if (Objects.nonNull(updateTime)) {
43                beanMap.put("updateTime",DateUtil.format(updateTime,format));
44            }
45            list.add(beanMap);
46        }
47        return list;
48    }

这里还需要注意点,IndexRequest
opType
不管有没有赋值,最终indexResponse
返回的状态码都是201
,开始我是以200
判断,但是后来查了一下发现有ES
数据。

2 、删除

这里根据id
删除也是比较简单的,代码如下:

 1    @Override
2    public void deleteById(String id) 
{
3        DeleteRequest deleteRequest = new DeleteRequest(USER_INDEX_NAME).id(id);
4        try {
5            DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest,RequestOptions.DEFAULT);
6            if (deleteResponse.status().getStatus() != RestStatus.OK.getStatus()) {
7                log.error(">>>> 删除id={}数据失败,返回状态码={} <<<<",id,deleteResponse.status().getStatus());
8            }
9        } catch (IOException e) {
10            log.error(">>>> 删除数据发生异常,id={},异常信息={} <<<<",id,e.getMessage());
11        }
12    }

这里比较简单就不再细述了。

3 、修改

修改其实也比较简单,通过下面的代码看下:

 1    @Override
2    public UserEntity update(UserEntity userEntity) {
3        String id = userEntity.getId();
4        Map<String,Object> sourceMap = BeanUtil.beanToMap(userEntity,false,true);
5        if (CollUtil.isNotEmpty(userEntity.getOrderEntityList())) {
6            sourceMap.put("orderEntityList",createNestedMap(userEntity.getOrderEntityList()));
7        }
8        try {
9            UpdateRequest updateRequest = new UpdateRequest(USER_INDEX_NAME,id).doc(sourceMap);
10            UpdateResponse updateResponse = restHighLevelClient.update(updateRequest,RequestOptions.DEFAULT);
11            if (updateResponse.status().getStatus() != RestStatus.OK.getStatus()) {
12                log.error(">>>> 修改id={}数据失败,返回状态码={} <<<<",id,updateResponse.status().getStatus());
13            }
14        } catch (IOException e) {
15            e.printStackTrace();
16        }
17        return queryById(userEntity.getId());
18    }

4 、查询

查询的话根据id
查询和条件查询(分页),条件查询的用法和使用TransportClient
基本没什么区别,通过下面的代码看一下:

 1### 根据id
2    @Override
3    public UserEntity queryById(String id
{
4        GetRequest getRequest = new GetRequest(USER_INDEX_NAME).id(id);
5        UserEntity userEntity = null;
6        try {
7            GetResponse getResponse = restHighLevelClient.get(getRequest,RequestOptions.DEFAULT);
8            Map<String,Object> map = getResponse.getSource();
9            userEntity = BeanUtil.mapToBean(map,UserEntity.class,false,CopyOptions.create());
10        } catch (IOException e) {
11            e.printStackTrace();
12        }
13        return userEntity;
14    }
15
16### 分页条件查询
17    @Override
18    public Page<UserEntity> pageQuery(QueryDTO queryDTO
{
19        String[] includes = {"userName","id","userCode","userMobile","userGrade","status"};
20        // 分页默认从0开始,按照userGrade逆向排序
21        PageRequest pageRequest = PageRequest.of(queryDTO.getPageNum() - 1,queryDTO.getPageSize(), Sort.by(Sort.Direction.DESC,"userAge"));
22        Page<UserEntity> page = null;
23        // 条件查询
24        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().mustNot(QueryBuilders.matchQuery("status","00"));
25        if (StrUtil.isNotBlank(queryDTO.getUserCode())) {
26            queryBuilder.must(QueryBuilders.termQuery("userCode",queryDTO.getUserCode()));
27        }
28        int pageNum = queryDTO.getPageNum() - 1;
29        int pageSize = queryDTO.getPageSize();
30        SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource().fetchSource(includes,null)
31                .query(queryBuilder).sort("userGrade", SortOrder.DESC)
32                .from(pageNum * pageSize).size(pageSize);
33        SearchRequest searchRequest = new SearchRequest(USER_INDEX_NAME).source(searchSourceBuilder);
34
35        try {
36            SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
37            long total = searchResponse.getHits().getTotalHits().value;
38            SearchHit[] searchHits = searchResponse.getHits().getHits();
39            List<UserEntity> records = convertSource2List(searchHits);
40            page = new PageImpl(records,pageRequest,total);
41        } catch (IOException e) {
42            log.error(">>>> 查询失败,异常信息={} <<<<",e.getMessage());
43        }
44        return page;
45    }
46    private List<UserEntity> convertSource2List(SearchHit[] searchHits{
47        if (ArrayUtil.isEmpty(searchHits)) {
48            return Collections.EMPTY_LIST;
49        }
50        List<UserEntity> resultList = new ArrayList<>(searchHits.length);
51        for (SearchHit hit : searchHits) {
52            String jsonString = hit.getSourceAsString();
53            UserEntity userEntity = JSONUtil.toBean(jsonString,UserEntity.class);
54            resultList.add(userEntity);
55        }
56        return resultList;
57    }

当然根据id
查询也可以使用SearchRequest
,但是个人感觉SearchRequest
相对更偏底层一些,当然也可能是自己对GetRequest
不是很熟悉,因为确实没怎么看这个类有那些功能,后面有时间的话自己再看吧。另外在分页查询中使用的查询某些属性在GetRequest
也可以使用的,其实本质都差不多。通过上面的几个方法我们可以看出每种操作基本都对应着一个请求。其实综合来看各种请求还是比较多的,比如我想查询一个index
mapping
那就需要创建一个GetMappingsRequest
,一个index
setting
同样有GetSettingsRequest
。关于这几个方法的测试这里就都省略了。

四、总结

通过上面的学习来看RestHighLevelClient
使用起来并不太复杂,如果对ES
熟悉的话可以说马上就能上手使用。相对之前使用Spring Data Elasticsearch
来讲确实稍微麻烦了一点,尤其是创建索引的mapping
,当然我觉得其实不妨自己实现一下相关的功能。另外我的pom.xml
引入了Spring Data Commons
,自己不妨尝试下能否自动创建索引以及setting、mapping
。整体来看使用起来还是比较简单的,当然还是那句话,关键是自己对ES
本身的了解。好了,今天的学习就到这里,我的代码已经推送到我的github:https://github.com/ypcfly/elastic如果各位小伙伴有什么疑问也可以和我联系。


文章转载自超超学堂,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论