前言
前面我已经搭建好了ElasticSearch服务,并完成了MySQL到ElasticSearch的数据迁移;
使用ES专门做搜索功能,打破MySQL搜索瓶颈;
ElasticSearch的应用场景
- 数据库字段太多,查询太慢,索引没有办法再做优化;
- 数据库1个count查询就拖死全表;
- MySQL的limit翻到几十几百万页后实在是太慢;
- 数据库like实在太慢,每次like整个服务器cpu内存飙高,拖慢整个线上服务;
- 想要对外/内提供db里的数据的全文检索服务;
- 提供日志(程序运行)查询功能;
本文将使用ElasticSearch的倒排索引取代MySQL的索引,进行大数据查询,提升查询效率;
一、精确查询(termQuery)
termQuery不会对查询条件进行分词 ,但这并不以为着查询的字段没有进行分词存储;
1.使用Kibana的DevTools查询
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
{
"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>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">http://ip地址:端口号/词典文件</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
五、多域(字段)查询(query_string)
当用户在搜索框输入查询条件时,为了给用户展示更多的数据,该条件不应该仅仅作用于某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查询
使用match_all查询所有数据,对price和salesVolume域(字段)进行排序;
#排序查询:支持多字段排序
GET hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "desc"
}
},
{
"salesVolume": {
"order": "asc"
}
}
]
}
2.JavaAPI查询
//根据销量排序查询
@Override
public Map<String, Object> salesSortQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//设置查询
SearchRequest searchRequest = new SearchRequest("hotel");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//设置按销量排序
String sortWay = (String) searchParam.get("sortWay");
//组装sort部分
if (StringUtils.hasText(sortWay)) {
if ("asc".equals(sortWay)) {
searchSourceBuilder.sort("price", SortOrder.ASC).sort("salesVolume", SortOrder.ASC);
} else {
searchSourceBuilder.sort("price", SortOrder.DESC).sort("salesVolume", SortOrder.DESC);
}
}
//设置分页
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);
map.put("sortWay", searchParam.get("sortWay"));
} catch (IOException e) {
throw new RuntimeException(e);
}
return map;
}
HotelServiceImpl.java
七、范围查询(range)
当用户要搜索商品时,有时会对某一个特定的价格区间进行查询,搜索出符合心理预期价格的商品;
1.Kibana查询
区间范围查询:查询价格在100-500元范围之间的商品;
GET hotel/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 500
}
}
}
}
2.JavaAPI查询
//处理前端参数
public Long transferToLong(Object param){
if(param==null || "".equals(param)){
return null;
}else{
return Long.parseLong((String)param);
}
}
//根据价格范围查询
@Override
public Map<String, Object> priceRangeQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//设置查询
SearchRequest searchRequest = new SearchRequest("hotel");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//根据价格范围查询
//1.获取参数
Long maxPrice = transferToLong(searchParam.get("maxPrice"));
Long minPrice = transferToLong(searchParam.get("minPrice"));
//2.组装搜索条件
if (maxPrice != null || maxPrice != null) {
RangeQueryBuilder priceRangeQueryBuilder = QueryBuilders.rangeQuery("price");
if (maxPrice != null) {
priceRangeQueryBuilder.lte(maxPrice);
}
if (minPrice != null) {
priceRangeQueryBuilder.gte(maxPrice);
}
}
//设置分页
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<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
map.put("minPrice", searchParam.get("minPrice"));
map.put("maxPrice", searchParam.get("maxPrice"));
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
HotelServiceImpl.java
八、多条件查询(boolQuery)
多字段查询类似于没MySQL中的以下SQL
select * from table where 字段1='条件1' or 字段2='条件2' ;
当用户进行搜索时,不会仅仅只输入一个搜索条件,有时会传递多个条件,将符合多条件的商品搜索出来。
boolQuery可以对多个查询条件连接。
连接方式有
- must(and): 条件必须成立
- must_not(not): 条件必须不成立
- should(or): 1个条件可以成立即可
- filter: 条件必须成立,性能比must高。
1.Kibana查询
查询北京市的万豪价格区间在500-2000,最好是五星级;
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"brand": {
"value": "万豪"
}
}
},
{
"term": {
"area": {
"value": "北京市"
}
}
}
],
"should": [
{
"term": {
"specs": {
"value": "五星级"
}
}
}
],
"filter": [
{
"range": {
"price": {
"gte": 500,
"lte": 2000
}
}
}
]
}
}
}
2.JavaAPI查询
//多条件查询
//搜索框多域、品牌精确、城市精确、星级精确、价格范围、销量排序
@Override
public Map<String, Object> searchBoolQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//设置查询
SearchRequest searchRequest = new SearchRequest("hotel");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//todo 多条件查询 :多域、品牌精确、城市精确、星级精确、价格范围、销量排序
//设置查询方式
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//多域
if (StringUtils.hasText(searchParam.get("condition"))) {
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery(searchParam.get("condition").toString())
.field("name")
.field("synopsis")
.field("area")
.field("address")
.defaultOperator(Operator.OR);
boolQueryBuilder.must(queryBuilder);
}
//品牌精确
if (StringUtils.hasText(searchParam.get("brand"))) {
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brand", searchParam.get("brand"));
boolQueryBuilder.filter(termQueryBuilder);
}
//城市精确
if (StringUtils.hasText(searchParam.get("area"))) {
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("area", searchParam.get("area"));
boolQueryBuilder.filter(termQueryBuilder);
}
//星级精确
if (StringUtils.hasText(searchParam.get("specs"))) {
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("specs", searchParam.get("specs"));
boolQueryBuilder.filter(termQueryBuilder);
}
//价格范围
//1.获取参数
Long maxPrice = transferToLong(searchParam.get("maxPrice"));
Long minPrice = transferToLong(searchParam.get("minPrice"));
//2.根据情况组装条件
if (maxPrice!=null||minPrice!=null){
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price");
//gte great than and equal 大于等于
//lte less than and equal 小于等于
if (maxPrice!=null){
rangeQueryBuilder.lte(maxPrice);
}
if (minPrice!=null){
rangeQueryBuilder.gte(minPrice);
}
boolQueryBuilder.must(rangeQueryBuilder);
}
//销量排序
if (StringUtils.hasText(searchParam.get("sortWay"))) {
if ("desc".equalsIgnoreCase(searchParam.get("sortWay").toString())) {
searchSourceBuilder.sort("salesVolume", SortOrder.DESC);
} else {
searchSourceBuilder.sort("salesVolume", SortOrder.ASC);
}
}
searchSourceBuilder.query(boolQueryBuilder);
//设置分页
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<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
map.put("brand", searchParam.get("brand"));
map.put("area", searchParam.get("area"));
map.put("specs", searchParam.get("specs"));
map.put("sortWay", searchParam.get("sortWay"));
map.put("minPrice", searchParam.get("minPrice"));
map.put("maxPrice", searchParam.get("maxPrice"));
return map;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
HotelServiceImpl.java
九、纠错查询(fuzzy)
用户在进行搜索时,有可能打错字输入了错误的信息。 那么后台在处理时,应该智能的识别错误信息,更加友好的展示相关数据。
fuzzyQuery
自动纠错查询,会自动尝试将搜索条件进行纠错,然后去跟term进行匹配。
- fuzziness: 查询允许的最大编辑距离,默认为0,允许搜索条件出现几个错别字;
- prefix_length:设置前几个字符不允许编辑
在未经处理的情况下,一旦输入信息有误,对应不到分词的话,则不会有任何结果返回;
1.Kibana查询
自动纠错查询
- fuzzines:允许搜索条件出现x个错别字
- prefix_length:不允许搜索条件的第1字出错
用户输入搜索条件为北漂去,也可以搜索area为北京的酒店;
GET hotel/_search
{
"query": {
"fuzzy": {
"area": {
"value": "北漂去",
"fuzziness": 2,
"prefix_length": 1
}
}
}
}
2.JavaAPI查询
//地址纠错查询
@Override
public Map<String, Object> searchFuzzyQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//设置查询
SearchRequest searchRequest = new SearchRequest("hotel");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//设置查询方式
String area=(String) searchParam.get("area");
//设置城市为纠错查询
if(StringUtils.hasText(area)){
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("area", area)
.fuzziness(Fuzziness.TWO) //允许搜索条件出现2个错别字
.prefixLength(1); //不允许搜索条件的第1字出错
searchSourceBuilder.query(fuzzyQueryBuilder);
}
//设置分页
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<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", totalHits);
map.put("current", current);
map.put("brand", searchParam.get("brand"));
map.put("area", searchParam.get("area"));
map.put("specs", searchParam.get("specs"));
map.put("sortWay", searchParam.get("sortWay"));
map.put("minPrice", searchParam.get("minPrice"));
map.put("maxPrice", searchParam.get("maxPrice"));
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
HotelServiceImpl.java
十、高亮展示搜索结果
当用户在搜索框输入搜索条件后,对于查询结果的展示,应将搜索条件以特殊的样式展示,这种查询就称为高亮结果查询;
1.Kibana查询
#高亮查询结果
#告诉前端为什么匹配中了
GET hotel/_search
{
"query": {
"match": {
"name": "北京市"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": "<font color='red'>",
"post_tags": "</font>"
}
}
}
}
2.JavaAPI查询
//按名称高亮查询
@Override
public Map<String, Object> searchHighLight(Integer current, Integer size, Map<String, Object> searchParam) {
//设置查询
SearchRequest searchRequest = new SearchRequest("hotel");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//设置查询方式
if (!StringUtils.isEmpty(searchParam.get("condition"))) {
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery(searchParam.get("condition").toString())
.field("name")
.field("synopsis")
.field("area")
.field("address")
.defaultOperator(Operator.OR);
searchSourceBuilder.query(queryBuilder);
}
//设置分页
searchSourceBuilder.from((current - 1) * size);
searchSourceBuilder.size(size);
//查询高亮设置
HighlightBuilder highlightBuider = new HighlightBuilder();
highlightBuider.field("name");
highlightBuider.preTags("<font color='red'>");
highlightBuider.postTags("</font>");
searchSourceBuilder.highlighter(highlightBuider);
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();
HotelEntity hotelEntity = JSON.parseObject(sourceAsString, HotelEntity.class);
//处理高亮结果
Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
HighlightField highlightField = highlightFields.get("name");
if (highlightField!=null){
Text[] fragments = highlightField.fragments();
hotelEntity.setName(fragments[0].toString());
}
list.add(hotelEntity);
}
Map<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
HotelServiceImpl.java
十一、短语查询(match_phrase)
在搜索条件部分词的前提下,使用短语进行查询;
1.kibana查询
#磁盘空间写满
GET k8s-2023.02.13/_search
{
"query": {
"bool": {
"must": [
{ "match_phrase": {
"message": "app-workspace-373-1675214627414-podgroup-s4l6-5qr7g"
}},
{ "match_phrase": {
"message": "Evicted"
}}
]}
},
"sort": [
{
"@timestamp": {
"order": "desc"
}
}],
"from": 0,
"_source": {
"includes":["@timestamp","host_ip","log.file.path","message"]
}
}