ElasticSeach(六、springboot集成ES high level client)
这里之所以选择high level client方式是因为transportClient在7.X版本中废弃,预计会在8版本彻底删除。
可参考官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.6/java-rest-high-getting-started-maven.html
配置文件
pom.xml
这里有个小坑,在使用官方文档的依赖包时,发现maven下载的es相关jar包竟然是6.4版本的,导致出现了很多问题。
通过 https://www.jianshu.com/p/acc8e86cc772 可解决jar包版本不一致的问题,再次感谢大佬分享学习的精神。
<!-- es --> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.6.1</version> <exclusions> <exclusion> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> </exclusion> <exclusion> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>7.6.1</version> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.6.1</version> </dependency>
application.yml
es:
host: 10.32.16.179
port: 9200
schema: http
索引管理
判断索引是否存在
@Service
public class ElasticSearchServiceImpl implements ElasticSearchService {
private static final Logger logger = LoggerFactory.getLogger(ElasticSearchServiceImpl.class);
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 根据索引名称判断索引是否已存在
* @param indexName
* @return
* @throws IOException
*/
@Override
public boolean isExistsIndex(String indexName) throws IOException {
return restHighLevelClient.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT);
}
}
创建索引
官方提供了三种创建mapping的方式,这里在后端写死了mapping,如果需要索引管理,可通过前端传入参数构建。
/**
* 创建索引
* @param indexName
* @throws IOException
*/
@Override
public void createIndex(String indexName) throws IOException {
if(isExistsIndex(indexName)){
logger.error("indexName={} 已存在,无法创建", indexName);
return;
}
CreateIndexRequest request = new CreateIndexRequest(indexName);
//设置分片和备份
request.settings(Settings.builder()
.put("index.number_of_shards",3)
.put("index.number_of_replicas",2)
.build());
//第一种,json字符串
request.mapping("{\n" +
"\t\"properties\": {\n" +
"\t\t\"username\": {\n" +
"\t\t\t\"type\": \"text\"\n" +
"\t\t},\n" +
"\t\t\"city\": {\n" +
"\t\t\t\"type\": \"keyword\"\n" +
"\t\t}\n" +
"\t}\n" +
"}", XContentType.JSON);
//第二种,Map
// Map<String,Object> username = new HashMap<>();
// username.put("type","text");
// Map<String,Object> city = new HashMap<>();
// city.put("type","keyword");
// Map<String,Object> properties = new HashMap<>();
// properties.put("username",username);
// properties.put("city",city);
// Map<String,Object> mapping = new HashMap<>();
// mapping.put("properties",properties);
// request.mapping(mapping);
//第三种,XContentBuilder
// XContentBuilder builder = XContentFactory.jsonBuilder();
// builder.startObject();
// {
// builder.startObject("properties");
// {
// builder.startObject("username");
// {
// builder.field("type","text");
// }
// builder.endObject();
//
// builder.startObject("city");
// {
// builder.field("type","keyword");
// }
// builder.endObject();
// }
// builder.endObject();
// }
// builder.endObject();
//
restHighLevelClient.indices().create(request,RequestOptions.DEFAULT);
}
删除索引
/**
* 删除索引
* @param indexName
* @throws IOException
*/
@Override
public void deleteIndex(String indexName) throws IOException {
if(!isExistsIndex(indexName)){
logger.error("indexName={} 索引不存在", indexName);
return;
}
restHighLevelClient.indices().delete(new DeleteIndexRequest(indexName),RequestOptions.DEFAULT);
}
文档管理
新增或更新文档
id为空则新增,不为空则更新
ElasticEntity
package com.wk.entity;
/**
* 泛型,es入参实体类
* @param <T>
*/
public class ElasticEntity<T> {
private String id;
private T data;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
EsTestEntity
package com.wk.entity;
/**
* controller接收实体类
*/
public class EsTestEntity {
private String id;
private String username;
private String city;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
业务类
官方新增文档也有三种方式,和创建索引类似,这里不一一列举了
/**
* 新增或更新文档
* @param indexName
* @param elasticEntity
* @throws IOException
*/
@Override
public void insertOrUpdateDocument(String indexName, ElasticEntity elasticEntity) throws IOException {
IndexRequest indexRequest = new IndexRequest(indexName);
indexRequest.id(elasticEntity.getId());
indexRequest.source(JSONObject.toJSONString(elasticEntity.getData()),XContentType.JSON);
restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
}
controller
还可在插入之前校验一下index是否存在,这里省略了只简单实现了demo
@PostMapping("insertOrUpdateDocument/{indexName}")
public Map<String,String> insertOrUpdateDocument(@PathVariable String indexName,@RequestBody EsTestEntity esTestEntity){
ElasticEntity<EsTestEntity> entity = new ElasticEntity<>();
//注册生成UUID插入实体类的ID
if(StringUtils.isEmpty(esTestEntity.getId())){
String id = UUID.randomUUID().toString();
entity.setId(id);
esTestEntity.setId(id);
}
entity.setId(esTestEntity.getId());
entity.setData(esTestEntity);
try {
elasticSearchServiceImpl.insertOrUpdateDocument(indexName,entity);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
条件查询
controller
@PostMapping("searchDocument/{indexName}")
public List<EsTestEntity> searchDocument(@PathVariable String indexName, @RequestBody EsTestEntity esTestEntity){
try {
SearchSourceBuilder builder = new SearchSourceBuilder();
Map<String,Object> requestMap = BeanUtil.convertToMap(esTestEntity);
for(Map.Entry<String,Object> entry:requestMap.entrySet()){
builder.query(QueryBuilders.matchQuery(entry.getKey(),entry.getValue()));
}
//分页
builder.from(0);
builder.size(5);
//排序
builder.sort("city", SortOrder.ASC);
//设置超时时间
builder.timeout(new TimeValue(60, TimeUnit.SECONDS));
return elasticSearchServiceImpl.searchDocument(indexName,builder,EsTestEntity.class);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
service
/**
* 条件查询
* @param indexName
* @param builder
* @param c
* @param <T>
* @return
* @throws IOException
*/
@Override
public <T> List<T> searchDocument(String indexName, SearchSourceBuilder builder, Class<T> c) throws IOException {
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(builder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] searchHits = searchResponse.getHits().getHits();
List<T> res = new ArrayList<>();
for(SearchHit searchHit:searchHits){
res.add(JSONObject.parseObject(searchHit.getSourceAsString(),c));
}
return res;
}
高亮条件查询
controller
@PostMapping("searchHighlightDocument/{indexName}")
public List<EsTestEntity> searchHighlightDocument(@PathVariable String indexName, @RequestBody EsTestEntity esTestEntity){
try {
SearchSourceBuilder builder = new SearchSourceBuilder();
Map<String,Object> requestMap = BeanUtil.convertToMap(esTestEntity);
for(Map.Entry<String,Object> entry:requestMap.entrySet()){
builder.query(QueryBuilders.matchQuery(entry.getKey(),entry.getValue()));
}
builder.from(0);
builder.size(5);
builder.sort("city", SortOrder.ASC);
builder.timeout(new TimeValue(60, TimeUnit.SECONDS));
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<front color = 'red'>");
highlightBuilder.postTags("</front>");
HighlightBuilder.Field field = new HighlightBuilder.Field("username");
highlightBuilder.field(field);
builder.highlighter(highlightBuilder);
return elasticSearchServiceImpl.searchHighlightDocument(indexName,builder,EsTestEntity.class);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
service
/**
* 高亮条件查询
* @param indexName
* @param builder
* @param c
* @param <T>
* @return
* @throws IOException
*/
@Override
public <T> List<T> searchHighlightDocument(String indexName, SearchSourceBuilder builder, Class<T> c) throws IOException {
SearchRequest searchRequest = new SearchRequest(indexName);
searchRequest.source(builder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] searchHits = searchResponse.getHits().getHits();
List<T> res = new ArrayList<>();
for(SearchHit searchHit:searchHits){
//返回结果转换成Map
Map<String,Object> sourceMap = searchHit.getSourceAsMap();
//获取高亮的返回结果
Map<String, HighlightField> map = searchHit.getHighlightFields();
//循环设置的高亮字段
for(Map.Entry<String, HighlightField> entry:map.entrySet()){
//将高亮字段格式替换原结果中的值
sourceMap.put(entry.getKey(),entry.getValue().getFragments()[0].toString());
}
res.add(JSONObject.parseObject(JSONObject.toJSONString(sourceMap),c));
}
return res;
}
返回结果:
删除文档
/**
* 根据ID删除文档
* @param indexName
* @param id
* @throws IOException
*/
@Override
public void deleteDocumentById(String indexName, String id) throws IOException {
DeleteRequest deleteRequest = new DeleteRequest(indexName,id);
restHighLevelClient.delete(deleteRequest,RequestOptions.DEFAULT);
}
还有条件删除等API,可以参考官方文档,这里不一一列举了
批量操作
批量新增
/**
* 批量新增
* @param indexName
* @param elasticEntities
* @throws IOException
*/
@Override
public void insertDocumentBatch(String indexName, List<ElasticEntity> elasticEntities) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
elasticEntities.forEach(item ->{
bulkRequest.add(new IndexRequest(indexName).id(item.getId()).source(JSONObject.toJSONString(item.getData()),XContentType.JSON));
});
restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
}
批量删除
/**
* 批量删除文档
* @param indexName
* @param idList
* @param <T>
* @throws IOException
*/
public <T> void deleteDocumentBatch(String indexName, Collection<T> idList) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
idList.forEach(item ->{
bulkRequest.add(new DeleteRequest(indexName, item.toString()));
});
restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
}