组件讲解-如何对Elasticsearch进行高效封装
本章节可以跳过,就是单纯的封装Elasticsearch
Elasticsearch 的功能和作用
Elasticsearch 是当今世界上最受欢迎的搜索引擎之一,广泛应用于日志分析、全文搜索、安全情报、业务分析等多个领域。它以其高性能、强大的数据分析能力和易用性而闻名。Elasticsearch 能够处理各种类型的数据,包括文本、数字、地理位置、结构化和非结构化数据。
核心功能
- 全文搜索: Elasticsearch 提供高级的全文搜索功能,包括多语言处理、自动类型推断、自然语言处理等,使得搜索结果更加准确和相关
- 实时分析: 与传统数据库相比,Elasticsearch 能够在数据写入的同时进行实时分析,为用户提供即时的业务洞察
- 水平可扩展性: Elasticsearch 可以轻松扩展到多个节点,无需更改应用程序代码,就能处理 PB 级别的数据
- 高可用性和分布式: Elasticsearch 的分布式特性使其具有高可用性和容错能力,即使在节点故障的情况下也能保证服务的连续性
- 多租户能力: 通过使用 Elasticsearch 的索引和别名功能,可以在单个 Elasticsearch 集群上支持多个租户的数据隔离
应用场景
- 日志和事件数据分析: Elasticsearch 被广泛用于收集、分析和可视化日志数据,帮助组织发现系统中的问题和优化机会
- 网站搜索: 提供网站内容的快速、相关搜索,改善用户体验
- 安全情报: 分析网络流量和用户行为,以检测恶意活动和安全威胁
- 商业智能: 对客户数据、市场趋势进行实时分析,支持决策制定
特点
Elasticsearch 的搜索能力是其最核心的功能之一,提供了从简单的文本匹配到复杂的聚合查询等广泛的搜索功能。它通过灵活的数据索引结构和强大的查询语言(Query DSL)来实现高效、精确的搜索结果。以下是 Elasticsearch 搜索能力的详细介绍:
全文搜索
Elasticsearch 强大的全文搜索能力是其最引人注目的特性之一。它使用了倒排索引(Inverted Index)来高效地存储和检索文本数据。用户可以进行模糊查询、词干匹配、同义词处理等,以及利用 Elasticsearch 提供的各种文本分析器来优化搜索结果的相关性
复杂查询构建
Elasticsearch 的 Query DSL 提供了丰富的查询构建块,允许用户执行精确匹配、范围查询、布尔查询、嵌套查询等复杂搜索操作。这些查询可以组合使用,构建出能够满足几乎任何搜索需求的复杂查询
聚合分析
除了搜索文档,Elasticsearch 还提供了强大的聚合功能,允许用户在搜索结果上进行统计分析。这包括但不限于计数、求和、平均值、最小/最大值等。聚合功能可以帮助用户洞察数据模式,支持复杂的数据分析需求
实时搜索
Elasticsearch 能够提供几乎实时的搜索体验。当文档被索引时,它几乎立即就可以被搜索到。这对于需要实时反馈的应用场景(如日志监控、社交媒体分析等)至关重要
Elasticsearch的type问题
Elasticsearch存储数据是以每个索引index作为维度来存储的,可以理解成关系型数据库中的表,而字段是以文档document存在的,可以理解成表的字段
而在Elasticsearch6.x之前还存在type的概念,介于index和document之间
Elasticsearch6.x之前一个index下可以有多个不同的type,而在7.x开始就type的概念去掉了,采用同一个doc表示type,也就是一个index只能有一个type,这个type还必须是doc,而在8.x中,彻底将type的概念去除了
7.x为什么去掉了type?
映射爆炸(Mapping Explosion)
在使用多类型时,如果不同类型之间有大量不同的字段,这会导致映射的数量急剧增加,进而引发映射爆炸问题。映射爆炸不仅会消耗大量的内存资源,还会降低 Elasticsearch 的性能,尤其是在处理大量数据时
字段名冲突
在同一个index的不同type中,如果有相同名称但映射类型不同的字段,会造成字段名冲突。这是因为 Elasticsearch 在内部是将这些字段扁平化处理的,而不同类型的相同名称字段可能会导致数据解析和查询时的混乱
我们可以和关系型数据库来对比,在同一个数据库中,这些不同的表,可以有名称相同但类型不同的字段。而在 Elasticsearch 同一个index的不同type中,如果有不同document的字段名相同,但是类型不同,就会报错
总结
综上所述,Elasticsearch 从 7.x 版本开始废弃类型的主要目的是为了提升系统的性能、避免映射爆炸和字段冲突的问题,以及简化数据模型的设计和管理。这一改变反映了 Elasticsearch 对于提高性能、可维护性和用户体验的持续追求
操作
Springboot提供了对Elasticsearch的操作,需要添加以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>但使用中还是有一些问题,在平时开发中还是使用关系型数据库更加普遍,对于数据库、表、字段的概念更为熟悉,也更加习惯对表概念的操作。而在操作Elasticsearch时,提供的api其实是很复杂的,各种操作的对象,如SearchSourceBuilder FieldSortBuilder BoolQueryBuilder 等等,操作上其实算不上简单,所以为了解决这个问题,本人在大麦网中在springboot操作Elasticsearch的基础上,进一步的封装,使用起来贴近于关系型数据库的方式,操作起来更加的容易上手
damai-elasticsearch-framework
介绍
此组件提供了对Elasticsearch常见的操作,操作起来很简单,并且兼容了6.x 、7.x 、8.x的版本
使用
引入依赖
<dependency>
<groupId>com.example</groupId>
<artifactId>damai-elasticsearch-framework</artifactId>
<version>${revision}</version>
</dependency>添加地址配置
elasticsearch:
ip: #地址:#端口号
userName: #账号
passWord: #密码
# 是否开启type,默认为false
esTypeSwitch: false此type的作用就是为了兼容6.x、7.x中的type概念,默认是关闭
引入api
@Autowired
private BusinessEsHandle businessEsHandle;
public void test(){
boolean result = businessEsHandle.checkIndex(ProgramDocumentParamName.INDEX_NAME, ProgramDocumentParamName.INDEX_TYPE);
}关于此组件的详细使用,在节目服务下的搜索节目和查询节目的业务中有非常详细的示例,包括了索引的创建、数据的添加、数据的查询等功能,本文篇幅有限,都贴出来实在是太长了,小伙伴可直接跳转到 业务讲解 模块下的 节目服务部分 来学习,有详细的介绍
讲解
参数配置
@Data
@ConfigurationProperties(prefix = BusinessEsProperties.PREFIX)
public class BusinessEsProperties {
public static final String PREFIX = "elasticsearch";
/**
* 地址
* */
private String[] ip;
/**
* 账户
* */
private String userName;
/**
* 密码
* */
private String passWord;
/**
* 是否开启使用,默认 true
* */
private Boolean esSwitch = true;
/**
* 是否开启type,默认 false
* */
private Boolean esTypeSwitch = false;
/**
* 连接超时时间
* */
private Integer connectTimeOut = 40000;
/**
* socket超市时间
* */
private Integer socketTimeOut = 40000;
/**
* 连接请求超时时间
* */
private Integer connectionRequestTimeOut = 40000;
/**
* 最大连接数
* */
private Integer maxConnectNum = 400;
}自动装配类
@EnableConfigurationProperties(BusinessEsProperties.class)
@ConditionalOnProperty(value = "elasticsearch.ip")
public class BusinessEsAutoConfig {
private static final int ADDRESS_LENGTH = 2;
private static final String HTTP_SCHEME = "http";
/**
* 配置Elasticsearch的客户端
* */
@Bean
public RestClient businessEsRestClient(BusinessEsProperties businessEsProperties) {
String defaultValue = "default";
HttpHost[] hosts = Arrays.stream(businessEsProperties.getIp()).map(this::makeHttpHost).filter(Objects::nonNull)
.toArray(HttpHost[]::new);
RestClientBuilder builder = RestClient.builder(hosts);
String userName = businessEsProperties.getUserName();
String passWord = businessEsProperties.getPassWord();
if (StringUtil.isNotEmpty(userName) && !defaultValue.equals(userName) && StringUtil.isNotEmpty(passWord) && !defaultValue.equals(passWord)) {
//开始设置用户名和密码
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, passWord));
builder.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
.setDefaultIOReactorConfig(
IOReactorConfig.custom()
// 设置线程数
.setIoThreadCount(businessEsProperties.getMaxConnectNum())
.build()));
}
Header[] defaultHeaders = { new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"),
new BasicHeader("Role", "Read") };
// 设置每个请求需要发送的默认headers,这样就不用在每个请求中指定它们。
builder.setDefaultHeaders(defaultHeaders);
// 设置相关参数
builder.setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder
.setConnectTimeout(businessEsProperties.getConnectTimeOut())
.setSocketTimeout(businessEsProperties.getSocketTimeOut())
.setConnectionRequestTimeout(businessEsProperties.getConnectionRequestTimeOut()));
return builder.build();
}
/**
* Elasticsearch的操作api
* */
@Bean
public BusinessEsHandle businessEsHandle(@Qualifier("businessEsRestClient")RestClient businessEsRestClient, BusinessEsProperties businessEsProperties){
return new BusinessEsHandle(businessEsRestClient,businessEsProperties.getEsSwitch(),businessEsProperties.getEsTypeSwitch());
}
/**
* 获取HttpHost对象
*
*/
private HttpHost makeHttpHost(String s) {
assert StringUtil.isNotEmpty(s);
String[] address = s.split(":");
if (address.length == ADDRESS_LENGTH) {
String ip = address[0];
int port = Integer.parseInt(address[1]);
return new HttpHost(ip, port, HTTP_SCHEME);
} else {
return null;
}
}
}操作的api
@Slf4j
@AllArgsConstructor
public class BusinessEsHandle {
private final RestClient restClient;
private final Boolean esSwitch;
private final Boolean esTypeSwitch;
/**
* 创建索引
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param list 参数集合
*/
public void createIndex(String indexName, String indexType, List<EsDocumentMappingDto> list) throws IOException {
if (!esSwitch) {
return;
}
if (CollectionUtil.isEmpty(list)) {
return;
}
IndexRequest indexRequest = new IndexRequest();
XContentBuilder builder = JsonXContent.contentBuilder().startObject().startObject("mappings");
if (esTypeSwitch) {
builder = builder.startObject(indexType);
}
builder = builder.startObject("properties");
for (EsDocumentMappingDto esDocumentMappingDto : list) {
String paramName = esDocumentMappingDto.getParamName();
String paramType = esDocumentMappingDto.getParamType();
if ("text".equals(paramType)) {
Map<String,Map<String,Object>> map1 = new HashMap<>(8);
Map<String,Object> map2 = new HashMap<>(8);
map2.put("type","keyword");
map2.put("ignore_above",256);
map1.put("keyword",map2);
builder = builder.startObject(paramName).field("type", "text").field("fields",map1).endObject();
}else {
builder = builder.startObject(paramName).field("type", paramType).endObject();
}
}
if (esTypeSwitch) {
builder.endObject();
}
builder = builder.endObject().endObject().startObject("settings").field("number_of_shards", 3)
.field("number_of_replicas", 1).endObject().endObject();
indexRequest.source(builder);
String source = indexRequest.source().utf8ToString();
log.info("create index execute dsl : {}",source);
HttpEntity entity = new NStringEntity(source, ContentType.APPLICATION_JSON);
Request request = new Request("PUT","/"+ indexName);
request.setEntity(entity);
request.addParameters(Collections.<String, String>emptyMap());
Response performRequest = restClient.performRequest(request);
}
/**
* 检查索引是否存在
*
* @param indexName 索引名字
* @param indexType 索引类型
* @return boolean
*/
public boolean checkIndex(String indexName, String indexType) {
if (!esSwitch) {
return false;
}
try {
String path = "";
if (esTypeSwitch) {
path = "/" + indexName + "/" + indexType + "/_mapping?include_type_name";
}else {
path = "/" + indexName + "/_mapping";
}
Request request = new Request("GET", path);
request.addParameters(Collections.<String, String>emptyMap());
Response response = restClient.performRequest(request);
return "OK".equals(response.getStatusLine().getReasonPhrase());
}catch (Exception e) {
if (e instanceof ResponseException && ((ResponseException)e).getResponse().getStatusLine().getStatusCode() == RestStatus.NOT_FOUND.getStatus()) {
log.warn("index not exist ! indexName:{}, indexType:{}", indexName,indexType);
}else {
log.error("checkIndex error",e);
}
return false;
}
}
/**
* 删除索引
*
* @param indexName 索引名字
* @return boolean
*/
public boolean deleteIndex(String indexName) {
if (!esSwitch) {
return false;
}
try {
Request request = new Request("DELETE", "/" + indexName);
request.addParameters(Collections.<String, String>emptyMap());
Response response = restClient.performRequest(request);
return "OK".equals(response.getStatusLine().getReasonPhrase());
}catch (Exception e) {
log.error("deleteIndex error",e);
}
return false;
}
/**
* 清空索引下所有数据
*
* @param indexName 索引名字
*/
public void deleteData(String indexName) {
if (!esSwitch) {
return;
}
deleteIndex(indexName);
}
/**
* 添加
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param params 参数 key:字段名 value:具体值
* @return boolean
*/
public boolean add(String indexName, String indexType,Map<String,Object> params) {
return add(indexName, indexType, params, null);
}
/**
* 添加
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param params 参数 key:字段名 value:具体值
* @param id 文档id 如果为空,则使用es默认id
* @return boolean
*/
public boolean add(String indexName, String indexType,Map<String,Object> params, String id) {
if (!esSwitch) {
return false;
}
if (CollectionUtil.isEmpty(params)) {
return false;
}
try {
String jsonString = JSON.toJSONString(params);
HttpEntity entity = new NStringEntity(jsonString, ContentType.APPLICATION_JSON);
String endpoint = "";
if (esTypeSwitch) {
endpoint = "/" + indexName + "/" + indexType;
}else {
endpoint = "/" + indexName + "/_doc";
}
if (StringUtil.isNotEmpty(id)) {
endpoint = endpoint + "/" + id;
}
log.info("add dsl : {}",jsonString);
Request request = new Request("POST",endpoint);
request.setEntity(entity);
request.addParameters(Collections.<String, String>emptyMap());
Response indexResponse = restClient.performRequest(request);
String reasonPhrase = indexResponse.getStatusLine().getReasonPhrase();
return "created".equalsIgnoreCase(reasonPhrase) || "ok".equalsIgnoreCase(reasonPhrase);
}catch (Exception e) {
log.error("add error",e);
}
return false;
}
/**
* 查询
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esDataQueryDtoList 参数
* @param clazz 返回的类型
* @return List
*/
public <T> List<T> query(String indexName, String indexType, List<EsDataQueryDto> esDataQueryDtoList, Class<T> clazz) throws IOException {
if (!esSwitch) {
return new ArrayList<>();
}
return query(indexName, indexType, null, esDataQueryDtoList, null, null, null, null, null, clazz);
}
/**
* 查询
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esGeoPointDto 经纬度查询参数
* @param esDataQueryDtoList 参数
* @param clazz 返回的类型
* @return List
*/
public <T> List<T> query(String indexName, String indexType, EsGeoPointDto esGeoPointDto, List<EsDataQueryDto> esDataQueryDtoList, Class<T> clazz) throws IOException {
if (!esSwitch) {
return new ArrayList<>();
}
return query(indexName, indexType, esGeoPointDto, esDataQueryDtoList, null, null, null, null,null,clazz);
}
/**
* 查询
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esDataQueryDtoList 参数
* @param sortParam 普通参数排序 不排序则为空 如果进行了排序,会返回es中的排序字段sort,需要用户在返回的实体类中添加sort字段
* @param sortOrder 升序还是降序,为空则降序
* @param clazz 返回的类型
* @return List
*/
public <T> List<T> query(String indexName, String indexType, List<EsDataQueryDto> esDataQueryDtoList, String sortParam, SortOrder sortOrder, Class<T> clazz) throws IOException {
if (!esSwitch) {
return new ArrayList<>();
}
return query(indexName, indexType, null, esDataQueryDtoList, sortParam, null, sortOrder, null, null, clazz);
}
/**
* 查询
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esDataQueryDtoList 参数
* @param geoPointDtoSortParam 经纬度參數排序 不排序则为空 如果进行了排序,会返回es中的排序字段sort,需要用户在返回的实体类中添加sort字段
* @param sortOrder 升序还是降序,为空则降序
* @param clazz 返回的类型
* @return List
*/
public <T> List<T> query(String indexName, String indexType, List<EsDataQueryDto> esDataQueryDtoList, EsGeoPointSortDto geoPointDtoSortParam, SortOrder sortOrder, Class<T> clazz) throws IOException {
if (!esSwitch) {
return new ArrayList<>();
}
return query(indexName, indexType, null, esDataQueryDtoList, null, geoPointDtoSortParam, sortOrder,null,null, clazz);
}
/**
* 查询
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esGeoPointDto 经纬度查询参数
* @param esDataQueryDtoList 参数
* @param sortParam 普通參數排序 不排序则为空 如果进行了排序,会返回es中的排序字段sort,需要用户在返回的实体类中添加sort字段
* @param geoPointDtoSortParam 经纬度參數排序 不排序则为空 如果进行了排序,会返回es中的排序字段sort,需要用户在返回的实体类中添加sort字段
* @param sortOrder 升序还是降序,为空则降序
* @param pageSize searchAfterSort搜索的页大小
* @param searchAfterSort sort值
* @param clazz 返回的类型
* @return List
*/
public <T> List<T> query(String indexName, String indexType, EsGeoPointDto esGeoPointDto, List<EsDataQueryDto> esDataQueryDtoList, String sortParam, EsGeoPointSortDto geoPointDtoSortParam, SortOrder sortOrder, Integer pageSize, Object[] searchAfterSort, Class<T> clazz) throws IOException {
List<T> list = new ArrayList<>();
if (!esSwitch) {
return list;
}
SearchSourceBuilder sourceBuilder = getSearchSourceBuilder(esGeoPointDto,esDataQueryDtoList,sortParam,geoPointDtoSortParam,sortOrder);
executeQuery(indexName,indexType,list,null,clazz,sourceBuilder);
return list;
}
/**
* 查询(分页)
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esDataQueryDtoList 参数
* @param pageNo 页码
* @param pageSize 页大小
* @param clazz 返回的类型
* @return PageInfo
*/
public <T> PageInfo<T> queryPage(String indexName, String indexType, List<EsDataQueryDto> esDataQueryDtoList, Integer pageNo, Integer pageSize, Class<T> clazz) throws IOException {
return queryPage(indexName, indexType, esDataQueryDtoList, null, null, pageNo, pageSize, clazz);
}
/**
* 查询(分页)
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esDataQueryDtoList 参数
* @param sortParam 排序参数 不排序则为空 如果进行了排序,会返回es中的排序字段sort,需要用户在返回的实体类中添加sort字段
* @param sortOrder 升序还是降序,为空则降序
* @param pageNo 页码
* @param pageSize 页大小
* @param clazz 返回的类型
* @return PageInfo
*/
public <T> PageInfo<T> queryPage(String indexName, String indexType, List<EsDataQueryDto> esDataQueryDtoList, String sortParam, SortOrder sortOrder, Integer pageNo, Integer pageSize, Class<T> clazz) throws IOException {
return queryPage(indexName, indexType, null, esDataQueryDtoList, sortParam, null,sortOrder, pageNo, pageSize, clazz);
}
/**
* 查询(分页)
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esGeoPointDto 经纬度查询参数
* @param esDataQueryDtoList 参数
* @param pageNo 页码
* @param pageSize 页大小
* @param clazz 返回的类型
* @return PageInfo
*/
public <T> PageInfo<T> queryPage(String indexName, String indexType, EsGeoPointDto esGeoPointDto, List<EsDataQueryDto> esDataQueryDtoList, Integer pageNo, Integer pageSize, Class<T> clazz) throws IOException {
return queryPage(indexName, indexType, esGeoPointDto, esDataQueryDtoList, null, null,null, pageNo, pageSize, clazz);
}
/**
* 查询(分页)
*
* @param indexName 索引名字
* @param indexType 索引类型
* @param esGeoPointDto 经纬度查询参数
* @param esDataQueryDtoList 参数
* @param sortParam 排序参数 不排序则为空 如果进行了排序,会返回es中的排序字段sort,需要用户在返回的实体类中添加sort字段
* @param sortOrder 升序还是降序,为空则降序
* @param pageNo 页码
* @param pageSize 页大小
* @param clazz 返回的类型
* @return
* @throws IOException
*/
public <T> PageInfo<T> queryPage(String indexName, String indexType, EsGeoPointDto esGeoPointDto,
List<EsDataQueryDto> esDataQueryDtoList, String sortParam,
EsGeoPointSortDto geoPointDtoSortParam, SortOrder sortOrder, Integer pageNo,
Integer pageSize, Class<T> clazz) throws IOException {
List<T> list = new ArrayList<>();
PageInfo<T> pageInfo = new PageInfo<>(list);
pageInfo.setPageNum(pageNo);
pageInfo.setPageSize(pageSize);
if (!esSwitch) {
return pageInfo;
}
SearchSourceBuilder sourceBuilder = getSearchSourceBuilder(esGeoPointDto,esDataQueryDtoList,sortParam,geoPointDtoSortParam,sortOrder);
sourceBuilder.from((pageNo - 1) * pageSize);
sourceBuilder.size(pageSize);
executeQuery(indexName,indexType,list,pageInfo,clazz,sourceBuilder);
return pageInfo;
}
/**
* 构建查询条件
* @param esGeoPointDto 经纬度类型查询数据
* @param esDataQueryDtoList 普通类型查询数据
* @param sortParam 排序字段
* @param geoPointDtoSortParam 经纬度排序
* @param sortOrder 排序类型
* */
private SearchSourceBuilder getSearchSourceBuilder(EsGeoPointDto esGeoPointDto, List<EsDataQueryDto> esDataQueryDtoList, String sortParam, EsGeoPointSortDto geoPointDtoSortParam, SortOrder sortOrder){
//构建搜索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
if (Objects.isNull(sortOrder)) {
sortOrder = SortOrder.DESC;
}
//排序
if (StringUtil.isNotEmpty(sortParam)) {
FieldSortBuilder sort = SortBuilders.fieldSort(sortParam);
sort.order(sortOrder);
sourceBuilder.sort(sort);
}
//经纬度排序
if (Objects.nonNull(geoPointDtoSortParam)) {
GeoDistanceSortBuilder sort = SortBuilders.geoDistanceSort("geoPoint", geoPointDtoSortParam.getLatitude().doubleValue(), geoPointDtoSortParam.getLongitude().doubleValue());
sort.unit(DistanceUnit.METERS);
sort.order(sortOrder);
sourceBuilder.sort(sort);
}
//经纬度查询匹配
if (Objects.nonNull(esGeoPointDto)) {
QueryBuilder geoQuery = new GeoDistanceQueryBuilder(esGeoPointDto.getParamName()).distance(Long.MAX_VALUE, DistanceUnit.KILOMETERS)
.point(esGeoPointDto.getLatitude().doubleValue(), esGeoPointDto.getLongitude().doubleValue()).geoDistance(GeoDistance.PLANE);
sourceBuilder.query(geoQuery);
}
//普通字段的查询匹配
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
for (EsDataQueryDto esDataQueryDto : esDataQueryDtoList) {
//字段名
String paramName = esDataQueryDto.getParamName();
//字段值
Object paramValue = esDataQueryDto.getParamValue();
//日期类型的查询 开始时间
Date startTime = esDataQueryDto.getStartTime();
//日期类型的查询 结束时间
Date endTime = esDataQueryDto.getEndTime();
//是否使用分词查询
boolean analyse = esDataQueryDto.isAnalyse();
if (Objects.nonNull(paramValue)) {
//如果参数的值是集合类型,相当于Mysql的in查询
if (paramValue instanceof Collection) {
//如果分词查询
if (analyse) {
//构造查询条件
BoolQueryBuilder builds = QueryBuilders.boolQuery();
Collection<?> collection = (Collection<?>)paramValue;
for (Object value : collection) {
builds.should(QueryBuilders.matchQuery(paramName, value));
}
boolQuery.must(builds);
}else {
//如果是精确查询
QueryBuilder builds = QueryBuilders.termsQuery(paramName, (Collection<?>)paramValue);
boolQuery.must(builds);
}
}else {
//如果参数的值是单个对象,相当于Mysql的=查询
QueryBuilder builds;
if (analyse) {
builds = QueryBuilders.matchQuery(paramName, paramValue);
} else {
builds = QueryBuilders.termQuery(paramName, paramValue);
}
boolQuery.must(builds);
}
}
//日期类型的查询
if (Objects.nonNull(startTime) || Objects.nonNull(endTime)) {
//构建查询条件
QueryBuilder builds = QueryBuilders.rangeQuery(paramName)
.from(startTime).to(endTime).includeLower(true);
boolQuery.must(builds);
}
}
sourceBuilder.trackTotalHits(true);
sourceBuilder.query(boolQuery);
return sourceBuilder;
}
/**
* 执行查询
* */
public <T> void executeQuery(String indexName, String indexType,List<T> list,PageInfo<T> pageInfo,Class<T> clazz,
SearchSourceBuilder sourceBuilder,List<String> highLightFieldNameList) throws IOException {
String string = sourceBuilder.toString();
//设置请求头的操作类型,为json
HttpEntity entity = new NStringEntity(string, ContentType.APPLICATION_JSON);
StringBuilder endpointStringBuilder = new StringBuilder("/" + indexName);
//如果开启type,则进行拼接
if (esTypeSwitch) {
endpointStringBuilder.append("/").append(indexType).append("/_search");
}else {
//没有开启type
endpointStringBuilder.append("/_search");
}
String endpoint = endpointStringBuilder.toString();
log.info("query execute query dsl : {}",string);
Request request = new Request("POST",endpoint);
request.setEntity(entity);
request.addParameters(Collections.emptyMap());
//执行请求
Response response = restClient.performRequest(request);
String result = EntityUtils.toString(response.getEntity());
if (StringUtil.isEmpty(result)) {
return;
}
//解析结果
JSONObject resultJsonObject = JSONObject.parseObject(result);
if (Objects.isNull(resultJsonObject)) {
return;
}
JSONObject hits = resultJsonObject.getJSONObject("hits");
if (Objects.isNull(hits)) {
return;
}
// 总条数
Long value = null;
if (esTypeSwitch) {
value = hits.getLong("total");
}else {
JSONObject totalJsonObject = hits.getJSONObject("total");
if (Objects.nonNull(totalJsonObject)) {
value = totalJsonObject.getLong("value");
}
}
if (Objects.nonNull(pageInfo) && Objects.nonNull(value)) {
pageInfo.setTotal(value);
}
//解析数据
JSONArray arrayData = hits.getJSONArray("hits");
if (Objects.isNull(arrayData) || arrayData.isEmpty()) {
return;
}
for (int i = 0, size = arrayData.size(); i < size; i++) {
JSONObject data = arrayData.getJSONObject(i);
if (Objects.isNull(data)) {
continue;
}
//文档id
String esId = data.getString("_id");
//数据
JSONObject jsonObject = data.getJSONObject("_source");
//排序字段
JSONArray jsonArray = data.getJSONArray("sort");
if (Objects.nonNull(jsonArray) && !jsonArray.isEmpty()) {
Long sort = jsonArray.getLong(0);
jsonObject.put("sort",sort);
}
//高亮显示字段
JSONObject highlight = data.getJSONObject("highlight");
if (Objects.nonNull(highlight) && Objects.nonNull(highLightFieldNameList)) {
for (String highLightFieldName : highLightFieldNameList) {
JSONArray highLightFieldValue = highlight.getJSONArray(highLightFieldName);
if (Objects.isNull(highLightFieldValue) || highLightFieldValue.isEmpty()) {
continue;
}
jsonObject.put(highLightFieldName,highLightFieldValue.get(0));
}
}
//设置文档id
if (StringUtil.isNotEmpty(esId)) {
jsonObject.put("esId",esId);
}
list.add(JSONObject.parseObject(jsonObject.toJSONString(),clazz));
}
}
/**
* 根据文档id删除数据
* */
public void deleteByDocumentId(String index,String documentId) {
if (!esSwitch) {
return;
}
try {
Request request = new Request("DELETE", "/" + index + "/_doc/" + documentId);
request.addParameters(Collections.<String, String>emptyMap());
Response response = restClient.performRequest(request);
log.info("deleteByDocumentId result : {}",response.getStatusLine().getReasonPhrase());
}catch (Exception e) {
log.error("deleteData error",e);
}
}
}能够解决的问题
本组件提供了多种封装的方法,包括索引的创建和创建、数据的添加、数据的查询、数据的分页查询、构造查询条件、执行查询解析结果、数据的删除
在数据查询和分页查询方法中,可以传入多个字段名和字段值进行查询,但要注意 只能查询嵌套一层的条件查询,如果是多条件嵌套了多层,那么就不能使用封装的查询方法了,只能自己来使用 QueryBuilders,来构造出查询条件,然后再用封装的executeQuery方法,来执行查询和解析结果。在节目服务的搜索功能中,就是这么做的
执行的语句
在执行api时,将真正执行dsl语句打印了出来,这里以操作节目为例,将语句贴出来,让小伙伴更加的理解对Elasticsearch操作的特点
创建节目索引的dsl
{
"mappings": {
"properties": {
"id": {
"type": "long"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"actor": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"place": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"itemPicture": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"areaId": {
"type": "long"
},
"areaName": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"programCategoryId": {
"type": "long"
},
"parentProgramCategoryId": {
"type": "long"
},
"programCategoryName": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"showTime": {
"type": "date"
},
"showDayTime": {
"type": "date"
},
"showWeekTime": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"minPrice": {
"type": "integer"
},
"maxPrice": {
"type": "integer"
}
}
},
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}添加节目数据的dsl
{
"showWeekTime": "周三",
"parentProgramCategoryId": 1,
"showDayTime": 1710259200000,
"showTime": 1710331200000,
"title": "韦礼安「一直都在」音乐会",
"actor": "韦礼安",
"areaId": 2,
"itemPicture": "img.alicdn.com/bao/uploaded/i4/2251059038/O1CN01ktjGjk2GdSYcofEUT_!!2-item_pic.png_q60.jpg_.webp",
"minPrice": 288,
"programCategoryId": 1,
"id": 2,
"place": "JDG英特尔电子竞技中心",
"maxPrice": 488,
"programCategoryName": "演唱会"
}查询节目列表数据的dsl
{
"from": 0,
"size": 10,
"query": {
"bool": {
"must": [{
"term": {
"areaId": {
"value": 2,
"boost": 1.0
}
}
}, {
"terms": {
"parentProgramCategoryId": [1],
"boost": 1.0
}
}],
"adjust_pure_negative": true,
"boost": 1.0
}
},
"track_total_hits": 2147483647
}查询后的结果
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 2,
"hits": [
{
"_index": "damai-program",
"_id": "RUcS_40BBcQ0HdIV2_Ze",
"_score": 2,
"_source": {
"showWeekTime": "周三",
"parentProgramCategoryId": 1,
"showDayTime": 1710259200000,
"showTime": 1710331200000,
"title": "韦礼安「一直都在」音乐会",
"actor": "韦礼安",
"areaId": 2,
"itemPicture": "img.alicdn.com/bao/uploaded/i4/2251059038/O1CN01ktjGjk2GdSYcofEUT_!!2-item_pic.png_q60.jpg_.webp",
"minPrice": 288,
"programCategoryId": 1,
"id": 2,
"place": "JDG英特尔电子竞技中心",
"maxPrice": 488,
"programCategoryName": "演唱会"
}
},
{
"_index": "damai-program",
"_id": "RkcS_40BBcQ0HdIV2_am",
"_score": 2,
"_source": {
"showWeekTime": "周四",
"parentProgramCategoryId": 1,
"showDayTime": 1710950400000,
"showTime": 1711022400000,
"title": "魏嘉莹&Arrow Band 喜欢我吧 2023初秋巡回",
"actor": "魏嘉莹",
"areaId": 2,
"itemPicture": "img.alicdn.com/bao/uploaded/https://img.alicdn.com/imgextra/i1/2251059038/O1CN01MX41P32GdSYP98YBp_!!2251059038.jpg_q60.jpg_.webp",
"minPrice": 120,
"programCategoryId": 1,
"id": 1,
"place": "朝阳剧场",
"maxPrice": 180,
"programCategoryName": "演唱会"
}
},
{
"_index": "damai-program",
"_id": "R0cS_40BBcQ0HdIV2_by",
"_score": 2,
"_source": {
"showWeekTime": "周四",
"parentProgramCategoryId": 1,
"showDayTime": 1710345600000,
"showTime": 1710415800000,
"title": "冬季恋歌—《请回答1988》韩剧主题曲演唱会",
"actor": "冬季恋歌",
"areaId": 2,
"itemPicture": "img.alicdn.com/bao/uploaded/https://img.alicdn.com/imgextra/i2/2251059038/O1CN01tEJkEK2GdSYJ19cF5_!!2251059038.jpg_q60.jpg_.webp",
"minPrice": 108,
"programCategoryId": 1,
"id": 3,
"place": "秦乐宫剧院",
"maxPrice": 338,
"programCategoryName": "演唱会"
}
}
]
}
}更新: 2025-10-13 11:54:00
原文: https://www.yuque.com/u22210564/ykdrdh/ti6u5ovgfuhftlum