1、安装elasticSearch和ik分词器
2、新增实体类
package io.renren.modules.elasticsearch.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
/**
* @Document
*
* 作用在类,标记实体类为文档对象,一般有两个属性
*
* indexName:对应索引库名称
* type:对应在索引库中的类型
* shards:分片数量,默认5
* replicas:副本数量,默认1
* @Id 作用在成员变量,标记一个字段作为id主键
*
* @Field
* 作用在成员变量,标记为文档的字段,并指定字段映射属性:
*
* type:字段类型,是是枚举:FieldType
* index:是否索引,布尔类型,默认是true
* store:是否存储,布尔类型,默认是false
* analyzer:分词器名称
*/
@Data
@Document(indexName = "blog",type = "doc",
useServerConfiguration = true,createIndex =false)
public class EsBlog {
@Id
private String id;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String author;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String content;
@Field(type = FieldType.Date,format = DateFormat.custom,
pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date createDate;
@Field(type = FieldType.Date,format = DateFormat.custom,
pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date updateDate;
}
创建elasticSearch的mapper类
package io.renren.common.utils;
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.document.DocumentField;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.SearchHit;
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.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
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.*;
@Component
public class MyElasticSearchMapper extends AbstractResultMapper {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
public MyElasticSearchMapper() {
this(new SimpleElasticsearchMappingContext());
}
public MyElasticSearchMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
super(new DefaultEntityMapper(mappingContext));
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mappingContext = mappingContext;
}
public MyElasticSearchMapper(EntityMapper entityMapper) {
this(new SimpleElasticsearchMappingContext(), entityMapper);
}
public MyElasticSearchMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
EntityMapper entityMapper) {
super(entityMapper);
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mappingContext = mappingContext;
}
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
long totalHits = response.getHits().getTotalHits();
float maxScore = response.getHits().getMaxScore();
List<T> results = new ArrayList<>();
for (SearchHit hit : response.getHits()) {
if (hit != null) {
T result = null;
if (!StringUtils.isEmpty(hit.getSourceAsString())) {
result = mapEntity(hit.getSourceAsString(), clazz);
} else {
result = mapEntity(hit.getFields().values(), clazz);
}
setPersistentEntityId(result, hit.getId(), clazz);
setPersistentEntityVersion(result, hit.getVersion(), clazz);
setPersistentEntityScore(result, hit.getScore(), clazz);
populateScriptFields(result, hit);
results.add(result);
}
}
return new AggregatedPageImpl<T>(results, pageable, totalHits, response.getAggregations(), response.getScrollId(),
maxScore);
}
private String concat(Text[] texts) {
StringBuilder sb = new StringBuilder();
for (Text text : texts) {
sb.append(text.toString());
}
return sb.toString();
}
private <T> 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();
DocumentField 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);
}
}
}
}
}
for (HighlightField field : hit.getHighlightFields().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 <T> T mapEntity(Collection<DocumentField> values, Class<T> clazz) {
return mapEntity(buildJSONFromFields(values), clazz);
}
private String buildJSONFromFields(Collection<DocumentField> values) {
JsonFactory nodeFactory = new JsonFactory();
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonGenerator generator = nodeFactory.createGenerator(stream, JsonEncoding.UTF8);
generator.writeStartObject();
for (DocumentField 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> T mapResult(GetResponse response, Class<T> clazz) {
T result = mapEntity(response.getSourceAsString(), clazz);
if (result != null) {
setPersistentEntityId(result, response.getId(), clazz);
setPersistentEntityVersion(result, response.getVersion(), clazz);
}
return result;
}
@Override
public <T> LinkedList<T> mapResults(MultiGetResponse responses, Class<T> clazz) {
LinkedList<T> 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 <T> void setPersistentEntityId(T result, String id, Class<T> clazz) {
if (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 <T> void setPersistentEntityVersion(T result, long version, Class<T> clazz) {
if (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);
}
}
}
private <T> void setPersistentEntityScore(T result, float score, Class<T> clazz) {
if (clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(clazz);
if (!entity.hasScoreProperty()) {
return;
}
entity.getPropertyAccessor(result) //
.setProperty(entity.getScoreProperty(), score);
}
}
}
创建控制层
package io.renren.modules.elasticsearch.controller;
import io.renren.common.constant.Constant;
import io.renren.common.page.PageData;
import io.renren.common.utils.Result;
import io.renren.modules.elasticsearch.entity.EsBlog;
import io.renren.modules.elasticsearch.serivce.ElasticSearchService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Map;
@RestController
@RequestMapping("elasticSearch")
@Api(tags="elasticSearch测试")
public class ElasticSearchController {
@Autowired
private ElasticSearchService elasticSearchService;
/**
* 单个字段匹配查询与高亮
* @param params
* @return
*/
@GetMapping("pageForOnlyKey")
@ApiOperation("分页(单个字段匹配查询与高亮)")
@ApiImplicitParams({
@ApiImplicitParam(name = Constant.PAGE, value = "当前页码,从1开始", paramType = "query", required = true, dataType="int") ,
@ApiImplicitParam(name = Constant.LIMIT, value = "每页显示记录数", paramType = "query",required = true, dataType="int") ,
@ApiImplicitParam(name = "content", value = "content", paramType = "query", dataType="String")
})
public Result<PageData<EsBlog>> pageForOnlyKey(@ApiIgnore @RequestParam Map<String, Object> params){
PageData<EsBlog> page = elasticSearchService.pageForOnlyKey(params);
return new Result<PageData<EsBlog>>().ok(page);
}
@GetMapping("page")
@ApiOperation("分页(多字段匹配查询与高亮)")
@ApiImplicitParams({
@ApiImplicitParam(name = Constant.PAGE, value = "当前页码,从1开始", paramType = "query", required = true, dataType="int") ,
@ApiImplicitParam(name = Constant.LIMIT, value = "每页显示记录数", paramType = "query",required = true, dataType="int") ,
@ApiImplicitParam(name = "keyWord", value = "关键字", paramType = "query", dataType="String")
})
public Result<PageData<EsBlog>> page(@ApiIgnore @RequestParam Map<String, Object> params){
PageData<EsBlog> page = elasticSearchService.page(params);
return new Result<PageData<EsBlog>>().ok(page);
}
@GetMapping("searchForLike")
@ApiOperation("模糊查询")
public Result searchForLike(String name){
return elasticSearchService.searchForLike(name);
}
@PostMapping
@ApiOperation("保存")
public Result save(@RequestBody EsBlog esBlog){
return elasticSearchService.save(esBlog);
}
@PutMapping
@ApiOperation("修改")
public Result update(@RequestBody EsBlog esBlog){
return elasticSearchService.update(esBlog);
}
@DeleteMapping
@ApiOperation("删除")
public Result delete(String id){
return elasticSearchService.delete(id);
}
}
添加服务层接口
package io.renren.modules.elasticsearch.serivce;
import com.alibaba.fastjson.JSONObject;
import io.renren.common.page.PageData;
import io.renren.common.utils.MyElasticSearchMapper;
import io.renren.common.utils.Result;
import io.renren.modules.elasticsearch.dao.ElasticeSearchDao;
import io.renren.modules.elasticsearch.entity.EsBlog;
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.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.*;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Service
public class ElasticSearchService {
@Autowired
private ElasticeSearchDao elasticeSearchDao;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Autowired
private MyElasticSearchMapper myElasticSearchMapper;
public Result save(EsBlog esBlog) {
esBlog.setId(UUID.randomUUID().toString().replaceAll("-",""));
//保存至mysql数据库
elasticeSearchDao.save(esBlog);
//保存到es
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(esBlog.getId())
.withObject(esBlog)
.build();
elasticsearchTemplate.index(indexQuery);
return new Result();
}
public Result update(EsBlog esBlog) {
//更新mysql数据库
elasticeSearchDao.update(esBlog);
//更新es
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(esBlog.getId())
.withObject(esBlog)
.build();
elasticsearchTemplate.index(indexQuery);
return new Result();
}
public Result delete(String id) {
DeleteQuery deleteQuery = new DeleteQuery();
deleteQuery.setIndex("blog");
deleteQuery.setType("doc");
deleteQuery.setQuery(new BoolQueryBuilder().must(QueryBuilders.matchQuery("id",id)));
elasticsearchTemplate.delete(deleteQuery);
elasticeSearchDao.delete(id);
return new Result();
}
public Result searchForLike(String name) {
SearchQuery searchQuery;
if (StringUtils.isNotBlank(name)) {
searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("title",name))
.should(QueryBuilders.matchQuery("author",name)))
.build();
}else {
searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchAllQuery())
.build();
}
List<EsBlog> esBlog = elasticsearchTemplate.queryForList(searchQuery,EsBlog.class);
return new Result().ok(esBlog);
}
public PageData<EsBlog> pageForOnlyKey(Map<String, Object> params) {
Pageable pageable = PageRequest.of(Integer.valueOf(params.get("page").toString())-1,Integer.valueOf(params.get("limit").toString()));
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("content",params.get("content").toString()))
.withHighlightFields(new HighlightBuilder.Field("content").preTags("<span style=\"color:red\">").postTags("</span>"))
.build();
searchQuery.setPageable(pageable);
// 不需要高亮直接return ideas
// AggregatedPage<EsBlog> esBlogss = elasticsearchTemplate.queryForPage(searchQuery, EsBlog.class);
// return new PageData<>(esBlogs.getContent(),esBlogs.getTotalElements());
//高亮字段
AggregatedPage<EsBlog> esBlogs = elasticsearchTemplate.queryForPage(searchQuery, EsBlog.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
List<EsBlog> chunk = new ArrayList<>();
for (SearchHit searchHit : searchResponse.getHits()) {
if (searchResponse.getHits().getHits().length <= 0) {
return null;
}
EsBlog esBlog = JSONObject.parseObject(searchHit.getSourceAsString(), EsBlog.class);
Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
HighlightField content = highlightFields.get("content");
if (content != null) {
esBlog.setContent(content.fragments()[0].toString());
}
chunk.add(esBlog);
}
if (chunk.size() > 0) {
return new AggregatedPageImpl<>((List<T>) chunk);
}
return null;
}
});
if(null != esBlogs){
return new PageData<>(esBlogs.getContent(), esBlogs.getTotalElements());
}else {
return new PageData<>(new ArrayList<>(),0);
}
}
public PageData<EsBlog> page(Map<String, Object> params) {
Pageable pageable = PageRequest.of(Integer.valueOf(params.get("page").toString())-1,Integer.valueOf(params.get("limit").toString()));
//设置需要高亮的字段 title和author 对应的是es中字段名称(key键)
//preTags postTags 这两个是设置的标签内容,实现开发中可按照实际场景设置不同的标签
//requireFieldMatch 多字段高亮需设置成false
HighlightBuilder.Field allHighLight = new HighlightBuilder.Field("title").
preTags("<span style='color:red'>").postTags("</span>").requireFieldMatch(false);
HighlightBuilder.Field allHighLight1 = new HighlightBuilder.Field("author").requireFieldMatch(false).
preTags("<span style='color:red'>").postTags("</span>");
HighlightBuilder.Field[] ary = new HighlightBuilder.Field[2];
ary[0] = allHighLight;
ary[1] = allHighLight1;
SearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("title",params.get("keyWord").toString()))
.should(QueryBuilders.matchQuery("author",params.get("keyWord").toString())))
.withHighlightFields(ary)
.withPageable(pageable)
.build();
//搜索
Page<EsBlog> search = elasticsearchTemplate.queryForPage(query,EsBlog.class, myElasticSearchMapper);
if(null != search){
return new PageData<>(search.getContent(),search.getTotalPages());
}else {
return new PageData<>(new ArrayList<>(),0);
}
}
}
添加Dao层接口
package io.renren.modules.elasticsearch.dao;
import io.renren.modules.elasticsearch.entity.EsBlog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ElasticeSearchDao{
EsBlog getInfo(String id);
void save(EsBlog esBlog);
void update(EsBlog esBlog);
void delete(@Param("id") String id);
}
接口测试
1、测试新增
mysql数据库中添加成功
es中添加成功
2、多添加几条数据,测试搜索
查询title和author中包含“啊”关键字的记录
查询title和author中包含“为什么”关键字的记录