前言
前面我已经搭建好了ElasticSearch服务,并完成了MySQL到ElasticSearch的数据迁移;
使用ElasticSearch的初衷就是为了大数据搜索,本文将介绍ElaticSearch中各种查询方法;
一、精确查询(termQuery)
termQuery不会对查询条件进行分词 ,但这并不以为着查询的字段没有进行分词存储;
1.Kibana查询
term精确查询并不会对查询条件进行分词,类似于MySQL中 select * from table where 字段='xx值';- GET hotel/_search
- {
- "query": {
- "term": {
- "brand": {
- "value": "万豪"
- }
- }
- },
- "from": 0,
- "size": 20
- }
复制代码 2.JavaAPI查询
将以上在Kibana输入的DSM转换成Java代码;
- //按照品牌精确查询
- @Override
- public Map<String, Object> brandTermQuery(int current, int size, Map<String, Object> searchParam) {
- //按品牌精确查询实现
- //1.获取前端参数
- String brand = (String) searchParam.get("brand");
- //响应前端的Map
- Map<String, Object> resultMap = new HashMap<>();
- //2.构建查询条件
- //查询请求
- SearchRequest hotelSearchRequest = new SearchRequest("hotel");
- //请求体
- SearchSourceBuilder hotelSearchSourceBuilder = new SearchSourceBuilder();
- //如果查询条件为空就查询所有
- if (StringUtils.hasText(brand)) {
- //请求体-查询部分
- TermQueryBuilder hotelTermQueryBuilder = QueryBuilders.termQuery("brand", brand);
- hotelSearchSourceBuilder.query(hotelTermQueryBuilder);
- }
- //请求体-分页部分
- hotelSearchSourceBuilder.from((current - 1) * size);
- hotelSearchSourceBuilder.size(size);
- //查询请求-封装请求体
- hotelSearchRequest.source(hotelSearchSourceBuilder);
- //3.去查询
- try {
- SearchResponse hotelSearchResponse = restHighLevelClient.search(hotelSearchRequest, RequestOptions.DEFAULT);
- //4.处理查询结果集
- SearchHits hotelSearchResponseHits = hotelSearchResponse.getHits();
- //获取命中总条目
- Long totalHotelHits = hotelSearchResponseHits.getTotalHits().value;
- //获取命中的每1个条
- SearchHit[] hoteHits = hotelSearchResponseHits.getHits();
- //前端
- ArrayList<HotelEntity> hotelEntitieList = new ArrayList<>();
- if (hoteHits != null || hoteHits.length > 0) {
- for (SearchHit hoteHit : hoteHits) {
- String sourceAsString = hoteHit.getSourceAsString();
- //字符串转换成Java对象
- HotelEntity hotelEntity = JSON.parseObject(sourceAsString, HotelEntity.class);
- hotelEntitieList.add(hotelEntity);
- }
- }
- //前端展示
- resultMap.put("list", hotelEntitieList);
- resultMap.put("totalResultSize", totalHotelHits);
- //设置分页相关
- resultMap.put("current", current);
- resultMap.put("totalPage", (totalHotelHits + size - 1) / size);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return resultMap;
- }
复制代码 HotelServiceImpl.java
二、中文分词器
如果设置了字段的type为keyword,就可以对该字段使用term精确查询;
如果设置了字段的type为text,当添加文档时,对于该域的值会进行分词,形成若干term(词条)存储在倒排索引中。
当用户进行term查询时,ES会将当前查询条件当做1个term(词条),和当前倒排索引中term(词条)进行匹配?
匹配成功则会查询到数据,如果倒排索引中不存在该term(词条)则查询不到数据。
那我们如何对text类型的字段进行term查询呢?
这就需要利用中文分词器对文档中的内容进行中文分词, 重构ES的倒排索引的结构,把整个文档分词成为若干中文term(词条)
1.ElasticSearch内置分词器
在ElasticSearch默认内置了多种分词器:
- Standard Analyzer - 默认分词器,按英文空格切分
- Simple Analyzer - 按照非字母切分(符号被过滤)
- Stop Analyzer - 小写处理,停用词过滤(the,a,is)
- Whitespace Analyzer - 按照空格切分,不转小写
- Keyword Analyzer - 不分词,直接将输入当作输出
- Patter Analyzer - 正则表达式,默认\W+(非字符分割)
2.默认分词无法对中文分词
看看ES是默认使用Standard Analyzer分词器对文档内容进行分词;- GET _analyze
- {<br>"text": "北京市东城区万豪酒店"
- }
复制代码 此时可以发现Standard Analyzer分词器把每1个汉字形成了一个词,这显然无法满足汉语搜索习惯;
3.安装IK分词器
IK分词器是国人开发的1款智能中文分词器,基于Java语言开发、具有60万字/秒的高速处理能力。
并且用户可以按需求,设置停用词与扩展词。
3.1.上传IK安装包
- #因为启动es时候 已经做好的目录挂载
- 容器内部:/usr/share/elasticsearch/plugins
- 宿主机:/mydata/elasticsearch/plugins
- 所以只需要将文件复制到/mydata/elasticsearch/plugins 目录下即可
复制代码 3.2.重启容器
- docker restart elasticsearch
复制代码 3.3.测试
- GET /_analyze
- {
- "analyzer": "ik_max_word",
- "text": "北京市东城区万豪酒店"
- }
复制代码
4.使用IK分词器
4.1.分词模式
IK分词器有两种分词模式it_max_word和ik_smart模式
- ik_max_word:细粒度分词,存储的时候分词可以细粒度一些;
- ik_smart:粗粒度分词,搜索的时候可以对搜索条件分词粗粒度一些;
4.2.配置自定义词库
IK分词器虽然非常智能。但是中华语言博大精深,其并不能完全识别所有的词。
如在文档存储时,无法对专业术语、店名、网络词语等等。所以IK提供了扩展词库,用户可以按需求添加自定义分词数据。
4.2.1.定义扩展词
4.2.2.定义停用词
4.3.修改IK分词器的配置文件
- vim IKAnalyzer.cfg.xml
- #修改配置文件 注意这个地方 不要把搞乱码了!!!
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
- <properties>
- <comment>IK Analyzer 扩展配置</comment>
-
- <entry key="ext_dict">my.dic</entry>
-
- <entry key="ext_stopwords">extra_stopword.dic</entry>
-
- <entry key="remote_ext_dict">http://106.75.109.43:28888/remote.dic</entry>
-
-
- </properties>
复制代码
4.4.重建索引指定分词器
把数据从原索引(表)中迁移到目标索引(表);
如果是线上环境,在重建索引时,一定要选择异步构建和平滑构建;
- PUT hotel_2
- {
- "mappings": {
- "properties": {
- "name":{
- "type": "text",
- "analyzer": "ik_max_word",
- "search_analyzer": "ik_smart"
- },
- "address":{
- "type": "text",
- "analyzer": "ik_max_word",
- "search_analyzer": "ik_smart"
- },
- "brand":{
- "type": "keyword"
- },
- "type":{
- "type": "keyword"
- },
- "price":{
- "type": "integer"
- },
- "specs":{
- "type": "keyword"
- },
- "salesVolume":{
- "type": "integer"
- },
- "area":{
- "type": "text",
- "analyzer": "ik_max_word",
- "search_analyzer": "ik_smart"
- },
- "imageUrl":{
- "type": "text"
- },
- "synopsis":{
- "type": "text",
- "analyzer": "ik_max_word",
- "search_analyzer": "ik_smart"
- },
- "createTime":{
- "type": "date",
- "format": "yyyy-MM-dd"
- },
- "isAd":{
- "type":"integer"
- }
- }
- }
- }
- #重建索引 异步构建和平滑构建
- POST _reindex?wait_for_completion=false&requests_per_second=2000
- {
- "source": {
- "index": "原始索引名字"
- },
- "dest": {
- "index": "目标索引名字"
- }
- }
- #查看任务完成情况
- GET _tasks/任务id
- #重建别名关联关系
- #断开原来的关系
- POST _aliases
- {
- "actions": [
- {
- "remove": {
- "index": "hotel_1",
- "alias": "hotel"
- }
- }
- ]
- }
- #删除原来的索引表
- DELETE hotel_1
- #新建hotel_2的关系
- POST _aliases
- {
- "actions": [
- {
- "add": {
- "index": "hotel_2",
- "alias": "hotel"
- }
- }
- ]
- }
复制代码 dms
4.5.测试文档是否被分词
此时文档在存储时已经被中文分词器进行了中文分词并存储,我们就可以使用termQuery精确查询进行分词结果测试了;
由于termQuery精确查询,不会对查询条件进行分词,所依我根据分词结果进行查询,如果分词成功,就会查询到text字段的结果;
三、分词查询(mathQuery)
上述的term精确查询必须要根据分词之后的结果进行精确查询;
可是用户不知道你的文档是怎么分词的,所以我们需要对用户的查询条件也进行分词;
1.Kibana分词查询
- GET hotel/_search
- {
- "query": {
- "match": {
- "name":"北京市东城区瑞麟湾"
- }
- }
- }
复制代码 matchQuery会对查询条件进行分词,并拿分词后的结果,去ES中进行逐一匹配,默认取结果并集。
2.JavaAPI分词查询
- //根据酒店名称匹配查询
- @Override
- public Map<String, Object> nameMatchQuery(Integer current, Integer size, Map<String, Object> searchParam) {
- //设置查询
- SearchRequest searchRequest = new SearchRequest("hotel");
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- Map<String, Object> map = new HashMap<>();
- //获取name参数
- String name = (String) searchParam.get("name");
- if (StringUtils.hasText(name)) {
- //组装查询对象
- MatchQueryBuilder nameMatchQueryBuilder = QueryBuilders.matchQuery("name", name);
- searchSourceBuilder.query(nameMatchQueryBuilder);
- }
- //设置分页
- searchSourceBuilder.from((current - 1) * size);
- searchSourceBuilder.size(size);
- searchRequest.source(searchSourceBuilder);
- //处理查询结果
- try {
- SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
- SearchHits hits = searchResponse.getHits();
- long totalHits = hits.getTotalHits().value;
- SearchHit[] searchHits = hits.getHits();
- List<HotelEntity> list = new ArrayList<>();
- for (SearchHit searchHit : searchHits) {
- String sourceAsString = searchHit.getSourceAsString();
- list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
- }
- map.put("list", list);
- map.put("totalResultSize", totalHits);
- map.put("current", current);
- //设置总页数
- map.put("totalPage", (totalHits + size - 1) / size);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return map;
- }
复制代码 HotelServiceImpl.java
四、模糊搜索(wildcardQuery)
当在搜索框进行搜索时,展示出所有品牌以美开头的酒店。
wildcardQuery:会对查询条件进行分词,还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
1.Kibana分词查询
- GET hotel/_search
- {
- "query": {
- "wildcard": {
- "brand": {
- "value": "美*"
- }
- }
- }
- }
复制代码 2.JavaAPI分词查询
- //根据酒店品牌模糊查询
- @Override
- public Map<String, Object> nameWildcardQuery(Integer current, Integer size, Map<String, Object> searchParam) {
- //设置查询
- SearchRequest searchRequest = new SearchRequest("hotel");
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- //根据酒店名称模糊查询
- //1.获取前端参数
- String name = (String) searchParam.get("name");
- //2.组装查询对象
- if (StringUtils.hasText(name)) {
- WildcardQueryBuilder brandWildcardQuery = QueryBuilders.wildcardQuery("brand", name+"*");
- searchSourceBuilder.query(brandWildcardQuery);
- }
- //设置分页
- searchSourceBuilder.from((current - 1) * size);
- searchSourceBuilder.size(size);
- searchRequest.source(searchSourceBuilder);
- Map<String, Object> map = new HashMap<>();
- try {
- SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
- SearchHits hits = searchResponse.getHits();
- long totalHits = hits.getTotalHits().value;
- SearchHit[] searchHits = hits.getHits();
- List<HotelEntity> list = new ArrayList<>();
- for (SearchHit searchHit : searchHits) {
- String sourceAsString = searchHit.getSourceAsString();
- list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
- }
- map.put("list", list);
- map.put("totalResultSize", totalHits);
- map.put("current", current);
- //设置总页数
- map.put("totalPage", (totalHits + size - 1) / size);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return map;
- }
复制代码 HotelServiceImpl.java
五、多域(字段)查询
当用户在搜索框输入查询条件时,为了给用户展示更多的数据,该条件不应该仅仅作用于某1个域(字段),而要让其作用于多个域进行搜索,从而搜索出更多的查询结果。
类似于MySQL数据中的 select * from table 字段1=条件 or 字段2=条件.....;
简而言之就是使用1个条件去多个字段中查询;
当前需要将用户的搜索条件,作用于:酒店名称(name)、酒店描述(synopsis)、酒店地区(area)、酒店地址(address);
1.Kibana查询
- GET hotel/_search
- {
- "query": {
- "query_string": {
- "fields": ["name","brand","address","synopsis"],
- "query": "万豪 OR 北京 OR 上海"
- }
- }
- }
复制代码 2.JavaAPI查询
- //根据name,synopsis,area,address进行多域(字段)查询
- @Override
- public Map<String, Object> searchQueryStringQuery(Integer current, Integer size, Map<String, Object> searchParam) {
- //设置查询
- SearchRequest searchRequest = new SearchRequest("hotel");
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- Map<String, Object> map = new HashMap<>();
- //根据name,synopsis,area,address进行多域查询
- String condition = (String) searchParam.get("condition");
- //组装查询对象
- if (StringUtils.hasText(condition)) {
- QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(condition)
- .field("name")
- .field("address")
- .field("synopsis")
- .field("area")
- .defaultOperator(Operator.OR);
- searchSourceBuilder.query(queryStringQueryBuilder);
- }
- //设置分页
- searchSourceBuilder.from((current - 1) * size);
- searchSourceBuilder.size(size);
- searchRequest.source(searchSourceBuilder);
- try {
- SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
- SearchHits hits = searchResponse.getHits();
- long totalHits = hits.getTotalHits().value;
- SearchHit[] searchHits = hits.getHits();
- List<HotelEntity> list = new ArrayList<>();
- for (SearchHit searchHit : searchHits) {
- String sourceAsString = searchHit.getSourceAsString();
- list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
- }
- map.put("list", list);
- map.put("totalResultSize", totalHits);
- map.put("current", current);
- //设置总页数
- map.put("totalPage", (totalHits + size - 1) / size);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return map;
- }
复制代码 HotelServiceImpl
六、排序查询(sort)
当用户进行搜索时,有时会关注该商品的销量、评论数等信息,对这些进行进行排序,搜索出销量最高或评论数最多的商品。
1.Kibana查询
- #排序查询:支持多字段排序
- GET hotel/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "price": {
- "order": "desc"
- }
- },
- {
- "salesVolume": {
- "order": "asc"
- }
- }
- ]
- }
复制代码 2.JavaAPI查询
七、范围查询(range)
八、多条件查询
九、纠错查询(fuzzy)
十、高亮展示搜索结果
当用户在搜索框输入搜索条件后,对于查询结果的展示,应将搜索条件以特殊的样式展示,这种查询就称为高亮结果查询;
参考
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |