Elasticsearch实战(一):Springboot实现Elasticsearch统一检索功能

系列文章索引

Elasticsearch实战(一):Springboot实现Elasticsearch统一检索功能
Elasticsearch实战(二):Springboot实现Elasticsearch自动汉字、拼音补全,Springboot实现自动拼写纠错
Elasticsearch实战(三):Springboot实现Elasticsearch搜索推荐
Elasticsearch实战(四):Springboot实现Elasticsearch指标聚合与下钻分析
Elasticsearch实战(五):Springboot实现Elasticsearch电商平台日志埋点与搜索热词

一、准备工作

1、搭建docker环境

快速搭建centos7虚拟机——使用virtualbox+vagrant
centos7安装与卸载docker-简单而详细无坑

2、安装es+kibana

docker安装elasticSearch+kibana

注意,我们本次使用的是elasticSearch7.4.0 + kibana7.4.0

3、es安装ik分词器

(1)下载安装

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
找到对应版本的分词器:https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.0/elasticsearch-analysis-ik-7.4.0.zip

# 将分词器插件拷贝到es容器
docker cp /root/elasticsearch-analysis-ik-7.4.0.zip 7f1456dff26d:/usr/share/elasticsearch/plugins
# 进入容器
docker exec -it cb5bffb16ac5 /bin/bash
# 安装zip命令
yum install -y unzip zip
# 创建目录
mkdir /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-7.4.0
# 解压
unzip -d /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-7.4.0 /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-7.4.0.zip
rm -f /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-7.4.0.zip
# 重启es
docker restart cb5bffb16ac5
# 查看日志
docker logs cb5bffb16ac5

(2)测试

GET _analyze
{
    
    
  "analyzer" : "ik_smart",
  "text" : "小米全面屏手机奥利给"
}


GET _analyze
{
    
    
  "analyzer" : "ik_max_word",
  "text" : "小米全面屏手机奥利给"
}

我们发现,执行结果,并不识别“全面屏” 、 “奥利给”。我们需要自定义分词。

cd /mydata/elasticsearch/plugins/elasticsearch-analysis-ik-7.4.0/config
vi IKAnalyzer.cfg.xml

#修改内容:
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">my.dic</entry>

# 自定义分词
vi my.dic
# 内容:
奥利给
全面屏

# 重启es
docker restart cb5bffb16ac5

此时,奥利给 、 全面屏 这两个网络词语,就支持了。

4、Springboot

(1)引包

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.0</version>
</dependency>

(2)客户端公共方法

private final static String cluster_host = "192.168.56.10";
private final static Integer eNode3_port = 9200;
/**
 * 超时时间设为5分钟
 */
private static final int TIME_OUT = 5 * 60 * 1000;

private static final RestHighLevelClient client = highLevelClient();

private static RestClientBuilder restClientBuilder() {
    
    
    return RestClient.builder( // 可以传多个作为集群
            new HttpHost(cluster_host, eNode3_port, "http"));

}

/**
 * 获取客户端
 */
public static RestHighLevelClient highLevelClient() {
    
    
    RestClientBuilder restClientBuilder = restClientBuilder();
    restClientBuilder.setRequestConfigCallback(
            new RestClientBuilder.RequestConfigCallback() {
    
    
                @Override
                public RequestConfig.Builder customizeRequestConfig(
                        RequestConfig.Builder requestConfigBuilder) {
    
    
                    return requestConfigBuilder.setSocketTimeout(TIME_OUT);
                }
            });

    return new RestHighLevelClient(restClientBuilder);
}

(3)公共实体类

