由于实际工作需要,需要将es8与springboot进行集成。检索了目前存在的文章,大部分都断章取义,有的甚至是拼凑(很鄙视这种)。所以我通过实际开发,整理一篇,供大家学习使用。代码可以直接copy使用。当然,肯定存在需要优化的地方,期待你的留言,废话少说,直接上内容。。。。
首先,ES8与之前的版本在Springboot中完全不同,这点需要注意。我们先加入ES8的MAVEN依赖库,如下:
co.elastic.clients elasticsearch-java 8.1.0 com.fasterxml.jackson.core jackson-databind 2.12.3 jakarta.json jakarta.json-api 2.0.1
在集成中,有的项目对ES访问有的需要做安全配置,有的不需要,这两种方式,直接影响到配置类的编写。这里我只提供需要安全的配置类,如下:
import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.json.jackson.JacksonJsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.rest_client.RestClientTransport; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; /** *@Description: springboot整合elasticsearch8 * *@Author *@CreateTime 2022/12/20 13:35 */ @Configuration public class ElasticSearchConfig { @Value("${spring.elasticsearch.uris}") private String hosts; @Value("${spring.elasticsearch.username}") private String userName; @Value("${spring.elasticsearch.password}") private String passWord; @Bean public ElasticsearchClient elasticsearchClient(){ HttpHost[] httpHosts = toHttpHost(); final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials( AuthScope.ANY, new UsernamePasswordCredentials(userName, passWord)); RestClientBuilder builder = RestClient.builder(httpHosts); builder.setRequestConfigCallback( new RestClientBuilder.RequestConfigCallback() { @Override public RequestConfig.Builder customizeRequestConfig( RequestConfig.Builder requestConfigBuilder) { return requestConfigBuilder.setSocketTimeout(60000).setConnectTimeout(5000); } }); builder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) { return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider); } }); RestClient restClient = builder.build(); ElasticsearchTransport transport = new RestClientTransport(restClient,new JacksonJsonpMapper()); return new ElasticsearchClient(transport); } private HttpHost[] toHttpHost() { if (!StringUtils.hasLength(hosts)) { throw new RuntimeException("invalid elasticsearch configuration. elasticsearch.hosts不能为空!"); } // 多个IP逗号隔开 String[] hostArray = hosts.split(","); HttpHost[] httpHosts = new HttpHost[hostArray.length]; HttpHost httpHost; for (int i = 0; i < hostArray.length; i++) { String[] strings = hostArray[i].split(":"); httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "http"); httpHosts[i] = httpHost; } return httpHosts; } }
在配置类中存在@Value部分,这个需要我们在配置中加入做需要的配置信息,如下:
spring.elasticsearch.uris = IP:port spring.elasticsearch.username = 用户名 spring.elasticsearch.password = 密码
到这里,ES8的基本集成已经能够满足我们日常的开发需要。我这里提供一个集成了对ES8相关操作的工具类,如下:
import cn.you80.daq.es.dto.base.EsQueryDTO; import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.elasticsearch._types.SortOptions; import co.elastic.clients.elasticsearch._types.SortOrder; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch.core.CountResponse; import co.elastic.clients.elasticsearch.core.GetResponse; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import com.alibaba.fastjson.JSON; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.*; /** * @author 贵小才 * @title: ElasticClientUtils * @description: ES工具类 * @date 2022/4/2 9:50 */ @Component public class ElasticClientUtils{ /** * @param * @param client * @param dto * @param target * @return java.util.List * @author liuch * @description 根据关键字查询 * @date 2022/4/2 17:15 */ public List queryByFiled(ElasticsearchClient client, EsQueryDTO dto, Class target) throws Exception { List result = new ArrayList<>(); List sorts = new ArrayList<>(); if (StringUtils.isNotBlank(dto.getOrder())) { SortOptions sortOptions = SortOptions.of(s -> s.field(f -> f.field(dto.getOrder()).order(SortOrder.valueOf(dto.getOrderType())))); sorts.add(sortOptions); } SearchResponse search = client.search(s -> s .index(dto.getIndexName()) .query(q -> q.term(t -> t .field(dto.getField()) .value(dto.getWord()) )).sort(sorts), HashMap.class); return getResult(target, result, search); } /** * @param * @param client * @param dto * @param target * @return java.util.List * @author liuch * @description 根据关键字查询,基于游标查询scroll * @date 2022/4/2 17:15 */ public List queryByFileds(ElasticsearchClient client, EsQueryDTO dto, List queries, Class target) throws Exception { List result = new ArrayList<>(); List sorts = new ArrayList<>(); if (StringUtils.isNotBlank(dto.getOrder())) { SortOptions sortOptions = SortOptions.of(s -> s.field(f -> f.field(dto.getOrder()).order(SortOrder.valueOf(dto.getOrderType())))); sorts.add(sortOptions); } getFieldValues(dto, queries); //使用scroll深度分页查询 SearchResponse search = client.search(s -> s .index(dto.getIndexName()).query(q -> q.bool(b -> b.must(queries))).size(5000).scroll(t -> t.time("5s")) .sort(sorts), HashMap.class); StringBuffer scrollId = new StringBuffer(search.scrollId()); //循环查询,直到查不到数据 do { getResult(target, result, search); StringBuffer finalScrollId = scrollId; search = client.scroll(s -> s.scrollId(finalScrollId.toString()).scroll(t -> t.time("5s")), HashMap.class); scrollId = new StringBuffer(search.scrollId()); } while (!search.hits().hits().isEmpty()); //getResult(target, result, search) return result; } /** * @param * @param client * @param dto * @param target * @return java.util.List * @author liuch * @description 根据关键字分页查询 * @date 2022/4/2 17:15 */ public List queryByFiledWithPage(ElasticsearchClient client, EsQueryDTO dto, Class target) throws Exception { List result = new ArrayList<>(); List sorts = new ArrayList<>(); if (StringUtils.isNotBlank(dto.getOrder())) { SortOptions sortOptions = SortOptions.of(s -> s.field(f -> f.field(dto.getOrder()).order(SortOrder.valueOf(dto.getOrderType())))); sorts.add(sortOptions); } SearchResponse search = client.search(s -> s .index(dto.getIndexName()) .query(q -> q.term(t -> t .field(dto.getField()) .value(dto.getWord()) )).sort(sorts).from(dto.getFrom()).size(dto.getSize()), HashMap.class); return getResult(target, result, search); } private List getResult(Class target, List result, SearchResponse search) { List > hits = search.hits().hits(); Iterator > iterator = hits.iterator(); while (iterator.hasNext()) { Hit decodeBeanHit = iterator.next(); Map docMap = decodeBeanHit.source(); docMap.put("id", decodeBeanHit.id()); String json = JSON.toJSONString(docMap); T obj = JSON.parseObject(json, target); result.add(obj); } return result; } /** * @param * @param client * @param dto * @return long * @author liuch * @description 根据关键字查询总条数 * @date 2022/4/2 17:15 */ public static long queryCountByFiled(ElasticsearchClient client, EsQueryDTO dto) throws Exception { CountResponse count = client.count(c -> c.index(dto.getIndexName()).query(q -> q.term(t -> t .field(dto.getField()) .value(dto.getWord()) ))); long total = count.count(); return total; } /** * @param * @param client * @param dto * @return long * @author liuch * @description 根据关键字查询总条数-复合查询 * @date 2022/4/2 17:15 */ public static long queryCountByFileds(ElasticsearchClient client, List queries, EsQueryDTO dto) throws Exception { getFieldValues(dto, queries); CountResponse count = client.count(c -> c.index(dto.getIndexName()).query(q -> q.bool(b -> b.must(queries)))); long total = count.count(); return total; } /** * @param client * @param dto * @param target * @return java.util.List * @author liuch * @description 根据关键字分页查询- 复合查询 must * @date 2022/4/2 17:15 */ public List queryMustByFiledsWithPage(ElasticsearchClient client, EsQueryDTO dto, List queries, Class target) throws Exception { List result = new ArrayList<>(); List sorts = new ArrayList<>(); if (StringUtils.isNotBlank(dto.getOrder())) { SortOptions sortOptions = SortOptions.of(s -> s .field(f -> f.field(dto.getOrder()).order(SortOrder.valueOf(dto.getOrderType())))); sorts.add(sortOptions); } SearchResponse search = client.search(s -> s .index(dto.getIndexName()) .query(q -> q.bool(b -> b.must(queries))) .sort(sorts).from(dto.getFrom()).size(dto.getSize()), HashMap.class); return getResult(target, result, search); } /** * @param client * @param dto * @param target * @return java.util.List * @author liuch * @description 根据关键字分页查询- 复合查询 should * @date 2022/4/2 17:15 */ public List queryShouldByFiledsWithPage(ElasticsearchClient client, EsQueryDTO dto, List queries, Class target) throws Exception { List result = new ArrayList<>(); List sorts = new ArrayList<>(); if (StringUtils.isNotBlank(dto.getOrder())) { SortOptions sortOptions = SortOptions.of(s -> s .field(f -> f.field(dto.getOrder()).order(SortOrder.valueOf(dto.getOrderType())))); sorts.add(sortOptions); } SearchResponse search = client.search(s -> s .index(dto.getIndexName()) .query(q -> q.bool(b -> b.should(queries))) .sort(sorts).from(dto.getFrom()).size(dto.getSize()), HashMap.class); return getResult(target, result, search); } /** * 构件复合查询条件 * * @param dto * @param queries */ private static void getFieldValues(EsQueryDTO dto, List queries) { List fieldValues = new ArrayList<>(); //根据关键字列表构件复合查询的值 dto.getWords().stream().forEach(word -> fieldValues.add(FieldValue.of(word))); //查询条件列表 queries.add(Query.of(q -> q.terms(t -> t.field(dto.getField()).terms(v -> v.value(fieldValues))))); } /** * @param * @param client * @param dto * @param target * @return java.lang.Object * @author liuch * @description 根据文档id查询 * @date 2022/4/2 17:16 */ public Object queryByDocumentId(ElasticsearchClient client, EsQueryDTO dto, Class target) throws Exception { GetResponse getResponse = client.get(s -> s .index(dto.getIndexName()).id(dto.getWord()), HashMap.class); getResponse.source(); Map docMap = getResponse.source(); String json = JSON.toJSONString(docMap); T obj = JSON.parseObject(json, target); return obj; } }
这个类基本满足我们日常大部分对ES8的操作。
我们在ES8操作中,需要创建INDEX(索引)和创建文档(Document),我在此也做了封装。
创建INDEX(索引)代码如下:
创建INDEX(索引)--接口类
import co.elastic.clients.elasticsearch._types.mapping.Property; import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; import co.elastic.clients.elasticsearch.indices.GetIndexResponse; import co.elastic.clients.elasticsearch.indices.IndexSettings; import co.elastic.clients.util.ObjectBuilder; import java.io.IOException; import java.util.HashMap; import java.util.function.Function; /** * Created with IntelliJ IDEA. * * @Author: * @Date: 2022/12/28/14:28 * @Description:业务类---索引操作---接口 */ public interface EsIndexService { /** * 新建索引,指定索引名称 * * @param name * @throws IOException */ void createIndex(String name) throws IOException; /** * 创建索引,指定索引名称和setting和mapping * * @param name - 索引名称 * @param settingFn - 索引参数 * @param mappingFn - 索引结构 * @throws IOException */ void createIndex(String name, Function> settingFn, Function > mappingFn) throws IOException; /** * 删除索引 * * @param name * @throws IOException */ void deleteIndex(String name) throws IOException; /** * 修改索引字段信息
* 字段可以新增,已有的字段只能修改字段的 search_analyzer 属性。 * * @param name - 索引名称 * @param propertyMap - 索引字段,每个字段都有自己的property * @throws IOException */ void updateIndexProperty(String name, HashMappropertyMap) throws IOException; /** * 查询索引列表 * * @return * @throws IOException */ GetIndexResponse getIndexList() throws IOException; /** * 查询索引详情 * * @param name * @return * @throws IOException */ GetIndexResponse getIndexDetail(String name) throws IOException; /** * 检查指定名称的索引是否存在 * * @param name * @return - true:存在 * @throws IOException */ boolean indexExists(String name) throws IOException; }
创建INDEX(索引)--实现类
import cn.you80.daq.service.EsIndexService; import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.mapping.Property; import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; import co.elastic.clients.elasticsearch.indices.*; import co.elastic.clients.util.ObjectBuilder; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.HashMap; import java.util.function.Function; /** * @author: 贵小才 * @since: 2022/12/28 14:29 * @description: 业务类---索引操作---实现类 */ @Service @Slf4j public class EsIndexServiceImpl implements EsIndexService { @Autowired private ElasticsearchClient elasticsearchClient; @Override public void createIndex(String name) throws IOException { CreateIndexResponse response = elasticsearchClient.indices().create(c -> c.index(name)); log.info("createIndex方法,acknowledged={}", response.acknowledged()); } @Override public void createIndex(String name, Function> settingFn, Function > mappingFn) throws IOException { CreateIndexResponse response = elasticsearchClient .indices() .create(c -> c .index(name) .settings(settingFn) .mappings(mappingFn) ); log.info("createIndex方法,acknowledged={}", response.acknowledged()); } @Override public void deleteIndex(String name) throws IOException { DeleteIndexResponse response = elasticsearchClient.indices().delete(c -> c.index(name)); log.info("deleteIndex方法,acknowledged={}", response.acknowledged()); } @Override public void updateIndexProperty(String name, HashMap propertyMap) throws IOException { PutMappingResponse response = elasticsearchClient.indices() .putMapping(typeMappingBuilder -> typeMappingBuilder .index(name) .properties(propertyMap) ); log.info("updateIndexMapping方法,acknowledged={}", response.acknowledged()); } @Override public GetIndexResponse getIndexList() throws IOException { //使用 * 或者 _all都可以 GetIndexResponse response = elasticsearchClient.indices().get(builder -> builder.index("_all")); log.info("getIndexList方法,response.result()={}", response.result().toString()); return response; } @Override public GetIndexResponse getIndexDetail(String name) throws IOException { GetIndexResponse response = elasticsearchClient.indices().get(builder -> builder.index(name)); log.info("getIndexDetail方法,response.result()={}", response.result().toString()); return response; } @Override public boolean indexExists(String name) throws IOException { return elasticsearchClient.indices().exists(b -> b.index(name)).value(); } }
然后,继续,下面是文档创建代码。
文档--接口类
import co.elastic.clients.elasticsearch.core.BulkResponse; import co.elastic.clients.elasticsearch.core.IndexResponse; import jdk.nashorn.internal.ir.ObjectNode; import java.io.IOException; import java.util.List; /** * Created with IntelliJ IDEA. * * @Author: * @Date: 2022/12/28/14:22 * @Description: 业务类 --文档操作 */ public interface EsDocumentDemoService { /** * 新增一个文档 * * @param idxName 索引名 * @param idxId 索引id * @param document 文档对象 * @return */ IndexResponse createByFluentDSL(String idxName, String idxId, Object document) throws Exception; /** * 新增一个文档 * * @param idxName 索引名 * @param idxId 索引id * @param document 文档对象 * @return */ IndexResponse createByBuilderPattern(String idxName, String idxId, Object document) throws Exception; /** * 批量增加文档 * * @param idxName 索引名 * @param documents 要增加的对象集合 * @return 批量操作的结果 * @throws Exception */ BulkResponse bulkCreate(String idxName, List
文档--实现类
import cn.you80.daq.service.EsDocumentDemoService; import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.core.*; import jdk.nashorn.internal.ir.ObjectNode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.List; /** * @author: 贵小才 * @since: 2022/12/28 14:23 * @description: 业务类---文档操作---实现类 */ @Service public class EsDocumentDemoServiceImpl implements EsDocumentDemoService { @Autowired private ElasticsearchClient elasticsearchClient; @Override public IndexResponse createByFluentDSL(String idxName, String idxId, Object document) throws Exception { IndexResponse response = elasticsearchClient.index(idx -> idx .index(idxName) .id(idxId) .document(document)); return response; } @Override public IndexResponse createByBuilderPattern(String idxName, String idxId, Object document) throws Exception { IndexRequest.Builder
同时,对ES的基本属性,也做了封装,代码如下:
import cn.hutool.core.date.DateUtil; import co.elastic.clients.elasticsearch._types.SortOrder; import lombok.Data; import org.apache.commons.lang3.StringUtils; import java.util.Date; import java.util.List; /** * @author 贵小才 * @title: EsQueryDTO * @description: 查询对象实体类 * @date 2022/4/2 10:32 */ @Data public class EsQueryDTO { private String indexName;//索引名称 private String field;//关键字属性 private String word;//关键字值 private Listwords;//关键字值数组 private Integer from;//起始行 private Integer index;//当前页 private Integer size;//分页条数 private String order;//排序字段 private String orderType;//排序方式 asc/desc private String dateField;//时间字段 private String startTime;//时间范围-开始时间 private String endTime;//时间范围-开始时间 public String getOrderType() { if (StringUtils.isBlank(orderType)) { orderType = SortOrder.Desc.name(); } return orderType; } public Integer getSize() { return size == 0 ? 30 : size; } public Integer getFrom() { return getIndex() != 0 ? ((getIndex() - 1) * getSize()) : 0; } public Integer getIndex() { return null == index ? 0 : index; } public String getStartTime(int offset) { if (StringUtils.isBlank(startTime)) { startTime = DateUtil.format(DateUtil.offsetDay(new Date(), offset), "yyyy-MM-dd 00:00:00"); return String.valueOf(DateUtil.parse(startTime, "yyyy-MM-dd 00:00:00").getTime()); } return startTime; } public String getEndTime() { if (StringUtils.isBlank(endTime)) { endTime = String.valueOf(System.currentTimeMillis()); } return endTime; } }
到这里,与Springboot的集成完成。大家或许问,怎么进行调用呢,甚至在上面的一些类中,会有类似Query集合,如何做封装呢,我这边参考了ES8的基础语句,做了下面实现,废话并不多说,直接上代码:
String index = hold_id + "-user"; if (false == esIndexService.indexExists(index)) { log.info("索引不存在,创建索引 ={}", index); ListentityList = userService.load(); if (CollectionUtil.isEmpty(entityList)) { return Result.builder().errCode("00001").errMsg("不存在用户信息资料").errData(msgResult).build().getString(); } List esUserInfoList = Lists.newArrayList(); for (Entity entity : entityList) { EsUserInfo esUserInfo = new EsUserInfo(); esUserInfo.setReal_name(entity.getStr("real_name")); esUserInfo.setMobile_phone(entity.getStr("mobile_phone")); esUserInfo.setUser_id(entity.getStr("user_id")); esUserInfoList.add(esUserInfo); } esDocumentDemoService.bulkCreate(index, Collections.singletonList(esUserInfoList)); } EsQueryDTO dto = new EsQueryDTO(); dto.setIndexName(index); dto.setSize(size); List queries = Lists.newArrayList(); Query queryName = MatchQuery.of(m -> m .field("real_name") .query(keyword))._toQuery(); queries.add(queryName); Query queryPhone = MatchQuery.of(m -> m .field("mobile_phone") .query(keyword))._toQuery(); queries.add(queryPhone); List infoDtoList = esQueryService.queryShouldByFiledsWithPage(eslient, dto, queries, UserInfoDto.class); msgResult.put("result", infoDtoList);
在实际开发中一定注意这部分的组装:
Query queryName = MatchQuery.of(m -> m
.field("real_name")
.query(keyword))
._toQuery();
queries.add(queryName);
Query queryPhone = MatchQuery.of(m -> m
.field("mobile_phone")
.query(keyword))
._toQuery();
queries.add(queryPhone);
为了保证整个代码的完整性,我给出一个业务对象,这样整个ES8与Springboot的集成到这里结束。
等.......给出这个代码,才算结束!
import lombok.Data; import lombok.experimental.Accessors; /** * @author: 贵小才 * @since: 2022/12/21 17:12 * @description: */ @Data @Accessors(chain = true) public class UserInfoDto { private String real_name; private String mobile_phone; private String user_id; @Override public String toString() { return "UserInfoDto{" + "real_name='" + real_name + '\'' + ", mobile_phone='" + mobile_phone + '\'' + ", user_id='" + user_id + '\'' + '}'; } }
有错的地方,希望大家指正!