博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
和我一起打造个简单搜索之SpringDataElasticSearch关键词高亮
阅读量:7222 次
发布时间:2019-06-29

本文共 10948 字,大约阅读时间需要 36 分钟。

前面几篇文章详细讲解了 ElasticSearch 的搭建以及使用 SpringDataElasticSearch 来完成搜索查询,但是搜索一般都会有搜索关键字高亮的功能,今天我们把它给加上。

系列文章

  • 一、
  • 二、
  • 三、
  • 四、
  • 五、
  • 六、
  • ...

环境依赖

本文以及后续 es 系列文章都基于 5.5.3 这个版本的 elasticsearch ,这个版本比较稳定,可以用于生产环境。

SpringDataElasticSearch 的基本使用可以看我的上一篇文章 ,本文就不再赘述。

高亮关键字实现

前文查询是通过写一个接口来继承 ElasticsearchRepository 来实现的,但是如果要实现高亮,我们就不能这样做了,我们需要使用到 ElasticsearchTemplate来完成。

查看这个类的源码

public class ElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {    ...}

可以看到,ElasticsearchTemplate 实现了接口 ApplicationContextAware,所以这个类是被 Spring 管理的,可以在类里面直接注入使用。

代码如下:

@Slf4j@Componentpublic class HighlightBookRepositoryTest extends EsSearchApplicationTests {    @Autowired    private ElasticsearchTemplate elasticsearchTemplate;    @Resource    private ExtResultMapper extResultMapper;    @Test    public void testHighlightQuery() {        BookQuery query = new BookQuery();        query.setQueryString("穿越");        // 复合查询        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();        // 以下为查询条件, 使用 must query 进行查询组合        MultiMatchQueryBuilder matchQuery = QueryBuilders.multiMatchQuery(query.getQueryString(), "name", "intro", "author");        boolQuery.must(matchQuery);        PageRequest pageRequest = PageRequest.of(query.getPage() - 1, query.getSize());        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()                .withQuery(boolQuery)                .withHighlightFields(                        new HighlightBuilder.Field("name").preTags("").postTags(""),                        new HighlightBuilder.Field("author").preTags("").postTags(""))                .withPageable(pageRequest)                .build();        Page
books = elasticsearchTemplate.queryForPage(searchQuery, Book.class, extResultMapper); books.forEach(e -> log.info("{}", e)); //
穿越小道人 }}

注意这里 的

Page
books = elasticsearchTemplate.queryForPage(searchQuery, Book.class, extResultMapper);

这里返回的是分页对象。

查询方式和上文的差不多,只不过是是 Repository 变成了 ElasticsearchTemplate,操作方式也大同小异。

这里用到了 ExtResultMapper,请接着看下文。

自定义ResultMapper

ResultMapper 是用于将 ES 文档转换成 Java 对象的映射类,因为 SpringDataElasticSearch 默认的的映射类 DefaultResultMapper 不支持高亮,因此,我们需要自己定义一个 ResultMapper。

复制 DefaultResultMapper 类,重命名为 ExtResultMapper,对构造方法名称修改为正确的值。

新增一个方法,用于将高亮的内容赋值给需要转换的 Java 对象内。

在 mapResults 方法内调用这个方法。

注意:这个类可以直接拷贝到你的项目中直接使用!

我写这么多,只是想说明为什么这个类是这样的。

