Spring boot + Elastic search实战
文章目录
前言
上篇带大家一起了解了ES的一些基础知识点,下篇主要带大家结合着spring boot项目一起去了解一些ES的几个不错的Java API。
一、elastic search的一些Java API有哪些?
1.JestClient
一款不错的ElasticSearch Java REST client,基本上能满足各种需求,包括普通查询,带聚合的复杂查询,批量删除等,唯一遗憾的是很久不更新了,最后一次是2018年更新的,6.3.1的版本,如果你的spring boot版本是2.1.x,且连接的ES客户端是7.x及7.x以下,用这个应该也能满足你的基本需求
2.RestHighLevelClient
应该是网上呼声最高的ES客户端,也是spring一直在维护的客户端,该客户端应该能满足你的所有增删改查需求,唯一的遗憾是,该客户端默认对应ES 7.x的版本,如果你得spring boot版本不是2.2.x以上,最好还是老老实实用JestClient吧
3.ElasticsearchRepository,ElasticSearchTemplate
ElasticsearchRepository基于JPA框架,只需要定义接口,定义方法名,底层会根据方法名去自动生成语句,使开发者从繁琐的语句语法中解放出来。好处很明显,坏处也很明显,就是不能满足一些复杂查询,所以就有了ElasticSearchTemplate来支持你编写复杂查询语法
因为选择的多样性,怕大家有选择恐惧症,这里我会给大家把这三种主流的API,结合进代码里,我们一起分析
笔者这里用的ES版本为7.7.1,下面相同
二、实战之JestClient
因为我在实际项目中用的是JestClient,所以我可能会着重介绍一下
1.依赖(maven)
spring boot版本:2.1.5.RELEASE
代码如下(示例):
elasticsearch依赖,不指定版本,默认为boot对应的版本,这里对应的版本是6.4.3
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
jest依赖,不指定版本,默认为最新版本,这里对应的版本是6.3.1
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
</dependency>
2.配置文件
从上到下分别填写ES的连接信息包括:url,username,password,timeout
3.查询代码
创建一个接口:ElasticSearchService
package cn.com.mgcc.kol.elasticSearch.service;
import cn.com.mgcc.kol.account.base.ElasticSearchCondition;
import io.searchbox.core.SearchResult;
import org.elasticsearch.search.builder.SearchSourceBuilder;
/**
* @author WD
* @date 2021/3/1 0001 上午 10:10
* @description
*/
public interface ElasticSearchService {
/**
* 根据条件从ES中查询
*
* @param condition
* @return
*/
SearchResult getFromElasticSearchByCondition(ElasticSearchCondition condition);
/**
* 组装基础分页
*
* @param from
* @param size
* @param orderBy
* @param asc
* @return
*/
SearchSourceBuilder getPageSearchSourceBuilder(int from, int size, String orderBy, boolean asc);
/**
* 设置返回全部数据条数
*
* @param searchSourceBuilder
* @return
*/
String putTrackTotalHits(SearchSourceBuilder searchSourceBuilder);
/**
* 从ES查询删除(主要是供品牌变更用)
* @param indexName
* @param keyword
*/
void deleteByQueryBrandsKeyword(String indexName, String keyword);
}
生成该接口对应的实现类:ElasticSearchServiceImpl
package cn.com.mgcc.kol.elasticSearch.service.impl;
import cn.com.mgcc.kol.account.base.ElasticSearchCondition;
import cn.com.mgcc.kol.elasticSearch.service.ElasticSearchService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.searchbox.client.JestClient;
import io.searchbox.client.JestResult;
import io.searchbox.core.DeleteByQuery;
import io.searchbox.core.Search;
import io.searchbox.core.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;
/**
* @author WD
* @date 2021/3/1 0001 上午 10:11
* @description
*/
@Slf4j
@Service
public class ElasticSearchServiceImpl implements ElasticSearchService {
@Autowired
private JestClient jestClient;
@Override
public SearchResult getFromElasticSearchByCondition(ElasticSearchCondition condition) {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.filter(QueryBuilders.termQuery("uuid.keyword", condition.getUuid()));
searchSourceBuilder.query(boolQueryBuilder);
Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex(condition.getIndexName()).build();
System.out.println(searchSourceBuilder.toString());
SearchResult result = null;
try {
result = jestClient.execute(search);
} catch (IOException e) {
log.error(e.getMessage());
}
return result;
}
@Override
public SearchSourceBuilder getPageSearchSourceBuilder(int from, int size, String orderBy, boolean asc) {
return new SearchSourceBuilder()
.from(from)
.size(size)
.sort(orderBy, asc ? SortOrder.ASC : SortOrder.DESC)
.trackTotalHits(true);
}
@Override
public String putTrackTotalHits(SearchSourceBuilder searchSourceBuilder) {
Map stringToMap = JSONObject.parseObject(searchSourceBuilder.toString());
System.out.println("StringToMap=>" + stringToMap);
stringToMap.put("track_total_hits", true);
System.out.println("StringToMap=>" + JSON.toJSONString(stringToMap));
return JSON.toJSONString(stringToMap);
}
@Override
public void deleteByQueryBrandsKeyword(String indexName, String keyword) {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("brands.keyword", keyword));
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(100000);
DeleteByQuery deleteByQuery = new DeleteByQuery.Builder(this.putTrackTotalHits(searchSourceBuilder)).addIndex(indexName).build();
//删除ES
JestResult jestResult;
try {
jestResult = jestClient.execute(deleteByQuery);
log.info("删除成功:{}", jestResult.toString());
} catch (IOException e) {
log.error("删除有误:{}", e.getMessage());
}
}
}
我简单给大家总结一下查询步骤
① 自动注入JestClient 客户端
@Autowired
private JestClient jestClient;
② 初始化查询载体:SearchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//看你的需求需不需要分页 from:页数,size:每页最大数量,sort:根据哪个字段进行倒序还是正序
searchSourceBuilder.from(1).size(10)
// SortOrder.DESC:倒序,SortOrder.ASC:正序
.sort("要排序的字段名", SortOrder.ASC)
//设置返回全部数量,对ES客户端为7.x以上的,不生效,需要转为JSON手动把该参数拼写进去
.trackTotalHits(true);
③ 初始化查询Buillder:BoolQueryBuilder,并组装查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//termQuery精准查询
boolQueryBuilder.filter(QueryBuilders.termQuery("username.keyword", "美女"));
//termsQuery一个字段精准匹配多个值
String[] s = {"肤白","貌美","大长腿"};
boolQueryBuilder.filter(QueryBuilders.termsQuery("tags", s));
//范围查询,如粉丝数在1000-10000之间的
boolQueryBuilder.filter(QueryBuilders.rangeQuery("fans").from(1000));
boolQueryBuilder.filter(QueryBuilders.rangeQuery("fans").to(10000));
//范围查询,如价格在0-10之间的,这里注意一下,ES中不支持bigDecimal小数类型,所以针对是小数的范围查询,一定要转为字符串
BigDecimal lowPrice = BigDecimal.ZERO;
BigDecimal highPrice = BigDecimal.TEN;
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").from(lowPrice + ""));
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").to(highPrice + ""));
//模糊查询,要用**括住
boolQueryBuilder.filter(QueryBuilders.wildcardQuery("area.keyword", "*濮阳*"));
④ 执行查询
searchSourceBuilder.query(boolQueryBuilder);
Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex("索引名称").build();
//打印查询体语句
System.out.println(searchSourceBuilder.toString());
SearchResult result = null;
try {
result = jestClient.execute(search);
} catch (IOException e) {
log.error(e.getMessage());
}
//BiliMapping为实体类,返回结果转为对应的实体类
List<SearchResult.Hit<BiliMapping, Void>> hits = result.getHits(BiliMapping.class);
List<BiliMapping> list = new ArrayList<>();
//把结果输出到实体类list中
hits.forEach(hit -> list.add(hit.source));
4.插入代码
代码如下(示例):
① 自动注入JestClient 客户端
@Autowired
private JestClient jestClient;
② 初始化Builder并设置索引名称
Bulk.Builder bulk = new Bulk.Builder().defaultIndex("索引名");
③ 填充数据
BiliPerson biliPerson = new BiliPerson();
biliPerson.setId("123456789");
biliPerson.setAccountNo("asdcdfada");
biliPerson.setAccountName("叫我红领巾");
Index index = new Index.Builder(biliPerson).id(biliPerson.getId()).build();
bulk.addAction(index);
④ 同步
//同步数据至ES
BulkResult bulkResult = jestClient.execute(bulk.build());
if (!bulkResult.isSucceeded()) {
log.error(bulkResult.getErrorMessage());
throw new BusinessException("同步数据至ES失败");
}
同步数据时,注意要指定一个ID,指定ID的方式有两种
①Index index = new Index.Builder(biliPerson).id(“具体的ID”).build();
②在实体类的字段上加注解@JestId
5.删除代码(这里使用的是DeleteByQuery)
代码如下(示例):
① 自动注入JestClient 客户端
@Autowired
private JestClient jestClient;
② 初始化查询载体:SearchSourceBuilder,初始化查询Buillder:BoolQueryBuilder,并组装查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("brands.keyword", keyword));
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(100000);
③ 执行删除
DeleteByQuery deleteByQuery = new DeleteByQuery.Builder(this.putTrackTotalHits(searchSourceBuilder)).addIndex("索引名称").build();
//删除ES
JestResult jestResult;
try {
jestResult = jestClient.execute(deleteByQuery);
log.info("删除成功:{}", jestResult.toString());
} catch (IOException e) {
log.error("删除有误:{}", e.getMessage());
}
三、实战之RestHighLevelClient
1.依赖(maven)
spring boot版本:2.4.4
elasticsearch依赖,不指定版本,默认为boot对应的版本,这里对应的版本是7.9.3
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2.配置文件
从上到下分别填写ES的连接信息包括:url,username,password
3.示例代码
创建一个接口 ClientService
package com.elasticsearch.demo.service;
import com.elasticsearch.demo.entity.ElasticsearchCondition;
import com.elasticsearch.demo.entity.WeiboPersoninfo;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
/**
* @author WD
* @date 2021/4/14 0014 下午 16:33
* @description
*/
public interface ClientService {
/**
* 同步到ES
* @param weiboPersoninfo
*/
void sync(WeiboPersoninfo weiboPersoninfo);
/**
* 组装分页
* @param elasticsearchCondition
* @return
*/
SearchSourceBuilder getPageSearchSourceBuilder(ElasticsearchCondition elasticsearchCondition);
/**
* 根据条件查询
* @param elasticsearchCondition
* @throws IOException
*/
void search(ElasticsearchCondition elasticsearchCondition) throws IOException;
/**
* 根据条件查询删除
* @param elasticsearchCondition
* @throws IOException
*/
void deleteByQuery(ElasticsearchCondition elasticsearchCondition) throws IOException;
}
创建一个接口实现类 ClientServiceImpl
package com.elasticsearch.demo.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.elasticsearch.demo.entity.ElasticsearchCondition;
import com.elasticsearch.demo.entity.WeiboPersoninfo;
import com.elasticsearch.demo.mapping.DyMapping;
import com.elasticsearch.demo.service.ClientService;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
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.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @author WD
* @date 2021/4/14 0014 下午 16:34
* @description
*/
@Service
public class ClientServiceImpl implements ClientService {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Value("${elasticsearch.index.kol-account-dy}")
private String dyAccountIndexName;
@Value("${elasticsearch.index.kol-account-xhs}")
private String xhsAccountIndexName;
@Value("${elasticsearch.index.kol-account-wb}")
private String wbAccountIndexName;
@Override
public void sync(WeiboPersoninfo weiboPersoninfo) {
String s = JSON.toJSONString(weiboPersoninfo);
IndexRequest indexRequest = new IndexRequest(wbAccountIndexName);
indexRequest.source(s, XContentType.JSON);
IndexResponse response;
try {
response = restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 组装基础分页
*
* @param elasticsearchCondition es查询条件实体类
* @return
*/
@Override
public SearchSourceBuilder getPageSearchSourceBuilder(ElasticsearchCondition elasticsearchCondition) {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().trackTotalHits(true);
if (null != elasticsearchCondition.getFrom()) {
searchSourceBuilder.from(elasticsearchCondition.getFrom());
}
if (null != elasticsearchCondition.getSize()) {
searchSourceBuilder.size(elasticsearchCondition.getSize());
}
if (null != elasticsearchCondition.getOrderBy()) {
searchSourceBuilder.sort(elasticsearchCondition.getOrderBy());
}
if (null != elasticsearchCondition.getOrderBy()) {
searchSourceBuilder.sort(elasticsearchCondition.getOrderBy(),elasticsearchCondition.getAsc() ? SortOrder.ASC : SortOrder.DESC);
}
return searchSourceBuilder;
}
@Override
public void search(ElasticsearchCondition elasticsearchCondition) throws IOException {
SearchRequest searchRequest = new SearchRequest();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//指定索引名称
searchRequest.indices(dyAccountIndexName);
//声明searchSourceBuilder,这里是通过一个自定义的分页方法,生成的,不需要分页的话,直接new初始化一个即可
SearchSourceBuilder searchSourceBuilder = this.getPageSearchSourceBuilder(elasticsearchCondition);
//组装查询条件
if (StringUtils.isNotBlank(elasticsearchCondition.getKeyword())) {
BoolQueryBuilder boolQueryBuilderWildcard = QueryBuilders.boolQuery();
boolQueryBuilderWildcard.should(QueryBuilders.wildcardQuery("accountName.keyword","*" + elasticsearchCondition.getKeyword() + "*"));
boolQueryBuilderWildcard.should(QueryBuilders.wildcardQuery("supplier.keyword","*" + elasticsearchCondition.getKeyword() + "*"));
boolQueryBuilderWildcard.should(QueryBuilders.wildcardQuery("accountNo.keyword","*" + elasticsearchCondition.getKeyword() + "*"));
boolQueryBuilder.filter(boolQueryBuilderWildcard);
}
searchSourceBuilder.query(boolQueryBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
this.analysisSearchResponse(searchResponse);
System.out.println(searchRequest.source().toString());
System.out.println(searchResponse.toString());
}
@Override
public void deleteByQuery(ElasticsearchCondition elasticsearchCondition) throws IOException {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest("kol_dy_videoinfo");
deleteByQueryRequest.setQuery(new TermQueryBuilder("brands.keyword","智利食品"));
restHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
}
public void analysisSearchResponse(SearchResponse searchResponse){
SearchHits searchHits = searchResponse.getHits();
long total = searchHits.getTotalHits().value;
for (SearchHit hit : searchHits.getHits()) {
String sourceAsString = hit.getSourceAsString();
DyMapping dyMapping = JSON.toJavaObject(JSONObject.parseObject(sourceAsString),DyMapping.class);
System.out.println(dyMapping.getAllTag());
}
}
}
我简单给大家总结一下查询步骤
① 自动注入RestHighLevelClient客户端
@Autowired
private RestHighLevelClient restHighLevelClient;
② 初始化查询载体:SearchRequest,及拼装查询语句,再执行
SearchRequest searchRequest = new SearchRequest();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//指定索引名称
searchRequest.indices("索引名");
//声明searchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().trackTotalHits(true);
//组装查询条件
if (StringUtils.isNotBlank(elasticsearchCondition.getKeyword())) {
BoolQueryBuilder boolQueryBuilderWildcard = QueryBuilders.boolQuery();
boolQueryBuilderWildcard.should(QueryBuilders.wildcardQuery("accountName.keyword","*" + "肤白" + "*"));
boolQueryBuilderWildcard.should(QueryBuilders.wildcardQuery("supplier.keyword","*" + "貌美"+ "*"));
boolQueryBuilderWildcard.should(QueryBuilders.wildcardQuery("accountNo.keyword","*" + "大长腿" + "*"));
boolQueryBuilder.filter(boolQueryBuilderWildcard);
}
searchSourceBuilder.query(boolQueryBuilder);
searchRequest.source(searchSourceBuilder);
//执行
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//解析查询的返回结果
SearchHits searchHits = searchResponse.getHits();
//返回数据总数
long total = searchHits.getTotalHits().value;
//循环解析返回结果,这里用fastjson转换即可
for (SearchHit hit : searchHits.getHits()) {
String sourceAsString = hit.getSourceAsString();
DyMapping dyMapping = JSON.toJavaObject(JSONObject.parseObject(sourceAsString),DyMapping.class);
System.out.println(dyMapping.getAllTag());
}
4.写入数据代码
代码如下(示例):
① 自动注入RestHighLevelClient 客户端
@Autowired
private RestHighLevelClient restHighLevelClient;
② 初始化并同步
//填充数据
WeiboPersoninfo weiboPersoninfo = new WeiboPersoninfo();
weiboPersoninfo.setUsername("叫我红领巾");
weiboPersoninfo.setArea("河南·濮阳");
//转为json字符串
String s = JSON.toJSONString(weiboPersoninfo);
IndexRequest indexRequest = new IndexRequest("索引名称");
indexRequest.source(s, XContentType.JSON);
IndexResponse response;
try {
response = restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
5.删除数据代码(DeleteByQuery)
代码如下(示例):
① 自动注入RestHighLevelClient客户端
@Autowired
private RestHighLevelClient restHighLevelClient;
② 初始化查询载体:DeleteByQueryRequest,并组装查询条件
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest("kol_dy_videoinfo");
deleteByQueryRequest.setQuery(new TermQueryBuilder("brands.keyword","智利食品"));
③ 执行删除
restHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
四、实战之ElasticsearchRepository
1.spring boot版本及ES的依赖同上(和二、实战之RestHighLevelClient中的配置一样)
2.配置文件同上(和二、实战之RestHighLevelClient中的配置一样)
3.示例代码
创建一个接口 XhsElasticsearchService ,继承ElasticsearchRepository
package com.elasticsearch.demo.service;
import com.elasticsearch.demo.entity.XhsNoteinfo;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author WD
* @date 2021/4/12 0012 上午 11:16
* @description
*/
@Repository
public interface XhsElasticsearchService extends ElasticsearchRepository<XhsNoteinfo,String> {
List<XhsNoteinfo> findByKeyword(String keyword);
void deleteByBrandsContains(String[] brands);
}
ElasticsearchRepository比较简单,遵循JPA框架,我在这里不再赘述,感兴趣的同学可以去网上查询资料
总结
这一大章为spring boot集成ES的教程,因为时间紧可能有部分写的不太严谨,有时间的话我会完善好,希望大家能找我多多交流,我们一起进步