之前写过一篇SpringBoot集成ElasticSearch 7.9.2 教程和简单增删改查案例https://blog.csdn.net/weixin_43753812/article/details/117733551
这里提供一个通用的操作接口和文档的方法类,大多数的使用场景都已经覆盖,话不多说,开整:
- 首先,创建一个自定义注解,结合elasticsearch提供的注解一起,配合实体类操作文档和索引。
package com.cqvip.innocence.common.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
public @interface documentId {
}
2.写一个与文档对应的DTO(这里只是示例,具体的实体根据对应需求建立)
package com.cqvip.innocence.project.model.entity;
import com.cqvip.innocence.common.annotation.documentId;
import lombok.Data;
import org.springframework.data.elasticsearch.annotations.document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
@Data
@document(indexName = "test_info")
public class EsTestInfo implements Serializable {
private static final long serialVersionUID = 6735913456541605819L;
@documentId
@Field(type = FieldType.Keyword)
private String infoId;
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Keyword)
private String sex;
@Field(type = FieldType.Keyword)
private String[] roles;
}
3.索引操作的接口和实现
package com.cqvip.innocence.project.esservice; public interface IndexService{ Boolean createIndexByClass(Class clazz); Boolean createIndexByName(String indexName); Boolean isIndexExist(String indexName); Boolean deleteIndexByName(String indexName); String getIndexName(Class clazz); }
package com.cqvip.innocence.project.esservice.impl; import cn.hutool.core.util.StrUtil; import com.cqvip.innocence.project.esservice.IndexService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.annotations.document; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.stereotype.Service; import java.lang.annotation.Annotation; @Service public class IndexServiceImplimplements IndexService { @Autowired private ElasticsearchRestTemplate restTemplate; @Override public String getIndexName(Class clazz){ Annotation documentAnnotation = clazz.getDeclaredAnnotation(document.class); if(documentAnnotation==null){ return null; } String indexName = ((document) documentAnnotation).indexName(); if (StrUtil.isNotBlank(indexName)){ return indexName; } return null; } @Override public Boolean createIndexByClass(Class clazz) { Boolean indexExist = isIndexExist(getIndexName(clazz)); if (indexExist){ return false; } IndexOperations indexOps = restTemplate.indexOps(clazz); boolean result1 = indexOps.create(); boolean result2 = indexOps.putMapping(indexOps.createMapping(clazz)); return result1&result2; } @Override public Boolean createIndexByName(String indexName) { Boolean indexExist = isIndexExist(indexName); if (indexExist){ return false; } IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of(indexName)); return indexOps.create(); } @Override public Boolean isIndexExist(String indexName) { IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of(indexName)); return indexOps.exists(); } @Override public Boolean deleteIndexByName(String indexName) { IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of(indexName)); return indexOps.delete(); } }
4.文档操作的接口和实现类
package com.cqvip.innocence.project.esservice; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.query.Query; import java.io.IOException; import java.util.List; import java.util.Map; public interface documentService{ HighlightBuilder getHighlightBuilder(String[] fields); Boolean isExist(String id, Class clazz); String saveByEntity(T t); List saveBatchByEntities(List entities) throws Exception; void updateByEntity(T t); void updateByEntities(List entities); String deleteById(String id, Class clazz); void deleteByIds(List ids, Class clazz); void deleteByQuery(Query query, Class clazz); T getEntityById(String id,Class clazz); List getEntityByIds(List ids, Class clazz); Long getCount(Query query,Class clazz); List getInfoList(Query query, Class clazz,Boolean isHighLight); Map getPageList(Query query, PageRequest pageRequest,Class clazz); Map > getFacetByQuery(SearchSourceBuilder query, Class clazz) throws IOException; }
package com.cqvip.innocence.project.esservice.impl; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.cqvip.innocence.common.annotation.documentId; import com.cqvip.innocence.common.exception.ElasticServiceException; import com.cqvip.innocence.project.esservice.documentService; import com.cqvip.innocence.project.esservice.IndexService; import org.apache.commons.beanutils.PropertyUtils; import org.apache.lucene.search.IndexSearcher; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.*; import org.springframework.data.elasticsearch.core.document.document; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; import org.springframework.stereotype.Service; import java.io.IOException; import java.lang.reflect.*; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Service public class documentServiceImplimplements documentService { @Autowired private ElasticsearchRestTemplate restTemplate; @Qualifier("highLevelClient") @Autowired private RestHighLevelClient highLevelClient; @Autowired private IndexService indexService; private String getPrimaryNameByClass(Class clazz){ Field[] fields = clazz.getDeclaredFields(); for (Field f:fields) { documentId id = f.getAnnotation(documentId.class); if (id != null){ return f.getName(); } } return null; } private String getPrimaryValueByEntity(T t){ Class clazz = (Class ) t.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field f:fields) { Class> type = f.getType(); if (type.getTypeName().equals(String.class.getTypeName())){ documentId id = f.getAnnotation(documentId.class); if (id != null){ try { String replace = f.getName() .replace(f.getName().substring(0, 1), f.getName().substring(0, 1).toUpperCase()); Method method = clazz.getMethod("get" + replace); String invoke = (String) method.invoke(t); if (StrUtil.isNotBlank(invoke)){ return invoke; }else { return null; } } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } return null; } private String indexNameExceptionHandler(Class clazz){ String indexName = indexService.getIndexName(clazz); if (StrUtil.isBlank(indexName)){ throw new ElasticServiceException("The index name on the current class does not exist!"); }else { return indexName; } } private List mappingHighlight(List > searchHits){ List infoList = new ArrayList<>(); for (SearchHit searchHit : searchHits) { T content = searchHit.getContent(); Map > highlightFields = searchHit.getHighlightFields(); for (Map.Entry > entry : highlightFields.entrySet()) { try { PropertyUtils.setProperty(content,entry.getKey(),entry.getValue().get(0)); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { e.printStackTrace(); } } infoList.add(content); } return infoList; } @Override public HighlightBuilder getHighlightBuilder(String[] fields) { HighlightBuilder highlightBuilder = new HighlightBuilder(); for (String field : fields) { highlightBuilder.field(field); } highlightBuilder.requireFieldMatch(false); //如果要多个字段高亮,这项要为false highlightBuilder.preTags(""); highlightBuilder.postTags(""); //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等 highlightBuilder.fragmentSize(800000); //最大高亮分片数 highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段 return highlightBuilder; } @Override public Boolean isExist(String id,Class clazz) { String name = indexNameExceptionHandler(clazz); return restTemplate.exists(id, IndexCoordinates.of(name)); } @Override public String saveByEntity(T t) { String id = getPrimaryValueByEntity(t); String indexName = indexNameExceptionHandler((Class ) t.getClass()); if (StrUtil.isBlank(id)){ throw new ElasticServiceException("document id cannot be empty!"); } IndexQuery build = new IndexQueryBuilder().withId(id).withObject(t).build(); String index = restTemplate.index(build, IndexCoordinates.of(indexName)); //业务需要,新增后马上刷新,ElasticsearchRestTemplate是默认不立即刷新(立即刷新会影响性能) restTemplate.indexOps(t.getClass()).refresh(); return index; } @Override public List saveBatchByEntities(List entities){ Class clazz =(Class ) entities.get(0).getClass(); String indexName = indexNameExceptionHandler(clazz); List queryList = new ArrayList<>(); for (T item:entities){ String id = getPrimaryValueByEntity(item); if (StrUtil.isBlank(id)){ throw new ElasticServiceException("document id cannot be empty!"); } IndexQuery build = new IndexQueryBuilder().withId(id).withObject(item).build(); queryList.add(build); } List idList = restTemplate.bulkIndex(queryList, IndexCoordinates.of(indexName)); restTemplate.indexOps(IndexCoordinates.of(indexName)).refresh(); return idList; } @Override public void updateByEntity(T t) { String indexName = indexNameExceptionHandler((Class ) t.getClass()); document document = document.parse(JSON.toJSonString(t)); document.setId(getPrimaryValueByEntity(t)); UpdateQuery build = UpdateQuery.builder(document.getId()) .withRefresh(UpdateQuery.Refresh.Wait_For) //更新后立即刷新可见(影响性能,按需注释或使用) .withdocument(document) .build(); restTemplate.update(build, IndexCoordinates.of(indexName)); } @Override public void updateByEntities(List entities) { Class clazz =(Class ) entities.get(0).getClass(); String indexName = indexNameExceptionHandler(clazz); List updateQueries = new ArrayList<>(); entities.forEach(item->{ document document = document.parse(JSON.toJSonString(item)); document.setId(getPrimaryValueByEntity(item)); UpdateQuery build = UpdateQuery.builder(document.getId()) .withRefresh(UpdateQuery.Refresh.Wait_For) // .withDocAsUpsert(true) //不加默认false。true表示更新时不存在就插入 .withdocument(document) .build(); updateQueries.add(build); }); restTemplate.bulkUpdate(updateQueries,IndexCoordinates.of(indexName)); } @Override public String deleteById(String id, Class clazz) { String indexName = indexNameExceptionHandler(clazz); return restTemplate.delete(id,IndexCoordinates.of(indexName)); } @Override public void deleteByIds(List ids, Class clazz) { String indexName = indexNameExceptionHandler(clazz); StringQuery query = new StringQuery(QueryBuilders.termsQuery(getPrimaryNameByClass(clazz), ids).toString()); restTemplate.delete(query,clazz,IndexCoordinates.of(indexName)); } @Override public void deleteByQuery(Query query,Class clazz) { String indexName = indexNameExceptionHandler(clazz); restTemplate.delete(query,clazz,IndexCoordinates.of(indexName)); } @Override public T getEntityById(String id,Class clazz) { return restTemplate.get(id,clazz); } @Override public List getEntityByIds(List ids, Class clazz) { String indexName = indexNameExceptionHandler(clazz); NativeSearchQuery build = new NativeSearchQueryBuilder().withIds(ids).build(); return restTemplate.multiGet(build, clazz, IndexCoordinates.of(indexName)); } @Override public Long getCount(Query query,Class clazz) { return restTemplate.count(query,clazz); } @Override public List getInfoList(Query query,Class clazz,Boolean isHighLight) { query.setTrackTotalHits(true); SearchHits search = restTemplate.search(query,clazz); if (isHighLight){ return mappingHighlight(search.getSearchHits()); } List infoList = new ArrayList<>(); for (SearchHit searchHit : search){ infoList.add(searchHit.getContent()); } return infoList; } @Override public Map getPageList(Query query, PageRequest pageRequest,Class clazz) { query.setTrackTotalHits(true); query.setPageable(pageRequest); SearchHits search = restTemplate.search(query,clazz); Aggregations aggregations = search.getAggregations(); List > searchHits = search.getSearchHits(); List esSourceInfos = mappingHighlight(searchHits); Page infos = new PageImpl( esSourceInfos, pageRequest, search.getTotalHits()); Map map = new HashMap<>(); map.put("page",infos); map.put("ag",formatFacet(aggregations)); return map; } @Override public Map > getFacetByQuery(SearchSourceBuilder query, Class clazz) throws IOException { String indexName = indexNameExceptionHandler(clazz); SearchRequest request = new SearchRequest(indexName); SearchSourceBuilder builder = query; request.source(builder); SearchResponse response = highLevelClient.search(request, RequestOptions.DEFAULT); Aggregations aggregations = response.getAggregations(); return formatFacet(aggregations); } private Map > formatFacet(Aggregations aggregations){ if (aggregations == null){ return null; } Map > map = new HashMap<>(); List list = aggregations.asList(); list.forEach(item->{ ParsedStringTerms newItem = (ParsedStringTerms) item; String name = newItem.getName(); List extends Terms.Bucket> buckets = newItem.getBuckets(); map.put(name,buckets); }); return map; } }
5.使用示例,使用@Autowired自动注入,并且带上泛型类
package com.cqvip.innocence.tests;
import com.cqvip.innocence.project.esservice.documentService;
import com.cqvip.innocence.project.esservice.IndexService;
import com.cqvip.innocence.project.model.entity.EsTestInfo;
import org.apache.commons.lang3.builder.ToStringExclude;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class EsTest {
@Autowired
private IndexService indexService;
@Autowired
private documentService documentService;
@Test
public void indexOperation(){
Boolean index = indexService.createIndexByClass(EsTestInfo.class);
System.out.println("index = " + index);
}
@Test
public void saveByEntity(){
EsTestInfo esTestInfo = new EsTestInfo();
esTestInfo.setAge(20);
esTestInfo.setInfoId("test_02");
esTestInfo.setName("一库一库");
esTestInfo.setSex("男");
String entity = null;
try {
entity = documentService.saveByEntity(esTestInfo);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("entity = " + entity);
}
@Test
public void isExist(){
Boolean test_01 = documentService.isExist("test_01", EsTestInfo.class);
System.out.println("test_01 = " + test_01);
}
@Test
public void updateByEntity(){
EsTestInfo info = documentService.getEntityById("test_01", EsTestInfo.class);
info.setName(info.getName()+"_update");
documentService.updateByEntity(info);
}
@Test
public void deleteById(){
String deleteById = documentService.deleteById("test_01", EsTestInfo.class);
System.out.println("deleteById = " + deleteById);
}
@Test
public void saveBatchByEntities(){
List list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
EsTestInfo info = new EsTestInfo();
info.setInfoId("test_batch_"+(i+1)+"");
info.setName("name_batch_"+(i+1)+"");
info.setSex("N");
info.setAge((i+1)*10);
info.setRoles(new String[]{"管理员","超级管理员"});
list.add(info);
}
try {
documentService.saveBatchByEntities(list);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void getInfoList(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.wildcardQuery("infoId","test_batch_*"));
NativeSearchQuery build = queryBuilder.withQuery(boolQuery).build();
List infoList = documentService.getInfoList(build,EsTestInfo.class,true);
}
@Test
public void count(){
Long count = documentService.getCount(new NativeSearchQueryBuilder().build(),EsTestInfo.class);
System.out.println("count = " + count);
}
@Test
public void deleteByIds(){
ArrayList strings = new ArrayList<>();
strings.add("test_01");
strings.add("test_02");
Class clazz = EsTestInfo.class;
documentService.deleteByIds(strings, clazz);
}
@Test
public void update() {
EsTestInfo info = new EsTestInfo();
info.setName("wait_for");
info.setInfoId("test_batch_wait_for_2");
documentService.saveByEntity(info);
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.wildcardQuery("infoId","test_batch_*"));
NativeSearchQuery build = queryBuilder.withQuery(boolQuery).build();
List infoList = documentService.getInfoList(build,EsTestInfo.class);
System.out.println("infoList = " + infoList);
}
}



