在上次的文章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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object>> 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。如果各位小伙伴有什么疑问也可以和我联系。




