【Java】ElasticSearch 在项目里的应用
一、前言:
好久没写笔记了,最近忙一个项目,用到ES查询,以往的笔记写ES都是搭建环境,用Kibana玩一玩
这次是直接调用API操作了,话不多说,进入主题
二、环境前提:
公司用的还是纯ElasticSearch的API库,并没有Spring-Data-ES的包装
ElasticSearch版本是7.3.1
这是封装的包:
<!-- es start -->
<dependency>
<groupId>cn.ymcd.comm</groupId>
<artifactId>comm-elasticsearch</artifactId>
<version>1.0.3</version>
</dependency>
然后看下里面的ES依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.3.0</version><!--$NO-MVN-MAN-VER$-->
</dependency> <dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.3.0</version><!--$NO-MVN-MAN-VER$-->
</dependency> <dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>7.3.0</version><!--$NO-MVN-MAN-VER$-->
</dependency> <dependency>
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
<version>7.3.0</version><!--$NO-MVN-MAN-VER$-->
</dependency> <dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.3.0</version><!--$NO-MVN-MAN-VER$-->
</dependency>
包源码只有一个客户端类:
类似JDBC的连接,提供主机,账户密码信息,调用客户端对象方法获取连接资源
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package cn.ymcd.comm.elasticsearch; import cn.ymcd.comm.base.log.LogFactory;
import cn.ymcd.comm.base.log.YmcdLogger;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
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.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component; @Configuration
@Component("esClient")
public class EsRestClient implements AutoCloseable {
private YmcdLogger logger = LogFactory.getLogger(this.getClass());
@Value("${elasticsearch.host-name}")
private String hostName;
@Value("${elasticsearch.port:9200}")
private int port;
@Value("${elasticsearch.cluster:}")
private String cluster;
@Value("${elasticsearch.userName:}")
private String userName;
@Value("${elasticsearch.password:}")
private String password;
protected RestHighLevelClient client; public EsRestClient() {
} public RestHighLevelClient getEsClient() {
RestClientBuilder builder = null;
if (StringUtils.isNotBlank(this.cluster)) {
this.logger.debug("connect to cluster server...");
List<HttpHost> esHosts = (List)Arrays.stream(this.cluster.split(",")).map(HttpHost::create).collect(Collectors.toList());
builder = RestClient.builder((HttpHost[])esHosts.toArray(new HttpHost[esHosts.size()]));
} else {
this.logger.debug("connect to single node server...");
builder = RestClient.builder(new HttpHost[]{new HttpHost(this.hostName, this.port)});
} if (StringUtils.isNotBlank(this.userName) && StringUtils.isNotBlank(this.password)) {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(this.userName, this.password));
builder.setHttpClientConfigCallback(new HttpClientConfigCallback() {
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}
});
} this.client = new RestHighLevelClient(builder);
this.logger.debug("connected to server!");
return this.client;
} public void close() {
if (this.client != null) {
this.logger.debug("close es client..."); try {
this.client.close();
} catch (Exception var2) {
this.logger.error("close es client error!", var2);
}
} }
}
三、API封装:
封装了,但是没完全封装
1、我要做一个翻页查询都没有,还得我自己加上去整一个,麻了
2、有提供一个ES的索引名称注解和泛型声明,为什么返回类型没有一个按泛型返回的,还得是自己写
package cn.ymcd.perception.common.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder; import java.util.List; /**
* ES接口
*
* @projectName: perception-task-server
* @author: panx
* @date: 2023年09月12日 10:04
* @version: 1.0
*/
public interface IEsBaseService<T> { /**
* 基础查询 根据id查询 数据
*
* @param entity
* @param id
* @return java.lang.String
* @author panx
* @createTime 2023/9/12 0012 17:14
*/
String getById(T entity, String id); /**
* 根据多个id 查询数据信息
*
* @param entity
* @param list
* @return org.elasticsearch.action.search.SearchResponse
* @author panx
* @createTime 2023/9/12 0012 17:14
*/
SearchResponse findByIdsList(T entity, List<String> list); /**
* 查询所有信息
*
* @param from
* @param size
* @param entity
* @return org.elasticsearch.search.SearchHit[]
* @author panx
* @createTime 2023/9/12 0012 17:15
*/
SearchHit[] queryAll(int from, int size, T entity); /**
* 根据条件查询
*
* @param indexName
* @param page
* @param boolQueryBuilder
* @param highlightBuilder
* @param sortBuilder
* @return org.elasticsearch.action.search.SearchResponse
* @author panx
* @createTime 2023/9/12 0012 17:15
*/
SearchResponse whereQuery(String indexName, Page page, BoolQueryBuilder boolQueryBuilder, HighlightBuilder highlightBuilder, SortBuilder sortBuilder); /**
* @author OnCloud9
* @date 2023/9/14 13:36
* @description 翻页查询
* @param tClass
* @param page
* @param boolQueryBuilder
* @param highlightBuilder
* @param sortBuilder
* @return com.baomidou.mybatisplus.extension.plugins.pagination.Page<T>
*/
<Entity> Page<Entity> pageQuery(Class<Entity> tClass, Page page, BoolQueryBuilder boolQueryBuilder, HighlightBuilder highlightBuilder, SortBuilder sortBuilder); /**
* 设置高亮显示字段
*
* @param fields
* @return org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder
* @author panx
* @createTime 2023/9/12 0012 17:15
*/
HighlightBuilder highlightBuilder(String... fields); /**
* 获取排序规则
*
* @param fieldSort
* @param isDesc
* @return org.elasticsearch.search.sort.SortBuilder
* @author panx
* @createTime 2023/9/12 0012 17:15
*/
SortBuilder getSortBuilder(String fieldSort, Boolean isDesc); /**
* 获取高亮显示的值
*
* @param hit
* @param field
* @return java.lang.String
* @author panx
* @createTime 2023/9/12 0012 17:15
*/
String getHighlightContent(SearchHit hit, String field); /**
* 查询 索引中所有满足条件数据 游标 查询
*
* @param response
* @param restHighLevelClient
* @return java.util.List<org.elasticsearch.search.SearchHits>
* @author panx
* @createTime 2023/9/12 0012 17:16
*/
List<SearchHits> getAllData(SearchResponse response, RestHighLevelClient restHighLevelClient); /**
* 获取ES中的总数
*
* @param indexName
* @param boolQueryBuilder
* @return long
* @author panx
* @createTime 2023/9/12 0012 17:16
*/
long getCount(String indexName, BoolQueryBuilder boolQueryBuilder); /**
* 获取 注解索引名称
*
* @param entity
* @return java.lang.String
* @author panx
* @createTime 2023/9/12 0012 17:16
*/
String getIndexName(T entity); /**
* @author OnCloud9
* @date 2023/9/17 17:10
* @description 聚合查询
* @params [tClass, aggregationBuilder, resultName]
* @return java.util.List<java.util.Map<java.lang.String,java.lang.String>>
*/
<Entity> List<Terms.Bucket> getAggregationQuery(Class<Entity> tClass, AggregationBuilder aggregationBuilder, String resultName); /**
* @author OnCloud9
* @date 2023/9/18 15:40
* @description 条件聚合查询
* @params [tClass, boolQueryBuilder, aggregationBuilder, resultName]
* @return java.util.List<org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket>
*/
<Entity> List<Terms.Bucket> getConditionAggregationQuery(
Class<Entity> tClass,
BoolQueryBuilder boolQueryBuilder,
AggregationBuilder aggregationBuilder,
String resultName
);
}
首先是实体的索引名称注解:
我们项目的mysql表名 直接对应到es的索引名上,数据来源也是mysql推到es上面,统一规范了
package cn.ymcd.perception.base; import java.lang.annotation.*; /***
* 注解 索引信息
* @param
* @return
* @author gaof
* @createTime 2020/3/17 17:05
* @version: 1.0
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface EsIndex { /***
* 索引名称
* @param
* @return java.lang.String
* @author gaof
* @createTime 2020/3/17 17:06
* @version: 1.0
*/
String indexName(); }
注解的获取方式,因为做ES的CRUD都需要知晓是在哪个索引下操作
所以每个方法必定需要索引的参数传入,
叼毛同事非要从对象反射过去找注解,我给他重载下,从字节对象读就行了
这里说下接口上的泛型声明很不好,因为我在写业务的时候发现并不是只有一个索引要操作,需要多个索引操作,这个泛型声明限定死了
所以我写的方法都改用方法泛型,这样调用的时候才支持不同的业务实体
@Override
public String getIndexName(T entity) {
EsIndex index = entity.getClass().getAnnotation(EsIndex.class);
String indexName = index.indexName();
if (StringUtils.isBlank(indexName)) {
logger.error("注解索引名称为空");
throw new ElasticsearchException("注解indexName(索引名称)为空");
}
return indexName;
}
public <Entity> String getIndexName(Class<Entity> tClass) {
EsIndex index = tClass.getAnnotation(EsIndex.class);
String indexName = index.indexName();
if (StringUtils.isBlank(indexName)) {
logger.error("注解索引名称为空");
throw new ElasticsearchException("注解indexName(索引名称)为空");
}
return indexName;
}
其它的就是如何操作API了
这里重点说下PageQuery这个方法,要解决几个问题:
1、怎么接收返回的结果,ES的结果叫命中对象,放在一个数组里面,存的是JSON串
这里根据入参的实体类字节对象,交给可以做JSON序列化的工具活化JSON给对象就行了,这里用的FastJson
2、解决翻页问题,这里我不想冗余代码了,所以直接在同事写的whereQuery基础上套参写
在查询前算好from + to的参数值,返回的结果集塞回Page翻页对象交出去,
可以不传Page对象,那我默认认为调用者需要查询全部记录,就按一般索引支持的最大记录数翻页
3、索引翻页问题,因为在2上面说过,索引存在一个最大记录数的限制,有可能这个索引存了一万五千条数据,但是翻页查询只能翻到前一万条数据
在这个封装的工具方法中可以使用getCount方法获取真实的总记录数,也可以通过查询响应的命中对象获取总共的命中数量
解决的方法可以参考下链接: https://zhuanlan.zhihu.com/p/489562200
无非就三种, 1 调参数加大、2 Scroll滚动查询、3 SearchAfter标记查询
而在我的业务场景就是把ES数据带到功能上,要翻页查询,经理说不能调参数,后面两种办法又不能实现分页功能
所以折中的办法就是不调整,查到1万位置,默认认为用户不需要再看后面的内容
@Override
public String getById(T entity, String id) {
String real = "";
String indexName = getIndexName(entity);
SearchSourceBuilder builder = new SearchSourceBuilder();
SearchRequest request = new SearchRequest(indexName);
builder.query(QueryBuilders.termQuery("id", id));
request.source(builder);
SearchResponse response = null;
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
for (SearchHit hit : response.getHits()) {
real = hit.getSourceAsString();
}
} catch (IOException e) {
logger.error("根据id查询数据获取索引异常", e);
}
return real;
}
@Override
public SearchResponse findByIdsList(T entity, List<String> list) {
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
SearchRequest request = new SearchRequest(getIndexName(entity));
sourceBuilder.query(QueryBuilders.termsQuery("id", list));
request.source(sourceBuilder);
SearchResponse response = null;
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
} catch (IOException e) {
logger.error("根据多个id查询数据异常", e);
}
return response;
}
@Override
public SearchHit[] queryAll(int from, int size, T entity) {
SearchRequest searchRequest = new SearchRequest(getIndexName(entity));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query();
searchSourceBuilder.from(from);
searchSourceBuilder.size(size);
SearchHit[] hitsArr = null;
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
close();
SearchHits hits = searchResponse.getHits();
hitsArr = hits.getHits();
} catch (IOException e) {
this.logger.error("查询数据失败", e);
}
return hitsArr;
}
@Override
public SearchResponse whereQuery(String indexName, Page page, BoolQueryBuilder boolQueryBuilder, HighlightBuilder highlightBuilder, SortBuilder sortBuilder) {
if (StringUtils.isBlank(indexName)) {
return null;
}
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 设置查询条数
if (null != page) {
Long current = page.getCurrent();
Long size = page.getSize();
searchSourceBuilder.from(current.intValue());
searchSourceBuilder.size(size.intValue());
}
//设置需要排序的字段
if (null != sortBuilder) {
searchSourceBuilder.sort(sortBuilder);
}
// 设置高亮,使用默认的highlighter高亮器
if (null != highlightBuilder) {
searchSourceBuilder.highlighter(highlightBuilder);
}
searchSourceBuilder.query(boolQueryBuilder);
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
searchRequest.source(searchSourceBuilder);
logger.info("query ES where... indexName = " + indexName + ":" + searchSourceBuilder.toString());
return restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
} catch (Exception e) {
logger.error("查询ES数据信息失败", e);
}
return null;
}
@Override
public <Entity> Page<Entity> pageQuery(Class<Entity> tClass, Page page, BoolQueryBuilder boolQueryBuilder, HighlightBuilder highlightBuilder, SortBuilder sortBuilder) {
String indexName = getIndexName(tClass);
if (Objects.isNull(page)) page = new Page<>(1, 10000);
Long current = page.getCurrent();
page.setCurrent((current - 1L) * page.getSize());
SearchResponse searchResponse = whereQuery(indexName, page, boolQueryBuilder, highlightBuilder, sortBuilder);
SearchHits searchHits = searchResponse.getHits();
long value = searchResponse.getHits().getTotalHits().value; /* 超出最大记录数配置,按最大记录数返回 */
List<Entity> records = new ArrayList<>();
for (SearchHit searchHit : searchHits) {
String recordJson = searchHit.getSourceAsString();
logger.info("recordJson " + recordJson);
Entity t = JSON.parseObject(recordJson, tClass);
records.add(t);
}
page.setRecords(records);
page.setTotal(value);
return page;
}
@Override
public HighlightBuilder highlightBuilder(String... fields) {
if (null != fields && fields.length > 0) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
for (String field : fields) {
highlightBuilder.field(field);
}
highlightBuilder.preTags("<span style=\"color:red;\">")
.postTags("</span>");
return highlightBuilder;
}
return null;
}
@Override
public SortBuilder getSortBuilder(String fieldSort, Boolean isDesc) {
if (Boolean.TRUE.equals(isDesc)) {
return SortBuilders.fieldSort(fieldSort).order(SortOrder.DESC);
}
return SortBuilders.fieldSort(fieldSort).order(SortOrder.ASC);
}
@Override
public String getHighlightContent(SearchHit hit, String field) {
if (StringUtils.isBlank(field)) {
return null;
}
HighlightField highlightField = hit.getHighlightFields().get(field);
StringBuilder sub = new StringBuilder();
if (null != highlightField) {
Text[] contents = highlightField.getFragments();
if (null != contents) {
for (Text t : contents) {
sub.append(t);
}
}
}
return sub.toString();
}
@Override
public List<SearchHits> getAllData(SearchResponse response, RestHighLevelClient restHighLevelClient) {
boolean succeeded = false;
List<SearchHits> hitList = new ArrayList<>();
try {
String scrollId = response.getScrollId();
SearchHits searchHits = response.getHits();
hitList.add(searchHits);
// 根据游标查询所有数据
while (searchHits.getHits() != null && searchHits.getHits().length > 0) {
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
scrollRequest.scroll(TimeValue.timeValueMillis(30));
response = restHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
scrollId = response.getScrollId();
searchHits = response.getHits();
hitList.add(searchHits);
}
// 查询完清除游标
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
ClearScrollResponse clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
succeeded = clearScrollResponse.isSucceeded();
} catch (IOException e) {
logger.error("统一方法索引查询所有数据发生异常", e);
}
if (!succeeded) {
return new ArrayList<>();
}
return hitList;
}
@Override
public long getCount(String indexName, BoolQueryBuilder boolQueryBuilder) {
CountRequest countRequest = new CountRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(boolQueryBuilder);
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
countRequest.source(searchSourceBuilder);
CountResponse countResponse = restHighLevelClient.count(countRequest, RequestOptions.DEFAULT);
return countResponse.getCount();
} catch (Exception e) {
logger.error("统计ES数据失败", e);
}
return 0L;
}
@SuppressWarnings("Duplicates")
@Override
public <Entity> List<Terms.Bucket> getAggregationQuery(Class<Entity> tClass, AggregationBuilder aggregationBuilder, String resultName) {
String indexName = getIndexName(tClass);
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.aggregation(aggregationBuilder);
searchSourceBuilder.size(0);
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
searchRequest.source(searchSourceBuilder);
logger.info("query ES where... indexName = " + indexName + ":" + searchSourceBuilder.toString());
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
Terms terms = aggregations.get(resultName);
List<? extends Terms.Bucket> buckets = terms.getBuckets();
List<Terms.Bucket> returnBuckets = new ArrayList<>(buckets.size());
returnBuckets.addAll(buckets);
return returnBuckets;
} catch (Exception e) {
logger.error("查询ES数据信息失败", e);
return Collections.emptyList();
}
}
@SuppressWarnings("Duplicates")
@Override
public <Entity> List<Terms.Bucket> getConditionAggregationQuery(Class<Entity> tClass, BoolQueryBuilder boolQueryBuilder, AggregationBuilder aggregationBuilder, String resultN
String indexName = getIndexName(tClass);
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.aggregation(aggregationBuilder);
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(0);
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
searchRequest.source(searchSourceBuilder);
logger.info("query ES where... indexName = " + indexName + ":" + searchSourceBuilder.toString());
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
Terms terms = aggregations.get(resultName);
List<? extends Terms.Bucket> buckets = terms.getBuckets();
List<Terms.Bucket> returnBuckets = new ArrayList<>(buckets.size());
returnBuckets.addAll(buckets);
return returnBuckets;
} catch (Exception e) {
logger.error("查询ES数据信息失败", e);
return Collections.emptyList();
}
}
聚合查询的问题:
ES的聚合叫桶聚合,按查询结果来看,就是把聚合的结果丢到桶里
我在封装这个桶结果的时候遇到挺多麻烦的,费半天劲找到
1、首先从结果获取开始,是从Aggregtions对象拿取,需要提供设定聚合的名称
2、拿到Buckets集合后,不能直接返回,这个通配泛型没有指定具体类型,所以要自己重新创建具体泛型的集合装填桶元素
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
Terms terms = aggregations.get(resultName);
List<? extends Terms.Bucket> buckets = terms.getBuckets();
List<Terms.Bucket> returnBuckets = new ArrayList<>(buckets.size());
returnBuckets.addAll(buckets);
return returnBuckets;
查询条件构建:
单个分组条件,即按某个字段分组
String resultName = "typeCount";
TermsAggregationBuilder aggBuilder = AggregationBuilders.terms(resultName).field("strength.keyword");
aggBuilder.size(Integer.MAX_VALUE);
List<Terms.Bucket> buckets = esBaseService.getAggregationQuery(ObuBeTrackDTO.class, aggBuilder, resultName);
如果是多个字段分组,居然是用字符拼接处理... 属实没想到
注意指定这个分隔符,然后按分隔符处理
boolean hasStartTime = StringUtils.isNotBlank(dto.getStartTime());
boolean hasEndTime = StringUtils.isNotBlank(dto.getEndTime());
Map<String, List> resultMap = new HashMap<>();
String resultName = "roadCashCount";
TermsAggregationBuilder aggBuilder = AggregationBuilders
.terms(resultName)
.script(new Script("doc['roadNo.keyword'].value +','+ doc['cashNo.keyword'].value"))
.order(BucketOrder.count(false)) /* 按count降序 */
.size(10); /* 限制十条 */
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (hasStartTime && hasEndTime) boolQueryBuilder.must(QueryBuilders.rangeQuery("captureTime").format("yyyy-MM-dd HH:mm:ss").from(dto.getStartTime()).to(dto.getEndTime()));
List<Terms.Bucket> buckets = esBaseService.getConditionAggregationQuery(ObuEtInleaveDTO.class, boolQueryBuilder, aggBuilder, resultName);
2023年11月20日 更新:
一、震荡查询问题:
应用上线内测后发现有个问题,统计数据时不时就查的0,一点数据没有
所以我猜测是符合ES查询震荡的情况
现在的解决方案是追加分片查询类型,分片的优先级方式
每个查询实现后都追加了这个配置,后面没有再收到这个问题,说明是解决了
preference参数还有点坑,要把双引号带进去,因为这个参数是url参数,下划线好像不能直接跟等号一起
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH);
searchRequest.preference("\"_primary_first\"");
资料参考见:
查询参数
https://blog.csdn.net/m0_37739193/article/details/82628185
几种查询类型参数
https://blog.csdn.net/HuoqilinHeiqiji/article/details/103460430
二、另外两种查询方式的API实现:
SearchAfter查询的API实现
/**
* @author OnCloud9
* @date 2023/11/13 17:04
* @description
* @params [tClass, capacity, saMarkArray, boolQueryBuilder, highlightBuilder, sortBuilders, searchAfterSetter]
* @return java.util.List<Entity>
*/
@Override
@SuppressWarnings("Duplicates")
public <Entity> List<Entity> searchAfterQuery(
Class<Entity> tClass,
Integer capacity,
Object[] saMarkArray,
BoolQueryBuilder boolQueryBuilder,
HighlightBuilder highlightBuilder,
Collection<SortBuilder> sortBuilders,
BiFunction<Entity, Object[], Entity> searchAfterSetter,
Consumer<Entity> consumer
) {
String indexName = getIndexName(tClass);
if (StringUtils.isBlank(indexName)) return Collections.emptyList(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from(0); /* 使用searchAfter必须指定from为0 */
searchSourceBuilder.size(Objects.isNull(capacity) ? 10 : capacity);
if (Objects.nonNull(highlightBuilder)) searchSourceBuilder.highlighter(highlightBuilder);
if (Objects.nonNull(boolQueryBuilder)) searchSourceBuilder.query(boolQueryBuilder);
if (CollectionUtils.isNotEmpty(sortBuilders)) sortBuilders.forEach(searchSourceBuilder::sort);
if (Objects.nonNull(saMarkArray) && saMarkArray.length > 0) searchSourceBuilder.searchAfter(saMarkArray); /* 根据排序顺序依次放置上一次的排序关键字段值 */ SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH);
searchRequest.preference("\"_primary_first\""); SearchResponse searchResponse;
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
searchRequest.source(searchSourceBuilder);
logger.info("searchAfter query ES where... indexName = " + indexName + ":" + searchSourceBuilder.toString());
searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
if (Objects.isNull(searchResponse)) return Collections.emptyList();
} catch (Exception e) {
logger.error("查询ES数据信息失败", e);
return Collections.emptyList();
}
SearchHits searchHits = searchResponse.getHits();
SearchHit[] hits = searchHits.getHits();
List<Entity> entities = new ArrayList<>(hits.length);
for (SearchHit searchHit : searchHits.getHits()) {
Object[] sortValues = searchHit.getSortValues();
String recordJson = searchHit.getSourceAsString();
Entity t = JSON.parseObject(recordJson, tClass);
searchAfterSetter.apply(t, sortValues); /* 存放searchAfter值 */
consumer.accept(t);
entities.add(t);
} return entities;
}
Scroll滚动查询的API实现:
/**
* @author OnCloud9
* @date 2023/11/20 16:32
* @description scroll滚动查询
* @params [tClass, boolQueryBuilder, highlightBuilder, sortBuilders, consumer]
* @return java.util.List<Entity>
*/
@Override
public <Entity> List<Entity> scrollQuery(
Class<Entity> tClass,
BoolQueryBuilder boolQueryBuilder,
HighlightBuilder highlightBuilder,
Collection<SortBuilder> sortBuilders,
Consumer<Entity> consumer
) {
String indexName = getIndexName(tClass);
if (StringUtils.isBlank(indexName)) return Collections.emptyList(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from(0);
searchSourceBuilder.size(MAXIMUM_SIZE);
if (Objects.nonNull(highlightBuilder)) searchSourceBuilder.highlighter(highlightBuilder);
if (Objects.nonNull(boolQueryBuilder)) searchSourceBuilder.query(boolQueryBuilder);
if (CollectionUtils.isNotEmpty(sortBuilders)) sortBuilders.forEach(searchSourceBuilder::sort); Scroll scroll = new Scroll(TimeValue.timeValueMinutes(10L));
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.scroll(scroll);
searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH);
searchRequest.preference("\"_primary_first\""); SearchResponse searchResponse;
List<Entity> resultList = new ArrayList<>();
try (RestHighLevelClient restHighLevelClient = getEsClient()) {
searchRequest.source(searchSourceBuilder);
logger.info("scroll query ES where... indexName = " + indexName + ":" + searchSourceBuilder.toString());
searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
if (Objects.isNull(searchResponse)) return Collections.emptyList(); String scrollId = searchResponse.getScrollId();
SearchHit[] hits = searchResponse.getHits().getHits();
esSourceToEntity(tClass, consumer, resultList, hits); /* 持续滚动 */
while (ObjectUtil.isNotNull(hits) && hits.length > 0) {
//构造滚动查询条件
SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
searchScrollRequest.scroll(scroll);
//响应必须是上面的响应对象,需要对上一层进行覆盖。
searchResponse = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);
scrollId = searchResponse.getScrollId();
hits = searchResponse.getHits().getHits();
esSourceToEntity(tClass, consumer, resultList, hits);
} /* 查不到更多数据,清除滚动 */
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
ClearScrollResponse clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
boolean isSuccess = clearScrollResponse.isSucceeded();
logger.info("是否成功清除了滚动? " + isSuccess);
} catch (Exception e) {
logger.error("查询ES数据信息失败", e);
return Collections.emptyList();
} return resultList;
}
【Java】ElasticSearch 在项目里的应用的更多相关文章
- [大数据从入门到放弃系列教程]在IDEA的Java项目里,配置并加入Scala,写出并运行scala的hello world
[大数据从入门到放弃系列教程]在IDEA的Java项目里,配置并加入Scala,写出并运行scala的hello world 原文链接:http://www.cnblogs.com/blog5277/ ...
- 我在生产项目里是如何使用Redis发布订阅的?(二)Java版代码实现(含源码)
上篇文章讲了在实际项目里的哪些业务场景用到Redis发布订阅,这篇文章就讲一下,在Java中如何实现的. 图解代码结构 发布订阅的理论以及使用场景大家都已经有了大致了解了,但是怎么用代码实现发布订阅呢 ...
- 关于Web项目里的给表单验证控件添加结束时间不得小于开始时间的验证方法,日期转换和前台显示格式之间,还有JSON取日期数据格式转换成标准日期格式的问题
项目里有些不同页面间的日期显示格式是不同的, 第一个问题: 比如我用日期控件WdatePicker.js导包后只需在input标签里加上onClick="WdatePicker()" ...
- Java创建Web项目
首先下载Tomcat服务,用来运行JAVA程序,跟windows中的IIS类似 下载地址:tomcat.apache.org ,最好下载ZIP压缩版的,解压后就可以直接用.如下图: 检查Tomcat是 ...
- python实现文章或博客的自动摘要(附java版开源项目)
python实现文章或博客的自动摘要(附java版开源项目) 写博客的时候,都习惯给文章加入一个简介.现在可以自动完成了!TF-IDF与余弦相似性的应用(三):自动摘要 - 阮一峰的网络日志http: ...
- 纯java从apk文件里获取包名、版本号、icon
简洁:不超过5个java文件 依赖:仅依赖aapt.exe 支持:仅限windows 功能:用纯java获取apk文集里的包名,版本号,图标文件[可获取到流直接保存到文件系统] 原理:比较上一篇文章里 ...
- Java Spring MVC项目搭建(一)——Spring MVC框架集成
1.Java JDK及Tomcat安装 我这里安装的是JDK 1.8 及 Tomcat 8,安装步骤详见:http://www.cnblogs.com/eczhou/p/6285248.html 2. ...
- 架构师入门:搭建基本的Eureka架构(从项目里抽取)
没有废话,直接上干货,理论部分大家可以看其它资料. 这里是部分关键代码,如果需要全部可运行的代码,请给本人留言. 在后继,还将给出搭建高可用Eureka架构的方式. 1 Eureka的框架图 在Eur ...
- Java Elasticsearch新手入门教程
概要: 1.使用Eclipse搭建Elasticsearch详情参考下面链接 2.Java Elasticsearch 配置 3.ElasticSearch Java Api(一) -添加数据创建索引 ...
- Elasticsearch.net项目实战
elasticsearch.net项目实战 目录 Elasticsearch+kibana 环境搭建 windows 10环境配置 安装Elasticsearch head安装(非必需) 安装kiba ...
随机推荐
- 【Socket】解决TCP粘包问题
一.介绍 TCP一种面向连接的.可靠的.基于字节流的传输层协议. 三次握手: 客户端发送服务端连接请求,等待服务端的回复. 服务端收到请求,服务端回复客户端,可以建立连接,并等待. 客户端收到回复并发 ...
- docker——存储配置与管理
docker存储配置与管理 查看docker info [root@hmm overlay2]# docker info Client: Docker Engine - Community Versi ...
- P9174
problem & blog 子任务 \(1\) 和子任务 \(2\) 都比较好做.所以我们这里不讲. 状态将是数字 \(n\) (每个颜色的频率的排序数组)的所有分区,因为当我们旋转每种颜色 ...
- ABC319题解
直接从 D 开始了. D 可可爱爱的二分捏. check 就按照题目里写的就行了. 然后 \(l\) 的初值要注意一下,就是 \(\max^{i \le n}_{i=1}a_i\). 代码: #inc ...
- 网站_域名_DNS_端口_web访问过程
网站基本概念 服务器:能够提供服务器的机器,取决于机器上所安装的服务软件 web服务器:提供web服务(网站访问),需要安装web服务软件,Apache,tomcat,iis等 域名 (Domain ...
- Externalizable接口实现序列化与反序列化
Externalizable接口实现序列化与反序列化 package com.example.core.mydemo.java; import com.example.core.mydemo.json ...
- Linux高级命令
重定向 重定向也称为输出重定向,用于将命令的输出保存到目标文件. 使用方法:> 文件名 或 >> 文件名.前者会覆盖文件内容,后者会追加内容到文件. 查看文件内容命令 cat: 显示 ...
- python webdriver.remote远程创建火狐浏览器会话报错,Unable to create new service: GeckoDriverService
问题: 使用selenium.webdriver.remote,远程指定地址的浏览器,并创建会话对象:创建火狐浏览器会话时,报错,错误信息如下: Message: Unable to create n ...
- python中globals()的用法
python中globals()的用法 1. 获取所有的全局变量, 获取到的内容如下: {'__name__': '__main__', '__doc__': None, '__package__': ...
- 一款开源、免费、现代化风格的WPF UI控件库 - ModernWpf
前言 今天大姚给大家分享一款开源(MIT License).免费.现代化风格的WPF UI控件库:ModernWpf. 项目介绍 ModernWpf是一个开源项目,它为 WPF 提供了一组现代化的控件 ...