//如果加该注解的字段为null,那么就不序列化
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CommonEntity implements Serializable {
    
    
    //页码
    private int pageNumber;
    //每页数据条数
    private int pageSize;
    //索引名称
    private String indexName;
    //高亮列
    private String highlight;
    //排序 DESC  ASC
    private String sortOrder;
    //排序列
    private String sortField;
    //自动补全建议列
    private String suggestFileld;
    //自动补全建议值
    private String suggestValue;
    //自动补全返回个数
    private Integer suggestCount;
    //动态查询参数封装(重要)
    Map<String, Object> map;
    //批量增加list
    private List<Map<String, Object>> list;

	// ... get  set
}
public class CommonMap<K,V> extends HashMap<K,V> {
    
    

    public CommonMap putData(K key, V value) {
    
    
        super.put(key, value);
        return this;
    }
}

(4)公共工具类

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @Class: SearchTools 查询服务工具类
 */
public class SearchTools {
    
    

    /*
     * @Description: 高亮前端显示组装,SearchResponse传递引用
     * 为什么二次处理高亮?
     * 原因:被设置的高亮列,es自动放到了highlight属性中;这个属性渲染了高亮的着色
     * 数据传输的时候,我们需要将它取出来
     * 覆盖到我们的_source中
     * @Method: setHighResult
     * @Param: [searchResponse, commonEntity]
     * @Update:
     * @since: 1.0.0
     * @Return: java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
     *
     *
     */
    public static void setHighResultForCleintUI(SearchResponse searchResponse, String highlightField) {
    
    
        if (StringUtils.isNoneEmpty(highlightField)) {
    
    
            for (SearchHit hit : searchResponse.getHits()) {
    
    
                //获取高亮字段map
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                //获取到具体的高亮列
                HighlightField highlightFieldName = highlightFields.get(highlightField);
                //getSourceAsMap拿到具体的数据
                Map<String, Object> source = hit.getSourceAsMap();
                if (highlightFieldName != null) {
    
    
                    //获取渲染后的文本
                    Text[] fragments = highlightFieldName.fragments();
                    String name = "";
                    for (Text text : fragments) {
    
    
                        name += text;
                    }
                    source.put(highlightField, name);   //高亮字段替换掉原本的内容
                }
            }


        }
    }
    /*
     * @Description: 获取高亮构建器
     * @Method:
     * @Param:
     * @Update:
     * @since: 1.0.0
     * @Return:
     *
     */

