sprinboot 整合 elasticsearch实现各种查询:高亮查询、termQuery、rangeQuery、matchQuery、multiMatchQuery、分页查询

  • Post author:
  • Post category:其他


注意:本文使用 Springboot 2.4.3,、elasticsearchRestTemplate,elasticsearch使用的是 7.9.3

主要是学习使用 ElasticsearchRestTemplate 的 API,termQuery、matchQuery、rangeQuery、fuzzyQuery、matchAllQuery、multiMatchQuery、分页查询、高亮查询、排序等查询方式

/**
	 * 查询所有文档
	 * {"from":0,"size":10000,"query":{"match_all":{"boost":1.0}},"version":true}
	 *
	 * @return
	 */
	@GetMapping("/matchAllQuery")
	public Object matchAllQuery() {
		NativeSearchQuery matchQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).build();
		SearchHits<Brand> searchHits = elasticsearchRestTemplate.search(matchQuery, Brand.class);
		return searchHits;
	}


	/**
	 * 根据搜索关键字查询文档,搜索关键字不分词
	 * {"from":0,"size":10000,"query":{"term":{"name":{"value":"小米","boost":1.0}}},"version":true}
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/termQuery")
	public Object termQuery(String keyword) {
		NativeSearchQuery termQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.termQuery("name", keyword)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(termQuery, Brand.class);
		return search;
	}

	/**
	 * 根据搜索关键字分词后的字段搜索文档
	 * 关键字会分词,其实该方式等价于matchQuery
	 * {
	 * "from": 0,
	 * "size": 10000,
	 * "query": {
	 * "common": {
	 * "name": {
	 * "query": "宇华华为",
	 * "high_freq_operator": "OR",
	 * "low_freq_operator": "OR",
	 * "cutoff_frequency": 0.01,
	 * "boost": 1.0
	 * }* 		}
	 * },
	 * "version": true
	 * }
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/commonTermsQuery")
	public Object commonTermsQuery(String keyword) {
		NativeSearchQuery commonTermsQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.commonTermsQuery("name", keyword)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(commonTermsQuery, Brand.class);
		return search;
	}

	/**
	 * 模糊匹配、接近匹配
	 * 查询包含搜索关键字 分词后的字段 的数据
	 * 如果在一个精确值的字段上使用它,例如数字、日期、布尔或者一个 not_analyzed 字   符串字段,那么它将会精确匹配给定的值:
	 * {
	 * "from": 0,
	 * "size": 10000,
	 * "query": {
	 * "match": {
	 * "name": {
	 * "query": "中华华为",
	 * "operator": "OR",
	 * "prefix_length": 0,
	 * "max_expansions": 50,
	 * "fuzzy_transpositions": true,
	 * "lenient": false,
	 * "zero_terms_query": "NONE",
	 * "auto_generate_synonyms_phrase_query": true,
	 * "boost": 1.0
	 * }* 		}
	 * },
	 * "version": true
	 * }
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/matchQuery")
	public Object matchQuery(String keyword) {
		NativeSearchQuery matchQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchQuery("name", keyword)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(matchQuery, Brand.class);
		return search;
	}

	/**
	 * 精确匹配查询,搜索关键字不分词 ,针对的是一个语句
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/matchPhraseQuery")
	public Object matchPhraseQuery(String keyword) {
		NativeSearchQuery matchPhraseQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchPhraseQuery("name", keyword)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(matchPhraseQuery, Brand.class);
		return search;
	}

	/**
	 * 这种精准查询满足的条件有点苛刻,有时我们想要包含 ""广东靓仔靓女"" 的文档也能够匹配 "广东靓女"。这时就要以用到 "slop" 参数来控制查询语句的灵活度。
	 * slop 表示相隔多远时,还能匹配到。比如 搜索关键字为 广东靓女,设置slop为2,表示中间可以相隔为2,所以广东靓仔靓女这条数据也是满足条件的
	 * {
	 * "from": 0,
	 * "size": 10000,
	 * "query": {
	 * "match_phrase": {
	 * "name": {
	 * "query": "广东靓女",
	 * "slop": 2,
	 * "zero_terms_query": "NONE",
	 * "boost": 1.0
	 * }* 		}
	 * },
	 * "version": true
	 * }
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/matchPhraseQueryWithSlop")
	public Object matchPhraseQueryWithSlop(String keyword) {
		NativeSearchQuery matchPhraseQueryWithSlop = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchPhraseQuery("name", keyword).slop(2)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(matchPhraseQueryWithSlop, Brand.class);
		return search;
	}


	/**
	 * 跟matchPhrase作用大致相同,会先进行一次matchPhrase查询,然后根据分词后的最后一个词汇作为前缀模糊查询
	 * 举个例子:查询列的值: this is a handsome boy ,查询关键字: this is a hand
	 * es 先进行一次matchPhrase查询筛选出 含有this is a hand 的文档,然后进一步查询 以 hand 开头的数据,如 this is a handsome boy 会命中
	 * {
	 * "from": 0,
	 * "size": 10000,
	 * "query": {
	 * "match_phrase_prefix": {
	 * "name": {
	 * "query": "宇华华为",
	 * "slop": 0,
	 * "max_expansions": 50,
	 * "boost": 1.0
	 * }* 		}
	 * },
	 * "version": true
	 * }
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/matchPhrasePrefixQuery")
	public Object matchPhrasePrefixQuery(String keyword) {
		NativeSearchQuery matchPhrasePrefixQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchPhrasePrefixQuery("name", keyword)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(matchPhrasePrefixQuery, Brand.class);
		return search;
	}


	/**
	 * 根据搜索关键查询文档
	 * 搜索关键字不分词,测出仅支持单个字去查询?这个的作用还有待验证
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/prefixQuery")
	public Object prefixQuery(String keyword) {
		NativeSearchQuery prefixQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.prefixQuery("name", keyword)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(prefixQuery, Brand.class);
		return search;
	}

	/**
	 * 从多个列中查询包含搜索关键字分词后的字段的数据:如中华华为分词后的字段大概有:中华、华为、华,会从name、brandName、subTile三个列搜索包含分词后的字段的数据
	 * {
	 * "from": 0,
	 * "size": 10000,
	 * "query": {
	 * "multi_match": {
	 * "query": "中华华为",
	 * "fields": ["brandName^1.0", "name^1.0", "subTitle^1.0"],
	 * "type": "best_fields",
	 * "operator": "OR",
	 * "slop": 0,
	 * "prefix_length": 0,
	 * "max_expansions": 50,
	 * "zero_terms_query": "NONE",
	 * "auto_generate_synonyms_phrase_query": true,
	 * "fuzzy_transpositions": true,
	 * "boost": 1.0
	 * }* 	},
	 * "version": true
	 * }
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/multiMatchQuery")
	public Object multiMatchQuery(String keyword) {
		NativeSearchQuery multiMatchQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.multiMatchQuery(keyword, "name", "subTitle", "brandName")).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(multiMatchQuery, Brand.class);
		return search;
	}

	/**
	 * 范围查询
	 * {
	 * "from": 0,
	 * "size": 10000,
	 * "query": {
	 * "range": {
	 * "id": {
	 * "from": 100,
	 * "to": null,
	 * "include_lower": false,
	 * "include_upper": true,
	 * "boost": 1.0
	 * }* 		}
	 * },
	 * "version": true
	 * }
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/rangeQuery")
	public Object rangeQuery(String keyword) {
		NativeSearchQuery rangeQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.rangeQuery("id").gt(100)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(rangeQuery, Brand.class);
		return search;
	}


	/**
	 * 根据正则表达式查询
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/regexpQuery")
	public Object regexpQuery(String keyword) {
		NativeSearchQuery regexpQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.regexpQuery("name", "正则表达式")).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(regexpQuery, Brand.class);
		return search;
	}


	/**
	 * 对多个查询的结果做去重合并
	 * <p>
	 * {
	 * "from": 0,
	 * "size": 10000,
	 * "query": {
	 * "dis_max": {
	 * "tie_breaker": 0.0,
	 * "queries": [{
	 * "match": {
	 * "name": {
	 * "query": "华",
	 * "operator": "OR",
	 * "prefix_length": 0,
	 * "max_expansions": 50,
	 * "fuzzy_transpositions": true,
	 * "lenient": false,
	 * "zero_terms_query": "NONE",
	 * "auto_generate_synonyms_phrase_query": true,
	 * "boost": 1.0
	 * }* 				}
	 * }, {
	 * "term": {
	 * "name": {
	 * "value": "华为",
	 * "boost": 1.0
	 * }
	 * }
	 * }],
	 * "boost":        0
	 * }
	 * },
	 * "version": true
	 * }
	 *
	 * @param keyword
	 * @return
	 */
	@GetMapping("/disMaxQuery")
	public Object disMaxQuery(String keyword) {
		NativeSearchQuery disMaxQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.disMaxQuery().add(QueryBuilders.matchQuery("name", keyword)).add(QueryBuilders.termQuery("name", "华为"))).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(disMaxQuery, Brand.class);
		return search;
	}

	/**
	 * 实际的搜索中,我们有时候会打错字,从而导致搜索不到。在Elasticsearch中,我们可以使用fuzziness属性来进行模糊查询,从而达到搜索有错别字的情形。
	 * match查询具有“fuziness”属性。它可以被设置为“0”, “1”, “2”或“auto”。“auto”是推荐的选项,它会根据查询词的长度定义距离。
	 * 简单的说,就是搜索关键有错别字,es可以会自动进行纠错,假如查询列的数据是周星驰,但用户搜索的时候输入的是周星迟,fuzzyQuery可以将正确的数据查询出来
	 *
	 * 搜索华未会将华为的数据搜索出来
	 * {
	 * 	"from": 0,
	 * 	"size": 10000,
	 * 	"query": {
	 * 		"fuzzy": {
	 * 			"name": {
	 * 				"value": "华未",
	 * 				"fuzziness": "1",
	 * 				"prefix_length": 0,
	 * 				"max_expansions": 50,
	 * 				"transpositions": true,
	 * 				"boost": 1.0
	 *                        }* 		}
	 * 	},
	 * 	"version": true
	 * }
	 * @param keyword
	 * @return
	 */
	@GetMapping("/fuzzyQuery")
	public Object fuzzyQuery(String keyword) {
		// 默认是auto,修改为只允许修正一个错别字
		NativeSearchQuery fuzzyQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.fuzzyQuery("name", keyword).fuzziness(Fuzziness.ONE)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(fuzzyQuery, Brand.class);
		return search;
	}

	/**
	 * 轻量级字符串搜索,关键字会分词
	 * @param keyword
	 * @return
	 */
	@GetMapping("/queryStringQuery")
	public Object queryStringQuery(String keyword) {
		// 默认是auto,修改为只允许修正一个错别字
		NativeSearchQuery queryStringQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.queryStringQuery(keyword).fuzziness(Fuzziness.ONE)).build();
		SearchHits<Brand> search = elasticsearchRestTemplate.search(queryStringQuery, Brand.class);
		return search;
	}




