引言
在我们很多业务场景中,都需要进行检索和匹配,尤其是在电商的商品以及日志搜索中尤为明显,而现在成熟的搜索工具我更喜欢使用elasticsearch,它的搜索功能特别好用,并且易上手,而且springboot中也有对应的starter,使用简单方便
依赖引入
这里的版本就不做多的讨论了,可以使用springboot的parent进行版本管理
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置文件
spring:
elasticsearch:
rest:
uris: 替换成你的es (ip+端口)
username: elastic
password: Hxxx
test:
es:
index-name: product_test1
开始使用
其他的配置就不需要列举出来了,这里直接开始使用
1、实体创建
/**
* indexName要全小写
* 如果需要使用spel表达式,只能获取配置类中的属性,不能直接使用${}获取配置文件中的值
*/
@Data
// @Document(indexName = "#{@esConfig.getIndexName()}")
@Document(indexName = "product")
public class ProductVO {
/**
* 商品id
*/
@Id
private String spuId;
/**
* 分类id
* 指定为关键词不会进行分词
*/
@Field(type = FieldType.Keyword, store = true)
private String categoryId;
/**
* 商品名称
* 指定为文档,并且分词器为ik,查询分词为ik_smart
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word", store = true, searchAnalyzer = "ik_smart")
private String spuName;
/**
* 属性值
* 指定为实体
*/
@Field(type = FieldType.Nested, store = true)
private List<ProductAttrVO> attrEntities;
/**
* 序号
*/
@Field(type = FieldType.Keyword)
private Integer seq;
/**
* 商品卖点
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word", store = true)
private String sellingPoint;
/**
* 商品价格
*/
private Long priceFee;
/**
* 分区code
*/
@Field(type = FieldType.Keyword, store = true)
private String partitionCode;
/**
* 标签
*/
@Field(type = FieldType.Keyword, store = true)
private List<String> labels;
}
2、查询
要说查询,无非就是几种精确匹配,模糊匹配,分词匹配,或者条件,排序,而es中多了一个名叫做分数的东西,我们可以通过权重来提高他的分数
public Page<ProductVO> page(ProductPageQueryDTO dto) {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
// 关键词
String keywords = dto.getKeywords();
if (StrUtil.isNotBlank(keywords)) {
// 多条件or查询,boost是设置权重,并不是越高得分就越高,默认为1,越高比例越高,只是加分
// wildcardQuery是通配符匹配,"*测试*" 就相当于mysql中的like '%测试%'
// matchQuery会进行分词处理,termQuery不会进行分词(指查询条件分词)查询的关键词字段可以为数组,比如说 lables
// minimumShouldMatch 指的是最少匹配多少个,1就是为至少匹配一个
BoolQueryBuilder keywordShouldQuery = QueryBuilders.boolQuery()
.should(QueryBuilders.wildcardQuery("spuName", StrUtil.concat(Boolean.TRUE, "*", keywords.toLowerCase(), "*")).boost(2))
.should(QueryBuilders.matchQuery("spuName", keywords).boost(3))
.should(QueryBuilders.wildcardQuery("labels", StrUtil.concat(Boolean.TRUE, "*", keywords, "*")).boost(6))
.minimumShouldMatch(1);
// 多条件
queryBuilder.must(keywordShouldQuery);
}
// 属性匹配
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrEntities", QueryBuilders.matchQuery("attrEntities.attrValueName", keywords), ScoreMode.None);
// 价格
// must() 为必须满足当前条件
// rangeQuery() 就是指的大于小于(范围匹配)
Long max = dto.getMax();
Long min = dto.getMin();
if (ObjectUtil.isNotNull(max) && max > 0 && (ObjectUtil.isNull(min) || min == 0 || min > max)) {
queryBuilder.must(QueryBuilders.rangeQuery("priceFee").lte(max));
} else if (ObjectUtil.isNotNull(min) && min > 0 && (ObjectUtil.isNull(max) || max == 0 || max < min)) {
queryBuilder.must(QueryBuilders.rangeQuery("priceFee").gte(min));
} else if (ObjectUtil.isNotNull(max) && ObjectUtil.isNotNull(min) && max >= min && min > 0) {
queryBuilder.must(QueryBuilders.rangeQuery("priceFee").lte(max).gte(min));
}
if (ObjectUtil.isNotNull(dto.getPriceAsc())) {
FieldSortBuilder fieldSortBuilder = SortBuilders.fieldSort("priceFee").order(dto.getPriceAsc() ? SortOrder.ASC : SortOrder.DESC);
nativeSearchQueryBuilder.withSort(fieldSortBuilder);
}
// 排序
if (StrUtil.isNotEmpty(partitionCode) && StrUtil.equalsIgnoreCase("MRTJ", partitionCode)) {
FieldSortBuilder fieldSortBuilder = SortBuilders.fieldSort("seq").order(SortOrder.DESC);
nativeSearchQueryBuilder.withSort(fieldSortBuilder);
} else {
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
}
// 分页查询
nativeSearchQueryBuilder
.withPageable(PageRequest.of(dto.getPageNum(), dto.getPageSize()))
.withQuery(queryBuilder);
NativeSearchQuery query = nativeSearchQueryBuilder.build();
return queryPage(query, dto.getPageNum(), dto.getPageSize());
}
/**
* 构建成mybatis-plus的分页
*/
private Page<ProductVO> queryPage(NativeSearchQuery query, Integer pageNum, Integer pageSize) {
AggregatedPage<ProductVO> result = elasticsearchRestTemplate.queryForPage(query, ProductVO.class);
Page<ProductVO> page = new Page<>(pageNum, pageSize);
page.setRecords(result.getContent());
page.setTotal(result.getTotalElements());
return page;
}
这里就只介绍一下分页查询,其他的查询提供了直接的方法进行查询比如说列表和单个
注意
- 权重只是增加得分,并不一定能够拿到自己想要的
- ik分词器只是按照他的规则来分词,要想达到自己想要的效果,只能自己维护分词器
- 使用ik分词器要在es中安装
- 写入和删除时,一定要注意默认1000,一定要注意这个