前言
本文准备记录一下ElasticSearch在SpringBoot中的使用,也是正巧项目中用的es了,之前学习es偏向自学,不清楚具体使用在哪里,以及最强大的全文搜索怎么有效的使用。本篇提炼一下项目中的使用的API;
写操作包括,索引的创建删除,mapping的格式,读操作包括,常规API的操作,嵌套结构,模糊查询,聚合操作,高亮操作。
启动一个ElasticSearch
本文就不说es怎么安装了,可以用docker安装,比较简单:
docker pull elasticsearch:7.5.1
docker run --name esSingle -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.5.1
SpringBoot引入ElasticSearch
- 首先是pom的引入
<!--es-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.5.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.5.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
application.yml 配置如下,接下来我们自己注入一下bean
elasticsearch:
host: 127.0.0.1
port: 9200
username: test
password: 12345678
connTimeout: 3000
socketTimeout: 5000
connectionRequestTimeout: 500
- 创建elasticSearch的bean对象
package com.zy.integrate.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author zhangyong
* Created on 2021-03-01
*/
@Component
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchProperties {
private String userName;
private String password;
private String host;
private Integer port;
private Integer connTimeout;
private Integer socketTimeout;
private Integer connectionRequestTimeout;
-------->setter/getter
}
----------------------------------------分隔线------------------
package com.zy.integrate.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zhangyong
* Created on 2021-03-01
*/
@EnableConfigurationProperties(ElasticSearchProperties.class)
@Configuration
public class ElasticSearchConfig {
@Autowired
private ElasticSearchProperties elasticSearchProperties;
@Bean(destroyMethod = "close", name = "restHighLevelClient")
public RestHighLevelClient initRestClient() {
RestClientBuilder builder = RestClient.builder(new HttpHost(elasticSearchProperties.getHost(), elasticSearchProperties.getPort()));
builder.setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(elasticSearchProperties.getConnTimeout())
.setSocketTimeout(elasticSearchProperties.getSocketTimeout())
.setConnectionRequestTimeout(elasticSearchProperties.getConnectionRequestTimeout()));
// TODO 如果有账号密码这块要加上
return new RestHighLevelClient(builder);
}
}
以上的代码就可以使用elastic的客户端来操作API了、
索引创建&&更新插入删除(写)操作
- 下面是我的代码demo,这里面的注释已经足够清楚的表示方法API了。测试的时候需要先执行创建索引的接口/es-index-create,然后执行bulk插入数据的接口/es-bulk-insert。然后es就有数据可以测试其他操作了。
/**
* 记载elasticSearch的update、insert、delete操作以及bulk操作
* @author zhangyong
* Created on 2021-03-01
*/
@RestController
public class ElasticController {
private static final Logger logger = LoggerFactory.getLogger(ElasticController.class);
/**
* 索引名称
*/
public static final String ES_INDEX = "zy_test_es";
/**
* 创建索引mapping的properties信息(类似MySQL中的字段)
*/
private static final String INDEX_PROPERTY = "{\"properties\": {\"id\":{\"type\":\"keyword\"},\"businessId\":{\"type\": \"long\"},\"signalList\":{\"type\":\"text\"},\"name\":{\"type\":\"text\",\"analyzer\": \"standard\"},\"age\":{\"type\":\"integer\"},\"attribute\":{\"type\": \"nested\",\"properties\": {\"language\":{\"type\":\"text\"},\"money\":{\"type\":\"double\"}}}}}";
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 创建索引,这个方法创建设计好的mapping并指定索引的shard和副本
* mapping中的属性,有嵌套结构,也有测试模糊搜索的text类型,以及默认的分词器standard,可以使用中文支持好的分词器 ik_max_word 下载安装插件
* 当然如果你用kibana或者curl啥的通过API创建也行
* PUT zy_test_es {"settings":{SETTING},"mapping":{INDEX_PROPERTY}}
* @author zhangyong
* 2021/3/1
*/
@PostMapping("/es-index-create")
public Boolean createIndex(){
GetIndexRequest request = new GetIndexRequest(ES_INDEX);
try {
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
if (exists){
logger.info("索引已经存在了: "+ES_INDEX);
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
CreateIndexRequest createIndexRequest = new CreateIndexRequest(ES_INDEX);
// GET zy_test_es/_mapping 获取mapping信息
Settings settings = Settings.builder()
.put("index.number_of_shards", 1)
.put("index.number_of_replicas", 0).build();
createIndexRequest.settings(settings);
// 设置字段映射,如果不设置的话,es可以自动更新(开启dynamic)插入文档的时候会根据文档自动生成,但是可能会有偏差,所以还是自己设置吧
createIndexRequest.mapping(INDEX_PROPERTY, XContentType.JSON);
try {
CreateIndexResponse response = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
if (response.isAcknowledged()){
logger.info("索引创建完毕: "+ES_INDEX);
}
return response.isAcknowledged();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 删除索引
* @author zhangyong
* 2021/3/1
*/
@PostMapping("/es-index-delete")
public Boolean deleteIndex(){
GetIndexRequest request = new GetIndexRequest(ES_INDEX);
try {
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
if (!exists){
logger.info("索引不存在: "+ES_INDEX);
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(ES_INDEX);
try {
AcknowledgedResponse response = restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
if (response.isAcknowledged()){
logger.info("索引删除完毕: "+ES_INDEX);
}
return response.isAcknowledged();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 保存文档,指定id(es默认会生成,但是随机的东西跟我们业务联系比较难)
* 因为保存文档在es中的叫法是索引一个文档,所以就叫indexRequest了
* @author zhangyong
* 2021/3/1
*/
@PostMapping("/es-doc-insert")
public Boolean saveDocument(){
long id = System.currentTimeMillis()%100000;
IndexRequest indexRequest = new IndexRequest(ES_INDEX);
// 获取测试文档对象
Map<String, Object> map = getObject(id);
indexRequest.id(String.valueOf(id)).source(map);
try {
restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 批量导入,一次连接
* 批量操作适用于增删改操作,不能用于查询
* 对于批量查询而言有一个multiGet操作,是通过id查询的,也有专门批量查定制化search的multiSearch
* @author zhangyong
* 2021/3/1
*/
@PostMapping("/es-bulk-insert")
public Boolean bulkInsertDocument(){
BulkProcessor bulkProcessor;
BulkProcessor.Listener listener = new BulkProcessor.Listener() {
@Override
public void beforeBulk(long executionId, BulkRequest request) {
int numberOfActions = request.numberOfActions();
logger.debug("Executing bulk [{}] with {} requests",
executionId, numberOfActions);
}
@Override
public void afterBulk(long executionId, BulkRequest request,
BulkResponse response) {
if (response.hasFailures()) {
logger.warn("Bulk [{}] executed with failures", executionId);
} else {
logger.debug("Bulk [{}] completed in {} milliseconds",
executionId, response.getTook().getMillis());
}
}
@Override
public void afterBulk(long executionId, BulkRequest request,
Throwable failure) {
logger.error("Failed to execute bulk", failure);
}
};
BulkProcessor.Builder builder = BulkProcessor.builder(
(request, bulkListener) -> restHighLevelClient.bulkAsync(request, RequestOptions.DEFAULT, bulkListener),listener);
// 每500个request flush一次
builder.setBulkActions(500);
// bulk数据每达到1MB flush一次
builder.setBulkSize(new ByteSizeValue(1L, ByteSizeUnit.MB));
// 0代表同步提交即只能提交一个request;即同步bulk操作,例如500个提交,那就500提交完再继续,异步不等
// 1代表当有一个新的bulk正在累积时,1个并发请求可被允许执行
builder.setConcurrentRequests(1);
// 每10s刷一次数据提交
builder.setFlushInterval(TimeValue.timeValueSeconds(10L));
// 设置策略
// Set a constant back off policy that initially waits for 1 second and retries up to 3 times.
builder.setBackoffPolicy(BackoffPolicy.constantBackoff(TimeValue.timeValueSeconds(1L), 3));
bulkProcessor = builder.build();
// 要插入的数量
int loop = 100;
for (int i = 0; i < loop; i++) {
long id = 10000+i;
IndexRequest indexRequest = new IndexRequest(ES_INDEX);
// The number of object passed must be even but was [1]
Map<String, Object> map = getObject(id);
indexRequest.id(String.valueOf(id)).source(map);
bulkProcessor.add(indexRequest);
if (i%2 == 0){
// 批量操作,控制刷新(提交)时刻;
bulkProcessor.flush();
}
}
bulkProcessor.flush();
try {
// 关闭bulk连接
bulkProcessor.awaitClose(100, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
/**
* 创建测试对象
* @author zhangyong
* 2021/3/2
*/
private Map<String,Object> getObject(long id){
AttrDTO attrDTO = new AttrDTO("JAVA-C", BigDecimal.valueOf(10000).add(BigDecimal.valueOf(id)));
List<String> signal = new ArrayList<>();
signal.add("qwe");
signal.add("asd");
signal.add(String.valueOf(id));
EsDemoPO demo = new EsDemoPO(String.valueOf(id),String.valueOf(id),attrDTO,(int)(id%100),"sari-"+id,signal);
System.out.println(JSONObject.toJSONString(demo));
// The number of object passed must be even but was [1]
// 这块如果用json形式会报错 需要在source的时候,改用map
Gson gson = new Gson();
String jsonString = JSONObject.toJSONString(demo);
return gson.fromJson(jsonString, Map.class);
}
/**
* 通过id批量查文档
* @author zhangyong
* 2021/3/2
*/
@PostMapping("/es-multi-get")
public MultiGetItemResponse[] multiGet(@RequestBody List<String> ids){
MultiGetRequest request = new MultiGetRequest();
if (CollectionUtils.isEmpty(ids)){
return null;
}
for (String id : ids) {
request.add(new MultiGetRequest.Item(ES_INDEX, id));
}
try {
MultiGetResponse responses = restHighLevelClient.mget(request, RequestOptions.DEFAULT);
return responses.getResponses();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 更新文档 通过id
* 里面还有upsert 不存在就插入(用id来判存在的)
* update table set k1=v1... where id = x
* @author zhangyong
* 2021/3/1
*/
@PostMapping("/es-doc-update")
public Boolean updateDocument(@RequestParam("id")String id) throws IOException {
if (Objects.isNull(id)){
return false;
}
AttrDTO attrDTO = new AttrDTO("JAVA123", BigDecimal.valueOf(15001));
List<String> signal = new ArrayList<>();
signal.add("asd");
signal.add("qwe");
// 不为空的字段就会覆盖旧值
EsDemoPO demo = new EsDemoPO("123",null,attrDTO,null,"update_"+id,signal);
UpdateRequest updateRequest = new UpdateRequest(ES_INDEX,id);
// UpdateRequest updateRequest = new UpdateRequest(ES_INDEX,id+"123");//换成开启这行在upsert时会新增source
Gson gson = new Gson();
String jsonString = JSONObject.toJSONString(demo);
// doc语法的更新就是该字段如果存在就覆盖,该字段没有就在这个source加上该字段
updateRequest.doc(gson.fromJson(jsonString, Map.class));
// 如果不存在就插入,存在就更新(这里是指id的这个文档是否存在)
updateRequest.docAsUpsert(true);
UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
return response.status().equals(RestStatus.OK);
}
/**
* 删除文档 通过id
* delete from table where id = x
* @author zhangyong
* 2021/3/1
*/
@PostMapping("/es-doc-delete")
public Boolean deleteDocument(@RequestParam("id")String id){
if (Objects.isNull(id)){
return false;
}
DeleteRequest deleteRequest = new DeleteRequest(ES_INDEX,id);
try {
DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
return response.status().equals(RestStatus.OK);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 通过查询条件删除
* delete from table where age = 10;
* @author zhangyong
* 2021/3/2
*/
@PostMapping("/es-doc-delete-query")
public Long deleteDocumentByQuery(){
DeleteByQueryRequest delete = new DeleteByQueryRequest(ES_INDEX);
// 这里用一下BoolQueryBuilder 就是我们用的bool DSL语法
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(QueryBuilders.termQuery("age",10));
delete.setQuery(boolQueryBuilder);
try {
BulkByScrollResponse response = restHighLevelClient.deleteByQuery(delete, RequestOptions.DEFAULT);
return response.getDeleted();
} catch (IOException e) {
e.printStackTrace();
}
return 0L;
}
/**
* 通过查询条件更新
* update table set name = 'zhangyong',set money = money+1 where age = 18;
* @author zhangyong
* 2021/3/2
*/
@PostMapping("/es-doc-update-query")
public Long updateDocumentByQuery(){
UpdateByQueryRequest update = new UpdateByQueryRequest(ES_INDEX);
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(QueryBuilders.termQuery("age",69));
update.setQuery(boolQueryBuilder);
// es可以利用脚本更新 ctx是上下文然后 . 字段就行了
update.setScript(new Script("ctx._source['name']='zhangyong';ctx._source.attribute.money++;"));
try {
BulkByScrollResponse response = restHighLevelClient.updateByQuery(update, RequestOptions.DEFAULT);
return response.getUpdated();
} catch (IOException e) {
e.printStackTrace();
}
return 0L;
}
}
ElasticSearch的API查询操作
查询操作包括常规API、嵌套结构nested的使用、模糊查询、聚合查询、高亮查询。
/**
* 记载查询操作
* @author zhangyong
* Created on 2021-03-03
*/
@RestController
public class ElasticSearchController {
private static final Logger logger = LoggerFactory.getLogger(ElasticSearchController.class);
@Autowired
private RestHighLevelClient client;
/**
* 记录一些常用的过滤查询手段。包括bool查询中must、mustNot、范围查询、嵌套查询、范围查询
* 搜索也可以设置搜索模板 searchTemplate 这个还没真正使用过,看API没看出来哪里好,有替换变量的功能
* @author zhangyong
* 2021/3/3
*/
@GetMapping("/es-normal-search")
public Map<String,Map<String, Object>> normalSearch(){
// 查询请求 类似 where
SearchRequest searchRequest = new SearchRequest(ElasticController.ES_INDEX);
// 构建查询文档条件的builder构建器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// bool 查询
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.mustNot(QueryBuilders.termQuery("businessId",10000));
// 范围查询
boolQueryBuilder.must(QueryBuilders.rangeQuery("age").gte(0).lt(20));
// 集合查询,满足一个集合属性就会返回
boolQueryBuilder.must(QueryBuilders.termsQuery("signalList","asd"));
// 嵌套查询
BoolQueryBuilder nestBoolQuery = new BoolQueryBuilder();
// 需要注意的是term查询如果是字符串是会有大小写问题的,match查询可以避免这个问题,把matchQuery改成termQuery会发现搜不到,因为JAVA是大写
// term表示词条查询,被查询的字段不会进行分词,需要完全匹配。
nestBoolQuery.must(QueryBuilders.matchQuery("attribute.language","JAVA"));
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attribute", nestBoolQuery, ScoreMode.None);
boolQueryBuilder.must(nestedQueryBuilder);
logger.info(boolQueryBuilder.toString());
// 设置查询条件,设置分页数量 类似 limit 0,20
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.from(0);
searchSourceBuilder.size(20);
// 设置展示全部total 不然如果你数据比较多,超过10000了就会展示10000而不是真实的数据
searchSourceBuilder.trackTotalHits(true);
// 排序
searchSourceBuilder.sort("id", SortOrder.DESC);
searchRequest.source(searchSourceBuilder);
return search(searchRequest);
}
/**
* 搜索
* @author zhangyong
* 2021/3/3
*/
private Map<String,Map<String, Object>> search(SearchRequest searchRequest){
Map<String,Map<String, Object>> res = new LinkedHashMap<>(32);
try {
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
SearchHit[] hitsHits = hits.getHits();
if(hitsHits !=null && hitsHits.length > 0){
for (SearchHit hit : hitsHits) {
Map<String, Object> source = hit.getSourceAsMap();
if (!CollectionUtils.isEmpty(source) && Objects.nonNull(source.get("businessId"))){
res.put(source.get("businessId").toString(),source);
}
}
}
return res;
} catch (IOException e) {
e.printStackTrace();
}
return res;
}
/**
* 模糊匹配---也是es最大的优势,全文检索模糊匹配
* 投影返回,只返回businessId和name
* @author zhangyong
* 2021/3/3
*/
@GetMapping("/es-fuzzy-match")
public Map<String, Map<String, Object>> fuzzyMatching(){
SearchRequest searchRequest = new SearchRequest(ElasticController.ES_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//GET zy_test_es/_analyze
//{"field": "name","text": "sari-1111"}
//以上是获取分词信息的查询DSL,分词分啥样跟你选的分词器直接相关,minimumShouldMatch是设置匹配程度,可以是百分比,operator是or/and默认是or
//表示包含多少分词,可以是百分比,也可以是数字 比方说是 2代表 包含两个分词的文档才返回。
//还可以是百分比,比如 50%代表 sari和1111占全部分词数量的一半 2*50%向下取整,乘积为0时就是1了。百分比就是你输入的话进行分词之后要满足百分之多少的分词
boolQueryBuilder.must(QueryBuilders.matchQuery("name","sari").minimumShouldMatch("1").operator(Operator.OR));
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(20);
// 投影操作
searchSourceBuilder.fetchSource(new String[]{"name","businessId"},null);
// 设置随机排序,分页返回时是随机返回的
Script script = new Script("Math.random()");
ScriptSortBuilder sortBuilder = new ScriptSortBuilder(script, ScriptSortBuilder.ScriptSortType.NUMBER);
searchSourceBuilder.sort(sortBuilder);
searchRequest.source(searchSourceBuilder);
return search(searchRequest);
}
/**
* 批量自定义查询,有时我们有批量搜索的场景,multiSearch只发送一次HTTP请求比起for发请求好太多
* @author zhangyong
* 2021/3/3
*/
@GetMapping("/es-multi-search")
public Map<String,Map<String, Object>> multiSearch(){
MultiSearchRequest multiSearchRequest = new MultiSearchRequest();
SearchRequest searchRequest1 = new SearchRequest(ElasticController.ES_INDEX);
SearchSourceBuilder sourceBuilder1 = new SearchSourceBuilder();
sourceBuilder1.query(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("businessId",10004)));
searchRequest1.source(sourceBuilder1);
multiSearchRequest.add(searchRequest1);
SearchRequest searchRequest2 = new SearchRequest(ElasticController.ES_INDEX);
SearchSourceBuilder sourceBuilder2 = new SearchSourceBuilder();
sourceBuilder2.query(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("businessId",10002)));
searchRequest2.source(sourceBuilder2);
multiSearchRequest.add(searchRequest2);
SearchRequest searchRequest3 = new SearchRequest(ElasticController.ES_INDEX);
SearchSourceBuilder sourceBuilder3 = new SearchSourceBuilder();
sourceBuilder3.query(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("businessId",10001)));
searchRequest3.source(sourceBuilder3);
multiSearchRequest.add(searchRequest3);
Map<String,Map<String, Object>> allRes = new LinkedHashMap<>(32);
try {
MultiSearchResponse msearch = client.msearch(multiSearchRequest, RequestOptions.DEFAULT);
MultiSearchResponse.Item[] responses = msearch.getResponses();
for (MultiSearchResponse.Item res : responses) {
if (Objects.nonNull(res.getFailure())){
logger.warn("批量请求异常: "+res.getFailureMessage());
continue;
}
SearchResponse response = res.getResponse();
SearchHits hits = response.getHits();
SearchHit[] hitsHits = hits.getHits();
if(hitsHits !=null && hitsHits.length > 0){
for (SearchHit hit : hitsHits) {
Map<String, Object> source = hit.getSourceAsMap();
if (!CollectionUtils.isEmpty(source) && Objects.nonNull(source.get("businessId"))){
allRes.put(source.get("businessId").toString(),source);
}
}
}
}
return allRes;
} catch (IOException e) {
e.printStackTrace();
}
return allRes;
}
/**
* 内嵌聚合查询 having查询
* 聚合算 每个年龄 平均薪水 > 10000的数量,count_age是数量
* select age,count(age) as count_age,avg(attribute.money) as average_money
* from table
* group by age
* having average_money > 20050 and count_age > 0
* @author zhangyong
* 2021/3/3
*/
@GetMapping("/es-aggregation-search")
public Map<String,Long> aggregationSearch(){
SearchRequest searchRequest = new SearchRequest(ElasticController.ES_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 聚合查询,按age字段分组,返回名称为count_age 返回100条(因为不指定不会将数据都返回) field指定group by 的字段
TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("count_age").field("age").size(100);
searchSourceBuilder.aggregation(aggregationBuilder);
// 能放到select 中的聚合操作,嵌套聚合,其中average_money是select的,nest_average_money是对象字段名字
aggregationBuilder.subAggregation(AggregationBuilders.nested("average_money","attribute")
.subAggregation(AggregationBuilders.avg("nest_average_money").field("attribute.money")));
// 非嵌套的聚合
/// aggregationBuilder.subAggregation(AggregationBuilders.avg("average_money").field("age"));
// 声明BucketPath,用于后面的bucket筛选
Map<String, String> bucketsPathsMap = new HashMap<>(8);
bucketsPathsMap.put("count_age", "_count");
// 如果解开"非嵌套的聚合"的代码,average_money.nest_average_money需要替换成average_money
bucketsPathsMap.put("average_money", "average_money.nest_average_money");
// 这个里面的params 执行的就是bucketsPathsMap 中的参数,bucketsPathsMap中是查出来值,可以理解是select的结果变量,对于内嵌结构语法如上
Script script = new Script("params.average_money >= 20050 && params.count_age > 0");
//构建bucket选择器
BucketSelectorPipelineAggregationBuilder bpab = PipelineAggregatorBuilders.bucketSelector("having", bucketsPathsMap, script);
// 加having条件
aggregationBuilder.subAggregation(bpab);
// 不要source数据,只要聚合数据,两者分开返回的,互不影响;只要聚合数据,这个数据就没用就不需要返回了、
searchSourceBuilder.size(0);
System.out.println(searchSourceBuilder.toString());
searchRequest.source(searchSourceBuilder);
Map<String,Long> map = new LinkedHashMap<>();
try {
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = response.getAggregations();
Terms terms = aggregations.get("count_age");
for (Terms.Bucket bucket : terms.getBuckets()) {
map.put(bucket.getKeyAsString(),bucket.getDocCount());
Map<String, Aggregation> asMap = bucket.getAggregations().getAsMap();
// 这个需要注意 强转的类型
ParsedNested averageMoney = (ParsedNested) asMap.get("average_money");
Avg avg = averageMoney.getAggregations().get("nest_average_money");
System.out.println(avg.value());
}
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
/**
* 高亮搜索
* @author zhangyong
* 2021/3/3
*/
@GetMapping("/es-high-light")
public Map<String,List<String>> highlightSearch(){
SearchRequest searchRequest = new SearchRequest(ElasticController.ES_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 模糊匹配搜name 包含 sari的 或者language是Java的
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.should(QueryBuilders.matchQuery("name","sari"));
BoolQueryBuilder nestBool = new BoolQueryBuilder();
nestBool.must(QueryBuilders.matchQuery("attribute.language","java"));
boolQueryBuilder.should(QueryBuilders.nestedQuery("attribute",nestBool,ScoreMode.None));
searchSourceBuilder.query(boolQueryBuilder);
// 高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 高亮 name 字段搜出来的结果
HighlightBuilder.Field highlightName =new HighlightBuilder.Field("name");
HighlightBuilder.Field highlightLanguage =new HighlightBuilder.Field("attribute.language");
// 设置
highlightName.highlighterType("unified");
highlightBuilder.fields().add(highlightName);
highlightBuilder.fields().add(highlightLanguage);
// 设置要高亮字段的标签 默认是<em> 这个标签tag可以根据前端框架css来写
highlightBuilder.preTags("<span style=\"color:#F56C6C\">");
highlightBuilder.postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
searchRequest.source(searchSourceBuilder);
System.out.println(searchSourceBuilder.toString());
Map<String, List<String>> res = new HashMap<>();
try {
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
SearchHit[] hitsHits = hits.getHits();
if(hitsHits !=null && hitsHits.length > 0){
for (SearchHit hit : hitsHits) {
Map<String, Object> asMap = hit.getSourceAsMap();
String businessId = asMap.get("businessId").toString();
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
// 我们得到高亮的字段后就可以返回给前端了,前端展示的就是高亮的了
HighlightField highLightName = highlightFields.get("name");
HighlightField language = highlightFields.get("attribute.language");
if (Objects.nonNull(highLightName) && Objects.nonNull(language)){
Text[] fragments = highLightName.getFragments();
Text[] languageFragments = language.getFragments();
List<String> high = new ArrayList<>();
if (fragments.length > 0){
high.add(fragments[0].toString());
}
if (languageFragments.length > 0){
high.add(languageFragments[0].toString());
}
res.put(businessId,high);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return res;
}
}
源码
https://github.com/StrandingHeart/JavaIntegration/tree/feature/elasticsearch
参考
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.11/java-rest-high.html