文章目录
- 一、DSL查询文档
- 1.1 简单查询
- 1.2 复合查询
- 二、搜索结果处理
- 三、RestClient演示 查询与结果分析
- 四、案例
- 4.1 问题解析
- 4.2 代码
- 4.2.1 实体bean
- 4.2.2 控制层
- 4.2.3 业务service
- 4.2.4 启动类
一、DSL查询文档
1.1 简单查询
# 1. DSL查询
# 1.1 查询所有GET /hotel/_search
{"query": {"match_all": {}}
}# 1.2 全文检索查询:对用户输入的内容分词后查询,常用于搜索框查询
# 1)match查询 :all字段是在创建hotel索引库时创建的,里面包括brand name busiess字段(copy to)
# 例子:查询hotel中brand、name、businiss中有"外滩"二字的文档
GET /hotel/_search
{"query": {"match": {"all": "外滩"}}
}
# 2)muiti_match查询:效果和上面一样
# 例子:查询hotel中brand、name、businiss中有"外滩如家"四字的文档
GET /hotel/_search
{"query": {"multi_match": {"query": "外滩如家","fields": ["brand","name","business"]}}
}
# 3)match与multi_match的区别在于:match是单字段查询;而multi_match是多字段查询,字段越多性能越差;建议用copy to将多个字段拷到一个字段用match查询# 1.3 精确查询:一般查找类型为keyword、boolean、数值、日期等字段,不分词
# 1)term:根据词条的精确值查询
# 例子:查询hotel中city="上海"的文档
GET /hotel/_search
{"query": {"term": {"city": {"value": "上海"}}}
}
# 2)range:根据值的范围查询
# 例子:查询price在(1000,2000]的文档
GET /hotel/_search
{"query": {"range": {"price": {"gt": 1108,"lte": 2000}}}
}# 1.4 经纬度查询
# 1)geo_bounding_box:查询geo_point值落在某个矩形范围的所有文档
# 例子:查询hotel中location两个经纬度点矩形范围内内的文档
GET /hotel/_search
{"query": {"geo_bounding_box": {"location": {"top_left": {"lat": 31.1,"lon": 121.5},"bottom_right": {"lat": 30.9,"lon": 121.7}}}}
}# 2)geo_distance:查询到指定中心点小于某个距离值的所有文档
# 例子:查询(31.21,121.5)范围内5km的的文档
GET /hotel/_search
{"query": {"geo_distance": {"distance": "5km","location": "31.21, 121.5"}}
}
1.2 复合查询
# 1.5 复合查询
# 1)function socre:算分函数查询,可以控制文档相关性算分,控制文档的排名
# 例:在all为"外滩"的查询中将"如家"这个品牌的酒店排名靠前一些
GET /hotel/_search
{"query": {"function_score": {"query": {"match": {"all": "外滩"}},"functions": [{"filter": {"term": {"brand": "如家"}},"weight": 10}],"boost_mode": "sum"}}
}# 2)布尔查询:组合多个子查询
# must:必须匹配每个子查询,相当于“与”
# should:选择性匹配子查询,相当于“或”
# must_not:必须不匹配【不参与算分】,相当于“非”
# filter:必须匹配【不参与算法】# 例:查询name包含"如家",价格不高于400,坐标(31.21,121.5)范围内10km的hotel
# 下面代码中,如果将price和location放入must中会参与算分,为了节省性能,一般放在must_not或者filter中
GET /hotel/_search
{"query": {"bool": {"must": [{"match": {"name": "如家"}}],"must_not": [{"range": {"price": {"gt": 400}}}],"filter": [{"geo_distance": {"distance": "10km","location": {"lat": 31.21,"lon": 121.5}}}]}}
}
二、搜索结果处理
深度分页问题
# 2. 搜索结果处理
# 2.1 排序:es默认根据算分排序。可以用来排序的字段有:keyword、数值、坐标、日期
# 例1:对hotel数据按用户评价score降序,相同评价按价格price升序
GET /hotel/_search
{"query": {"match_all": {}},"sort": [{"score": "desc"},{"price": "asc"}]
}# 例2:对hotel数据按你的坐标位置(115.450059,38.866053)距离升序排序
# 获取经纬度的方式:https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-lnglat/
GET /hotel/_search
{"query": {"match_all": {}},"sort": [{"_geo_distance": {"location": {"lat": 38.866053,"lon": 115.45005},"order": "asc","unit": "km"}}]
}# 2.2 分页:es默认返回top10的数据,想要查询更多需要设置
# from表示分页开始位置,默认为0;size表示期望获取文档数GET /hotel/_search
{"query": {"match_all": {}},"from": 5,"size": 1,"sort": [{"price": "asc"}]
}# 2.3 高亮:将搜索结果的搜索关键字突出显示
# 原理:将搜索结果的关键字用标签标记出来,在页面中给标签添加css样式
# 注意:默认情况下ES搜索字段必须与高亮字段保持一致,而下面搜索字段为all,高亮字段为name,虽然all包括name,但是需要设置require_field_match=false
GET /hotel/_search
{"query": {"match": {"all": "如家"}},"highlight": {"fields": {"name": {"require_field_match": "false", "pre_tags": "<em>","post_tags": "</em>"}}}
}
三、RestClient演示 查询与结果分析
@SpringBootTest
class HotelSearchTest {private RestHighLevelClient client;@BeforeEachvoid setUp() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.101:9200")));}@AfterEachvoid tearDown() throws IOException {client.close();}/*** 解析json文档*/private void handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();// 4.1.总条数long total = searchHits.getTotalHits().value;System.out.println("总条数:" + total);// 4.2.获取文档数组SearchHit[] hits = searchHits.getHits();// 4.3.遍历for (SearchHit hit : hits) {// 4.4.获取sourceString json = hit.getSourceAsString();// 4.5.反序列化,非高亮的HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);// 4.6.处理高亮结果// 1)获取高亮mapMap<String, HighlightField> map = hit.getHighlightFields();// 2)根据字段名,获取高亮结果HighlightField highlightField = map.get("name");// 3)获取高亮结果字符串数组中的第1个元素String hName = highlightField.getFragments()[0].toString();// 4)把高亮结果放到HotelDoc中hotelDoc.setName(hName);// 4.7.打印System.out.println(hotelDoc);System.out.println(json);}}/*** 查询所有文档*/@Testvoid testMatchAll() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数request.source().query(QueryBuilders.matchAllQuery());// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}/*** 全文检索查询:match、multi_match*/@Testvoid testMatch() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数//request.source().query(QueryBuilders.matchQuery("all", "外滩如家"));request.source().query(QueryBuilders.multiMatchQuery("外滩如家", "name", "brand", "city"));// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}/*** 1.精确查询:term、range* 2.boolean组合查询* 查询city为杭州,price>=250的文档*/@Testvoid testBool() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数/*BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 2.1.mustboolQuery.must(QueryBuilders.termQuery("city", "上海"));// 2.2.filterboolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));request.source().query(boolQuery);*/request.source().query(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("city", "上海")).filter(QueryBuilders.rangeQuery("price").lte(250)));// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}/*** 排序和分页*/@Testvoid testSortAndPage() throws IOException {int page = 2,size = 5;// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数// 2.1.queryrequest.source().query(QueryBuilders.matchAllQuery());// 2.2.排序sortrequest.source().sort("price", SortOrder.ASC);// 2.3.分页 from\sizerequest.source().from((page - 1) * size).size(size);// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}/*** 结果高亮*/@Testvoid testHighlight() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数// 2.1.queryrequest.source().query(QueryBuilders.matchQuery("all", "外滩如家"));// 2.2.高亮request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}
}
四、案例
4.1 问题解析
4.2 代码
4.2.1 实体bean
PageResult.java
响应结果类:由搜索框得到的查询结果类
/*** 响应结果类:由搜索框得到的查询结果类*/
@Data
public class PageResult {private Long total; // 总条数private List<HotelDoc> hotels; // 酒店信息public PageResult() {}public PageResult(Long total, List<HotelDoc> hotels) {this.total = total;this.hotels = hotels;}
}
RequestParams.java
请求参数类:搜索框中有哪些参数
/*** 请求参数类:搜索框中有哪些参数*/
@Data
public class RequestParams {private String key; // 搜索关键字private Integer page;// 当前页码private Integer size;// 每页大小private String sortBy;// 排序字段private String brand;// 品牌private String city;// 城市private String starName;// 星级private Integer minPrice;// 最低价格private Integer maxPrice;// 最高价格private String location;// 位置
}
4.2.2 控制层
@RestController
@RequestMapping("hotel")
public class HotelController {@Autowiredprivate IHotelService hotelService;@PostMapping("list")public PageResult search(@RequestBody RequestParams params) {return hotelService.search(params);}
}
4.2.3 业务service
public interface IHotelService extends IService<Hotel> {PageResult search(RequestParams params);
}
@Slf4j
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {@Autowiredprivate RestHighLevelClient restHighLevelClient;@Overridepublic PageResult search(RequestParams params) {try {// 1.准备RequestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数// 2.1.多条件查询和过滤buildBasicQuery(params, request);// 2.2.分页int page = params.getPage();int size = params.getSize();request.source().from((page - 1) * size).size(size);/*** 2.3.距离排序*/String location = params.getLocation();if (StringUtils.isNotBlank(location)) {// 不为空则查询request.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(location)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));}// 3.发送请求SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);// 4.解析响应return handleResponse(response);} catch (IOException e) {throw new RuntimeException("搜索数据失败", e);}}private void buildBasicQuery(RequestParams params, SearchRequest request) {// 1.准备Boolean复合查询BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();/*** 1.查询关键字* must参与 算分*/// 1.1.关键字搜索,match查询,放到must中String key = params.getKey();if (StringUtils.isNotBlank(key)) {// 不为空,根据关键字查询boolQuery.must(QueryBuilders.matchQuery("all", key));} else {// 为空,查询所有boolQuery.must(QueryBuilders.matchAllQuery());}/*** 2.条件过滤:多条件复合查询* 根据 “品牌 城市 星级 价格范围” 过滤数据* filter不参与 算分*/// 1.2.品牌String brand = params.getBrand();if (StringUtils.isNotBlank(brand)) { // 不为空则查询boolQuery.filter(QueryBuilders.termQuery("brand", brand));}// 1.3.城市String city = params.getCity();if (StringUtils.isNotBlank(city)) {// 不为空则查询boolQuery.filter(QueryBuilders.termQuery("city", city));}// 1.4.星级String starName = params.getStarName();if (StringUtils.isNotBlank(starName)) {// 不为空则查询boolQuery.filter(QueryBuilders.termQuery("starName", starName));}// 1.5.价格范围Integer minPrice = params.getMinPrice();Integer maxPrice = params.getMaxPrice();if (minPrice != null && maxPrice != null) {// 不为空则查询maxPrice = maxPrice == 0 ? Integer.MAX_VALUE : maxPrice;boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));}/*** 3.算分函数查询* 置顶功能:给你置顶的酒店添加一个标记,并按其算分*/FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery, // 原始查询,boolQuerynew FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ // function数组new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("isAD", true), // 过滤条件ScoreFunctionBuilders.weightFactorFunction(10) // 算分函数)});/*** 4.设置查询条件*/request.source().query(functionScoreQuery);}private PageResult handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();// 4.1.总条数long total = searchHits.getTotalHits().value;// 4.2.获取文档数组SearchHit[] hits = searchHits.getHits();// 4.3.遍历List<HotelDoc> hotels = new ArrayList<>(hits.length);for (SearchHit hit : hits) {// 4.4.获取sourceString json = hit.getSourceAsString();// 4.5.反序列化,非高亮的HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);// 4.6.处理高亮结果// 1)获取高亮mapMap<String, HighlightField> map = hit.getHighlightFields();if (map != null && !map.isEmpty()) {// 2)根据字段名,获取高亮结果HighlightField highlightField = map.get("name");if (highlightField != null) {// 3)获取高亮结果字符串数组中的第1个元素String hName = highlightField.getFragments()[0].toString();// 4)把高亮结果放到HotelDoc中hotelDoc.setName(hName);}}// 4.8.排序信息Object[] sortValues = hit.getSortValues(); // 获取排序结果if (sortValues.length > 0) {/*** 由于该程序是根据距离[酒店距你选择位置的距离]进行排序,所以排序结果为距离*/hotelDoc.setDistance(sortValues[0]);}// 4.9.放入集合hotels.add(hotelDoc);}return new PageResult(total, hotels);}
}
4.2.4 启动类
@MapperScan("cn.itcast.hotel.mapper")
@SpringBootApplication
public class HotelDemoApplication {public static void main(String[] args) {SpringApplication.run(HotelDemoApplication.class, args);}@Beanpublic RestHighLevelClient restHighLevelClient(){return new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.101:9200"))); // 服务器IP+端口9200}
}