SpringBoot实战elasticSearchAPI微服务开发

前言

   本文准备记录一下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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Elasticsearch 简介 ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。elasticSearch 的使用场景 1、在海量数据前提下,对数据进行检索。比如:京东,淘宝等电商项目课程目标: 1. 了解企业级搜索引擎2. 安装elasticsearch 课程目录: 01 课程介绍02 elasticsearch 简介03 elasticsearch 使用场景04 安装elasticsearch 之前先安装jdk05 安装elasticsearch06 测试elasticsearch是否安装成功 07 安装kibana08 elasticsearch 基本认识 以及添加索引和删除索引09 elasticsearch 添加查询数据10 elasticsearch 修改删除数据11 elasticsearch 有条件的查询12 分词子属性fuzzy查询13 elasticsearch 过滤使用14 elasticsearch 排序与分页15 elasticsearch 如何查询指定的字段16 elasticsearch 高亮显示17 elasticsearch 聚合18 elasticsearch mapping 概念19 elasticsearch 的中文词库20 elasticsearch 中文词库安装测试21 elasticsearch 中文词库的使用案例22 elasticsearch 自定义词库配置23 安装nginx 配置中文词库24 测试elasticsearch 自定义中文词库25 搭建项目父工程26 搭建项目bean-interface-common27 搭建search 的service web 项目28 测试项目是否能与elasticsearch联通29 创建数据库并搭建首页30 数据上传功能的实现类完成31 数据上传控制器完成32 dubbo 介绍以及安装zookeeper33 将数据从mysql 上传到elasticsearch 中34 elasticsearch查询功能分析35 编写业务需求的dsl 语句36 编写输入参数返回结果集的实体类37 实现类编写38 编写实现类中dsl 语句39 返回集结果转换40 结果测试41 测试通过输入查询条件并将数据显示到页面

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值