import com.fasterxml.jackson.core.JsonEncoding;import com.fasterxml.jackson.core.JsonFactory;import com.fasterxml.jackson.core.JsonGenerator;import org.apache.commons.beanutils.PropertyUtils;import org.elasticsearch.action.get.GetResponse;import org.elasticsearch.action.get.MultiGetItemResponse;import org.elasticsearch.action.get.MultiGetResponse;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.common.text.Text;import org.elasticsearch.search.SearchHit;import org.elasticsearch.search.SearchHitField;import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;import org.springframework.data.domain.Pageable;import org.springframework.data.elasticsearch.ElasticsearchException;import org.springframework.data.elasticsearch.annotations.Document;import org.springframework.data.elasticsearch.annotations.ScriptedField;import org.springframework.data.elasticsearch.core.AbstractResultMapper;import org.springframework.data.elasticsearch.core.DefaultEntityMapper;import org.springframework.data.elasticsearch.core.EntityMapper;import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;import org.springframework.data.mapping.context.MappingContext;import org.springframework.stereotype.Component;import org.springframework.util.Assert;import org.springframework.util.StringUtils;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.lang.reflect.InvocationTargetException;import java.nio.charset.Charset;import java.util.*;/** * 类名称:ExtResultMapper * 类描述:自定义结果映射类 * 创建人:WeJan * 创建时间:2018-09-13 20:47 */@Componentpublic class ExtResultMapper extends AbstractResultMapper {    private MappingContext
, ElasticsearchPersistentProperty> mappingContext; public ExtResultMapper() { super(new DefaultEntityMapper()); } public ExtResultMapper(MappingContext
, ElasticsearchPersistentProperty> mappingContext) { super(new DefaultEntityMapper()); this.mappingContext = mappingContext; } public ExtResultMapper(EntityMapper entityMapper) { super(entityMapper); } public ExtResultMapper( MappingContext
, ElasticsearchPersistentProperty> mappingContext, EntityMapper entityMapper) { super(entityMapper); this.mappingContext = mappingContext; } @Override public
AggregatedPage
mapResults(SearchResponse response, Class
clazz, Pageable pageable) { long totalHits = response.getHits().totalHits(); List
results = new ArrayList<>(); for (SearchHit hit : response.getHits()) { if (hit != null) { T result = null; if (StringUtils.hasText(hit.sourceAsString())) { result = mapEntity(hit.sourceAsString(), clazz); } else { result = mapEntity(hit.getFields().values(), clazz); } setPersistentEntityId(result, hit.getId(), clazz); setPersistentEntityVersion(result, hit.getVersion(), clazz); populateScriptFields(result, hit); // 高亮查询 populateHighLightedFields(result, hit.getHighlightFields()); results.add(result); } } return new AggregatedPageImpl
(results, pageable, totalHits, response.getAggregations(), response.getScrollId()); } private
void populateHighLightedFields(T result, Map
highlightFields) { for (HighlightField field : highlightFields.values()) { try { PropertyUtils.setProperty(result, field.getName(), concat(field.fragments())); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { throw new ElasticsearchException("failed to set highlighted value for field: " + field.getName() + " with value: " + Arrays.toString(field.getFragments()), e); } } } private String concat(Text[] texts) { StringBuffer sb = new StringBuffer(); for (Text text : texts) { sb.append(text.toString()); } return sb.toString(); } private
void populateScriptFields(T result, SearchHit hit) { if (hit.getFields() != null && !hit.getFields().isEmpty() && result != null) { for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) { ScriptedField scriptedField = field.getAnnotation(ScriptedField.class); if (scriptedField != null) { String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name(); SearchHitField searchHitField = hit.getFields().get(name); if (searchHitField != null) { field.setAccessible(true); try { field.set(result, searchHitField.getValue()); } catch (IllegalArgumentException e) { throw new ElasticsearchException("failed to set scripted field: " + name + " with value: " + searchHitField.getValue(), e); } catch (IllegalAccessException e) { throw new ElasticsearchException("failed to access scripted field: " + name, e); } } } } } } private
T mapEntity(Collection
values, Class
clazz) { return mapEntity(buildJSONFromFields(values), clazz); } private String buildJSONFromFields(Collection
values) { JsonFactory nodeFactory = new JsonFactory(); try { ByteArrayOutputStream stream = new ByteArrayOutputStream(); JsonGenerator generator = nodeFactory.createGenerator(stream, JsonEncoding.UTF8); generator.writeStartObject(); for (SearchHitField value : values) { if (value.getValues().size() > 1) { generator.writeArrayFieldStart(value.getName()); for (Object val : value.getValues()) { generator.writeObject(val); } generator.writeEndArray(); } else { generator.writeObjectField(value.getName(), value.getValue()); } } generator.writeEndObject(); generator.flush(); return new String(stream.toByteArray(), Charset.forName("UTF-8")); } catch (IOException e) { return null; } } @Override public
T mapResult(GetResponse response, Class
clazz) { T result = mapEntity(response.getSourceAsString(), clazz); if (result != null) { setPersistentEntityId(result, response.getId(), clazz); setPersistentEntityVersion(result, response.getVersion(), clazz); } return result; } @Override public
LinkedList
mapResults(MultiGetResponse responses, Class
clazz) { LinkedList
list = new LinkedList<>(); for (MultiGetItemResponse response : responses.getResponses()) { if (!response.isFailed() && response.getResponse().isExists()) { T result = mapEntity(response.getResponse().getSourceAsString(), clazz); setPersistentEntityId(result, response.getResponse().getId(), clazz); setPersistentEntityVersion(result, response.getResponse().getVersion(), clazz); list.add(result); } } return list; } private
void setPersistentEntityId(T result, String id, Class
clazz) { if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) { ElasticsearchPersistentEntity
persistentEntity = mappingContext.getRequiredPersistentEntity(clazz); ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); // Only deal with String because ES generated Ids are strings ! if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { persistentEntity.getPropertyAccessor(result).setProperty(idProperty, id); } } } private
void setPersistentEntityVersion(T result, long version, Class
clazz) { if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) { ElasticsearchPersistentEntity
persistentEntity = mappingContext.getPersistentEntity(clazz); ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); // Only deal with Long because ES versions are longs ! if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) { // check that a version was actually returned in the response, -1 would indicate that // a search didn't request the version ids in the response, which would be an issue Assert.isTrue(version != -1, "Version in response is -1"); persistentEntity.getPropertyAccessor(result).setProperty(versionProperty, version); } } }}

注意这里使用到了 PropertyUtils ,需要引入一个 Apache 的依赖。

commons-beanutils
commons-beanutils
1.9.3

自定义 ResultMapper 写好之后,添加 @Component 注解,表示为 Spring 的一个组件,在类中进行注入使用即可。

最后

本文示例项目地址:https://github.com/Mosiki/SpringDataElasticSearchQuickStartExample

有疑问?

欢迎来信,

转载于:https://www.cnblogs.com/vcmq/p/9966693.html

你可能感兴趣的文章
动态的加载类型
查看>>
36.scrapy框架采集全球玻璃网数据
查看>>
python matplotlib
查看>>
心灵指南 刘墉 第三辑 肯定自己 笔记
查看>>
centos7 tomcat nginx 动静分离显示权限不足
查看>>
弹珠游戏
查看>>
一切的原点
查看>>
OC内存管理
查看>>
洛谷P1801 黑匣子
查看>>
body和普通div背景图宽高百分比的区别
查看>>
【数字图像处理】OpenCV最大化HSV图像的"S"和"V"部分
查看>>
MongoDB的配置和使用
查看>>
我是如何在SQL Server中处理每天四亿三千万记录的
查看>>
python学习笔记(9)-python编程风格
查看>>
在VS和QT下配置pthread
查看>>
open
查看>>
什么是anaconda【转载】
查看>>
Linux+Redis实战教程_day03_1、Redis-LinkedList【重点】
查看>>
spring-cloud feign (web服务客户端)(四)
查看>>
软件架构视图—4+1视图模式
查看>>