补充:

  • ​QueryBuilders.termQuery()​​是精确搜索,对检索词不分词
  • ​QueryBuilders.queryStringQuery(keyWord).defaultField(field)​​​ 先对检索词进行分词,再进行检索
    比如:搜索“斗罗大陆”,由于ik分词器将“斗罗大陆”分词为“斗”、“罗”、“大陆”和“陆”,若使用​​​termQuery()​​​,将搜索不出《斗罗大陆》,若使用​​queryStringQuery()​​则可以搜索出!!

  ElasticsearchTemplate是Springboot为我们自动装载的Elasticsearch模板,使用该模板,基本可以满足我们的搜索需求!

  案例背景介绍:用户输入一个关键词:“历史”,要求同时在索引库中“name”、“author”、"category"三个字段进行搜索。

package com.yuedu.service;

import com.yuedu.dao.BookMapper;
import com.yuedu.entity.Book;
import com.yuedu.entity.Category;
import com.yuedu.entity.ResultEnum;
import com.yuedu.exception.BaseException;
import com.yuedu.mapper.BookVo2Book;
import com.yuedu.util.StringUtil;
import com.yuedu.vo.BookVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
* @author 咸鱼
* @date 2019-01-25 11:08
*/
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class BookService {

private final BookMapper bookMapper;
private final CategoryService categoryService;

private final ElasticsearchTemplate elasticsearchTemplate;

private static final String BOOK_NAME = "name";
private static final String AUTHOR = "author";
private static final String CATEGORY = "category";
private static final Integer KEY_WORLD_MAX_LENGTH = 20;

/**
* 根据关键词查书籍
* @param keyWord 关键字
* @param start 分页开始下标
* @param size 每页数量
* @return {@link Book}
*/
public List<Book> searchBooks(String keyWord, Integer start, Integer size){
if (StringUtils.isBlank(keyWord)){
throw new BaseException(ResultEnum.PARAM_ERROR);
}
//处理搜索的关键词,避免过长
if (keyWord.length() > KEY_WORLD_MAX_LENGTH){
keyWord = keyWord.substring(0, KEY_WORLD_MAX_LENGTH);
}
return highLightQuery(keyWord, start, size);
}

/**
* 高亮查询
* @param keyWord 查询关键词
* @param start 开始下标
* @param size 每页数量
* @return {@link Book}
*/
private List<Book> highLightQuery(String keyWord, Integer start, Integer size) {
/*1.创建QueryBuilder(即设置查询条件)这儿创建的是组合查询(也叫多条件查询),后面会介绍更多的查询方法
*组合查询BoolQueryBuilder
* must(QueryBuilders) :AND
* mustNot(QueryBuilders):NOT
* should: :OR
*/
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.should(QueryBuilders.termQuery(BOOK_NAME, keyWord))
.should(QueryBuilders.termQuery(AUTHOR, keyWord))
.should(QueryBuilders.termQuery(CATEGORY, keyWord));

//设置分页(从第一页开始,一页显示10条)
//注意开始是从0开始,有点类似sql中的方法limit 的查询
Pageable page = PageRequest.of(start, size);

//构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
//设置高亮字段
.withHighlightFields(new HighlightBuilder.Field(BOOK_NAME),
new HighlightBuilder.Field(AUTHOR),
new HighlightBuilder.Field(CATEGORY))
.withPageable(page)
.build();

//实施查询,注意:这里的泛型最后和 elasticsearch 中的字段对应
AggregatedPage<BookVo> bookPage = elasticsearchTemplate.queryForPage(searchQuery, BookVo.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<BookVo> bookVoList = new ArrayList<>();
//命中记录
SearchHits hits = response.getHits();
for (SearchHit hit : hits){
if (hits.totalHits <= 0){
return null;
}
BookVo bookVo = new BookVo();
bookVo.setId(hit.getId());
bookVo.setName(String.valueOf(hit.getSource().get("name")));
bookVo.setAuthor(String.valueOf(hit.getSource().get("author")));
bookVo.setCover_img(String.valueOf(hit.getSource().get("cover_img")));
bookVo.setCreate_date(formateDate(String.valueOf(hit.getSource().get("create_date"))));

//设置高亮(若对应字段有高亮的话)
setHighLight(hit, BOOK_NAME, bookVo);
setHighLight(hit, AUTHOR, bookVo);

bookVoList.add(bookVo);
}
return new AggregatedPageImpl<>((List<T>)bookVoList);
}
});
//POJO 和 VO 转换
return BookVo2Book.MAPPER.bookVos2Books(bookPage.getContent()) ;
}

/**
* 设置高亮
* @param hit 命中记录
* @param filed 字段
* @param object 待赋值对象
*/
private static void setHighLight(SearchHit hit, String filed, Object object){
//获取对应的高亮域
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField highlightField = highlightFields.get(filed);
if (highlightField != null){
//取得定义的高亮标签
String highLightMessage = highlightField.fragments()[0].toString();
// 反射调用set方法将高亮内容设置进去
try {
String setMethodName = parSetMethodName(filed);
Class<?> Clazz = object.getClass();
Method setMethod = Clazz.getMethod(setMethodName, String.class);
setMethod.invoke(object, highLightMessage);
} catch (Exception e) {
log.error("反射报错", e);
}
}
}

/**
* 根据字段名,获取Set方法名
* @param fieldName 字段名
* @return Set方法名
*/
private static String parSetMethodName(String fieldName){
if (StringUtils.isBlank(fieldName)){
return null;
}
int startIndex = 0;
if (fieldName.charAt(0) == '_'){
startIndex = 1;
}
return "set" + fieldName.substring(startIndex, startIndex + 1).toUpperCase()
+ fieldName.substring(startIndex + 1);
}

private boolean insertBook(Book book){
return bookMapper.insertBook(book) == 1;
}

/**
* 将 yyyy-MM-dd'T'HH:mm:ss.SSS Z 转换成 Date
*/
private static Date formateDate(String dateStr){
SimpleDateFormat format = null;
try {
if (dateStr.endsWith("Z")){
dateStr = dateStr.replace("Z", " UTC");
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z");
return format.parse(dateStr);
} else {
format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.parse(dateStr);
}
} catch (ParseException e) {
log.error("转换时间失败!", e);
}
return null;
}
}

  上面用到的 ​​QueryBuilders.termQuery(BOOK_NAME, keyWord)​​ 是查询的一种,叫精确查询,关于其它查询,参见:

  • springdata整合elasticsearch实现搜索的高亮显示
  • elasticsearch基本操作之–使用QueryBuilders进行查询