高亮查询



一、通过注解 + 查询方法的方式

1、定义一个查询方法,并贴上 @Highligh 注解,设置要高亮的字段即可

public interface SpuRepository extends ElasticsearchRepository<EsSpu,String> {

	@Highlight(fields = {
			@HighlightField(name = "name"),
			@HighlightField(name = "caption")
	})
	List<SearchHit<EsSpu>> findByNameOrCaption(String name, String caption);
}

2、controller层调用

		@GetMapping("/highLightSearchMethod")
	public Object highLightSearchMethod(String keyword) {
		List<SearchHit<EsSpu>> byNameOrCaption = spuRepository.findByNameOrCaption(keyword, keyword);
		EsSpu content = null;
		for (SearchHit<EsSpu> esSpuSearchHit : byNameOrCaption) {
			// 得到对象
			content = esSpuSearchHit.getContent();
			// 只有一个高亮字段的情况
			List<String> highlightField = esSpuSearchHit.getHighlightField("name");
			if (highlightField.size() > 0) {
				content.setName(highlightField.get(0));
			}
		}
		return byNameOrCaption;
	}

3、响应结果

[
 {
        "index": "spu",
        "id": "2511918707000",
        "score": 9.83293,
        "sortValues": [],
        "content": {
            "id": "2511918707000",
            "sn": "",
            "name": "华为(HUAWEI) 华为nova2S 手机 银钻灰 {版本}",
            "caption": "华为新品上市,华为nova4",
            "introduction":"" 
            "specItems": "",
            "paraItems": "",
            "saleNum": 0,
            "commentNum": 0,
            "isMarketable": "1",
            "isEnableSpec": "1",
            "isDelete": "0",
            "status": "1"
        },
        "highlightFields": {
            "name": [
                "<em>华为</em>(HUAWEI) <em>华为</em>nova2S 手机 银钻灰 {版本}"
            ],
            "caption": [
                "<em>华为</em>新品上市,<em>华为</em>nova4"
            ]
        },
        "innerHits": {},
        "nestedMetaData": null
    }
 ]