    public static HighlightBuilder getHighlightBuilder(String highlightField) {
    
    

        // 设置高亮,使用默认的highlighter高亮器,默认em斜体
        HighlightBuilder highlightBuilder = new HighlightBuilder(); //生成高亮查询器
        highlightBuilder.field(highlightField);      //高亮查询字段
        highlightBuilder.requireFieldMatch(false);     //如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<span style= " +
                "color:red;font-weight:bold;font-size:15px;" +
                ">");   //高亮设置
        highlightBuilder.postTags("</span>");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段
        return highlightBuilder;
    }


    /*
     * @Description: 获取排序  DESC  ASC 前端不区分大小写,默认返回DESC
     * @Method: getSortOrder
     * @Param: [sortOrder]
     * @Update:
     * @since: 1.0.0
     * @Return: org.elasticsearch.search.sort.SortOrder
     *
     */
    public static SortOrder getSortOrder(String sortOrder) {
    
    
        SortOrder so = null;
        sortOrder = StringUtils.isEmpty(sortOrder) ? "" : sortOrder.toLowerCase();
        switch (sortOrder) {
    
    
            case "desc":
                so = SortOrder.DESC;
                break;
            case "asc":
                so = SortOrder.ASC;
                break;
            default:
                so = SortOrder.DESC;
                break;
        }
        return so;
    }


    /*
     * @Description: MAP转数组
     * @Method: mapToObjectGropu
     * @Param: [data]
     * @Update:
     * @since: 1.0.0
     * @Return: java.lang.Object[]
     *
     */
    public static Object[] mapToObjectGroup(Map<String, Object> data) {
    
    
        List<Object> args = new ArrayList<Object>();
        if (data != null) {
    
    
            data.forEach((key, value) -> {
    
    
                args.add(key);
                args.add(value);
            });
        }

        return args.toArray();
    }

    /*
     * @Description: 根据客户端传来的查询参数(标准的DSL语句)构建XContentParser
     * @Method: getXContentParser
     * @Param: []
     * @Update:
     * @since: 1.0.0
     * @Return: org.elasticsearch.common.xcontent.XContentParser
     *
     */
    public static XContentParser getXContentParser(CommonEntity commonEntity) throws IOException {
    
    

        //构建SearchModule对象置 ,通过构造器注册解析器、建议器、排序等
        SearchModule searchModule = new SearchModule(Settings.EMPTY, false, Collections.emptyList());
        //获取注册成功的注册解析器、建议器、排序
        NamedXContentRegistry registry = new NamedXContentRegistry(searchModule.getNamedXContents());
        //将前端传来的DSL参数通过解析解解析
        XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(registry, LoggingDeprecationHandler.INSTANCE, JSONObject.toJSONString(commonEntity.getMap()));
        return parser;
    }

    /*
     * @Description: 将查询出来的数据放到本地局部线程变量中
     * @Method: setResponseThreadLocal
     * @Param: [response]
     * @Update:
     * @since: 1.0.0
     * @Return: void
     *
     */
    public static void setResponseThreadLocal(SearchResponse response) {
    
    
        //查询出来的数据
        SearchHit[] sh = response.getHits().getHits();
        //定义list用来接收所有Resource下面的结果集
        List<JSONObject> list = new ArrayList<JSONObject>();
        if (sh != null) {
    
    
            for (SearchHit hit : sh) {
    
    
                list.add(JSONObject.parseObject(hit.getSourceAsString()));
            }
            //将数据放入到本地线程
            ResponseThreadLocal.set(list);
        }
    }

}
/**
 * @Description: 使用线程本地局部变量处理结果集
 */
public class ResponseThreadLocal {
    
    
    private static final ThreadLocal<List<JSONObject>> threadLocal = new ThreadLocal<List<JSONObject>>();

    /*
     * @Description: 通过本地线程局部变量获取结果集
     * @Method: getList
     * @Param: []
     * @Update:
     * @since: 1.0.0
     * @Return: java.util.List<com.alibaba.fastjson.JSONObject>
     *
     */
    public static List<JSONObject> get() {
    
    
        return threadLocal.get();
    }

    /*
     * @Description:
     * @Method: 将统计后的数据集放入到当前线程
     * @Param: [list]
     * @Update:
     * @since: 1.0.0
     * @Return: void
     *
     */
    public static void set(final List<JSONObject> list) {
    
    
        threadLocal.set(list);
    }

    /*
     * @Description:
     * @Method: 清空当前线程本地局部变量值;否则内存泄露
     * @Param: []
     * @Update:
     * @since: 1.0.0
     * @Return: void
     *
     */
    public static void clear() {
    
    
        threadLocal.set(null);
    }

}

5、数据准备

(1)添加映射

PUT product
{
    
    
  "settings": {
    
    
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    
    
    "properties": {
    
    
      "name": {
    
    
        "type": "text"
    
      },
      "price": {
    
    
        "type": "double"
      } 
    }
  }
}

或者使用代码:

/*
 * @Description: 新增索引+setting+映射+自定义分词器pinyin
 * setting可以为空(自定义分词器pinyin在setting中)
 * 映射可以为空
 * @Method: addIndexAndMapping
 * @Param: [commonEntity]
 * @Return: boolean
 *
 */
