文章目录
es 版本: 6.2.2
实战笔记
1. 利用IkAnalyzer获取中文分词
- 在kibana的Dev Tools的console里输入命令,将中文进行分词, 支持的请求方式有五种: GET, POST,PUT,DELETE,HEAD, 使用ik_max_word将中文分词,默认的分词方式为Standard, 此方式只讲中文进行单个拆分,在实际中开发中不太适合用。
将 “小米很不错” 用IKAnalyzer进行拆分:
GET /_analyze
{
"text": "小米很不错",
"tokenizer":"ik_max_word"
}
打印结果:
{
"tokens": [
{
"token": "小米",
"start_offset": 0,
"end_offset": 2,
"type": "CN_WORD",
"position": 0
},
{
"token": "很不错",
"start_offset": 2,
"end_offset": 5,
"type": "CN_WORD",
"position": 1
},
{
"token": "很不",
"start_offset": 2,
"end_offset": 4,
"type": "CN_WORD",
"position": 2
},
{
"token": "不错",
"start_offset": 3,
"end_offset": 5,
"type": "CN_WORD",
"position": 3
}
]
}
根据打印结果,可以发现字符串以比较友好的形式拆分成了若干个。
2. 使用DSL来调用ElasticSearch的RestFul接口实现搜索
DSL: 模糊查询所有包含name 、subTitle、keywords关键字中含小米的记录的, 同时根据id来降序排序。
GET /_search
{
"from":0,
"size":20,
"query":{
"multi_match": {
"query": "小米",
"fields": ["name","subTitle","keywords"]
}
}
,"sort": [
{
"id": "desc"
}
]
}
打印结果:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 1.5881127,
"hits": [
{
"_index": "pms",
"_type": "product",
"_id": "33",
"_score": 1.5881127,
"_source": {
"id": 33,
"productSn": "4609652",
"brandId": 6,
"brandName": "小米",
"productCategoryId": 35,
"productCategoryName": "手机数码",
"pic": "",
"name": "小米(MI)小米电视4A ",
"subTitle": "小米(MI)小米电视4A 55英寸 L55M5-AZ/L55M5-AD 2GB+8GB HDR 4K超高清 人工智能网络液晶平板电视",
"keywords": "",
"price": 2499,
"sale": 0,
"newStatus": 0,
"recommandStatus": 0,
"stock": 100,
"promotionType": 0,
"sort": 0,
"attrValueList": []
}
}
]
}
}
3. 使用ElasticSearchRepository将数据导入到ElasticSearch
将数据导入到es的原理很简单: 先从数据库select出来,然后再通过ElasticSearchRepository导入到ElasticSearch。在使用ElasticSearchRepository时,需要先定义一个实现类,并给ElasticsearchRepository一个Model类型,该类型为我们导入到es的数据模板类。
public interface EsProductRepository extends ElasticsearchRepository<EsProduct, Long> {
// 继承 ElasticsearchRepository 的方法 findBy 后的属性必须是model所包含的属性,否则会报错。
Page<EsProduct> findByNameOrSubTitleOrKeywords(String name, String subTitle, String keywords, Pageable page);
}
然后将数据库中的EsProduct数据给遍历出来,使用ElasticSearchRepository自带的saveAll()方法即可将所有数据导入到ElasticSearch。
@Autowired
private EsProductRepository productRepository;
@Override
public int importAllProduct() {
// 1. 获取所有商品列表
List<EsProduct> esproductlist = productDao.getAllEsProductList(null);
// 2. 将商品保存至es
Iterable<EsProduct> esProductIterable = productRepository.saveAll(esproductlist);
Iterator<EsProduct> iterator = esProductIterable.iterator();
// 获取保存到ealsticsearch中的商品数量,每保存一个商品数量+1
int result = 0;
while (iterator.hasNext()) {
result++;
iterator.next();
}
return result;
}
4. 使用ElasticSearch做聚类搜索同时满足关键字搜索。
举个栗子,例如我们需要根据某个商品的商标和商品类型来查询,比如我要查询商标为小米、类型为手机数码,关键字为手机。
然后点击搜索,就能够搜索出我们想要的商品了。
具体实现思路:
参照Elasticsearch的ASL写法,可以发现这里有条件过滤MUST和模糊匹配MUTI_MATCH,使用ElastichSearchRepository的search()方法就能够完成搜索,只不过我们需要先将这些条件和模糊匹配的Builder先组成起来。
条件查询 asl:
{
"query": {
"bool": {
"must": [{
"query_string": {
"query": "小米",
"fields": ["name"]
}
}, {
"query_string": {
"query": "2499",
"fields": ["price"]
}
}]
}
}
}
模糊匹配 asl:
{
"from":0,
"size":3,
"query":{
"multi_match": {
"query": "小米",
"fields": ["name","subTitle","keywords"]
}
}
,"sort": [
{
"id": "desc"
}
]
}
- NativeSearchQueryBuilder: 可以用来搜索.withQuery()和条件搜索.withFilter()。
- BoolQueryBuilder : 相当于ASL里的MUST,必须一样才算匹配成功。
- FunctionScoreQueryBuilder: 相当于ASL里的大query。
@Override
public Page<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize) {
Pageable pageable = PageRequest.of(pageNum, pageSize);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//分页
nativeSearchQueryBuilder.withPageable(pageable);
//过滤
if (brandId != null || productCategoryId != null) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (brandId != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("brandId", brandId));
}
if (productCategoryId != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("productCategoryId", productCategoryId));
}
nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
}
//搜索
if (StringUtils.isEmpty(keyword)) {
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
} else {
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", keyword),
ScoreFunctionBuilders.weightFactorFunction(10)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", keyword),
ScoreFunctionBuilders.weightFactorFunction(5)));
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords", keyword),
ScoreFunctionBuilders.weightFactorFunction(2)));
FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
filterFunctionBuilders.toArray(builders);
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
.setMinScore(2);
nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
}
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
LOGGER.info("DSL:{}", searchQuery.getQuery().toString());
return productRepository.search(searchQuery);
}
入坑笔记
坑一、简单模糊搜索的方法规范
继承 ElasticsearchRepository 的方法 findBy 后的属性必须是model所包含的属性,否则会报错:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property keyword found for type EsProduct! Did you mean ‘keywords’?
举个栗子:
你的model 要查询的分词字段为: name , subTitle , keywords, 需要注意的是这些字段要用@Field(analyzer=“ik_max_word”)标记,这样数据导入给elasticsearch后,elasticsearch才能把这些字段拆分成分词来匹配。
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String name;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String subTitle;
@Field(analyzer = "ik_max_word",type = FieldType.Text)
private String keywords;
那么在写自定义方法时应该定义为: findByNameOrSubTitleOrKeywords , 方法名的字段在by后面必须是驼峰命名,by后面有几个字段,传的参数就有几个。
package com.example.shop.nosql.elasticsearch.repository;
import com.example.shop.nosql.elasticsearch.document.EsProduct;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* 商品操作es
*/
public interface EsProductRepository extends ElasticsearchRepository<EsProduct, Long> {
// 继承 ElasticsearchRepository 的方法 findBy 后的属性必须是model所包含的属性,否则会报错。
Page<EsProduct> findByNameOrSubTitleOrKeywords(String name, String subTitle, String keywords, Pageable page);
}
另外ElasticSearchRepository支持关键字加条件的方式,如and, or, not等
condition | functionName | elasticsearch asl | 说明 |
---|---|---|---|
and | findByNameAndPrice | GET_search{"query":{"bool":{"must":[{"query_string":{"query":"小米","fields":["name"]}},{"query_string":{"query":"2499","fields":["price"]}}]}}} | 用must关键字, 搜索name 为小米并且price为2499的商品。 |
or | findByNameOrPrice | GET_search{"query":{"bool":{"should":[{"query_string":{"query":"小米","fields":["name"]}},{"query_string":{"query":"2499","fields":["price"]}}]}}} | 用should关键字,搜索name 为小米或price为2499的商品。 |
is | findByName | GET_search{"query":{"bool":{"must":[{"query_string":{"query":"小米","fields":["name"]}}]}}} | 根据Name进行搜索。 |
not | findByNameNot | GET_search{"query":{"bool":{"must_not":[{"query_string":{"query":"小米","fields":["name"]}}]}}} | 使用must_not关键字,查询除了name为小米的商品。 |
between | findByPriceBetween | GET_search{"query":{"bool":{"must":[{"range":{"price":{"gte":1000,"lte":3000}}}]}}} | 使用range关键字关键字,查询价格在1000-3000的商品。 |
坑二、升级Springboot版本造成ElasticSearch版本不兼容
如果你的Springboot版本升级到了2.3以上,那么你的ElasticSearch版本要升到 7.2.2。否则会出现 不兼容的问题。