1 Java客户端的使用
为了演示RestHighLevelClient的使用,需要创建一个Spring Boot Web项目。该项目的依赖配置如下:
<dependencies>
<!--Spring Boot Web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--ES客户端依赖-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.10.2</version>
</dependency>
<!--ES依赖-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.10.2</version>
</dependency>
<!--使用Lombok简化开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
在application.yml文件中配置ES的连接信息如下:
server:
port: 8888
elasticsearch:
rest:
hosts: 127.0.0.1:9200
username: elastic
password: password
如上所示,hosts、username和password需要分别填写协调节点服务地址(IP地址+端口号)、用户名、密码。当有多个协调节点时,可以设置hosts值为多个协调节点服务地址,中间用逗号分隔。
下面的代码定义了类EsClient,方法initSimpleClient()返回一个RestHighLevelClient并注册到Spring Boot中。
package com.inspur.elasticsearch.client;
import cn.hutool.core.util.StrUtil;
import lombok.val;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Objects;
/**
* es客户端
*
* 为生成RestHighLevelClient的实例,这里使用EsClient类注册Bean到Spring Boot的方式。
* RestHighLevelClient的构造方法接受一个RestClientBuilder实例,而调用RestClient.builder()方法可以生成RestClientBuilder实例。
* RestClient.builder()方法需要传递一个Http Host对象或者HttpHost数组。
* HttpHost是Apache lib库中对HTTP请求进行封装的一种方法,其构造方法接收主机地址、端口和请求协议3个参数。
* 当有多个协调节点时,需要读取配置文件的参数构造HttpHost数组。
*
* @author zhaoshuai-lc
* @date 2023/02/09
*/
@Component
public class EsClient {
@Value("${elasticsearch.rest.hosts}")
private String hosts;
@Bean
public RestHighLevelClient initSimpleClient() {
HttpHost[] httpHosts = Arrays.stream(hosts.split(StrUtil.COMMA)).map(host -> {
val hostParts = host.split(StrUtil.COLON);
val hostName = hostParts[0];
val port = Integer.parseInt(hostParts[1]);
return new HttpHost(hostName, port, HttpHost.DEFAULT_SCHEME_NAME);
}).filter(Objects::nonNull).toArray(HttpHost[]::new);
// 构建客户端
return new RestHighLevelClient(RestClient.builder(httpHosts));
}
}
下面定义Service。前面已经在Spring Boot中注册了RestHighLevelClient,在Service中就可以使用RestHighLevelClient成员变量了。Service的简要定义如下:
package com.inspur.elasticsearch.service;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* es服务
*
* @author zhaoshuai-lc
* @date 2023/02/09
*/
@Service
public class EsService {
@Autowired
private RestHighLevelClient client;
}
这样就创建了高级的REST客户端。
2 Java带验证客户端的使用
在生产环境下,ES一般都会开启安全验证功能,由于ES Java客户端使用的也是HTTP标准规范中的认证机制,因此在建立客户端和服务端的连接时需要一组凭证用于鉴定用户身份,而这组凭证就是用户名和密码。
CredentialsProvider类是一个凭证类,通过CredentialsProvider.setCredentials()方法可以设置凭证。UsernamePasswordCredentials类可以用明文的形式来表示认证凭证,通过CredentialsProvider.setCredentials()方法设置它的实例,就可以完成凭证的构建。
当使用RestClient.builder()方法构建完RestClientBuilder实例后,需要调用RestClient Builder.setHttpClientConfigCallback()方法完成认证。RestClientBuilder.setHttpClientConfig Callback()方法需要传入一个RestClientBuilder.HttpClientConfigCallback实例,构造完该实例后,可以在该实例的customizeHttpClient()方法中传输之前创建的凭证。下面是带验证的客户端的创建过程:
package com.inspur.elasticsearch.client;
import lombok.val;
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.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Objects;
@Component
public class EsCredentialsClient {
@Value("${elasticsearch.rest.hosts}") //读取ES主机+端口配置
private String hosts;
@Value("${elasticsearch.rest.username}") //读取ES用户名
private String esUser;
@Value("${elasticsearch.rest.password}") //读取ES密码
private String esPassword;
@Bean
public RestHighLevelClient initClient() {
//根据配置文件配置HttpHost数组
HttpHost[] httpHosts = Arrays.stream(hosts.split(",")).map(
host -> {
//分隔ES服务器的IP和端口
val hostParts = host.split(":");
val hostName = hostParts[0];
val port = Integer.parseInt(hostParts[1]);
return new HttpHost(hostName, port, HttpHost.DEFAULT_SCHEME_NAME);
}).filter(Objects::nonNull).toArray(HttpHost[]::new);
// CredentialsProvider类是一个凭证类,通过CredentialsProvider.setCredentials()方法可以设置凭证
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
// 通过CredentialsProvider.setCredentials()方法设置它的实例,就可以完成凭证的构建
credentialsProvider.setCredentials(AuthScope.ANY,
// UsernamePasswordCredentials类可以用明文的形式来表示认证凭证
new UsernamePasswordCredentials(esUser, esPassword));
//返回带验证的客户端
return new RestHighLevelClient(
// 使用RestClient.builder()方法构建完RestClientBuilder实例
RestClient.builder(httpHosts)
// 调用RestClientBuilder.setHttpClientConfigCallback()方法完成认证
// 传入一个HttpClientConfigCallback实例
.setHttpClientConfigCallback(httpClientBuilder -> {
// 构造完该实例后,可以在该实例的customizeHttpClient()方法中传输之前创建的凭证
httpClientBuilder.disableAuthCaching();
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}));
}
}
3 Java客户端搜索文档
就像在介绍Kibana一样,我们还是构建一个请求来学习搜索功能。使用Java客户端API构造一个搜索请求,该请求在hotel索引中按照title字段搜索“再来”。为方便进行结果展示,首先封装一个酒店的POJO类:
package com.inspur.elasticsearch.pojo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 酒店实体类
*
* @author zhaoshuai-lc
* @date 2023/02/09
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Hotel {
/**
* id
*/
private String id;
/**
* 索引
*/
private String index;
/**
* 分数
*/
private Float score;
/**
* 标题
*/
private String title;
/**
* 城市
*/
private String city;
/**
* 价格
*/
private Double price;
}
package com.inspur.elasticsearch.service;
import com.google.common.collect.Lists;
import com.inspur.elasticsearch.pojo.Hotel;
import lombok.val;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
/**
* es服务
*
* @author zhaoshuai-lc
* @date 2023/02/09
*/
@Service
public class EsService {
@Qualifier("initSimpleClient")
@Autowired
private RestHighLevelClient client;
/**
* 根据标题获得酒店
*
* @param keyword 关键字
* @return {@link List}<{@link Hotel}>
*/
public List<Hotel> getHotelFromTitle(String keyword) {
val request = new SearchRequest("hotel");
val builder = new SearchSourceBuilder();
// 构建query
val query = QueryBuilders.matchQuery("title", keyword);
builder.query(query);
request.source(builder);
List<Hotel> hotelList = Lists.newArrayList();
try {
val searchResponse = client.search(request, RequestOptions.DEFAULT);
val status = searchResponse.status();
if (!RestStatus.OK.equals(status)) {
return null;
}
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
val hotel = new Hotel();
hotel.setId(hit.getId());
hotel.setIndex(hit.getIndex());
hotel.setScore(hit.getScore());
val dataMap = hit.getSourceAsMap();
val title = String.valueOf(dataMap.get("title"));
hotel.setTitle(title);
val city = String.valueOf(dataMap.get("city"));
hotel.setCity(city);
val price = Double.parseDouble(String.valueOf(dataMap.get("price")));
hotel.setPrice(price);
hotelList.add(hotel);
}
return hotelList;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
@GetMapping(value = "/getRec")
public String getRec(@RequestParam String keyword) {
val hotelList = esService.getHotelFromTitle(keyword);
if (CollectionUtil.isNotEmpty(hotelList)) {
return hotelList.toString();
}
return "no data .";
}
4 Spring Boot客户端简介
Spring Data Elasticsearch是Spring Boot套件中的一个组件,在Spring Boot中连接ES可以使用Spring Data Elasticsearch。Spring Data Elasticsearch是Spring Data项目的一部分,该项目致力于提供一致的基于Spring的数据查询和存储编程模型。Spring Data Elasticsearch封装了创建客户端的逻辑并与服务端保持长连接,让我们不必关注于网络连接问题。并且,通过对Repository接口的自动实现,Spring Data Elasticsearch可以直接通过方法名的语义实现查询功能,如常见的findBy+字段名+操作。此外,Spring Data Elasticsearch还具有OR-Mapping功能,即查询到数据后,可以将数据直接封装到自定义的POJO中,方便后续对数据进行加工处理。
4.1创建Spring Boot客户端
为了演示Spring Data Elasticsearch客户端的使用,需要创建一个Spring Boot Web项目。该项目的依赖配置如下:
<dependencies>
<!-- Spring Boot Web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring Data Elasticsearch依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!--使用Lombok简化开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
在application.yml文件中,需要配置ES的连接信息:
spring:
elasticsearch:
rest:
uris: http://localhost:9200
username: elastic
password: password
其中,uris、username和password项需要填写协调节点服务地址(IP地址+端口号)、用户名和密码。当有多个协调节点时,可以设置uris的值为多个协调节点的服务地址,中间用逗号分隔。至此,当项目启动时Spring Data Elasticsearch就会创建客户端,然后连接ES服务端并与其保持长连接。
4.2 Spring Boot客户端搜索文档
本节使用之前的搜索示例,即按照标题搜索包含“再来”的酒店。首先封装一个酒店的POJO类:
package com.inspur.elasticsearch.pojo;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
@Data
@Document(indexName = "hotel")
public class Hotel_ {
/**
* 在ID字段中添加了一个注解@Id。
* 注意,一个Spring Data Elasticsearch的POJO必须定义一个被@Id修饰的字段,它是和索引中的_id相对应的。
* 当搜索完成时,Spring Data Elasticsearch会将该POJO的字段填充为搜索结果显示的数据,包括ID字段(填充为文档的_id值)。
*/
@Id
private String id;
private String title;
private String city;
private String price;
}
下面介绍EsRepository。这个EsRepository定义为接口,它需要继承CrudRepository接口。在EsRepository接口中,可以按照业务需求定义方法,Spring Data Elasticsearch会自动找到其类库中合适的实现类。EsRepository的定义代码如下:
package com.inspur.elasticsearch.pojo;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface EsRepository extends CrudRepository<Hotel_, String> {
List<Hotel_> findByTitleLike(String title);
}
在EsRepository中定义了findByTitleLike()方法,Spring Data Elasticsearch会自动根据方法名称识别出方法的具体逻辑,然后生成Bean并填充逻辑代码实现该方法。
完成EsRepository的定义后,在Service中就可以定义EsRepository类型的成员变量了,由Spring Boot完成注入,在getHotelFromTitle()方法中调用EsRepository的对应方法完成搜索。
package com.inspur.elasticsearch.service;
/**
* es服务
*
* @author zhaoshuai-lc
* @date 2023/02/09
*/
@Service
public class EsService {
@Autowired
private EsRepository esRepository;
public List<Hotel_> getHote_LFromTitle(String keyword) {
return esRepository.findByTitleLike(keyword);
}
}
下面介绍Controller的实现部分,URL还是只映射到/test。在Controller中,使用EsService搜索到结果以后直接将其打印到网页上。TestController的实现代码如下:
@GetMapping(value = "/getRec_")
public String getRec_(@RequestParam String keyword) {
val hote_lFromTitle = esService.getHote_LFromTitle(keyword);
if (CollectionUtil.isNotEmpty(hote_lFromTitle)) {
return hote_lFromTitle.toString();
}
return "no data .";
}