public boolean addIndexAndMapping(CommonEntity commonEntity) throws Exception {
    
    
    //设置setting的map
    Map<String, Object> settingMap = new HashMap<String, Object>();
    //创建索引请求
    CreateIndexRequest request = new CreateIndexRequest(commonEntity.getIndexName());
    //获取前端参数
    Map<String, Object> map = commonEntity.getMap();
    //循环外层的settings和mapping
    for (Map.Entry<String, Object> entry : map.entrySet()) {
    
    
        if ("settings".equals(entry.getKey())) {
    
    
            if (entry.getValue() instanceof Map && ((Map) entry.getValue()).size() > 0) {
    
    
                request.settings((Map<String, Object>) entry.getValue());
            }
        }
        if ("mapping".equals(entry.getKey())) {
    
    
            if (entry.getValue() instanceof Map && ((Map) entry.getValue()).size() > 0) {
    
    
                request.mapping((Map<String, Object>) entry.getValue());
            }

        }
    }
    //创建索引操作客户端
    IndicesClient indices = client.indices();
    //创建响应对象
    CreateIndexResponse response = indices.create(request, RequestOptions.DEFAULT);
    //得到响应结果
    return response.isAcknowledged();
}

(2)批量添加数据

/*
 * @Description: 批量新增文档,可自动创建索引、自动创建映射
 * @Method: bulkAddDoc
 * @Param: [indexName, map]
 *
 */
public static RestStatus bulkAddDoc(CommonEntity commonEntity) throws Exception {
    
    
    //通过索引构建批量请求对象
    BulkRequest bulkRequest = new BulkRequest(commonEntity.getIndexName());
    //循环前台list文档数据
    for (int i = 0; i < commonEntity.getList().size(); i++) {
    
    
        bulkRequest.add(new IndexRequest().source(XContentType.JSON, SearchTools.mapToObjectGroup(commonEntity.getList().get(i))));
    }
    //执行批量新增
    BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    return bulkResponse.status();
}

public static void main(String[] args) throws Exception {
    
    
	// 批量插入
    CommonEntity commonEntity = new CommonEntity();
    commonEntity.setIndexName("product"); // 索引名
    List<Map<String, Object>> list = new ArrayList<>();
    commonEntity.setList(list);
    list.add(new CommonMap<String, Object>().putData("name", "小米(MI)10手机全面屏").putData("price", 2233.8));
    list.add(new CommonMap<String, Object>().putData("name", "小米(MI)11手机划超酷炫").putData("price", 13123));
    list.add(new CommonMap<String, Object>().putData("name", "小米(MI)电脑大da屏").putData("price", 213));
    list.add(new CommonMap<String, Object>().putData("name", "华为手机全面屏大").putData("price", 342));
    list.add(new CommonMap<String, Object>().putData("name", "华为手机大屏大").putData("price", 1234));
    list.add(new CommonMap<String, Object>().putData("name", "华为电脑全面屏大奥利给").putData("price", 345));
    list.add(new CommonMap<String, Object>().putData("name", "华为平板电脑全面屏大奥利给").putData("price", 1234));
    list.add(new CommonMap<String, Object>().putData("name", "荣耀小米手机").putData("price", 45234));
    list.add(new CommonMap<String, Object>().putData("name", "手机平板全面屏").putData("price", 4532));
    bulkAddDoc(commonEntity);
}

查询一下,发现有九条数据了:

GET product/_search

二、全文检索

1、代码

/*
 * @Description: 全文检索
 * 使用matchQuery在执行查询时,搜索的词会被分词器分词
 * @Method: searchMatch
 * @Param: [indexName, key, value]
 * >>>>>>>>>>>>编写思路简短总结>>>>>>>>>>>>>
 * >>>>>>>1、构建远程查询
 * >>>>>>>2、构建查询请求
 * >>>>>>>3、构建查询条件
 * >>>>>>>4、设置高亮
 * >>>>>>>5、设置分页
 * >>>>>>>   加入SearchRequest
 * >>>>>>>6、处理高亮
 *
 */