二、 通过 ElasticsearchTemplate 方式

	/**
	 * 简易版高亮查询,这种方式在es 7.9.3已经过时
	 */
	 @GetMapping("/hightLightSearch")
	public Object hightLightSearch() {
		// 分页
		PageRequest page = PageRequest.of(0, 5);
		// 设置高亮属性
		HighlightBuilder highlightBuilder = new HighlightBuilder();
		highlightBuilder.field("subTitle").preTags("<em style='color:red'>").postTags("</em>");
		NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(QueryBuilders.termQuery("subTitle", "小米"))
				.withHighlightBuilder(highlightBuilder).withPageable(page).build();

		AggregatedPage<EsProduct> aggregatedPage = elasticsearchTemplate.queryForPage(query, EsProduct.class, new SearchResultMapper() {
			@Override
			public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
				List<EsProduct> list = new ArrayList<>();
				// hits 才是真正保存用户数据的字段
				response.getTook();
				response.getSuccessfulShards();
				SearchHits hits = response.getHits();
				hits.getTotalHits();
				hits.getMaxScore();
				SearchHit[] hits1 = hits.getHits();
				EsProduct product = null;
				for (SearchHit hit : hits) {
					if (hits.totalHits <= 0) {
						return null;
					}
					// 处理每一个实体类型
					product = new EsProduct();
					// 这种方式为自己手动获取对象的每一个值
					product.setId(Long.valueOf(hit.getId()));
					product.setBrandName(String.valueOf(hit.getSourceAsMap().get("brandName")));
					product.setSubTitle(String.valueOf(hit.getSourceAsMap().get("subTitle")));

					Map<String, HighlightField> highlightFields = hit.getHighlightFields();
					// 获取高亮的字段
					String subTitle = highlightFields.get("subTitle").fragments()[0].toString();
					// 重新赋值
					product.setSubTitle(subTitle);
					list.add(product);
				}
				return new AggregatedPageImpl<>((List<T>) list);
			}

			/**
			 * Map a single {@link SearchHit} to the given {@link Class type}.
			 *
			 * @param searchHit must not be {@literal null}.
			 * @param type      must not be {@literal null}.
			 * @return can be {@literal null}.
			 * @since 3.2
			 */
			@Override
			public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
				return null;
			}
		});
		return aggregatedPage;
	}



