-
1. 准备
(1)引入es的RestHighLevelClient依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
(2)因为SpringBoot默认的ES版本是7.6.2,所以需要覆盖默认的ES版本
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
(3)初始化RestHighLevelClient
将RestHighLevelClient交给你spring容器管理,否则报错:java.net.ConnectException: Timeout connecting to [localhost/127.0.0.1:9200]
@Bean
public RestHighLevelClient restHighLevelClient(){
return new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.217.129:9200")
));
//或者
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchConfig {
private String host;
private int port;
@Bean
public RestHighLevelClient client(){
return new RestHighLevelClient(RestClient.builder(
new HttpHost(host, port, "http")));
}
}
#自定义elasticsearch连接配置
elasticsearch:
host: 192.168.217.129
port: 9200
2.索引操作
(1)创建Request对象
CreateIndexRequest request = new CreateIndexRequest("索引名");
- CreateIndexRequest:创建索引库
- DeleteIndexRequest:删除索引库
- GetIndexRequest:判断索引库是否存在
(2)准备请求的参数:DSL语句
request.source(source, XContentType.JSON);
- source为DSL的JSON参数部分,delete时无参
(3)发送请求
client.indices().create(request, RequestOptions.DEFAULT);
//批量添加文档到es
@SpringBootTest
public class Test {
@Autowired
private ItemClient itemClient;
@Autowired
private RestHighLevelClient client;
@Test
public void addTset() throws Exception{
//分页将数据添加进es
int i = 1;
while (true) {
//远程调用获取item list
PageDTO<Item> pageDTO = itemClient.selectByPage(i, 500);
List<Item> dtoList = pageDTO.getList();
if (dtoList.isEmpty()){
break ;
}
BulkRequest request = new BulkRequest();
for (Item item : dtoList) {
if (item.getStatus()==2){
continue;
}
ItemDoc itemDoc = new ItemDoc(item);
request.add(new IndexRequest("hmall")
.source(JSON.toJSONString(itemDoc), XContentType.JSON)
.id(String.valueOf(item.getId())));
}
client.bulk(request, RequestOptions.DEFAULT);
i++;
}
}
}
3.文档操作
核心方法:根据DSL语句结构去使用API
3.1 增删改查
根据DSL语句写代码,如图:
- IndexRequest:新增
- GetRequest:查询(根据id查询)
- UpdateRequest:修改
- DeleteRequest:删除
- BulkRequest:批量删除
3.2 匹配查询
(1)发送查询请求
这里关键的API有两个,一个是request.source()
,其中包含了查询、排序、分页、高亮等所有功能:
另一个是QueryBuilders
,其中包含match、term、function_score、bool等各种查询:
(2)解析响应
(3)排序、分页、高亮
搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。
(4)高亮结果解析
高亮的结果与查询的文档结果默认是分离的,并不在一起。
因此解析高亮的代码需要额外处理:
(5)算分查询
3.3. 代码范例
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
@Override
public PageResult search(RequestParams params) throws IOException {
//创建request
SearchRequest request = new SearchRequest("hotel");
//1、设置请求参数
//多字段查询和算分查询
//注意:多字段查询用boolQuery,boolQuery必须为同一个,不能用多个QueryBuilders.boolQuery(),会导致多字段无法并联查询
BoolQueryBuilder boolQueryBuilder = basicSearch(params,request);
//2、传入请求参数
SearchSourceBuilder query = request.source().query(boolQueryBuilder);
//3、对查询结果处理:分页、按距离排序、高亮显示
sortBycondition(params,request);
//发送请求,解析响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return convertResponse(response);
}
----------------------------------------------------------------------------------------
//多字段查询和算分查询
private BoolQueryBuilder basicSearch(RequestParams params,SearchRequest request) {
//先创建一个boolQuery
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//搜索为空,查询全部,不为空则匹配查询
if (StringUtils.isNotBlank(params.getKey())){
//多字段查询,对匹配结果打分,使用布尔查询
boolQueryBuilder.must(QueryBuilders.matchQuery("all", params.getKey()));
}else{
boolQueryBuilder.must(QueryBuilders.matchAllQuery());
}
//城市:多字段查询,不需要算分,不需要分词
if (params.getCity() != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("city", params.getCity()));
}
//星级:多字段查询,不需要算分,不需要分词
if (params.getStarName()!=null){
boolQueryBuilder.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
//品牌:多字段查询,不需要算分,需要分词
if (params.getBrand()!=null){
boolQueryBuilder.filter(QueryBuilders.matchQuery("brand", params.getBrand()));
}
//价格:多字段查询,不需要算分,不需要分词
if (params.getMinPrice()!=null && params.getMaxPrice()!=null ){
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
}
//算分查询与多字段查询并列
request.source().query(QueryBuilders.functionScoreQuery(
// 原始查询,相关性算分的查询
boolQueryBuilder,
// function score的数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
// 其中的一个function score 元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 过滤条件
QueryBuilders.termQuery("isAD", true),
// 算分函数
ScoreFunctionBuilders.weightFactorFunction(10)
)
}));
return boolQueryBuilder;
}
----------------------------------------------------------------------------------------
//对查询结果处理:分页、按距离排序、高亮显示
private void sortBycondition(RequestParams params, SearchRequest request) {
//分页
int page = params.getPage();
int size = params.getSize();
request.source().from((page - 1) * size).size(size);
//按距离查询
String location = params.getLocation();
if (StringUtils.isNotBlank(location)){
request.source().sort(
SortBuilders.geoDistanceSort("location",new GeoPoint(location))
.order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));
}
//高亮显示:前提是要有全文检索
if (StringUtils.isNotBlank(params.getKey())) {
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
}
}
----------------------------------------------------------------------------------------
//处理封装数据,封装基本信息,距离,高亮显示
private PageResult convertResponse(SearchResponse response) {
long total = response.getHits().getTotalHits().value;
SearchHit[] hits = response.getHits().getHits();
List<HotelDoc> hotelDocs = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
//获取source
String string = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(string, HotelDoc.class);
//封装距离参数
Object[] distance = hit.getSortValues();
if (distance.length>0){
hotelDoc.setDistance(distance[0]);
}
//封装高亮参数
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
//根据字段名获取高亮结果
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
// 获取高亮值
String name = highlightField.getFragments()[0].string();
// 覆盖非高亮结果
hotelDoc.setName(name);
}
}
hotelDocs.add(hotelDoc);
}
return new PageResult(total,hotelDocs);
}
}
4. 聚合查询
聚合条件与query条件同级别,因此需要使用request.source()来指定聚合条件。
聚合条件的语法:
聚合的结果也与查询结果不同,API也比较特殊。不过同样是JSON逐层解析:
4.1 代码范例
@Override
public Map<String, List<String>> getFilters(RequestParams params) throws IOException {
//创建request
SearchRequest request = new SearchRequest("hotel");
//1、设置请求参数
//在每次动态聚合前先多字段查询,保证在前置条件下聚合查询字段
request.source().query(basicSearch(params,request));
//不展示hit里面的信息,只展示聚合信息
request.source().size(0);
//2、动态聚合
List<String> city = buildAggregation(request, "city");
List<String> starName = buildAggregation(request, "starName");
List<String> brand = buildAggregation(request, "brand");
List<String> price = buildAggregation(request, "price");
Map<String, List<String>> map = new HashMap<>();
map.put("city",city);
map.put("starName",starName);
map.put("brand",brand);
return map;
}
//聚合查询
private List<String> buildAggregation(SearchRequest request, String feild) throws IOException {
//聚合添加
request.source().aggregation(AggregationBuilders.terms(feild).field(feild).size(100));
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析响应结果
return getAggs(response,feild);
}
//解析响应结果
private List<String> getAggs(SearchResponse response, String feild) {
//将Aggregations强转成Terms,否则无法调出bucket
//Terms是Aggregations的子接口,子接口功能更多
Terms term = response.getAggregations().get(feild);
List<? extends Terms.Bucket> buckets = term.getBuckets();
//创建一个List装aggs信息
ArrayList<String> list = new ArrayList<>();
for (Terms.Bucket bucket : buckets) {
list.add(bucket.getKeyAsString());
}
return list;
}
5. 自动补全
elasticsearch提供了Completion Suggester查询来实现自动补全功能
对于文档中字段的类型有一些约束:
- 参与补全查询的字段必须是completion类型。
- 字段的内容一般是用来补全的多个词条形成的数组。
自动补全的结果解析的代码如下:
5.1 代码范例
@Override
public List<String> getSuggestion(String key) throws IOException {
SearchRequest request = new SearchRequest("hotel");
//1、设置请求参数
request.source().suggest(new SuggestBuilder()
.addSuggestion("my_suggest", SuggestBuilders
.completionSuggestion("suggestion")
.prefix(key) //指定前缀
.skipDuplicates(true)
.size(10)));
//2、发出请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//3、解析响应
//强转为CompletionSuggestion接口
CompletionSuggestion suggestions = response.getSuggest().getSuggestion("my_suggest");
//获取options
List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
//封装数据
ArrayList<String> list = new ArrayList<>();
for (CompletionSuggestion.Entry.Option option : options) {
list.add(option.getText().toString());
}
return list;
}