public static SearchResponse matchQuery(CommonEntity commonEntity) throws Exception {
    
    
    //构建查询响应
    SearchResponse response = null;
    //构建查询请求用来完成和搜索文档,聚合,建议等相关的任何操作同时也提供了各种方式来完成对查询结果的高亮操作。
    SearchRequest searchRequest = new SearchRequest(commonEntity.getIndexName());
    //构建DSL请求体;trackTotalHits如果不设置true,查询数据最大值还是10000
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().trackTotalHits(true);
    //获取前端的查询条件(Map查询条件)
    getClientConditions(commonEntity, searchSourceBuilder);
    //高亮设置
    searchSourceBuilder.highlighter(SearchTools.getHighlightBuilder(commonEntity.getHighlight()));
    //前端页码
    int pageNumber = commonEntity.getPageNumber();
    //前端每页数量
    int pageSize = commonEntity.getPageSize();
    //计算查询的下标,从0开始
    int dest = (pageNumber - 1) * pageSize;
    searchSourceBuilder.from(dest);
    //每页数量
    searchSourceBuilder.size(pageSize);
    //查询条件对象放入请求对象中
    searchRequest.source(searchSourceBuilder);
    //方法执行开始时间
    long startTime = System.currentTimeMillis();
    System.out.println("开始Elasticsearch查询...");
    //执行远程查询,使用RequestOptions.DEFAULT用来构建一个默认缓冲区限制为100MB(源码为DEFAULT_BUFFER_LIMIT = 100 * 1024 * 1024),和header为空、WarningsHandler为空
    //的参数选项
    response = client.search(searchRequest, RequestOptions.DEFAULT);
    //计算远程查询耗时
    System.out.println("结束Elasticsearch查询总耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");
    //处理高亮
    SearchTools.setHighResultForCleintUI(response, commonEntity.getHighlight());
    return response;

}

/*
 * @Description: 获取前端的查询条件
 * @Method: getClientConditions
 * @Param: [commonEntity, searchSourceBuilder]
 *
 */
private static void getClientConditions(CommonEntity commonEntity, SearchSourceBuilder searchSourceBuilder) {
    
    
    //循环前端的查询条件
    for (Map.Entry<String, Object> m : commonEntity.getMap().entrySet()) {
    
    
        if (StringUtils.isNotEmpty(m.getKey()) && m.getValue() != null) {
    
    
            String key = m.getKey();
            String value = String.valueOf(m.getValue());
            //构造请求体中“query”:{}部分的内容 ,QueryBuilders静态工厂类,方便构造queryBuilder
            //将搜索词分词,再与目标查询字段进行匹配,若分词中的任意一个词与目标字段匹配上,则可查询到。
            searchSourceBuilder.query(QueryBuilders.matchQuery(key, value));
            System.out.println(("search for the keyword:" + value));
        }
    }
}
public static void main(String[] args) throws Exception {
    
    
	// 全文检索
    CommonEntity queryEntity = new CommonEntity();
    queryEntity.setPageNumber(1);// 第一页
    queryEntity.setPageSize(5); // 一页条数
    queryEntity.setIndexName("product"); // 索引名
    queryEntity.setHighlight("name"); // 高亮字段
    queryEntity.setMap(new CommonMap<>().putData("name", "华为全面屏")); // 要查询的字段 + 内容
    SearchResponse searchResponse = matchQuery(queryEntity);
    long aSize = searchResponse.getHits().getTotalHits().value;
    System.out.println(("数据总数量为>>>" + aSize));
    long cSize = searchResponse.getHits().getHits().length;
    System.out.println(("本次获取数据量为>>>" + cSize));
    System.out.println("内容为>>>" + JSON.toJSONString(searchResponse.getHits().getHits()));
    System.out.println("全部内容>>>" + JSON.toJSON(searchResponse));
}

2、为什么二次处理高亮

原因:被设置的高亮列,es自动放到了highlight属性中;这个属性渲染了高亮的着色
数据传输的时候,我们需要将它取出来
覆盖到我们的_source中

三、结构化搜索与过滤

1、概述

