微服务技术栈 SpringCloud+RabbitMQ+Docker+Redis+Search+Distributed (V):分布式搜索 ES-medium
最编程
2024-03-16 08:42:44
...
文章目录
- 一、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;
@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
响应结果类:由搜索框得到的查询结果类
/**
* 响应结果类:由搜索框得到的查询结果类
*/
@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 {
@Autowired
private 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 {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Override
public PageResult search(RequestParams params) {
try {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel")