文章目录
- 一、DSL查询文档
-
- [1.1 简单查询](#1.1 简单查询)
- [1.2 复合查询](#1.2 复合查询)
- 二、搜索结果处理
- [三、RestClient演示 查询与结果分析](#三、RestClient演示 查询与结果分析)
- 四、案例
-
- [4.1 问题解析](#4.1 问题解析)
- [4.2 代码](#4.2 代码)
-
- [4.2.1 实体bean](#4.2.1 实体bean)
- [4.2.2 控制层](#4.2.2 控制层)
- [4.2.3 业务service](#4.2.3 业务service)
- [4.2.4 启动类](#4.2.4 启动类)
一、DSL查询文档
1.1 简单查询
json
# 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 复合查询
json
# 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
}
}
}
]
}
}
}
二、搜索结果处理
深度分页问题
json
# 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演示 查询与结果分析
java
@SpringBootTest
class HotelSearchTest {
private RestHighLevelClient client;
@BeforeEach
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.101:9200")
));
}
@AfterEach
void 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.获取source
String json = hit.getSourceAsString();
// 4.5.反序列化,非高亮的
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
// 4.6.处理高亮结果
// 1)获取高亮map
Map<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);
}
}
/**
* 查询所有文档
*/
@Test
void testMatchAll() throws IOException {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备请求参数
request.source().query(QueryBuilders.matchAllQuery());
// 3.发送请求,得到响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.结果解析
handleResponse(response);
}
/**
* 全文检索查询:match、multi_match
*/
@Test
void testMatch() throws IOException {
// 1.准备request
SearchRequest 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的文档
*/
@Test
void testBool() throws IOException {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备请求参数
/*
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2.1.must
boolQuery.must(QueryBuilders.termQuery("city", "上海"));
// 2.2.filter
boolQuery.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);
}
/**
* 排序和分页
*/
@Test
void testSortAndPage() throws IOException {
int page = 2,size = 5;
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备请求参数
// 2.1.query
request.source()
.query(QueryBuilders.matchAllQuery());
// 2.2.排序sort
request.source().sort("price", SortOrder.ASC);
// 2.3.分页 from\size
request.source().from((page - 1) * size).size(size);
// 3.发送请求,得到响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.结果解析
handleResponse(response);
}
/**
* 结果高亮
*/
@Test
void testHighlight() throws IOException {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备请求参数
// 2.1.query
request.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
响应结果类:由搜索框得到的查询结果类
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
请求参数类:搜索框中有哪些参数
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 控制层
java
@RestController
@RequestMapping("hotel")
public class HotelController {
@Autowired
private IHotelService hotelService;
@PostMapping("list")
public PageResult search(@RequestBody RequestParams params) {
return hotelService.search(params);
}
}
4.2.3 业务service
java
public interface IHotelService extends IService<Hotel> {
PageResult search(RequestParams params);
}
java
@Slf4j
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Override
public PageResult search(RequestParams params) {
try {
// 1.准备Request
SearchRequest 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, // 原始查询,boolQuery
new 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.获取source
String json = hit.getSourceAsString();
// 4.5.反序列化,非高亮的
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
// 4.6.处理高亮结果
// 1)获取高亮map
Map<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 启动类
java
@MapperScan("cn.itcast.hotel.mapper")
@SpringBootApplication
public class HotelDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HotelDemoApplication.class, args);
}
@Bean
public RestHighLevelClient restHighLevelClient(){
return new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.101:9200")
)); // 服务器IP+端口9200
}
}