实现查询价格在【2000-3000】并且是【京东物流】并且评论数【从大到小进行排序】的商品,filter也常和range范围查询一起结合使用,range范围可供组合的选项。

注意!被查询的字段类型是必须是keyword,这样字段在索引时不会进行分词。如果类型为text,字段值在索引时会分词,这样反而查不到结果了。

GET product/_search
{
    
    
    "query": {
    
    
        "bool": {
    
    
            "must": [
                {
    
    
                    "term": {
    
    
                        "storetype": "自营"
                    }
                },
                {
    
    
                    "term": {
    
    
                        "twolevel": "手机"
                    }
                }
            ],
            "filter": {
    
    
                "range": {
    
    
                    "price": {
    
    
                        "gte": 2000,
                        "lte": 3000
                    }
                }
            }
        }
    },
    "sort": [
        {
    
    
            "evalcount": {
    
    
                "order": "desc"
            }
        }
    ]
}

结构化查询(Query DSL)
query的时候,会先比较查询条件,然后计算分值,最后返回文档结果
结构化过滤(Filter DSL)
过滤器,对查询结果进行缓存,不会计算相关度,避免计算分值,执行速度非常快

2、结构化过滤(Filter DSL)

(1)term 过滤

term 主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed 的字符串(未经分析的文本数据类型),相当于sql age=26

{
    
     "term": {
    
     "age": 26 }}
{
    
     "term": {
    
     "date": "2014-09-01" }}

(2)terms 过滤

terms 允许指定多个匹配条件。如果某个字段指定了多个值,那么文档需要一起去做匹配。
相当于sql: age in

{
    
    "terms": {
    
    "age": [26, 27, 28]}}

(3)range 过滤

range 过滤允许我们按照指定范围查找一批数据:

{
    
    
    "range": {
    
    
        "price": {
    
    
            "gte": 2000,
            "lte": 3000
        }
    }
}

gt : 大于
lt : 小于
gte : 大于等于
lte :小于等于
相等于sql between

(4)exists 和 missing 过滤

exists 和 missing 过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL条件

{
    
    
	"exists": {
    
    
		"field": "title"
	}
}

(5)bool 过滤

用来合并多个过滤条件查询结果的布尔逻辑:
must:多个查询条件的完全匹配,相当于 and。
must_not: 多个查询条件的相反匹配,相当于 not;
should:至少有一个查询条件匹配,相当于 or;
相当于sql and 和or

{
    
    
    "bool": {
    
    
        "must": {
    
    
            "term": {
    
    
                "folder": "inbox"
            }
        },
        "must_not": {
    
    
            "term": {
    
    
                "tag": "spam"
            }
        },
        "should": [
            {
    
    
                "term": {
    
    
                    "starred": true
                }
            },
            {
    
    
                "term": {
    
    
                    "unread": true
                }
            }
        ]
    }
}

3、结构化查询(Query DSL)

(1)bool 查询

bool 查询与 bool 过滤相似,用于合并多个查询子句。不同的是,bool 过滤可以直接给出是否匹配成功, 而bool 查询要计算每一个查询子句的 _score

{
    
    
    "bool": {
    
    
        "must": {
    
    
            "match": {
    
    
                "title": "how to make millions"
            }
        },
        "must_not": {
    
    
            "match": {
    
    
                "tag": "spam"
            }
        },
        "should": [
            {
    
    
                "match": {
    
    
                    "tag": "starred"
                }
            },
            {
    
    
                "range": {
    
    
                    "date": {
    
    
                        "gte": "2014-01-01"
                    }
                }
            }
        ]
    }
}

(2)bool嵌套查询

{
    
    
    "bool": {
    
    
        "should": [
            {
    
    
                "term": {
    
    
                    "productID": "KDKE-B-9947-#kL5"
                }
            },
            {
    
    
                "bool": {
    
    
                    "must": [
                        {
    
    
                            "term": {
    
    
                                "productID": "JODL-X-1937-#pV7"
                            }
                        },
                        {
    
    
                            "term": {
    
    
                                "price": 30
                            }
                        }
                    ]
                }
            }
        ]
    }
}

