SpringBoot教程(二十八) | SpringBoot集成Elasticsearch(Java High Level Rest Client方式)
- 前言
- 添加maven依赖
- yml配置
- ElasticsearchConfig 连接配置类
- EsUtil 工具类
- 开始测试
前言
- 由ES官方提供,代码语法和DSL语法相似(即Json格式的ES操作语法);
- 用法灵活,可以自由使用;
- SpringBoot 和 ES 版本的关联性较小;
比较推荐使用这种方式
添加maven依赖
我看网上一般都是用以下这个
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.15.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.15.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.15.2</version>
</dependency>
依赖解释
-
elasticsearch
Elasticsearch的核心库,只是在客户端应用中与Elasticsearch集群交互,通常不需要直接包含这个依赖 -
elasticsearch-rest-client
低级别的REST客户端,提供了基础的HTTP请求构建和执行能力,但不包括高级抽象,如索引、搜索等操作的封装 -
elasticsearch-rest-high-level-client
高级REST客户端,它建立在elasticsearch-rest-client之上,提供了更高级别的抽象,如索引、搜索、更新等操作的封装。这使得与Elasticsearch的交互变得更加简单和直接。
yml配置
# es集群名称
elasticsearch.clusterName=single-node-cluster
#es用户名
elasticsearch.userName=elastic
#es密码
elasticsearch.password=elastic
# es host ip 地址(集群):本次使用的是单机模式
elasticsearch.hosts=127.0.0.1:9200
# es 请求方式
elasticsearch.scheme=http
# es 连接超时时间
elasticsearch.connectTimeOut=1000
# es socket 连接超时时间
elasticsearch.socketTimeOut=30000
# es 请求超时时间
elasticsearch.connectionRequestTimeOut=500
# es 最大连接数
elasticsearch.maxConnectNum=100
# es 每个路由的最大连接数
elasticsearch.maxConnectNumPerRoute=100
ElasticsearchConfig 连接配置类
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
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.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* restHighLevelClient 客户端配置类
*/
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticsearchConfig {
// es host ip 地址(集群)
private String hosts;
// es 用户名
private String userName;
// es 密码
private String password;
// es 请求方式
private String scheme;
// es 集群名称
private String clusterName;
// es 连接超时时间
private int connectTimeOut;
// es socket 连接超时时间
private int socketTimeOut;
// es 请求超时时间
private int connectionRequestTimeOut;
// es 最大连接数
private int maxConnectNum;
// es 每个路由的最大连接数
private int maxConnectNumPerRoute;
/**
* 如果@Bean没有指定bean的名称,那么这个bean的名称就是方法名
*/
@Bean(name = "restHighLevelClient")
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient restHighLevelClient = null;
try {
// 集群,拆分地址
List<HttpHost> hostLists = new ArrayList<>();
String[] hostList = hosts.split(",");
for (String addr : hostList) {
String host = addr.split(":")[0];
String port = addr.split(":")[1];
hostLists.add(new HttpHost(host, Integer.parseInt(port), scheme));
}
// 转换成 HttpHost 数组
HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
// 构建连接对象
RestClientBuilder builder = RestClient.builder(httpHost);
// 设置用户名、密码
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
// 连接延时配置
builder.setRequestConfigCallback(requestConfigBuilder -> {
requestConfigBuilder.setConnectTimeout(connectTimeOut);
requestConfigBuilder.setSocketTimeout(socketTimeOut);
requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeOut);
return requestConfigBuilder;
});
// 连接数配置
builder.setHttpClientConfigCallback(httpClientBuilder -> {
httpClientBuilder.setMaxConnTotal(maxConnectNum);
httpClientBuilder.setMaxConnPerRoute(maxConnectNumPerRoute);
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
return httpClientBuilder;
});
restHighLevelClient = new RestHighLevelClient(builder);
} catch (NumberFormatException e) {
log.error("ES 连接池初始化异常");
}
return restHighLevelClient;
}
}
EsUtil 工具类
工具类中,讲到的 index 其实就是 表名称
package com.example.springbootfull.elasticsearchclient.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
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.RestClient;
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.Strings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* es 的工具类
*
* @author
*/
@Slf4j
@Component
public class EsUtil {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 关键字
*/
public static final String KEYWORD = ".keyword";
/**
* 创建索引
*
* @param index 索引
* @return
*/
public boolean createIndex(String index) throws IOException {
if (isIndexExist(index)) {
log.error("Index is exits!");
return false;
}
//1.创建索引请求
CreateIndexRequest request = new CreateIndexRequest(index);
//2.执行客户端请求
CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
log.info("创建索引{}成功", index);
return response.isAcknowledged();
}
/**
* 删除索引
*
* @param index
* @return
*/
public boolean deleteIndex(String index) throws IOException {
if (!isIndexExist(index)) {
log.error("Index is not exits!");
return false;
}
//删除索引请求
DeleteIndexRequest request = new DeleteIndexRequest(index);
//执行客户端请求
AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
log.info("删除索引{}成功", index);
return delete.isAcknowledged();
}
/**
* 判断索引是否存在
*
* @param index
* @return
*/
public boolean isIndexExist(String index) throws IOException {
GetIndexRequest request = new GetIndexRequest(index);
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
return exists;
}
/**
* 数据添加,正定ID
*
* @param jsonObject 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID, 为null时es随机生成
* @return
*/
public String addData(JSONObject jsonObject, String index, String id) throws IOException {
//创建请求
IndexRequest request = new IndexRequest(index);
//规则 put /test_index/_doc/1
request.id(id);
request.timeout(TimeValue.timeValueSeconds(1));
//将数据放入请求 json
IndexRequest source = request.source(jsonObject, XContentType.JSON);
//客户端发送请求
IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
log.info("添加数据成功 索引为: {}, response 状态: {}, id为: {}", index, response.status().getStatus(), response.getId());
return response.getId();
}
/**
* 数据添加 随机id
*
* @param jsonObject 要增加的数据
* @param index 索引,类似数据库
* @return
*/
public String addData(JSONObject jsonObject, String index) throws IOException {
return addData(jsonObject, index, UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());
}
/**
* 通过ID删除数据
*
* @param index 索引,类似数据库
* @param id 数据ID
*/
public void deleteDataById(String index, String id) throws IOException {
//删除请求
DeleteRequest request = new DeleteRequest(index, id);
//执行客户端请求
DeleteResponse delete = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}删除数据成功", index, id);
}
/**
* 通过ID 更新数据
*
* @param object 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public void updateDataById(Object object, String index, String id) throws IOException {
//更新请求
UpdateRequest update = new UpdateRequest(index, id);
//保证数据实时更新
//update.setRefreshPolicy("wait_for");
update.timeout("1s");
update.doc(JSON.toJSONString(object), XContentType.JSON);
//执行更新请求
UpdateResponse update1 = restHighLevelClient.update(update, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}, 更新数据成功", index, id);
}
/**
* 通过ID 更新数据,保证实时性
*
* @param object 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public void updateDataByIdNoRealTime(Object object, String index, String id) throws IOException {
//更新请求
UpdateRequest update = new UpdateRequest(index, id);
//保证数据实时更新
update.setRefreshPolicy("wait_for");
update.timeout("1s");
update.doc(JSON.toJSONString(object), XContentType.JSON);
//执行更新请求
UpdateResponse update1 = restHighLevelClient.update(update, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}, 更新数据成功", index, id);
}
/**
* 通过ID获取数据
*
* @param index 索引,类似数据库
* @param id 数据ID
* @param fields 需要显示的字段,逗号分隔(缺省为全部字段)
* @return
*/
public Map<String, Object> searchDataById(String index, String id, String fields) throws IOException {
GetRequest request = new GetRequest(index, id);
if (StringUtils.isNotEmpty(fields)) {
//只查询特定字段。如果需要查询所有字段则不设置该项。
request.fetchSourceContext(new FetchSourceContext(true, fields.split(","), Strings.EMPTY_ARRAY));
}
GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
Map<String, Object> map = response.getSource();
//为返回的数据添加id
map.put("id", response.getId());
return map;
}
/**
* 通过ID判断文档是否存在
*
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public boolean existsById(String index, String id) throws IOException {
GetRequest request = new GetRequest(index, id);
//不获取返回的_source的上下文
request.fetchSourceContext(new FetchSourceContext(false));
request.storedFields("_none_");
return restHighLevelClient.exists(request, RequestOptions.DEFAULT);
}
/**
* 获取低水平客户端
*
* @return
*/
public RestClient getLowLevelClient() {
return restHighLevelClient.getLowLevelClient();
}
/**
* 高亮结果集 特殊处理
* map转对象 JSONObject.parseObject(JSONObject.toJSONString(map), Content.class)
*
* @param searchResponse
* @param highlightField
*/
public List<Map<String, Object>> setSearchResponse(SearchResponse searchResponse, String highlightField) {
//解析结果
ArrayList<Map<String, Object>> list = new ArrayList<>();
for (SearchHit hit : searchResponse.getHits().getHits()) {
Map<String, HighlightField> high = hit.getHighlightFields();
HighlightField title = high.get(highlightField);
hit.getSourceAsMap().put("id", hit.getId());
Map<String, Object> sourceAsMap = hit.getSourceAsMap();//原来的结果
//解析高亮字段,将原来的字段换为高亮字段
if (title != null) {
Text[] texts = title.fragments();
String nTitle = "";
for (Text text : texts) {
nTitle += text;
}
//替换
sourceAsMap.put(highlightField, nTitle);
}
list.add(sourceAsMap);
}
return list;
}
/**
* 查询并分页
*
* @param index 索引名称
* @param query 查询条件
* @param size 文档大小限制
* @param from 从第几页开始
* @param fields 需要显示的字段,逗号分隔(缺省为全部字段)
* @param sortField 排序字段
* @param highlightField 高亮字段
* @return
*/
public List<Map<String, Object>> searchListData(String index,
SearchSourceBuilder query,
Integer size,
Integer from,
String fields,
String sortField,
String highlightField) throws IOException {
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder builder = query;
if (StringUtils.isNotEmpty(fields)) {
//只查询特定字段。如果需要查询所有字段则不设置该项。
builder.fetchSource(new FetchSourceContext(true, fields.split(","), Strings.EMPTY_ARRAY));
}
from = from <= 0 ? 0 : from * size;
//设置确定结果要从哪个索引开始搜索的from选项,默认为0
builder.from(from);
builder.size(size);
if (StringUtils.isNotEmpty(sortField)) {
//排序字段,注意如果proposal_no是text类型会默认带有keyword性质,需要拼接.keyword
builder.sort(sortField + ".keyword", SortOrder.ASC);
}
//高亮
HighlightBuilder highlight = new HighlightBuilder();
highlight.field(highlightField);
//关闭多个高亮
highlight.requireFieldMatch(false);
highlight.preTags("<span style='color:red'>");
highlight.postTags("</span>");
builder.highlighter(highlight);
//不返回源数据。只有条数之类的数据。
//builder.fetchSource(false);
request.source(builder);
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
log.error("==" + response.getHits().getTotalHits());
if (response.status().getStatus() == 200) {
// 解析对象
return setSearchResponse(response, highlightField);
}
return null;
}
}
开始测试
实体类
package com.example.springbootfull.elasticsearchclient.bean;
import java.math.BigDecimal;
import java.util.Date;
public class EmployeeInfo2 {
private Long id;
/**
* 工号
*/
private String jobNo;
/**
* 姓名
*/
private String name;
/**
* 英文名
*/
private String englishName;
/**
* 工作岗位
*/
private String job;
/**
* 性别
*/
private Integer sex;
/**
* 年龄
*/
private Integer age;
/**
* 薪资
*/
private BigDecimal salary;
/**
* 入职时间
*/
private Date jobDay;
/**
* 备注
*/
private String remark;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getJobNo() {
return jobNo;
}
public void setJobNo(String jobNo) {
this.jobNo = jobNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEnglishName() {
return englishName;
}
public void setEnglishName(String englishName) {
this.englishName = englishName;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public BigDecimal getSalary() {
return salary;
}
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
public Date getJobDay() {
return jobDay;
}
public void setJobDay(Date jobDay) {
this.jobDay = jobDay;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public EmployeeInfo2() {
}
public EmployeeInfo2(Long id, String jobNo, String name, String englishName, String job, Integer sex, Integer age, BigDecimal salary, Date jobDay, String remark) {
this.id = id;
this.jobNo = jobNo;
this.name = name;
this.englishName = englishName;
this.job = job;
this.sex = sex;
this.age = age;
this.salary = salary;
this.jobDay = jobDay;
this.remark = remark;
}
@Override
public String toString() {
return "EmployeeInfo{" +
"id=" + id +
", jobNo='" + jobNo + '\'' +
", name='" + name + '\'' +
", englishName='" + englishName + '\'' +
", job='" + job + '\'' +
", sex=" + sex +
", age=" + age +
", salary=" + salary +
", jobDay=" + jobDay +
", remark='" + remark + '\'' +
'}';
}
}
Controller类
package com.example.springbootfull.elasticsearchclient.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.PropertyNamingStrategy;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.example.springbootfull.elasticsearchclient.bean.EmployeeInfo2;
import com.example.springbootfull.elasticsearchclient.util.EsUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
@RestController
@RequestMapping("/employeeInfo")
public class EmployeeElasticController {
@Autowired
private EsUtil esUtil;
//==========esUtil================
@RequestMapping("/createIndex")
public String createIndex() throws Exception {
esUtil.createIndex("employee_info_2");
return "success";
}
@RequestMapping("/deleteIndex")
public String deleteIndex() throws Exception {
esUtil.deleteIndex("employee_info_2");
return "success";
}
@RequestMapping("/save")
public String save() throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
EmployeeInfo2 employeeInfo = new EmployeeInfo2(6001L, "2001", "我去", "zhangsan", "Java", 1, 19, new BigDecimal("12500.01"), simpleDateFormat.parse("2019-09-10"), "备注");
//可以帮我把驼峰命名的属性转成下划线格式的(如果你需要的话)
SerializeConfig config = new SerializeConfig();
config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
String json1 = JSON.toJSONString(employeeInfo, config);
esUtil.addData(JSONObject.parseObject(json1), "employee_info_2", null);
//不然直接
//esUtil.addData((JSONObject) JSONObject.toJSON(employeeInfo), "employee_info_2", null);
return "success";
}
}
执行save 方法后,可以看到 es 可视化页面上面 出现了 一条数据
(其中的id,由于我给了null,es就随机帮我生成了)
参考文章:
【1】SpringBoot整合ElasticSearch的两种方式
【2】elasticsearch7.9 java工具类restHighLevelClient精华整理JavaRESTClient
【3】三种方式实现Java对象转json下划线格式