最近一个多月跟着师哥和同学们一起做了一个酒旅项目,这个项目是依托微信小程序提供线上预定酒店和旅游的互联网产品。希望解决的用户的痛点如下:
- 提高用户搜索酒店和预定酒店的效率
- 售后功能保障了用户的合法权益
- 基于数据分析提供用户多需求场景组合产品
以下是项目架构图:
- 首先经历了熟悉产品和产品流程梳理
- 然后进行项目代码熟悉和数据库设计
- 接下来进行了接口设计和任务分工
- 编写各自功能模块代码,最后交由师哥验收
在任务分工中,我被分配到了编写基于 Elasticsearch 实现酒店列表的搜索功能。期望根据不同的查询条件实现酒店列表的快速搜索展示,由于之前没有使用过 Elasticsearch 整合 Spring Boot ,因此需要通过边学习,边尝试的方式去完成这个功能。以下介绍本次项目所学习到的 Elasticsearch 知识和我本人编写的复杂查询功能。
什么是ES ElasticSearch是一款非常强大的、基于Lucene的开源搜索及分析引擎;它是一个实时的分布式搜索分析引擎,它能让你以前所未有的速度和规模,去探索你的数据。
主要功能和使用场景主要功能:
1)海量数据的分布式存储以及集群管理,达到了服务与数据的高可用以及水平扩展;
2)近实时搜索,性能卓越。对结构化、全文、地理位置等类型数据的处理;
3)海量数据的近实时分析(聚合功能)
应用场景:
1)网站搜索、垂直搜索、代码搜索;
2)日志管理与分析、安全指标监控、应用性能监控、Web抓取舆情分析;
ES 查询 在 Java 中的实现 在本次项目中主要使用 Elasticsearch 整合 Spring Boot 的方式,通过 ElasticsearchTemplate 完成多条件复合查询。
在本次项目中主要使用了 布尔查询、分页查询、过滤查询、地理位置查询等。
布尔查询 概念和特点- 子查询可以任意顺序出现
- 可以嵌套多个查询,包括bool查询
- 如果bool查询中没有must条件,should中必须至少满足一条才会返回结果。
bool查询包含四种 *** 作符,分别是must,should,must_not,filter。他们均是一种数组,数组里面是对应的判断条件。
must
: 必须匹配。贡献算分must_not
:过滤子句,必须不能匹配,但不贡献算分should
: 选择性匹配,至少满足一条。贡献算分filter
: 过滤子句,必须匹配,但不贡献算分
我们可以通过组合过滤器 filter
使用多条件等值查询。
query 和 filter 的区别:query 查询的时候,会先比较查询条件,然后计算分值,最后返回文档结果;而 filter 是先判断是否满足查询条件,如果不满足会缓存查询结果(记录该文档不满足结果),满足的话,就直接缓存结果,filter 不会对结果进行评分,能够提高查询效率。
例1 整合过滤查询、分页查询、布尔查询
查询书籍中 name 词项匹配计算机,价格大于等于 40 并且小于等于 100 的书籍,按价格升序,分页条件为第一页前 10 个。
DSL 查询语句:
GET books/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"name": {
"value":"计算机"
}
}
}
],
"filter": [
{
"range": {
"price": {
"gte": 40,
"lte": 100
}
}
}
]
}
},
"sort": [
{
"price": {
"order": "asc"
}
}
],
"size": 10,
"from": 0
}
Java 代码:
@Test
void contextLoads() {
// 复杂查询编写
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 1. bool 查询 (过滤型查询 builder)
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 1.2 范围查询 builder
RangeQueryBuilder range = new RangeQueryBuilder("price");
range.gte(40).lte(100);
TermQueryBuilder term = new TermQueryBuilder("name","计算机");
boolQueryBuilder.must(term);
boolQueryBuilder.filter(range);
// 2. 分页查询
PageRequest pageRequest = PageRequest.of(0, 10);
// 3. 按价格升序排序
FieldSortBuilder priceSortBuilder = SortBuilders.fieldSort("price");
priceSortBuilder.order(SortOrder.ASC);
// 整合复杂搜索
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.withFilter(boolQueryBuilder).withPageable(pageRequest).withSort(priceSortBuilder).build();
//搜索获取结果集
SearchHits<Books> booksSearchHits = restTemplate.search(searchQuery, Books.class);
ArrayList<Books> bookList = new ArrayList<>();
for (SearchHit<Books> booksSearchHit : booksSearchHits) {
Books book = booksSearchHit.getContent();
bookList.add(book);
}
for (Books books : bookList) {
System.out.println(books.getAuthor() + "==>" + books.getName() + "==>" + books.getPrice());
}
}
地理位置查询
使用 geo_distance query
实现给出一个中心点,查询距离该中心点指定范围内的文档的功能。
DSL 例子如下:
GET geo/_search
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": [
{
"geo_distance": {
"distance": "600km",
"location": {
"lat": 34.288991865037524,
"lon": 108.9404296875
}
}
}
]
}
}
}
Java 代码:
@Data
@EqualsAndHashCode(callSuper = false)
@Document(indexName = "geo",shards = 1,replicas = 1)
public class Geo {
private static final long serialVersionUID = -2L;
@Id
private Long id;
@Field(type = FieldType.Keyword, analyzer = "ik_max_word")
private String name;
@GeoPointField
private GeoPoint location;
}
public SearchHits<Geo> getDistanceQuery(@RequestParam double lat, @RequestParam double lon, @RequestParam int distance){
PageRequest page = PageRequest.of(0, 5);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder("location");
boolean validLatitude = GeoUtils.isValidLatitude(lat);
boolean validLongitude = GeoUtils.isValidLongitude(lon);
geoDistanceQueryBuilder.distance(distance, DistanceUnit.KILOMETERS).point(new GeoPoint(lat, lon));
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.withPageable(page).withQuery(new MatchAllQueryBuilder()).withFilter(geoDistanceQueryBuilder).build();
SearchHits<Geo> geoSearchHits = elasticsearchRestTemplate.search(searchQuery, Geo.class);
for (SearchHit<Geo> geoSearchHit : geoSearchHits) {
System.out.println(geoSearchHit.getContent());
}
return geoSearchHits;
}
设置得分权重查询
@Test
public void matchQuery(){
ArrayList<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name","计算机"),
ScoreFunctionBuilders.weightFactorFunction(5)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("info","计算机"),
ScoreFunctionBuilders.weightFactorFunction(1)));
FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders).scoreMode(FunctionScoreQuery.ScoreMode.SUM).setMinScore(1);
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(functionScoreQueryBuilder);
}
项目功能实现
根据对 ES 功能和查询 API 的学习,再根据项目需求编写代码如下:
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 分页
PageRequest page = PageRequest.of(vo.getPageIndex(), vo.getPageSize());
nativeSearchQueryBuilder.withPageable(page);
// 通过改变权重影响关键字查询:名称 > 品牌 > 地址 搜索
FunctionScoreQueryBuilder functionScoreQuery = getFunctionScoreQuery(vo);
if(ObjectUtil.isNotNull(functionScoreQuery)){
nativeSearchQueryBuilder.withQuery(functionScoreQuery);
}else{
nativeSearchQueryBuilder.withQuery(new MatchAllQueryBuilder());
}
// bool查询过滤 主要通过城市名,附近地标,星级,价格范围进行过滤
BoolQueryBuilder boolQuery = getBoolQuery(vo);
nativeSearchQueryBuilder.withFilter(boolQuery);
// 排序
Integer sort = vo.getSort();
SortBuilder sortQuery = getSortQuery(sort, vo);
nativeSearchQueryBuilder.withSort(sortQuery);
// 构造总查询
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
SearchHits<EsHotel> hotelSearchHits = elasticsearchRestTemplate.search(searchQuery, EsHotel.class);
// 封装统一分页返回类
return getCommonPageResult(hotelSearchHits,vo);
以上代码基本满足了项目需求,我也学习到了 ES 知识和相关 API 使用,更重要的是收获了整个项目流程开发的经验,为我以后更好地学习打下了坚实地基础。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)