(3)match_all 查询

使用match_all 可以查询到所有文档,是没有查询条件下的默认语句。

{
    
    
	"match_all": {
    
    }
}

(4)match 查询

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。
如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符

{
    
    
	"match": {
    
    
		"tweet": "About Search"
	}
}

(5)multi_match 查询

multi_match查询允许你做match查询的基础上同时搜索多个字段

{
    
    
	"multi_match": {
    
    
		"query": "full text search",
		"fields": [ "title", "body" ]
	}
}

(6)match_phrase

短语查询,full text search 是一个词组,意味着三个词的位置是连续且有顺序

{
    
    
	"match_phrase": {
    
    
		"title": "full text search",
	}
}

设置slop词组间隔

{
    
    
	"match_phrase": {
    
    
		"title": {
    
    
			"query": "full text search",
			"slop":1
		}
	}
}

(7)phrase_prefix 查询

与词组中最后一个词条进行前缀匹配。

{
    
    
"query": {
    
    
	"match_phrase_prefix": {
    
    
		"title": {
    
    
			"query": "全面屏"
		}
	}
},
"from":0,
"size":5
}

(8)regexp查询

通配符查询

{
    
    
    "query": {
    
    
        "regexp": {
    
    
            "title": "W[0-9].+"
        }
    }
}

(9)过滤查询

查询语句和过滤语句可以放在各自的上下文中,filtered已弃用,用bool代替。

{
    
    
    "query": {
    
    
        "bool": {
    
    
            "must": {
    
    
                "match": {
    
    
                    "text": "quick brown fox"
                }
            },
            "filter": {
    
    
                "term": {
    
    
                    "status": "published"
                }
            }
        }
    },
    "from": 0, // 从0开始
    "size": 10, // 显示条数
    "sort": {
    
    
        "publish_date": {
    
    
            "order": "desc"
        }
    }
}

4、Java实现通用结构化查询API

(1)Java实现

/*
 * @Description:结构化搜索
 * @Method: termQuery
 * @Param: [commonEntity]
 * @Update:
 * @since: 1.0.0
 * @Return: org.elasticsearch.action.search.SearchResponse
 * >>>>>>>>>>>>编写思路简短总结>>>>>>>>>>>>>
 * 1、构建远程查询
 * 2、定义响应
 * 3、定义查询请求
 * 3、定义查询构建器
 * 4、定义解析器--构建器解析
 * 5、定义高亮
 * 6、定义分页
 * 7、定义排序
 *    加入到SearchRequest
 * 8、高亮渲染
 */
public static SearchResponse termQuery(CommonEntity commonEntity) throws Exception {
    
    

    //构建查询响应
    SearchResponse response = null;
    //构建查询请求用来完成和搜索文档,聚合,建议等相关的任何操作同时也提供了各种方式来完成对查询结果的高亮操作。
    SearchRequest searchRequest = new SearchRequest(commonEntity.getIndexName());
    //构建DSL请求体trackTotalHits如果不设置true,查询数据最大值还是10000
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().trackTotalHits(true);
    //将前端的dsl查询转化为XContentParser
    XContentParser parser = SearchTools.getXContentParser(commonEntity);
    //将parser解析成功查询API
    searchSourceBuilder.parseXContent(parser);
    //高亮设置
    searchSourceBuilder.highlighter(SearchTools.getHighlightBuilder(commonEntity.getHighlight()));
    //前端页码
    int pageNumber = commonEntity.getPageNumber();
    //前端每页数量
    int pageSize = commonEntity.getPageSize();
    //计算查询的下标
    int dest = (pageNumber - 1) * pageSize;
    searchSourceBuilder.from(dest);
    //每页数量
    searchSourceBuilder.size(pageSize);
    //排序
    sort(commonEntity, searchSourceBuilder);
    //查询条件对象放入请求对象中
    searchRequest.source(searchSourceBuilder);
    //方法执行开始时间
    long startTime = System.currentTimeMillis();
    System.out.println("开始Elasticsearch查询...");
    //执行远程查询
    response = client.search(searchRequest, RequestOptions.DEFAULT);
    //计算远程查询耗时
    System.out.println("结束Elasticsearch查询总耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");
    //处理高亮
    SearchTools.setHighResultForCleintUI(response, commonEntity.getHighlight());
    return response;
}

