通过之前的资料调研,在上篇文章中我们已经整合好Springboot+Elasticsearch6.5的demo。为了满足系统需求,我们会将所有数据入口接入Elasticsearch,然后通过ES去查询数据。所以,我们决定单独出一个搜索服务,接入ES,封装一些操作索引的基本方法,其他服务需要使用,直接引用该服务即可。
• SpringBoot:2.1.9.RELEASE
• Elasticsearch:6.8.6
有了上面的工程结构之后,就可以开始写代码了。下面展示一些核心代码:
1. 引入Maven依赖
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.8.6</version>
</dependency>
<!-- Java Low Level REST Client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>6.8.6</version>
</dependency>
<!-- Java High Level REST Client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
<exclusion>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</exclusion>
</exclusions>
<version>6.8.6</version>
</dependency>
这里引入的依赖和Demo中的依赖有所不同,原因是版本不同,只引入两个client依赖的话,在调用es方法会报错,NoSuchMethod相关错误,所以,这里添加了org.elasticsearch依赖包。
2. 配置文件配置ES地址等相关信息
server:
port: 6001
spring:
application:
name: middle-search
profiles:
active: dev
elasticsearch:
nodes: ***
3. Elasticsearch客户端配置类
在配置类中,我们通过实现InitializingBean、DisposableBean去初始化和销毁客户端,在初始化过程中,我们通过配置文件读取ES的地址,并且设置请求头、连接超时时间等相关配置,核心代码如下:
/**
* Es客户端配置
*
* @author : huzhiting
* @date : 2020-09-07 11:22
*/
@Component
@Slf4j
public class EsClientConfig implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
private static final int CONNECT_TIMEOUT_MILLIS = 60 * 1000;
private static final int SOCKET_TIMEOUT_MILLIS = 5 * 60 * 1000;
private static final int CONNECTION_REQUEST_TIMEOUT_MILLIS = CONNECT_TIMEOUT_MILLIS;
@Value("${elasticsearch.nodes}")
private String nodes;
/**
* 实例化客户端
*/
private RestHighLevelClient restHighLevelClient;
private RestHighLevelClient buildClient() {
try {
// 这里可以读取多个集群地址,用逗号分割即可,创建HttpHost对象数组
// String[] cluster = nodes.split(",");
// HttpHost[] hosts = new HttpHost[cluster.length];
// for (int i = 0; i < cluster.length; i++) {
// hosts[i] = HttpHost.create(cluster[i]);
// }
//我这里直接使用的域名,直接创建单个HttpHost对象了
HttpHost hosts = HttpHost.create(nodes);
//设置请求头
BasicHeader[] basicHeaders = new BasicHeader[]{
new BasicHeader("Accept", "application/json; charset=UTF-8")
};
RestClientBuilder restClientBuilder = RestClient.builder(hosts);
//设置连接超时等相关参数
restClientBuilder.setDefaultHeaders(basicHeaders).setRequestConfigCallback((RequestConfig.Builder requestConfigBuilder) -> {
requestConfigBuilder.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);
requestConfigBuilder.setSocketTimeout(SOCKET_TIMEOUT_MILLIS);
requestConfigBuilder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MILLIS);
return requestConfigBuilder;
});
restHighLevelClient = new RestHighLevelClient(restClientBuilder);
} catch (Exception e) {
log.error(e.getMessage());
}
return restHighLevelClient;
}
@Override
public void afterPropertiesSet() {
restHighLevelClient = buildClient();
}
@Override
public void destroy() {
try {
if (restHighLevelClient != null) {
restHighLevelClient.close();
}
} catch (final Exception e) {
log.error("[EsClientConfig.destroy] [error] [Error closing ElasticSearch client:] ", e);
}
}
@Override
public RestHighLevelClient getObject() {
return restHighLevelClient;
}
@Override
public Class<?> getObjectType() {
return RestHighLevelClient.class;
}
}
在上述建立连接过程中,因为地址多了个"/",报了UnknownHostException,如果有遇到同样的问题,检查一下地址是否正确。
4. 分页查询索引方法
之前的示例工程中,贴出了新建索引和查询索引的相关代码,这里贴出分页查询索引的代码,以验证连接ES是否成功。
/**
* 条件查询数据(分页)
*
* @param request
* @return
*/
public PageResult search(SearchRequest request, Integer page, Integer size) {
PageResult pageResult = new PageResult();
pageResult.setCurrent(page);
pageResult.setSize(size);
SearchResponse response;
try {
response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
//从查询结果中获得想要数据
SearchHits searchHits = response.getHits();
//获得查询结果条数
long total = searchHits.getTotalHits();
pageResult.setTotal(Integer.parseInt(String.valueOf(total)));
//获得查询结果并转换为map
if (total > 0) {
SearchHit[] searchHitsArr = searchHits.getHits();
List<Map<String, Object>> resultMapList = convertResultToObject(searchHitsArr);
pageResult.setPages(pageResult.getTotal() / pageResult.getSize() + 1);
pageResult.setRecords(resultMapList);
}
} catch (IOException e) {
log.error("[ESUtil.queryAll] [error] [fail to query, param is {}]", JSON.toJSON(request), e);
return pageResult;
}
return pageResult;
}
上面的方法返回的PageResult对象是自己对ES返回的结果做了一次处理:
/**
* 分页查询结果
*
* @author : huzhiting
* @date : 2020-09-07 10:37
*/
@Data
public class PageResult {
/**
* 每页显示数量
*/
private Integer size;
/**
* 当前页
*/
private Integer current;
/**
* 总页数
*/
private Integer pages;
/**
* 总条数
*/
private Integer total;
/**
* 记录
*/
private List<?> records;
public PageResult() {
this.records = Collections.emptyList();
this.total = 0;
this.size = 10;
this.current = 1;
}
}
另外需要注意的是,Es的分页是从0页开始计算的,所以我们需要对分页参数做一次处理再使用from和size方法传递,核心代码如下:
/**
* 条件查询用户索引数据(分页)
*
* @param dto
* @return
*/
public PageResult list(UserIndexQueryDTO dto) {
//构建SearchRequest对象
SearchRequest searchRequest = new SearchRequest();
//读取配置文件中的索引名称
searchRequest.indices(INDEX_NAME);
//读取配置文件中的索引类型(ES6一个索引下仅有一个类型)
searchRequest.types(INDEX_TYPE);
SearchSourceBuilder builder = new SearchSourceBuilder();
//构造条件查询对象
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
if (StringUtils.isNotBlank(dto.getGradeName())) {
//termQuery 精确匹配
boolQueryBuilder.filter(QueryBuilders.termQuery("gradeName", dto.getGradeName()));
}
if (StringUtils.isNotBlank(dto.getNickName())) {
//wildcardQuery 模糊匹配
boolQueryBuilder.filter(QueryBuilders.wildcardQuery("nickName", String.format("*%s*", dto.getNickName())));
}
if (StringUtils.isNotBlank(dto.getPhone())) {
//wildcardQuery 模糊匹配
boolQueryBuilder.filter(QueryBuilders.wildcardQuery("phone", String.format("*%s*", dto.getPhone())));
}
PageResult pageResult = baseIndexService.transformPage(dto.getPage(), dto.getSize());
builder.query(boolQueryBuilder).from(pageResult.getCurrent()).size(pageResult.getSize());
searchRequest.source(builder);
return baseIndexService.queryIndexByCondition(searchRequest, dto.getPage(), dto.getSize());
}
/**
* 分页参数转换
* @param page
* @param size
* @return
*/
public PageResult transformPage(Integer page, Integer size) {
PageResult pageResult = new PageResult();
if (page == null || page < 0) {
pageResult.setCurrent(0);
} else {
pageResult.setCurrent(page - 1);
}
if (size == null || size < 0) {
pageResult.setSize(10);
} else {
pageResult.setSize(size);
}
return pageResult;
}
5. 分页查询索引结果
除了上面的核心代码,项目中抽离的Service和ServiceImpl代码就不展示了,主要是对其他服务暴露接口的作用。
这几天一直在学习Elasticsearch相关的东西,因为之前自己也没有真正接触过,所以接到搭建ES搜索服务的任务时压力还是很大的,什么都是从只是听过而没有用过开始。
好在从demo搭建到基础服务搭建,整个过程比较顺利,遇到的各种问题也都解决了,其他服务也顺利接入了es服务,接下来就是写相关业务代码了。