三、通过 ElasticsearchRestTemplate 方式

	/**
	 * @param keyword
	 * @return
	 */
	@GetMapping("/highLightSearch")
	public Object highLightSearch(String keyword) {
		// 分页查询
		PageRequest page = PageRequest.of(0, 10);
		NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
		// 设置高亮属性
		HighlightBuilder highlightBuilder = new HighlightBuilder();
		highlightBuilder.field("name");
		highlightBuilder.preTags("</em style='red'>");
		highlightBuilder.postTags("</em>");
		// HighlightBuilder.Field tags = new HighlightBuilder.Field(keyword).preTags("</em style='red'>").postTags("</em>");
		NativeSearchQuery searchQuery = queryBuilder.withQuery(QueryBuilders.matchQuery("name", keyword)).withPageable(page).withHighlightBuilder(highlightBuilder).build();
		// 第一个参数表示查询对象,第二个参数表示返回值,第三个参数表示要查询的索引
		SearchHits<EsSpu> searchHits = elasticsearchRestTemplate.search(searchQuery, EsSpu.class, IndexCoordinates.of("spu"));
		// elasticsearchRestTemplate.queryForPage()
		return searchHits;
	}

以上仅为个人学习总结,如有错误欢迎指出



版权声明:本文为weixin_43549350原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。