/*
 * @Description: 排序
 * @Method: sort
 * @Param: [commonEntity, searchSourceBuilder]
 */
private static void sort(CommonEntity commonEntity, SearchSourceBuilder searchSourceBuilder) {
    
    
    String sortField = commonEntity.getSortField();
    if (StringUtils.isNotEmpty(sortField)) {
    
    
        //排序,获取前端的order by子句,不区分大小写,参数为空则默认desc
        SortOrder sortOrder = SearchTools.getSortOrder(commonEntity.getSortOrder());
        //执行排序
        searchSourceBuilder.sort(new FieldSortBuilder(commonEntity.getSortField()).order(sortOrder));
    }
}

public static void main(String[] args) throws Exception {
    
    
    // 结构化查询
    CommonEntity queryEntity = new CommonEntity();
    SearchResponse result = termQuery(queryEntity);
    //查询数量除以每页数量  等于合计分页数量
    long aSize = result.getHits().getTotalHits().value;
    System.out.println(("总数据量:" + aSize + "条"));
    int cSize = result.getHits().getHits().length;
    System.out.println(("当前获取数据:" + cSize + "条"));
    //通过类型推断自动装箱(多个参数取交集)
    System.out.println(result.getHits().getHits()); // 结果
}

(2)排序方式一

{
    
    
    "pageNumber": 1,
    "pageSize": 1,
    "indexName": "product",
    "sortField": "evalcount",
    "sortOrder": "",
    "highlight": "name",
    "map": {
    
    
        "query": {
    
    
            "bool": {
    
    
                "must": [
                    {
    
    
                        "term": {
    
    
                            "storetype": "自营"
                        }
                    },
                    {
    
    
                        "term": {
    
    
                            "twolevel": "手机"
                        }
                    }
                ],
                "filter": {
    
    
                    "range": {
    
    
                        "price": {
    
    
                            "gte": 2000,
                            "lte": 3000
                        }
                    }
                }
            }
        }
    }
}

pageNumber:页码
pageSize:每页显示条数
indexName:不可以为空,查询的索引名称
sortField:排序列,可以为空
sortOrder:可以为空,默认DESC,排序规则【DESC/ASC】
highlight:高亮字段(注意:平台只接受被查询的字段名称)
map:里面的参数为动态DSL参数;可以随意增加(需符合ES规范)系统可自动解析

(3)排序方式二

{
    
    
    "pageNumber": 1,
    "pageSize": 1,
    "indexName": "product_list_info",
    "sortField": "",
    "sortOrder": "",
    "highlight": "productname",
    "map": {
    
    
        "query": {
    
    
            "bool": {
    
    
                "must": [
                    {
    
    
                        "term": {
    
    
                            "storetype": "自营"
                        }
                    },
                    {
    
    
                        "term": {
    
    
                            "twolevel": "手机"
                        }
                    }
                ],
                "filter": {
    
    
                    "range": {
    
    
                        "price": {
    
    
                            "gte": 2000,
                            "lte": 3000
                        }
                    }
                }
            }
        },
        "sort": [
            {
    
    
                "evalcount": {
    
    
                    "order": "desc"
                }
            }
        ]
    }
}

猜你喜欢

转载自blog.csdn.net/A_art_xiang/article/details/132229858